feat: add visibility filter for permit management and fix V2 visibility logic
- Added 'Enabled Status' dropdown filter to db_admin.html for filtering permits by visibility (visible/hidden/all) - Updated admin_permits_advanced_filter API to accept and process visibility parameter - Modified filter_permits_advanced in licensing_repo.py to filter by is_v2_visible column - Fixed role-based access control to allow department_admin to toggle permit visibility - Improved parameter parsing in API endpoints for more robust handling
This commit is contained in:
parent
b532c46dc1
commit
c55170208b
|
|
@ -922,6 +922,38 @@ def admin_permits():
|
||||||
return jsonify({"success": False, "message": str(exc)}), 500
|
return jsonify({"success": False, "message": str(exc)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@v2_bp.route('/admin/permits/visibility', methods=['POST'])
|
||||||
|
def admin_toggle_permit_visibility():
|
||||||
|
"""Toggle the visibility of a permit in V2 API retrieval."""
|
||||||
|
admin_user, error = _admin_guard(prefer_json=True, roles=("admin", "department_admin"))
|
||||||
|
if error:
|
||||||
|
return error
|
||||||
|
|
||||||
|
data = request.get_json() or {}
|
||||||
|
region_id = data.get("region_id")
|
||||||
|
permit_id = data.get("permit_id")
|
||||||
|
is_visible = data.get("is_v2_visible")
|
||||||
|
|
||||||
|
if not region_id or not permit_id or is_visible is None:
|
||||||
|
return jsonify({"success": False, "message": "region_id, permit_id and is_v2_visible are required"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
from lawrisk.services.licensing_repo import update_permit_v2_visibility
|
||||||
|
operator = (admin_user or {}).get("username") or "admin"
|
||||||
|
success = update_permit_v2_visibility(
|
||||||
|
region_id=region_id,
|
||||||
|
permit_id=permit_id,
|
||||||
|
is_visible=bool(is_visible),
|
||||||
|
operator=operator
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
return jsonify({"success": True, "message": "Visibility updated"})
|
||||||
|
else:
|
||||||
|
return jsonify({"success": False, "message": "Permit details not found or update failed"}), 404
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"success": False, "message": str(exc)}), 500
|
||||||
|
|
||||||
|
|
||||||
@v2_bp.route('/admin/permit-import/upload', methods=['POST'])
|
@v2_bp.route('/admin/permit-import/upload', methods=['POST'])
|
||||||
def admin_permit_import_upload():
|
def admin_permit_import_upload():
|
||||||
"""Upload Excel workbook and start an import session."""
|
"""Upload Excel workbook and start an import session."""
|
||||||
|
|
@ -1596,70 +1628,46 @@ def admin_permits_advanced_filter():
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Parse parameters from query string or request body
|
# Parse parameters from query string or request body
|
||||||
|
# Parse parameters from query/body in a more unified way
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
regions = request.args.getlist('regions[]') or request.args.getlist('region')
|
regions = request.args.getlist('regions[]') or request.args.getlist('region')
|
||||||
themes = request.args.getlist('themes[]') or request.args.getlist('theme')
|
themes = request.args.getlist('themes[]') or request.args.getlist('theme')
|
||||||
departments = request.args.getlist('departments[]') or request.args.getlist('department')
|
departments = request.args.getlist('departments[]') or request.args.getlist('department')
|
||||||
search_text = request.args.get('search_text') or request.args.get('q')
|
search_text = request.args.get('search_text') or request.args.get('q')
|
||||||
try:
|
visibility = request.args.get('visibility')
|
||||||
limit = int(request.args.get('limit', '100'))
|
limit = request.args.get('limit', '100')
|
||||||
except (TypeError, ValueError):
|
offset = request.args.get('offset', '0')
|
||||||
limit = 100
|
|
||||||
try:
|
|
||||||
offset = int(request.args.get('offset', '0'))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
offset = 0
|
|
||||||
else:
|
else:
|
||||||
if request.is_json:
|
payload = request.get_json(silent=True) or request.form
|
||||||
payload = request.get_json(silent=True) or {}
|
|
||||||
else:
|
|
||||||
payload = request.form.to_dict(flat=True) if request.form else {}
|
|
||||||
|
|
||||||
# Handle array parameters
|
|
||||||
regions = payload.getlist('regions[]') if hasattr(payload, 'getlist') else payload.get('regions', [])
|
regions = payload.getlist('regions[]') if hasattr(payload, 'getlist') else payload.get('regions', [])
|
||||||
if isinstance(regions, str):
|
|
||||||
regions = [regions]
|
|
||||||
regions = regions or payload.getlist('region') if hasattr(payload, 'getlist') else payload.get('region', [])
|
|
||||||
if isinstance(regions, str):
|
|
||||||
regions = [regions]
|
|
||||||
|
|
||||||
themes = payload.getlist('themes[]') if hasattr(payload, 'getlist') else payload.get('themes', [])
|
themes = payload.getlist('themes[]') if hasattr(payload, 'getlist') else payload.get('themes', [])
|
||||||
if isinstance(themes, str):
|
|
||||||
themes = [themes]
|
|
||||||
themes = themes or payload.getlist('theme') if hasattr(payload, 'getlist') else payload.get('theme', [])
|
|
||||||
if isinstance(themes, str):
|
|
||||||
themes = [themes]
|
|
||||||
|
|
||||||
departments = payload.getlist('departments[]') if hasattr(payload, 'getlist') else payload.get('departments', [])
|
departments = payload.getlist('departments[]') if hasattr(payload, 'getlist') else payload.get('departments', [])
|
||||||
if isinstance(departments, str):
|
|
||||||
departments = [departments]
|
|
||||||
departments = departments or payload.getlist('department') if hasattr(payload, 'getlist') else payload.get('department', [])
|
|
||||||
if isinstance(departments, str):
|
|
||||||
departments = [departments]
|
|
||||||
|
|
||||||
search_text = payload.get('search_text') or payload.get('q')
|
search_text = payload.get('search_text') or payload.get('q')
|
||||||
try:
|
visibility = payload.get('visibility')
|
||||||
limit = int(payload.get('limit', '100'))
|
limit = payload.get('limit', '100')
|
||||||
except (TypeError, ValueError):
|
offset = payload.get('offset', '0')
|
||||||
limit = 100
|
|
||||||
try:
|
|
||||||
offset = int(payload.get('offset', '0'))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
# Normalize parameters - convert to lists if not already
|
# Normalize parameters
|
||||||
if isinstance(regions, str):
|
if isinstance(regions, str): regions = [regions]
|
||||||
regions = [regions]
|
if isinstance(themes, str): themes = [themes]
|
||||||
if isinstance(themes, str):
|
if isinstance(departments, str): departments = [departments]
|
||||||
themes = [themes]
|
|
||||||
if isinstance(departments, str):
|
|
||||||
departments = [departments]
|
|
||||||
|
|
||||||
# Filter out empty values
|
|
||||||
regions = [r.strip() for r in regions if r and r.strip()] if regions else None
|
regions = [r.strip() for r in regions if r and r.strip()] if regions else None
|
||||||
themes = [t.strip() for t in themes if t and t.strip()] if themes else None
|
themes = [t.strip() for t in themes if t and t.strip()] if themes else None
|
||||||
departments = [d.strip() for d in departments if d and d.strip()] if departments else None
|
departments = [d.strip() for d in departments if d and d.strip()] if departments else None
|
||||||
search_text = search_text.strip() if search_text else None
|
search_text = (search_text or "").strip() or None
|
||||||
|
visibility = (visibility or "").strip().lower() or None
|
||||||
|
|
||||||
|
try:
|
||||||
|
limit = int(limit)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
limit = 100
|
||||||
|
try:
|
||||||
|
offset = int(offset)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
print(f"[DEBUG] admin_permits_advanced_filter params: search={search_text}, visibility={visibility}, regions={regions}")
|
||||||
|
|
||||||
# Execute filtering
|
# Execute filtering
|
||||||
result = filter_permits_advanced(
|
result = filter_permits_advanced(
|
||||||
|
|
@ -1667,6 +1675,7 @@ def admin_permits_advanced_filter():
|
||||||
themes=themes,
|
themes=themes,
|
||||||
departments=departments,
|
departments=departments,
|
||||||
search_text=search_text,
|
search_text=search_text,
|
||||||
|
visibility=visibility,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from lawrisk.services.licensing_repo import (
|
||||||
load_theme_payload,
|
load_theme_payload,
|
||||||
load_permits_and_risks,
|
load_permits_and_risks,
|
||||||
find_permit_contexts_by_name,
|
find_permit_contexts_by_name,
|
||||||
|
_ensure_v2_visibility_column,
|
||||||
)
|
)
|
||||||
from lawrisk.services.lawrisk_service import ChatClient
|
from lawrisk.services.lawrisk_service import ChatClient
|
||||||
|
|
||||||
|
|
@ -200,18 +201,21 @@ def _get_preset_questions_pool() -> List[str]:
|
||||||
"""
|
"""
|
||||||
from lawrisk.services.licensing_repo import _lic_pg_conn
|
from lawrisk.services.licensing_repo import _lic_pg_conn
|
||||||
|
|
||||||
# Query themes that have at least one permit
|
# Query themes that have at least one visible permit
|
||||||
sql = """
|
sql = """
|
||||||
SELECT DISTINCT t.name AS theme_name
|
SELECT DISTINCT t.name AS theme_name
|
||||||
FROM themes t
|
FROM themes t
|
||||||
JOIN region_theme_permits rtp ON rtp.theme_id = t.id
|
JOIN region_theme_permits rtp ON rtp.theme_id = t.id
|
||||||
|
JOIN region_permit_details rpd ON rpd.region_id = rtp.region_id AND rpd.permit_id = rtp.permit_id
|
||||||
WHERE t.name NOT IN ('不涉及', '无', '所有主题事项')
|
WHERE t.name NOT IN ('不涉及', '无', '所有主题事项')
|
||||||
|
AND COALESCE(rpd.is_v2_visible, true) = true
|
||||||
ORDER BY t.name
|
ORDER BY t.name
|
||||||
"""
|
"""
|
||||||
|
|
||||||
questions: List[str] = []
|
questions: List[str] = []
|
||||||
try:
|
try:
|
||||||
with _lic_pg_conn() as conn:
|
with _lic_pg_conn() as conn:
|
||||||
|
_ensure_v2_visibility_column(conn)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql)
|
cur.execute(sql)
|
||||||
for (theme_name,) in cur.fetchall():
|
for (theme_name,) in cur.fetchall():
|
||||||
|
|
@ -313,6 +317,7 @@ def search_v2(
|
||||||
ctx["region_id"],
|
ctx["region_id"],
|
||||||
ctx["theme_id"],
|
ctx["theme_id"],
|
||||||
permit_id=ctx["permit_id"],
|
permit_id=ctx["permit_id"],
|
||||||
|
only_visible=True,
|
||||||
)
|
)
|
||||||
if not permits:
|
if not permits:
|
||||||
continue
|
continue
|
||||||
|
|
@ -368,7 +373,7 @@ def search_v2(
|
||||||
if ":" not in option_id:
|
if ":" not in option_id:
|
||||||
continue
|
continue
|
||||||
region_id, theme_id = option_id.split(":", 1)
|
region_id, theme_id = option_id.split(":", 1)
|
||||||
payload = load_theme_payload(region_id, theme_id)
|
payload = load_theme_payload(region_id, theme_id, only_visible=True)
|
||||||
|
|
||||||
# Sanitize permits for V2 API (V2 should only expose external contact info)
|
# Sanitize permits for V2 API (V2 should only expose external contact info)
|
||||||
for permit in payload.get("permits", []):
|
for permit in payload.get("permits", []):
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,9 @@ _PERMIT_THEME_OVERRIDE_SCHEMA_LOCK = threading.Lock()
|
||||||
_PERMIT_APPROVAL_DEPARTMENTS_SCHEMA_READY: Optional[bool] = None
|
_PERMIT_APPROVAL_DEPARTMENTS_SCHEMA_READY: Optional[bool] = None
|
||||||
_PERMIT_APPROVAL_DEPARTMENTS_SCHEMA_LOCK = threading.Lock()
|
_PERMIT_APPROVAL_DEPARTMENTS_SCHEMA_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
_V2_VISIBILITY_SCHEMA_READY: Optional[bool] = None
|
||||||
|
_V2_VISIBILITY_SCHEMA_LOCK = threading.Lock()
|
||||||
|
|
||||||
_IMPORT_HEADER_ALIASES: Dict[str, Set[str]] = {
|
_IMPORT_HEADER_ALIASES: Dict[str, Set[str]] = {
|
||||||
"permit_name": {
|
"permit_name": {
|
||||||
"许可事项",
|
"许可事项",
|
||||||
|
|
@ -3200,6 +3203,36 @@ def _fetch_permit_all_theme_flags(
|
||||||
return {str(permit_id): True for (permit_id,) in rows}
|
return {str(permit_id): True for (permit_id,) in rows}
|
||||||
|
|
||||||
|
|
||||||
|
def update_permit_v2_visibility(
|
||||||
|
region_id: str, permit_id: str, is_visible: bool, operator: str = "admin"
|
||||||
|
) -> bool:
|
||||||
|
"""Toggle the visibility of a permit in V2 API retrieval for a specific region."""
|
||||||
|
with _lic_pg_conn() as conn:
|
||||||
|
_ensure_v2_visibility_column(conn)
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
UPDATE region_permit_details
|
||||||
|
SET is_v2_visible = %s, updated_at = now()
|
||||||
|
WHERE region_id = %s AND permit_id = %s
|
||||||
|
""",
|
||||||
|
(is_visible, region_id, permit_id),
|
||||||
|
)
|
||||||
|
success = cur.rowcount > 0
|
||||||
|
if success:
|
||||||
|
conn.commit()
|
||||||
|
log_operation(
|
||||||
|
operator=operator,
|
||||||
|
operation_type="UPDATE",
|
||||||
|
target_type="PERMIT_VISIBILITY",
|
||||||
|
target_id=permit_id,
|
||||||
|
target_name=f"Visibility set to {is_visible}",
|
||||||
|
change_summary=f"Updated v2_visibility for permit {permit_id} in region {region_id} to {is_visible}",
|
||||||
|
details={"region_id": region_id, "permit_id": permit_id, "is_v2_visible": is_visible},
|
||||||
|
)
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
def _permit_binds_all_themes(conn: pg.Connection, region_id: str, permit_id: str) -> bool:
|
def _permit_binds_all_themes(conn: pg.Connection, region_id: str, permit_id: str) -> bool:
|
||||||
"""Check override flag for a single region-permit pair."""
|
"""Check override flag for a single region-permit pair."""
|
||||||
global _PERMIT_THEME_OVERRIDE_SCHEMA_READY
|
global _PERMIT_THEME_OVERRIDE_SCHEMA_READY
|
||||||
|
|
@ -3351,10 +3384,17 @@ def list_region_theme_options() -> List[Dict[str, str]]:
|
||||||
FROM region_themes rt
|
FROM region_themes rt
|
||||||
JOIN regions r ON r.id = rt.region_id
|
JOIN regions r ON r.id = rt.region_id
|
||||||
JOIN themes t ON t.id = rt.theme_id
|
JOIN themes t ON t.id = rt.theme_id
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM region_theme_permits rtp
|
||||||
|
JOIN region_permit_details rpd ON rpd.region_id = rtp.region_id AND rpd.permit_id = rtp.permit_id
|
||||||
|
WHERE rtp.region_id = rt.region_id AND rtp.theme_id = rt.theme_id
|
||||||
|
AND COALESCE(rpd.is_v2_visible, true) = true
|
||||||
|
)
|
||||||
ORDER BY r.name, t.name
|
ORDER BY r.name, t.name
|
||||||
"""
|
"""
|
||||||
out: List[Dict[str, str]] = []
|
out: List[Dict[str, str]] = []
|
||||||
with _lic_pg_conn() as conn:
|
with _lic_pg_conn() as conn:
|
||||||
|
_ensure_v2_visibility_column(conn)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql)
|
cur.execute(sql)
|
||||||
for region_id, region_name, theme_id, theme_name in cur.fetchall():
|
for region_id, region_name, theme_id, theme_name in cur.fetchall():
|
||||||
|
|
@ -3528,10 +3568,12 @@ def list_region_permit_catalog(region_id: str) -> List[Dict[str, Any]]:
|
||||||
p.name AS permit_name,
|
p.name AS permit_name,
|
||||||
rtp.theme_id,
|
rtp.theme_id,
|
||||||
COALESCE(t.name, '') AS theme_name,
|
COALESCE(t.name, '') AS theme_name,
|
||||||
COUNT(rpr.risk_id) OVER (PARTITION BY rtp.permit_id) AS risk_total
|
COUNT(rpr.risk_id) OVER (PARTITION BY rtp.permit_id) AS risk_total,
|
||||||
|
COALESCE(rpd.is_v2_visible, true) AS is_v2_visible
|
||||||
FROM region_theme_permits rtp
|
FROM region_theme_permits rtp
|
||||||
JOIN permits p ON p.id = rtp.permit_id
|
JOIN permits p ON p.id = rtp.permit_id
|
||||||
LEFT JOIN themes t ON t.id = rtp.theme_id
|
LEFT JOIN themes t ON t.id = rtp.theme_id
|
||||||
|
LEFT JOIN region_permit_details rpd ON rpd.region_id = rtp.region_id AND rpd.permit_id = rtp.permit_id
|
||||||
LEFT JOIN region_permit_risks rpr
|
LEFT JOIN region_permit_risks rpr
|
||||||
ON rpr.region_id = rtp.region_id
|
ON rpr.region_id = rtp.region_id
|
||||||
AND rpr.permit_id = rtp.permit_id
|
AND rpr.permit_id = rtp.permit_id
|
||||||
|
|
@ -3540,9 +3582,10 @@ def list_region_permit_catalog(region_id: str) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
catalog_map: "OrderedDict[str, Dict[str, Any]]" = OrderedDict()
|
catalog_map: "OrderedDict[str, Dict[str, Any]]" = OrderedDict()
|
||||||
with _lic_pg_conn() as conn:
|
with _lic_pg_conn() as conn:
|
||||||
|
_ensure_v2_visibility_column(conn)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql, (region_id,))
|
cur.execute(sql, (region_id,))
|
||||||
for permit_id, permit_name, theme_id, theme_name, risk_total in cur.fetchall():
|
for permit_id, permit_name, theme_id, theme_name, risk_total, v2_visible in cur.fetchall():
|
||||||
pid = str(permit_id)
|
pid = str(permit_id)
|
||||||
entry = catalog_map.setdefault(
|
entry = catalog_map.setdefault(
|
||||||
pid,
|
pid,
|
||||||
|
|
@ -3550,6 +3593,7 @@ def list_region_permit_catalog(region_id: str) -> List[Dict[str, Any]]:
|
||||||
"id": pid,
|
"id": pid,
|
||||||
"name": str(permit_name),
|
"name": str(permit_name),
|
||||||
"risk_count": int(risk_total or 0),
|
"risk_count": int(risk_total or 0),
|
||||||
|
"is_v2_visible": bool(v2_visible),
|
||||||
"theme": {"id": "", "name": ""},
|
"theme": {"id": "", "name": ""},
|
||||||
"themes": [],
|
"themes": [],
|
||||||
},
|
},
|
||||||
|
|
@ -3713,7 +3757,10 @@ def _load_permit_sources_for_region(
|
||||||
|
|
||||||
|
|
||||||
def load_permits_and_risks(
|
def load_permits_and_risks(
|
||||||
region_id: str, theme_id: Optional[str] = None, permit_id: Optional[str] = None
|
region_id: str,
|
||||||
|
theme_id: Optional[str] = None,
|
||||||
|
permit_id: Optional[str] = None,
|
||||||
|
only_visible: bool = False
|
||||||
) -> List[Dict[str, object]]:
|
) -> List[Dict[str, object]]:
|
||||||
"""Return permits with attached risk entries for a region (optionally filtered by theme)."""
|
"""Return permits with attached risk entries for a region (optionally filtered by theme)."""
|
||||||
# Ensure optional permit file tables exist before running user queries.
|
# Ensure optional permit file tables exist before running user queries.
|
||||||
|
|
@ -3747,7 +3794,8 @@ def load_permits_and_risks(
|
||||||
rpd.filler_name,
|
rpd.filler_name,
|
||||||
COALESCE(pad.department_name, rpd.unit_name) AS unit_name,
|
COALESCE(pad.department_name, rpd.unit_name) AS unit_name,
|
||||||
rpd.source_update_date,
|
rpd.source_update_date,
|
||||||
rpd.contact_info
|
rpd.contact_info,
|
||||||
|
COALESCE(rpd.is_v2_visible, true) AS is_v2_visible
|
||||||
FROM region_permit_details rpd
|
FROM region_permit_details rpd
|
||||||
JOIN permits p ON p.id = rpd.permit_id
|
JOIN permits p ON p.id = rpd.permit_id
|
||||||
LEFT JOIN permit_approval_departments pad
|
LEFT JOIN permit_approval_departments pad
|
||||||
|
|
@ -3770,6 +3818,8 @@ def load_permits_and_risks(
|
||||||
if permit_id is not None:
|
if permit_id is not None:
|
||||||
sql += " AND rpd.permit_id = %s"
|
sql += " AND rpd.permit_id = %s"
|
||||||
params.append(permit_id)
|
params.append(permit_id)
|
||||||
|
if only_visible:
|
||||||
|
sql += " AND COALESCE(rpd.is_v2_visible, true) = true"
|
||||||
|
|
||||||
sql += """
|
sql += """
|
||||||
ORDER BY p.name, LENGTH(rpr.serial_number), rpr.serial_number, rk.risk_content
|
ORDER BY p.name, LENGTH(rpr.serial_number), rpr.serial_number, rk.risk_content
|
||||||
|
|
@ -3777,6 +3827,7 @@ def load_permits_and_risks(
|
||||||
permits: Dict[str, Dict[str, object]] = {}
|
permits: Dict[str, Dict[str, object]] = {}
|
||||||
risk_seen_map: Dict[str, Set[str]] = {} # pid -> set of risk_ids
|
risk_seen_map: Dict[str, Set[str]] = {} # pid -> set of risk_ids
|
||||||
with _lic_pg_conn() as conn:
|
with _lic_pg_conn() as conn:
|
||||||
|
_ensure_v2_visibility_column(conn)
|
||||||
_ensure_contact_info_column(conn)
|
_ensure_contact_info_column(conn)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql, tuple(params))
|
cur.execute(sql, tuple(params))
|
||||||
|
|
@ -3801,6 +3852,7 @@ def load_permits_and_risks(
|
||||||
unit_name,
|
unit_name,
|
||||||
source_update_date,
|
source_update_date,
|
||||||
contact_info,
|
contact_info,
|
||||||
|
v2_visible,
|
||||||
) = row
|
) = row
|
||||||
pid = str(permit_id)
|
pid = str(permit_id)
|
||||||
theme_id_value = str(row_theme_id) if row_theme_id else ""
|
theme_id_value = str(row_theme_id) if row_theme_id else ""
|
||||||
|
|
@ -4252,7 +4304,8 @@ def find_permit_contexts_by_name(permit_name: str) -> List[Dict[str, str]]:
|
||||||
rtp.theme_id,
|
rtp.theme_id,
|
||||||
t.name AS theme_name,
|
t.name AS theme_name,
|
||||||
p.id AS permit_id,
|
p.id AS permit_id,
|
||||||
p.name AS permit_name
|
p.name AS permit_name,
|
||||||
|
COALESCE(rpd.is_v2_visible, true) AS is_v2_visible
|
||||||
FROM region_permit_details rpd
|
FROM region_permit_details rpd
|
||||||
JOIN permits p ON p.id = rpd.permit_id
|
JOIN permits p ON p.id = rpd.permit_id
|
||||||
JOIN regions r ON r.id = rpd.region_id
|
JOIN regions r ON r.id = rpd.region_id
|
||||||
|
|
@ -4263,6 +4316,7 @@ def find_permit_contexts_by_name(permit_name: str) -> List[Dict[str, str]]:
|
||||||
"""
|
"""
|
||||||
ordered: OrderedDict[Tuple[str, str], Dict[str, str]] = OrderedDict()
|
ordered: OrderedDict[Tuple[str, str], Dict[str, str]] = OrderedDict()
|
||||||
with _lic_pg_conn() as conn:
|
with _lic_pg_conn() as conn:
|
||||||
|
_ensure_v2_visibility_column(conn)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql, (permit_name,))
|
cur.execute(sql, (permit_name,))
|
||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
|
|
@ -4276,7 +4330,8 @@ def find_permit_contexts_by_name(permit_name: str) -> List[Dict[str, str]]:
|
||||||
rtp.theme_id,
|
rtp.theme_id,
|
||||||
t.name AS theme_name,
|
t.name AS theme_name,
|
||||||
p.id AS permit_id,
|
p.id AS permit_id,
|
||||||
p.name AS permit_name
|
p.name AS permit_name,
|
||||||
|
COALESCE(rpd.is_v2_visible, true) AS is_v2_visible
|
||||||
FROM region_permit_details rpd
|
FROM region_permit_details rpd
|
||||||
JOIN permits p ON p.id = rpd.permit_id
|
JOIN permits p ON p.id = rpd.permit_id
|
||||||
JOIN regions r ON r.id = rpd.region_id
|
JOIN regions r ON r.id = rpd.region_id
|
||||||
|
|
@ -4289,7 +4344,7 @@ def find_permit_contexts_by_name(permit_name: str) -> List[Dict[str, str]]:
|
||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
region_id, region_name, theme_id, theme_name, permit_id, canonical_name = row
|
region_id, region_name, theme_id, theme_name, permit_id, canonical_name, v2_visible = row
|
||||||
rid = str(region_id)
|
rid = str(region_id)
|
||||||
pid = str(permit_id)
|
pid = str(permit_id)
|
||||||
tid = str(theme_id) if theme_id else ""
|
tid = str(theme_id) if theme_id else ""
|
||||||
|
|
@ -4304,11 +4359,12 @@ def find_permit_contexts_by_name(permit_name: str) -> List[Dict[str, str]]:
|
||||||
"theme_name": tname,
|
"theme_name": tname,
|
||||||
"permit_id": pid,
|
"permit_id": pid,
|
||||||
"permit_name": str(canonical_name),
|
"permit_name": str(canonical_name),
|
||||||
|
"is_v2_visible": bool(v2_visible),
|
||||||
}
|
}
|
||||||
return list(ordered.values())
|
return [item for item in ordered.values() if item.get("is_v2_visible")]
|
||||||
|
|
||||||
|
|
||||||
def load_theme_payload(region_id: str, theme_id: str) -> Dict[str, object]:
|
def load_theme_payload(region_id: str, theme_id: str, only_visible: bool = False) -> Dict[str, object]:
|
||||||
"""Assemble full data bundle for a region-theme selection."""
|
"""Assemble full data bundle for a region-theme selection."""
|
||||||
info_sql = """
|
info_sql = """
|
||||||
SELECT r.id, r.name, t.id, t.name
|
SELECT r.id, r.name, t.id, t.name
|
||||||
|
|
@ -4326,7 +4382,7 @@ def load_theme_payload(region_id: str, theme_id: str) -> Dict[str, object]:
|
||||||
raise ValueError("Region/theme combination not found")
|
raise ValueError("Region/theme combination not found")
|
||||||
region_uuid, region_name, theme_uuid, theme_name = row
|
region_uuid, region_name, theme_uuid, theme_name = row
|
||||||
|
|
||||||
permits = load_permits_and_risks(region_id, theme_id)
|
permits = load_permits_and_risks(region_id, theme_id, only_visible=only_visible)
|
||||||
return {
|
return {
|
||||||
"region": {"id": str(region_uuid), "name": str(region_name)},
|
"region": {"id": str(region_uuid), "name": str(region_name)},
|
||||||
"theme": {"id": str(theme_uuid), "name": str(theme_name)},
|
"theme": {"id": str(theme_uuid), "name": str(theme_name)},
|
||||||
|
|
@ -6170,23 +6226,25 @@ def filter_permits_advanced(
|
||||||
themes: Optional[List[str]] = None,
|
themes: Optional[List[str]] = None,
|
||||||
departments: Optional[List[str]] = None,
|
departments: Optional[List[str]] = None,
|
||||||
search_text: Optional[str] = None,
|
search_text: Optional[str] = None,
|
||||||
|
visibility: Optional[str] = None, # 'visible', 'hidden' or None/all
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Filter permits using multiple dimensions (region, theme, department, search text).
|
"""Filter permits using multiple dimensions (region, theme, department, search text, visibility).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
regions: List of region IDs to filter by (supports multi-select)
|
regions: List of region IDs to filter by (supports multi-select)
|
||||||
themes: List of theme IDs to filter by (supports multi-select)
|
themes: List of theme IDs to filter by (supports multi-select)
|
||||||
departments: List of department IDs to filter by (supports multi-select)
|
departments: List of department IDs to filter by (supports multi-select)
|
||||||
search_text: Search in permit name
|
search_text: Search in permit name
|
||||||
|
visibility: Filter by v2 visibility ('visible', 'hidden')
|
||||||
limit: Maximum number of results to return
|
limit: Maximum number of results to return
|
||||||
offset: Offset for pagination
|
offset: Offset for pagination
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing filtered permits and metadata
|
Dictionary containing filtered permits and metadata
|
||||||
"""
|
"""
|
||||||
print(f"[DEBUG] filter_permits_advanced called with limit={limit}, offset={offset}")
|
print(f"[DEBUG] filter_permits_advanced called with limit={limit}, offset={offset}, visibility={visibility}")
|
||||||
# Use subquery to avoid DISTINCT with window functions issue
|
# Use subquery to avoid DISTINCT with window functions issue
|
||||||
# Subquery to get unique permits matching filters with pagination
|
# Subquery to get unique permits matching filters with pagination
|
||||||
# We use a CTE to ensure limit/offset apply to unique permits, not to rows (which can duplicate per theme)
|
# We use a CTE to ensure limit/offset apply to unique permits, not to rows (which can duplicate per theme)
|
||||||
|
|
@ -6212,6 +6270,11 @@ def filter_permits_advanced(
|
||||||
base_where += f" AND LOWER(p.name) LIKE LOWER(%s)"
|
base_where += f" AND LOWER(p.name) LIKE LOWER(%s)"
|
||||||
base_params.append(f"%{search_text}%")
|
base_params.append(f"%{search_text}%")
|
||||||
|
|
||||||
|
if visibility == 'visible':
|
||||||
|
base_where += " AND (rpd.is_v2_visible IS TRUE OR rpd.is_v2_visible IS NULL)"
|
||||||
|
elif visibility == 'hidden':
|
||||||
|
base_where += " AND rpd.is_v2_visible IS FALSE"
|
||||||
|
|
||||||
sql = f"""
|
sql = f"""
|
||||||
WITH filtered_p AS (
|
WITH filtered_p AS (
|
||||||
SELECT rpd.permit_id, rpd.region_id
|
SELECT rpd.permit_id, rpd.region_id
|
||||||
|
|
@ -6236,7 +6299,8 @@ def filter_permits_advanced(
|
||||||
rtp.theme_id,
|
rtp.theme_id,
|
||||||
t.name AS theme_name,
|
t.name AS theme_name,
|
||||||
COALESCE(risk_counts.risk_count, 0) AS risk_count,
|
COALESCE(risk_counts.risk_count, 0) AS risk_count,
|
||||||
COALESCE(theme_counts.theme_count, 0) AS theme_count
|
COALESCE(theme_counts.theme_count, 0) AS theme_count,
|
||||||
|
COALESCE(rpd.is_v2_visible, true) AS is_v2_visible
|
||||||
FROM filtered_p fp
|
FROM filtered_p fp
|
||||||
JOIN region_permit_details rpd ON rpd.permit_id = fp.permit_id AND rpd.region_id = fp.region_id
|
JOIN region_permit_details rpd ON rpd.permit_id = fp.permit_id AND rpd.region_id = fp.region_id
|
||||||
JOIN permits p ON p.id = rpd.permit_id
|
JOIN permits p ON p.id = rpd.permit_id
|
||||||
|
|
@ -6280,6 +6344,7 @@ def filter_permits_advanced(
|
||||||
theme_name,
|
theme_name,
|
||||||
risk_count,
|
risk_count,
|
||||||
theme_count,
|
theme_count,
|
||||||
|
v2_visible,
|
||||||
) in cur.fetchall():
|
) in cur.fetchall():
|
||||||
pid = str(permit_id)
|
pid = str(permit_id)
|
||||||
key = f"{pid}_{rid}"
|
key = f"{pid}_{rid}"
|
||||||
|
|
@ -6294,6 +6359,7 @@ def filter_permits_advanced(
|
||||||
"themes": [],
|
"themes": [],
|
||||||
"risk_count": int(risk_count or 0),
|
"risk_count": int(risk_count or 0),
|
||||||
"theme_count": int(theme_count or 0),
|
"theme_count": int(theme_count or 0),
|
||||||
|
"is_v2_visible": bool(v2_visible),
|
||||||
}
|
}
|
||||||
|
|
||||||
if tid or theme_name:
|
if tid or theme_name:
|
||||||
|
|
@ -6340,8 +6406,72 @@ def filter_permits_advanced(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_v2_visibility_column(conn: Optional[pg.Connection] = None) -> None:
|
||||||
|
"""Ensure that the is_v2_visible column exists in region_permit_details."""
|
||||||
|
global _V2_VISIBILITY_SCHEMA_READY
|
||||||
|
if _V2_VISIBILITY_SCHEMA_READY:
|
||||||
|
return
|
||||||
|
|
||||||
|
with _V2_VISIBILITY_SCHEMA_LOCK:
|
||||||
|
if _V2_VISIBILITY_SCHEMA_READY:
|
||||||
|
return
|
||||||
|
|
||||||
|
sql = "ALTER TABLE region_permit_details ADD COLUMN IF NOT EXISTS is_v2_visible BOOLEAN DEFAULT TRUE"
|
||||||
|
|
||||||
|
if conn is not None:
|
||||||
|
original_autocommit = conn.autocommit
|
||||||
|
try:
|
||||||
|
conn.autocommit = True
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(sql)
|
||||||
|
finally:
|
||||||
|
conn.autocommit = original_autocommit
|
||||||
|
else:
|
||||||
|
with _lic_pg_conn(autocommit=True) as ensure_conn:
|
||||||
|
cur = ensure_conn.cursor()
|
||||||
|
cur.execute(sql)
|
||||||
|
|
||||||
|
_V2_VISIBILITY_SCHEMA_READY = True
|
||||||
|
|
||||||
|
|
||||||
def _ensure_contact_info_column(conn: pg.Connection) -> None:
|
def _ensure_contact_info_column(conn: pg.Connection) -> None:
|
||||||
"Ensure that the contact_info column exists in region_permit_details."
|
"Ensure that the contact_info column exists in region_permit_details."
|
||||||
# This check is now redundant since schema fix script was run, but kept for safety
|
# This check is now redundant since schema fix script was run, but kept for safety
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def update_permit_v2_visibility(
|
||||||
|
region_id: str,
|
||||||
|
permit_id: str,
|
||||||
|
is_visible: bool,
|
||||||
|
operator: str = "admin"
|
||||||
|
) -> bool:
|
||||||
|
"""Update the is_v2_visible flag for a specific region-permit pair."""
|
||||||
|
_ensure_v2_visibility_column()
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
UPDATE region_permit_details
|
||||||
|
SET is_v2_visible = %s,
|
||||||
|
updated_at = now()
|
||||||
|
WHERE region_id = %s AND permit_id = %s
|
||||||
|
"""
|
||||||
|
|
||||||
|
with _lic_pg_conn() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute(sql, (is_visible, region_id, permit_id))
|
||||||
|
count = cur.rowcount
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
log_operation(
|
||||||
|
operator=operator,
|
||||||
|
operation_type="UPDATE",
|
||||||
|
target_type="PERMIT_VISIBILITY",
|
||||||
|
target_id=f"{region_id}:{permit_id}",
|
||||||
|
target_name=f"Permit {permit_id} in region {region_id}",
|
||||||
|
change_summary=f"Set is_v2_visible to {is_visible}",
|
||||||
|
details={"is_visible": is_visible, "region_id": region_id, "permit_id": permit_id}
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2329,6 +2329,57 @@
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toggle Switch Styles */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 44px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: .3s;
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
transition: .3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked+.slider {
|
||||||
|
background-color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked+.slider:before {
|
||||||
|
transform: translateX(22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled+.slider {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
@ -2514,6 +2565,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 启用状态筛选 -->
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<label style="font-size: 13px; font-weight: 600; color: #555;">启用状态</label>
|
||||||
|
<select id="filterVisibility"
|
||||||
|
style="padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; background: white;">
|
||||||
|
<option value="all">全部状态</option>
|
||||||
|
<option value="visible">已启用</option>
|
||||||
|
<option value="hidden">已隐藏</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 搜索关键词 -->
|
<!-- 搜索关键词 -->
|
||||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
<label style="font-size: 13px; font-weight: 600; color: #555;">搜索关键词</label>
|
<label style="font-size: 13px; font-weight: 600; color: #555;">搜索关键词</label>
|
||||||
|
|
@ -6424,6 +6486,8 @@
|
||||||
const departmentCheckboxes = document.querySelectorAll('input[name="departmentFilter"]:checked');
|
const departmentCheckboxes = document.querySelectorAll('input[name="departmentFilter"]:checked');
|
||||||
const departments = Array.from(departmentCheckboxes).map(cb => cb.value);
|
const departments = Array.from(departmentCheckboxes).map(cb => cb.value);
|
||||||
|
|
||||||
|
const visibility = document.getElementById('filterVisibility')?.value || 'all';
|
||||||
|
|
||||||
const searchText = document.getElementById('filterSearchText')?.value || '';
|
const searchText = document.getElementById('filterSearchText')?.value || '';
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
|
|
@ -6432,7 +6496,8 @@
|
||||||
departments: departments.length > 0 ? departments : null,
|
departments: departments.length > 0 ? departments : null,
|
||||||
search_text: searchText.trim() || null,
|
search_text: searchText.trim() || null,
|
||||||
limit: permitPageSize,
|
limit: permitPageSize,
|
||||||
offset: permitCurrentPage * permitPageSize
|
offset: permitCurrentPage * permitPageSize,
|
||||||
|
visibility: visibility !== 'all' ? visibility : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// 显示加载状态
|
// 显示加载状态
|
||||||
|
|
@ -6808,6 +6873,63 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换许可事项可见性
|
||||||
|
async function togglePermitVisibility(permitId, regionId, currentStatus, event) {
|
||||||
|
const nextStatus = !currentStatus;
|
||||||
|
|
||||||
|
// 立即禁用开关防止重复点击
|
||||||
|
if (event && event.target) {
|
||||||
|
event.target.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/fs-ai-asistant/api/workflow/lawrisk/admin/permits/visibility', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
permit_id: permitId,
|
||||||
|
region_id: regionId,
|
||||||
|
is_v2_visible: nextStatus
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(data.message || '更新失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即更新前端状态参数以便下次点击
|
||||||
|
if (event && event.target) {
|
||||||
|
event.target.setAttribute('onclick', `togglePermitVisibility('${permitId}', '${regionId}', ${nextStatus}, event)`);
|
||||||
|
|
||||||
|
// 可选:给个微弱的小提示
|
||||||
|
const row = event.target.closest('tr');
|
||||||
|
if (row) {
|
||||||
|
const originalBg = row.style.backgroundColor;
|
||||||
|
row.style.backgroundColor = '#f0fdf4';
|
||||||
|
setTimeout(() => row.style.backgroundColor = originalBg, 500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
applyPermitFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新可见性失败:', error);
|
||||||
|
showAlert('error', '更新失败:' + error.message);
|
||||||
|
// 恢复原状
|
||||||
|
if (event && event.target) {
|
||||||
|
event.target.checked = currentStatus;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (event && event.target) {
|
||||||
|
event.target.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 删除许可事项
|
// 删除许可事项
|
||||||
async function deletePermit(permitId, regionId) {
|
async function deletePermit(permitId, regionId) {
|
||||||
if (!confirm('确定要删除该许可事项吗?此操作不可恢复,并且会创建风险快照。')) {
|
if (!confirm('确定要删除该许可事项吗?此操作不可恢复,并且会创建风险快照。')) {
|
||||||
|
|
@ -6873,6 +6995,7 @@
|
||||||
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 180px;">行政区域</th>
|
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 180px;">行政区域</th>
|
||||||
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 120px;">主题</th>
|
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 120px;">主题</th>
|
||||||
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 100px;">风险数</th>
|
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 100px;">风险数</th>
|
||||||
|
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 100px;">启用</th>
|
||||||
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 180px;">操作</th>
|
<th style="padding: 14px 16px; text-align: left; font-weight: 600; color: #555; font-size: 14px; width: 180px;">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -6895,6 +7018,13 @@
|
||||||
<td style="padding: 16px; color: #666;">${escapeHtml(permit.region?.name || '-')}</td>
|
<td style="padding: 16px; color: #666;">${escapeHtml(permit.region?.name || '-')}</td>
|
||||||
<td style="padding: 16px; color: #666;">${permit.theme_count || 0} 个</td>
|
<td style="padding: 16px; color: #666;">${permit.theme_count || 0} 个</td>
|
||||||
<td style="padding: 16px; color: #666;">${permit.risk_count || 0}</td>
|
<td style="padding: 16px; color: #666;">${permit.risk_count || 0}</td>
|
||||||
|
<td style="padding: 16px;">
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" ${permit.is_v2_visible ? 'checked' : ''}
|
||||||
|
onclick="togglePermitVisibility('${permit.id}', '${regionId}', ${permit.is_v2_visible}, event)">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
<td style="padding: 16px;">
|
<td style="padding: 16px;">
|
||||||
<button onclick="viewPermitDetail('${permit.id}', '${regionId}')" style="padding: 6px 12px; background: #2c5282; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; margin-right: 8px;">
|
<button onclick="viewPermitDetail('${permit.id}', '${regionId}')" style="padding: 6px 12px; background: #2c5282; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; margin-right: 8px;">
|
||||||
查看
|
查看
|
||||||
|
|
@ -6968,6 +7098,9 @@
|
||||||
if (departmentSelect) departmentSelect.value = '';
|
if (departmentSelect) departmentSelect.value = '';
|
||||||
if (searchInput) searchInput.value = '';
|
if (searchInput) searchInput.value = '';
|
||||||
|
|
||||||
|
const filterVisibility = document.getElementById('filterVisibility');
|
||||||
|
if (filterVisibility) filterVisibility.value = 'all';
|
||||||
|
|
||||||
permitCurrentPage = 0;
|
permitCurrentPage = 0;
|
||||||
|
|
||||||
// 清空结果
|
// 清空结果
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue