Преглед на файлове

Merge branch 'master' of https://gogs.tjp.com.cn/maoming/python-fastapi-mm-zhcs-yj-api

baoyubo преди 10 месеца
родител
ревизия
7f16b29d80

+ 2 - 1
common/db/db_area.py

@@ -10,4 +10,5 @@ from utils import *
 
 # 根据经纬度获取地区代码
 def get_region_code_by_gps(db: Session, lng: str, lat: str):
-    return "4409"
+    return "4409"
+

+ 3 - 3
exceptions.py

@@ -19,10 +19,10 @@ class Common404Exception(Exception):
 
 
 class AppException(Exception):
-    def __init__(self, errcode: int, errmsg: str):
+    def __init__(self, code: int, msg: str):
         super().__init__()
-        self.errcode = errcode
-        self.errmsg = errmsg
+        self.code = code
+        self.msg = msg
 
 class AlertException(Exception):
     def __init__(self, ret: int, msg: str):

+ 2 - 0
jobs/yzy_job.py

@@ -18,6 +18,7 @@ def proc():
     if redis_lock(lock_key):
         logger.info(datetime.now())
 
+        '''
         redirect_url = "{}/leader/index".format(settings.YJXP_WEB_ROOT_PATH) # 业务页面
         detail_url = YzyApi.format_redirect_url(redirect_url, "eb4kehgy6wj4qn0jhx1dk6")
         
@@ -43,6 +44,7 @@ def proc():
             YzyApi.add_to_msg_queue(db, data)
         db.close()
 
+        '''
         redis_unlock(lock_key)
 
 def yzy_msg_queue_proc():

+ 2 - 1
models/__init__.py

@@ -18,4 +18,5 @@ from .geojson_base import *
 from .xxfb_base import *
 from .addressbook_base import *
 from .risk_management import *
-from .online_roll_call import *
+from .online_roll_call import *
+from .duty_base import *

+ 49 - 0
models/duty_base.py

@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+from sqlalchemy import String, Column, Integer,DateTime,Text,BigInteger,Boolean,PrimaryKeyConstraint,Index,UniqueConstraint,CHAR,LargeBinary,TIMESTAMP
+from sqlalchemy.dialects.mysql import TINYINT
+from sqlalchemy.sql import func
+from database import Base
+from datetime import datetime
+
+class DutyShift(Base):
+    """
+    值班日历
+    """
+    __tablename__ = 'duty_shift'
+   
+    shift_id = Column(Integer, primary_key=True, autoincrement=True, comment='ID')
+    shift_date = Column(DateTime, nullable=False, comment="班次日期")
+    start_time = Column(DateTime, nullable=False, comment="开始时间")
+    end_time = Column(DateTime, nullable=False, comment="结束时间")
+    leader_id = Column(Integer, nullable=False, comment="领导ID")
+    primary_staff_id = Column(Integer, nullable=False, comment="主班人员ID")
+    secondary_staff_id = Column(Integer, nullable=False, comment="副班人员ID")
+    standby_staff_id = Column(Integer, nullable=False, comment="备班人员ID")
+
+    duty_type = Column(String, default='', server_default='', comment="值班类型")
+    shfit_status = Column(Integer, default='0', server_default='0', comment="值班状态 0 默认 1已交班")
+    handover_user_id = Column(Integer, comment="交班人员ID")
+    handover_time = Column(DateTime, comment="交班时间")
+    dept_id = Column(Integer, nullable=False, comment="部门ID")
+    area_code = Column(String, default='', server_default='', comment="行政区划代码")
+    
+    class Config:
+        orm_mode = True
+
+
+class DutyNotify(Base):
+    """
+    值班事项提醒
+    """
+    __tablename__ = 'duty_notify'
+   
+    id = Column(Integer, primary_key=True, autoincrement=True, comment='ID')
+    shift_id = Column(Integer, default='0', server_default='0', comment="班次ID")
+    notify_content = Column(String, default='', server_default='', comment="提示内容")
+    notify_type = Column(Integer, default='0', server_default='0', comment="1 待办事项 2 提醒事项")
+    create_time = Column(DateTime, default=datetime.now, comment='数据创建时间')
+    del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
+    recorded_by = Column(Integer, default=0, server_default='0', comment='记录用户ID')
+
+    class Config:
+        orm_mode = True

+ 4 - 4
models/online_roll_call.py

@@ -14,6 +14,7 @@ class OnlineRollCallBase(Base):
     create_time = Column(DateTime, default=datetime.now, comment='数据创建时间')
     end_time = Column(DateTime, comment='结束时间')
     del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
+    call_status = Column(Integer, default=0, server_default='0', comment='点名状态 1 进行中 2 已结束')
     user_count = Column(Integer, default=0, server_default='0', comment='用户数')
     ack_count = Column(Integer, default=0, server_default='0', comment='应答数')
     unack_count = Column(Integer, default=0, server_default='0', comment='未应答数')
@@ -32,16 +33,15 @@ class OnlineRollCallDetail(Base):
 
     id = Column(BigInteger, primary_key=True, autoincrement=True, comment='ID')
     pid = Column(BigInteger, default=0, server_default='0', comment='关联主表ID')
-    user_id = Column(Integer, default=0, server_default='0', comment='关联用户ID')
+    shift_id = Column(Integer, default=0, server_default='0', comment='值班ID')
     dept_id = Column(Integer, default=0, server_default='0', comment='关联用户所在部门ID')
-    nick_name = Column(String, default='', server_default='', comment='用户姓名')
     dept_name = Column(String, default='', server_default='', comment='部门名称')
     onduty_user = Column(String, default='', server_default='', comment='值班员(显示)')
     onduty_leader = Column(String, default='', server_default='', comment='值班领导(显示)')
     video_url = Column(String, default='', server_default='', comment='视频URL')
     call_url = Column(String, default='', server_default='', comment='电话URL')
-    ack_status = Column(Integer, default=0, server_default='0', comment='应答状态 0 未应答 1 已应答')
-    act_time = Column(DateTime, comment='应答时间')
+    ack_status = Column(Integer, default=0, server_default='0', comment='应答状态 0 未应答 1 已接通 2 呼叫中')
+    ack_time = Column(DateTime, comment='应答时间')
     create_time = Column(DateTime, default=datetime.now, comment='数据创建时间')
     del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
     ack_type = Column(Integer, default=0, server_default='0', comment='应答方式 1 视频  2 电话')

+ 1 - 0
models/ry_sys_base.py

@@ -310,6 +310,7 @@ class SysMenuLayer(Base):
     update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
     remark = Column(String(500), default='', comment='备注')
     del_flag = Column(String(1), default='0', comment='删除标志(0代表存在 2代表删除)')
+    layer_template = Column(String(100), default='', comment='图层模板')
 
     class Config:
         orm_mode = True

+ 2 - 1
routers/api/__init__.py

@@ -23,7 +23,7 @@ from . import rainfall
 from . import infoPublish
 from . import riskManagement
 from . import onlineRollCall
-
+from . import layerConfiguration
 from routers.prod_api import system
 from routers.prod_api import auth
 
@@ -47,6 +47,7 @@ router.include_router(taskRegistration.router, prefix="/taskRegistration")
 router.include_router(emergencyPlans.router, prefix="/emergency_plan")
 router.include_router(riskMonitor.router, prefix="/risk_monitor")
 router.include_router(temperaturePrecipitation.router, prefix="/temperature_precipitation")
+router.include_router(layerConfiguration.router, prefix="/layerConfiguration", tags=["图层配置"])
 
 router.include_router(eventManagement.router, prefix="/event_management", tags=["事件管理"])
 router.include_router(spatialAnalysis.router, prefix="/spatial_analysis", tags=["空间分析"])

+ 1 - 0
routers/api/eventManagement/checkin.py

@@ -5,6 +5,7 @@ from fastapi.responses import JSONResponse
 from database import get_db
 from sqlalchemy import text, exists, and_, or_, not_
 from sqlalchemy.orm import Session
+from sqlalchemy.sql import func
 from models import *
 import json
 import random

+ 716 - 0
routers/api/layerConfiguration/__init__.py

@@ -0,0 +1,716 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from fastapi import APIRouter, Request, Depends, Query, HTTPException, status
+from common.security import valid_access_token
+from pydantic import BaseModel
+from database import get_db
+from sqlalchemy.orm import Session
+from typing import List
+from models import *
+from utils import *
+from utils.ry_system_util import *
+import json
+from sqlalchemy.sql import func
+from common.auth_user import *
+
+router = APIRouter()
+
+
+
+# 定义 Meta 和 Child 模型用于嵌套结构
+class Meta(BaseModel):
+    title: str
+    icon: str
+    noCache: bool
+    link: str = None
+
+class Child(BaseModel):
+    name: str
+    path: str
+    hidden: bool
+    component: str
+    meta: Meta
+    children: List['Child'] = []
+
+class Router(BaseModel):
+    name: str
+    path: str
+    hidden: bool
+    redirect: str = 'noRedirect'
+    component: str
+    alwaysShow: bool = True
+    meta: Meta
+    children: List[Child] = []
+class Router_frame(BaseModel):
+    component:str
+    hidden: bool
+    meta:Meta
+    name: str
+    path: str
+    redirect: str = 'noRedirect'
+    children: List[Child] = []
+    alwaysShow: bool = True
+# checkedKeys roleMenuTreeselect
+
+@router.get('/roleMenuTreeselect/{roleId}')
+async def getmunutreeselect(request: Request,roleId:int,db: Session = Depends(get_db), user_id: int = Depends(valid_access_token)):
+    def build_dept_tree(menus, parent_dept):
+        menu_tree = []
+        for menu_info in menus:
+            menu = {
+                "id": menu_info.menu_id,
+                "label": menu_info.menu_name,
+                "parentId": menu_info.parent_id,
+                "weight": menu_info.order_num
+            }
+            # print(dept_info.dept_id)
+            children = parent_id_get_menu_info(db, menu_info.menu_id)
+            if len(children) > 0:
+                children_depts = build_dept_tree(children, menu)
+                menu["children"] = children_depts
+            menu_tree.append(menu)
+        return menu_tree
+
+    checkedKeys = role_id_get_role_menus(db,roleId)
+    menus = build_dept_tree(parent_id_get_menu_info(db, 0), None)
+    return {
+        "code": 200,
+        "msg": "操作成功",
+        "data": {"menus":menus,"checkedKeys":checkedKeys}
+    }
+
+@router.get('/treeselect')
+async def getmunutreeselect(request: Request,db: Session = Depends(get_db), user_id: int = Depends(valid_access_token)):
+    def build_dept_tree(menus, parent_dept):
+        menu_tree = []
+        for menu_info in menus:
+            menu = {
+                "id": menu_info.menu_id,
+                "label": menu_info.menu_name,
+                "parentId": menu_info.parent_id,
+                "weight": menu_info.order_num
+            }
+            # print(dept_info.dept_id)
+            children = parent_id_get_menu_info(db, menu_info.menu_id)
+            if len(children) > 0:
+                children_depts = build_dept_tree(children, menu)
+                menu["children"] = children_depts
+            menu_tree.append(menu)
+        return menu_tree
+
+    result = build_dept_tree(parent_id_get_menu_info(db, 0), None)
+    return {
+        "code": 200,
+        "msg": "操作成功",
+        "data": result
+    }
+
+@router.get('/getRouters')
+async def getRouters(request: Request, db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)):
+    try:
+        # 查询数据库中的所有菜单项,根据 parent_id 排序以构建树形结构
+        query = db.query(SysMenu)
+        query = query.filter_by(parent_id=0)
+        query = query.filter(SysMenu.menu_id!=11655)
+        query = query.filter(SysMenu.del_flag != '2')
+        menus =query.all()  # 顶级菜单
+
+        # 递归函数用于构建树形结构
+        def build_menu_tree(menus, parent_menu):
+            menu_tree = []  # 初始化一个列表来存储菜单树结构
+            for menu in menus:
+                if menu.is_frame==0:
+                    menu_data = Router_frame(
+                        component=menu.component or 'Layout',
+                        hidden=menu.visible == '1',
+                        name=menu.path,
+                        path='/'+menu.path,
+                        meta = Meta(
+                            title=menu.menu_name,
+                            icon=menu.icon,
+                            noCache=menu.is_cache == '1',
+                            link = menu.path
+                        ),
+                        children=[]
+                    )
+                else:
+                    menu_data = Router(
+                        name=menu.path,#menu.menu_name,
+                        path='/'+menu.path,
+                        hidden=menu.visible == '1',
+                        component=menu.component or 'Layout',
+                        meta=Meta(
+                            title=menu.menu_name,
+                            icon=menu.icon,
+                            noCache=menu.is_cache == '1'
+                        ),
+                        children=[]  # 初始化 children 列表,即使没有子菜单
+                    )
+                # 如果菜单有子菜单,则递归构建子菜单
+                if menu.menu_type == 'M':  # 假设 'M' 表示目录类型
+                    query = db.query(SysMenu)
+                    query = query.filter_by(parent_id=menu.menu_id)
+                    query = query.filter(SysMenu.del_flag != '2')
+                    # children_menus = db.query(SysMenu).filter_by(parent_id=menu.menu_id).all()
+                    children_menus = query.all()
+                    menu_data.children = build_menu_tree(children_menus, menu)
+                else:
+                    del menu_data.children #没有子菜单,删除children 列表
+                    del menu_data.redirect
+                    del menu_data.alwaysShow
+                    menu_data.path = menu_data.path[1:]
+                print(menu_data)
+                menu_tree.append(menu_data)  # 将当前菜单数据添加到菜单树列表
+            return menu_tree
+
+        # 构建顶级菜单的树形结构
+        routers = build_menu_tree(menus, None)
+        # routers_dict = [router.dict() for router in routers]
+        routers_dict = []
+        for router in routers:
+            router_info = router.dict()
+            if len(router_info['children'])==0:
+                del router_info['children']
+                del router_info['redirect']
+                del router_info['alwaysShow']
+                router_info['path'] = router_info['path'] #[1:]
+
+            routers_dict.append(router_info)
+        # 返回构建好的路由数据
+        return {
+            "code": 200,
+            "msg": "操作成功",
+            "data": routers_dict #[router.dict() for router in routers]  # 如果没有顶级菜单,返回空列表
+        }
+    except Exception as e:
+        # 处理异常,返回错误信息
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+@router.get('/qydt/getRouters')
+async def getRouters(request: Request, db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)):
+    try:
+        # 查询数据库中的所有菜单项,根据 parent_id 排序以构建树形结构
+        query = db.query(SysMenu)
+        query = query.filter_by(parent_id=0)
+        query = query.filter(SysMenu.menu_id==11655)
+        query = query.filter(SysMenu.del_flag != '2')
+        menus =query.all()  # 顶级菜单
+
+        # 递归函数用于构建树形结构
+        def build_menu_tree(menus, parent_menu):
+            menu_tree = []  # 初始化一个列表来存储菜单树结构
+            for menu in menus:
+                if menu.is_frame==0:
+                    menu_data = Router_frame(
+                        component=menu.component or 'Layout',
+                        hidden=menu.visible == '1',
+                        name=menu.path,
+                        path='/'+menu.path,
+                        meta = Meta(
+                            title=menu.menu_name,
+                            icon=menu.icon,
+                            noCache=menu.is_cache == '1',
+                            link = menu.path
+                        ),
+                        children=[]
+                    )
+                else:
+                    menu_data = Router(
+                        name=menu.path,#menu.menu_name,
+                        path='/'+menu.path,
+                        hidden=menu.visible == '1',
+                        component=menu.component or 'Layout',
+                        meta=Meta(
+                            title=menu.menu_name,
+                            icon=menu.icon,
+                            noCache=menu.is_cache == '1'
+                        ),
+                        children=[]  # 初始化 children 列表,即使没有子菜单
+                    )
+                # 如果菜单有子菜单,则递归构建子菜单
+                if menu.menu_type == 'M':  # 假设 'M' 表示目录类型
+                    query = db.query(SysMenu)
+                    query = query.filter_by(parent_id=menu.menu_id)
+                    query = query.filter(SysMenu.del_flag != '2')
+                    # children_menus = db.query(SysMenu).filter_by(parent_id=menu.menu_id).all()
+                    children_menus = query.all()
+                    menu_data.children = build_menu_tree(children_menus, menu)
+                else:
+                    del menu_data.children #没有子菜单,删除children 列表
+                    del menu_data.redirect
+                    del menu_data.alwaysShow
+                    menu_data.path = menu_data.path[1:]
+                menu_tree.append(menu_data)  # 将当前菜单数据添加到菜单树列表
+            return menu_tree
+
+        # 构建顶级菜单的树形结构
+        routers = build_menu_tree(menus, None)
+        # routers_dict = [router.dict() for router in routers]
+        routers_dict = []
+        for router in routers:
+            router_info = router.dict()
+            if len(router_info['children'])==0:
+                del router_info['children']
+                del router_info['redirect']
+                del router_info['alwaysShow']
+                router_info['path'] = router_info['path'][1:]
+
+            routers_dict.append(router_info)
+        # 返回构建好的路由数据
+        return {
+            "code": 200,
+            "msg": "操作成功",
+            "data": routers_dict #[router.dict() for router in routers]  # 如果没有顶级菜单,返回空列表
+        }
+    except Exception as e:
+        # 处理异常,返回错误信息
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+@router.get('/list')
+async def get_list(
+    # request: Request,
+    menuName: str = Query(None, max_length=100),
+    status: str =  Query(None, max_length=100),
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+    query = db.query(SysMenu)
+    query = query.filter(SysMenu.del_flag != '2')
+    if menuName:
+        query = query.filter(SysMenu.menu_name.like(f'%{menuName}%'))
+    if status:
+        query = query.filter(SysMenu.status.like(f'%{status}%'))
+    # 应用查询
+    # menu_list = db.query(SysMenu).filter(
+    #     (SysMenu.menu_name.like(f'%{menu_name}%')) ,
+    #     (SysMenu.status.like(f'%{status}%'))
+    # ).all()
+    menu_list = query.all()
+    # 将模型实例转换为字典
+    menu_list_dict = [{
+            "menuId": menu.menu_id,
+            "menuName": menu.menu_name,
+            "parentId": menu.parent_id,
+            "orderNum": menu.order_num,
+            "path": menu.path,
+            "component": menu.component,
+            "queryParam": menu.query_param,
+            "isFrame": str(menu.is_frame),
+            "isCache": str(menu.is_cache),
+            "menuType": menu.menu_type,
+            "visible": menu.visible,
+            "status": menu.status,
+            "perms": menu.perms,
+            "icon": menu.icon,
+            "createDept": menu.create_dept,
+            "remark": menu.remark,
+            "createTime": menu.create_time.strftime('%Y-%m-%d %H:%M:%S') if menu.create_time else '',
+            "children": []  # 递归调用以获取子菜单
+        } for menu in menu_list]
+
+    # 构建分页响应
+    # pagination_info = {
+    #     "total": total_count,
+    #     "page_num": page_num,
+    #     "page_size": page_size,
+    #     "total_pages": (total_count + page_size - 1) // page_size  # 计算总页数
+    # }
+
+    return {
+        "code": 200,
+        "data": menu_list_dict,
+        # 'pages': page_num,  # 总页数
+        # 'currentPage': page_num,  # 当前页数
+        # # 'current':current,
+        # # 'total' : total,
+        # 'total': total_count,  # 总数据量
+        # # 'size':size,
+        # 'pageSize': page_size,  # 页码
+        #        {
+        #     "items": menu_list_dict,
+        #     "pagination": pagination_info
+        # },
+        "msg": "操作成功"
+    }
+
+
+@router.get('/layer_list')
+async def get_list(
+    # request: Request,
+    menuName: str = Query(None, max_length=100),
+    status: str =  Query(None, max_length=100),
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+    query = db.query(SysMenuLayer)
+    query = query.filter(SysMenuLayer   .del_flag != '2')
+    if menuName:
+        query = query.filter(SysMenuLayer.menu_name.like(f'%{menuName}%'))
+    if status:
+        query = query.filter(SysMenuLayer.status.like(f'%{status}%'))
+    # 应用查询
+    # menu_list = db.query(SysMenu).filter(
+    #     (SysMenu.menu_name.like(f'%{menu_name}%')) ,
+    #     (SysMenu.status.like(f'%{status}%'))
+    # ).all()
+    menu_list = query.all()
+    # 将模型实例转换为字典
+    menu_list_dict = [{
+            "menuId": menu.menu_id,
+            "menuName": menu.menu_name,
+            "parentId": menu.parent_id,
+            "orderNum": menu.order_num,
+            "path": menu.path,
+            "component": menu.component,
+            "queryParam": menu.query_param,
+            "isFrame": str(menu.is_frame),
+            "isCache": str(menu.is_cache),
+            "menuType": menu.menu_type,
+            "visible": menu.visible,
+            "status": menu.status,
+            "perms": menu.perms,
+            "icon": menu.icon,
+            "createDept": menu.create_dept,
+            "remark": menu.remark,
+            "createTime": menu.create_time.strftime('%Y-%m-%d %H:%M:%S') if menu.create_time else '',
+            "children": []  # 递归调用以获取子菜单
+        } for menu in menu_list]
+
+    # 构建分页响应
+    # pagination_info = {
+    #     "total": total_count,
+    #     "page_num": page_num,
+    #     "page_size": page_size,
+    #     "total_pages": (total_count + page_size - 1) // page_size  # 计算总页数
+    # }
+
+    return {
+        "code": 200,
+        "data": menu_list_dict,
+        # 'pages': page_num,  # 总页数
+        # 'currentPage': page_num,  # 当前页数
+        # # 'current':current,
+        # # 'total' : total,
+        # 'total': total_count,  # 总数据量
+        # # 'size':size,
+        # 'pageSize': page_size,  # 页码
+        #        {
+        #     "items": menu_list_dict,
+        #     "pagination": pagination_info
+        # },
+        "msg": "操作成功"
+    }
+
+
+@router.get('/{menuid}')
+async def get_list(
+    # request: Request,
+    menuid: str = Query(None, max_length=100),
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+    query = db.query(SysMenuLayer)
+    query = query.filter(SysMenuLayer.del_flag != '2')
+    if menuid:
+        query = query.filter(SysMenuLayer.menu_id.like(f'{menuid}'))
+
+
+    menu= query.first()
+    # 将模型实例转换为字典
+    menu_list_dict = {
+            "menuId": menu.menu_id,
+            "menuName": menu.menu_name,
+            "parentId": menu.parent_id,
+            "orderNum": menu.order_num,
+            "path": menu.path,
+            "component": menu.component,
+            "queryParam": menu.query_param,
+            "isFrame": str(menu.is_frame),
+            "isCache": str(menu.is_cache),
+            "menuType": menu.menu_type,
+            "visible": menu.visible,
+            "status": menu.status,
+            "perms": menu.perms,
+            "icon": menu.icon,
+            "createDept": menu.create_dept,
+            "remark": menu.remark,
+            "createTime": menu.create_time.strftime('%Y-%m-%d %H:%M:%S') if menu.create_time else '',
+            "children": [],  # 递归调用以获取子菜单
+            "layer_template":menu.layer_template
+        }
+
+    return {
+        "code": 200,
+        "data": menu_list_dict,
+        "msg": "操作成功"
+    }
+
+
+class SysMuneCreateForm(BaseModel):
+    component:str = None
+    icon : str
+    isCache: str
+    isFrame:str
+    menuName: str
+    menuType:str
+    orderNum:int
+    parentId:int
+    path:str
+    perms:str=None
+    queryParam:str=None
+    status:str
+    visible:str
+    layer_template:str
+
+
+@router.post('/create')
+async def create(
+        form_data: SysMuneCreateForm,
+        db: Session = Depends(get_db),
+        body=Depends(remove_xss_json),
+        user_id=Depends(valid_access_token)
+):
+    try:
+        # 开始事务
+        db.begin()
+
+        # 创建新的菜单项
+        new_menu = SysMenu(
+            menu_name=form_data.menuName,
+            parent_id=form_data.parentId,
+            order_num=form_data.orderNum,
+            path=form_data.path,
+            is_frame=int(form_data.isFrame),
+            is_cache=int(form_data.isCache),
+            menu_type=form_data.menuType,
+            visible=form_data.visible,
+            status=form_data.status,
+            icon=form_data.icon,
+            component=form_data.component,
+            perms=form_data.perms,
+            query_param=form_data.queryParam,
+            create_by=user_id
+        )
+        db.add(new_menu)
+
+        # 创建新的菜单层,此时还不能使用new_menu.menu_id,因为它还没有被赋值
+        new_menu_layer = SysMenuLayer(
+            menu_id=None,  # 先设置为None,稍后更新
+            menu_name=form_data.menuName,
+            parent_id=form_data.parentId,
+            order_num=form_data.orderNum,
+            path=form_data.path,
+            is_frame=int(form_data.isFrame),
+            is_cache=int(form_data.isCache),
+            menu_type=form_data.menuType,
+            visible=form_data.visible,
+            status=form_data.status,
+            icon=form_data.icon,
+            component=form_data.component,
+            perms=form_data.perms,
+            query_param=form_data.queryParam,
+            create_by=user_id,
+            layer_template=form_data.layer_template
+        )
+        db.add(new_menu_layer)
+
+        # 提交事务之前刷新new_menu以确保menu_id被数据库生成
+        db.flush()  # 使用flush而不是commit,这样可以保存数据但不结束事务
+        db.refresh(new_menu)  # 刷新new_menu对象以获取数据库生成的menu_id
+        new_menu_layer.menu_id = new_menu.menu_id  # 现在可以设置new_menu_layer的menu_id了
+
+        # 如果需要,可以在这里再次刷新new_menu_layer,但通常不是必需的
+        # db.refresh(new_menu_layer)
+
+        # 提交事务
+        db.commit()
+
+        return {
+            "code": 200,
+            "data": new_menu_layer.menu_id,  # 返回新创建的SysMenuLayer的ID
+            "msg": "操作成功"
+        }
+    except Exception as e:
+        # 如果发生异常,回滚事务
+        db.rollback()
+        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
+
+
+class SysMuneUpdateForm(BaseModel):
+    menuId : str
+    component:str = None
+    icon : str  = None
+    isCache: str = None
+    isFrame:str = None
+    menuName: str = None
+    menuType:str = None
+    orderNum:int = None
+    parentId:int = None
+    path:str = None
+    perms:str=None
+    queryParam:str=None
+    status:str = None
+    visible:str = None
+
+@router.put('')
+async def update(
+    request: Request,
+    # form_data: SysMuneUpdateForm,
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+
+    try:
+        db.begin()
+        query = db.query(SysMenu)
+        query = query.filter(SysMenu.menu_id == body['menuId'])
+        query = query.filter(SysMenu.del_flag != '2')
+        menu = query.first()
+        if not menu:
+            detail = "菜单不存在"
+            raise HTTPException(status_code=404, detail="菜单不存在")
+
+        # 更新字段,排除主键和不可更新的字段
+        if 'component' in body:
+            menu.component=body['component']
+        if 'icon' in body:
+            menu.icon=body['icon']
+        if 'isCache' in body:
+            menu.is_cache=body['isCache']
+        if 'isFrame' in body:
+            menu.is_frame=body['isFrame']
+        if 'menuName' in body:
+            menu.menu_name=body['menuName']
+        if 'menuType' in body:
+            menu.menu_type=body['menuType']
+        if 'orderNum' in body:
+            menu.order_num=body['orderNum']
+        if 'parentId' in body:
+            menu.parent_id=body['parentId']
+        if 'path' in body:
+            menu.path=body['path']
+        if 'perms' in body:
+            menu.perms=body['perms']
+        if 'queryParam' in body:
+            menu.query_param=body['queryParam']
+        if 'status' in body:
+            menu.status=body['status']
+        if 'visible' in body:
+            menu.visible=body['visible']
+        if user_id:
+            menu.create_by = user_id
+        # for field, value in menu_data.items():
+        #     if field != 'menu_id' and field in menu_to_update.__dict__:
+        #         setattr(menu_to_update, field, value)
+        #
+        # db.add(menu_to_update)
+        # db.commit()
+
+
+        queryLayer = db.query(SysMenuLayer)
+        queryLayer = queryLayer.filter(SysMenuLayer.menu_id == body['menuId'])
+        queryLayer = queryLayer.filter(SysMenuLayer.del_flag != '2')
+        menu = queryLayer.first()
+        if not menu:
+            detail = "菜单不存在"
+            raise HTTPException(status_code=404, detail="菜单不存在")
+
+        # 更新字段,排除主键和不可更新的字段
+        if 'component' in body:
+            menu.component = body['component']
+        if 'icon' in body:
+            menu.icon = body['icon']
+        if 'isCache' in body:
+            menu.is_cache = body['isCache']
+        if 'isFrame' in body:
+            menu.is_frame = body['isFrame']
+        if 'menuName' in body:
+            menu.menu_name = body['menuName']
+        if 'menuType' in body:
+            menu.menu_type = body['menuType']
+        if 'orderNum' in body:
+            menu.order_num = body['orderNum']
+        if 'parentId' in body:
+            menu.parent_id = body['parentId']
+        if 'path' in body:
+            menu.path = body['path']
+        if 'perms' in body:
+            menu.perms = body['perms']
+        if 'queryParam' in body:
+            menu.query_param = body['queryParam']
+        if 'status' in body:
+            menu.status = body['status']
+        if 'visible' in body:
+            menu.visible = body['visible']
+        if user_id:
+            menu.create_by = user_id
+        if user_id:
+            menu.layer_template =body['layer_template']
+        db.commit()
+        return {
+            "code": 200,
+            "msg": "菜单更新成功"
+        }
+    except Exception as e:
+        # db.rollback()
+        if str(e)=='':
+            e = detail
+        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
+
+
+
+@router.delete('/{menu_id}')
+async def delete(
+    menu_id: int,
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+    try:
+        db.begin()
+        query = db.query(SysMenu)
+        query = query.filter(SysMenu.menu_id == menu_id)
+        query = query.filter(SysMenu.del_flag != '2')
+
+        menu_to_delete =query.first()
+        if not menu_to_delete:
+            detail = "菜单不存在"
+            raise HTTPException(status_code=404, detail="菜单不存在")
+        menu_to_delete.create_by = user_id
+        menu_to_delete.del_flag='2'
+        # db.delete(menu_to_delete)
+        # db.commit()
+
+
+        queryLayer = db.query(SysMenuLayer)
+        queryLayer = queryLayer.filter(SysMenuLayer.menu_id == menu_id)
+        queryLayer = queryLayer.filter(SysMenuLayer.del_flag != '2')
+
+        menu_to_delete = queryLayer.first()
+        if not menu_to_delete:
+            detail = "菜单不存在"
+            raise HTTPException(status_code=404, detail="菜单不存在")
+        menu_to_delete.create_by = user_id
+        menu_to_delete.del_flag = '2'
+
+        db.commit()
+        return {
+            "code": 200,
+            "msg": "菜单删除成功"
+        }
+    except Exception as e:
+        db.rollback()
+        if str(e)=='':
+            e = detail
+        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))

+ 482 - 48
routers/api/onlineRollCall/call.py

@@ -5,13 +5,14 @@ from fastapi import APIRouter, Request, Depends, Query, HTTPException
 from database import get_db
 from sqlalchemy import text, exists, and_, or_, not_
 from sqlalchemy.orm import Session
+from sqlalchemy.sql import func
 from models import *
-import json
+import uuid
 import os
 from sqlalchemy import create_engine, select
 from typing import Optional
 from utils.StripTagsHTMLParser import *
-from common.db import db_event_management, db_user, db_area, db_emergency_plan
+from common.db import db_event_management, db_user, db_area, db_dept
 from common.security import valid_access_token
 import traceback
 from utils import *
@@ -21,34 +22,268 @@ from common.db import db_dict
 from urllib.parse import quote
 import base64
 from config import settings
+from exceptions import AppException
+from typing import List, Dict,Set
 
 router = APIRouter()
 
-@router.post("/create")
-async def create_all(
+@router.get("/dept_by_key", response_model=Dict)
+def dept_by_key(db: Session = Depends(get_db), area_name: str = Query(None), keyword: str = Query(None)):
+
+    current_date = datetime.now().date()
+    current_time  = datetime.now().time()
+
+    def get_dept_tree(db: Session, dept_id: int):
+        dept = db.query(SysDept).filter(SysDept.dept_id == dept_id).first()
+        if not dept:
+            return None
+        
+        children = []
+        for child in db.query(SysDept).filter(SysDept.parent_id == dept.dept_id).all():
+            child = get_dept_tree(db, child.dept_id)
+            if child is not None:
+                children.append(child)
+
+        # 获取值班信息
+        '''
+        q = db.query(DutyShift)
+        q = q.filter(DutyShift.shift_date == current_date)
+        q = q.filter(DutyShift.shfit_status == 0)
+        q = q.filter(DutyShift.start_time < current_time).filter(DutyShift.end_time > current_time)
+        shift_row = q.first()
+        if shift_row is not None:
+            children.append({
+                "uuid":str(uuid.uuid4()).replace('-',''),
+                "id": shift_row.shift_id,
+                "label": str(shift_row.shift_id),
+                "deptType": False
+            })
+        '''
+
+        dept_info = {
+            "uuid":str(uuid.uuid4()).replace('-',''),
+            "id": dept.dept_id,
+            "label": dept.dept_name,
+            "deptType": True
+        }
+        if len(children) > 0:
+            dept_info["children"] = children
+
+        return dept_info
+    
+    dept_id = db_dept.get_dept_id_by_name(db, area_name)
+    if dept_id == 0:
+        return {
+            "code": 200,
+            "msg": "成功",
+            "data": []
+        }
+    print('dept_id:', dept_id)
+    data =  get_dept_tree(db, dept_id)
+    return {
+        "code": 200,
+        "msg": "成功",
+        "data": [data]
+    }
+
+# 一键点名全市至区县
+@router.post("/create_by_city_to_area")
+async def create_by_city_to_area(
     db: Session = Depends(get_db),
-    body = Depends(remove_xss_json),
     user_id = Depends(valid_access_token)
 ):
+    current_date = datetime.now().date()
+    current_time  = datetime.now().time()
     try:
-        dept_id = 0
-        dept_name = ''
+        # 过滤当前时间内的值班人员
+        q = db.query(DutyShift).filter(func.char_length(DutyShift.area_code) <= 6)
+        q = q.filter(DutyShift.shift_date == current_date)
+        q = q.filter(DutyShift.shfit_status == 0)
+        q = q.filter(DutyShift.start_time < current_time).filter(DutyShift.end_time > current_time)
+        rows = q.all()
+        
+        user_count = len(rows)
+        if user_count == 0:
+            raise AppException(500, "暂无相关值班人员信息")
+
+        new_call = OnlineRollCallBase(
+            call_type = 1,
+            recorded_by = user_id,
+            create_time = datetime.now(),
+            del_flag = '0',
+            call_status = 1,
+            user_count = user_count,
+            ack_count = 0,
+            unack_count = user_count,
+            remark = '',
+            update_time = datetime.now()
+        )
+        db.add(new_call)
+        db.commit()
+        db.refresh(new_call)
+        new_call_id = new_call.id
+
+        dept_list = []
+        for row in rows:
+            shift_id = row.shift_id
+            shift_dept_id = row.dept_id
+            shift_dept_name = db_dept.get_dept_name_by_id(db, row.dept_id)
 
-        user_row = db.query(SysUser).filter(SysUser.user_id == user_id).first()
-        user_name = user_row.user_name
-        nick_name = user_row.nick_name
-        dept_id = user_row.dept_id
+            onduty_user = db_user.get_nick_name_by_id(db, row.primary_staff_id)
+            onduty_leader = db_user.get_nick_name_by_id(db, row.leader_id)
+            video_url = "#"
+            call_url = "#"
 
-        dept_row = db.query(SysDept).filter(SysDept.dept_id == dept_id).first()
-        dept_name = dept_row.dept_name
+            # 避免同一个部门重复
+            if shift_dept_id in dept_list:
+                continue
+
+            dept_list.append(shift_dept_id)
+            new_detail = OnlineRollCallDetail(
+                pid = new_call_id,
+                shift_id = shift_id,
+                dept_id = shift_dept_id,
+                dept_name = shift_dept_name,
+                onduty_user = onduty_user,
+                onduty_leader = onduty_leader,
+                video_url = video_url,
+                call_url = call_url,
+                ack_status = 0,
+                ack_time = None,
+                create_time = datetime.now(),
+                del_flag = '0',
+                ack_type = 0
+            )
+            db.add(new_detail)
+            db.commit()
+
+        return {
+            "code": 200,
+            "msg": "点名创建成功",
+            "data": new_call_id
+        }
+
+    except AppException as e:
+        return {
+            "code": e.code,
+            "msg": e.msg
+        }
+
+    except Exception as e:
+        traceback.print_exc()
+        # 处理异常
+        raise HTTPException(status_code=500, detail=str(e))
+    
+
+# 一键点名全市至镇街
+@router.post("/create_by_city_to_district")
+async def create_by_city_to_district(
+    db: Session = Depends(get_db),
+    user_id = Depends(valid_access_token)
+):
+    current_date = datetime.now().date()
+    current_time  = datetime.now().time()
+    try:
+        # 过滤当前时间内的值班人员
+        q = db.query(DutyShift).filter(func.char_length(DutyShift.area_code) <= 9)
+        q = q.filter(DutyShift.shift_date == current_date)
+        q = q.filter(DutyShift.shfit_status == 0)
+        q = q.filter(DutyShift.start_time < current_time).filter(DutyShift.end_time > current_time)
+        rows = q.all()
+        
+        user_count = len(rows)
+        if user_count == 0:
+            raise AppException(500, "暂无相关值班人员信息")
 
         new_call = OnlineRollCallBase(
-            call_type = body['call_type'],
+            call_type = 1,
             recorded_by = user_id,
-            create_time = datetime.now,
+            create_time = datetime.now(),
             del_flag = '0',
             call_status = 1,
-            user_count = body['user_count'],
+            user_count = user_count,
+            ack_count = 0,
+            unack_count = user_count,
+            remark = '',
+            update_time = datetime.now()
+        )
+        db.add(new_call)
+        db.commit()
+        db.refresh(new_call)
+        new_call_id = new_call.id
+
+        dept_list = []
+        for row in rows:
+            shift_id = row.shift_id
+            shift_dept_id = row.dept_id
+            shift_dept_name = db_dept.get_dept_name_by_id(db, row.dept_id)
+
+            onduty_user = db_user.get_nick_name_by_id(db, row.primary_staff_id)
+            onduty_leader = db_user.get_nick_name_by_id(db, row.leader_id)
+            video_url = "#"
+            call_url = "#"
+
+            # 避免同一个部门重复
+            if shift_dept_id in dept_list:
+                continue
+
+            dept_list.append(shift_dept_id)
+            new_detail = OnlineRollCallDetail(
+                pid = new_call_id,
+                shift_id = shift_id,
+                dept_id = shift_dept_id,
+                dept_name = shift_dept_name,
+                onduty_user = onduty_user,
+                onduty_leader = onduty_leader,
+                video_url = video_url,
+                call_url = call_url,
+                ack_status = 0,
+                ack_time = None,
+                create_time = datetime.now(),
+                del_flag = '0',
+                ack_type = 0
+            )
+            db.add(new_detail)
+            db.commit()
+
+        return {
+            "code": 200,
+            "msg": "点名创建成功",
+            "data": new_call_id
+        }
+
+    except AppException as e:
+        return {
+            "code": e.code,
+            "msg": e.msg
+        }
+
+    except Exception as e:
+        traceback.print_exc()
+        # 处理异常
+        raise HTTPException(status_code=500, detail=str(e))
+
+
+# 新建点名(分区/县点名)
+@router.post("/create_by_dept_ids")
+async def create_by_dept_ids(
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+    current_date = datetime.now().date()
+    current_time  = datetime.now().time()
+    try:
+        if len(body['depts']) == 0:
+            raise AppException(500, "请勾选部门")
+        
+        new_call = OnlineRollCallBase(
+            call_type = 3,
+            recorded_by = user_id,
+            create_time = datetime.now(),
+            del_flag = '0',
+            call_status = 1,
+            user_count = 0,
             ack_count = 0,
             unack_count = 0,
             remark = body['remark'],
@@ -59,56 +294,73 @@ async def create_all(
         db.refresh(new_call)
         new_call_id = new_call.id
 
-        for u in body['users']:
-            call_user_id = u['user_id']
-            call_dept_id = 0
-            call_nick_name = ''
-            call_dept_name = ''
-            user_row = db.query(SysUser).filter(SysUser.user_id == call_user_id).first()
-            if user_row is not None:
-                call_nick_name = user_row.nick_name
-                call_dept_row = db.query(SysDept).filter(SysDept.dept_id == user_row.dept_id).first()
-                call_dept_id = call_dept_row.dept_id
-                call_dept_name = call_dept_row.dept_name
-
-                # 关联值班班获取值班人员信息
-                onduty_user = "-1"
-                onduty_leader = "-1"
-                video_url = "..."
-                call_url = "..."
+        for dept_id in body['depts']:
+            # 过滤当前时间内的值班人员
+            q = db.query(DutyShift).filter(DutyShift.dept_id == dept_id)
+            q = q.filter(DutyShift.shift_date == current_date)
+            q = q.filter(DutyShift.shfit_status == 0)
+            q = q.filter(DutyShift.start_time < current_time).filter(DutyShift.end_time > current_time)
+            row = q.first()
+            
+            if row is not None:
+                print(dept_id)
+            
+                shift_id = row.shift_id
+                shift_dept_id = row.dept_id
+                shift_dept_name = db_dept.get_dept_name_by_id(db, row.dept_id)
 
+                onduty_user = db_user.get_nick_name_by_id(db, row.primary_staff_id)
+                onduty_leader = db_user.get_nick_name_by_id(db, row.leader_id)
+                video_url = "#"
+                call_url = "#"
+                
                 new_detail = OnlineRollCallDetail(
                     pid = new_call_id,
-                    user_id = call_user_id,
-                    dept_id = call_dept_id,
-                    nick_name = call_nick_name,
-                    dept_name = call_dept_name,
+                    shift_id = shift_id,
+                    dept_id = shift_dept_id,
+                    dept_name = shift_dept_name,
                     onduty_user = onduty_user,
                     onduty_leader = onduty_leader,
                     video_url = video_url,
                     call_url = call_url,
                     ack_status = 0,
-                    act_time = None,
+                    ack_time = None,
                     create_time = datetime.now(),
                     del_flag = '0',
                     ack_type = 0
                 )
                 db.add(new_detail)
                 db.commit()
-            else:
-                raise Exception("用户不存在")
+
+        # 检查多次,避免人员为空
+        user_count = db.query(OnlineRollCallDetail).filter(OnlineRollCallDetail.pid == new_call_id).count()
+        if user_count == 0:
+            db.query(OnlineRollCallBase).filter(OnlineRollCallBase.id == new_call_id).delete()
+            db.commit()
+
+            raise AppException(500, "暂无相关值班人员信息")
+        
+        db.query(OnlineRollCallBase).filter(OnlineRollCallBase.id == new_call_id).update({"user_count": user_count, "unack_count": user_count})
+        db.commit()
 
         return {
             "code": 200,
             "msg": "点名创建成功",
             "data": new_call_id
         }
+    
+    except AppException as e:
+        return {
+            "code": e.code,
+            "msg": e.msg
+        }
 
     except Exception as e:
         traceback.print_exc()
         # 处理异常
         raise HTTPException(status_code=500, detail=str(e))
     
+
 # 结束应答
 @router.post("/end")
 async def end_call(
@@ -168,7 +420,7 @@ async def ack_all(
         }
     
     detail_row.ack_type = ack_type
-    detail_row.act_time = datetime.now()
+    detail_row.ack_time = datetime.now()
     db.commit()
 
     # 统计应答数
@@ -226,18 +478,32 @@ async def get_event_detail(
             }
         
         data = get_model_dict(base_row)
-        data['create_time'] = get_datetime_str(data['create_time'])
+        data['begin_time'] = get_datetime_str(data['create_time'])
         data['update_time'] = get_datetime_str(data['update_time'])
+        data['duration_time'] = ''
+
+        # 已应答
+        if data['call_status'] == 1:
+
+            # 处理垃圾数据
+            if base_row.end_time is None:
+                base_row.end_time = datetime.now()
+                db.commit()
+
+            time_diff = base_row.end_time - base_row.create_time
+            # hours,minutes,seconds = str(time_diff).split(':')
+            data['duration_time'] = str(time_diff)
     
         detail_rows = db.query(OnlineRollCallDetail).filter(OnlineRollCallDetail.pid == call_id).all()
-        users = []
+        items = []
         for row in detail_rows:
             detail_info = get_model_dict(row)
-            detail_info['create_time'] = get_datetime_str(detail_info['create_time'])
-            detail_info['act_time'] = get_datetime_str(detail_info['act_time'])
-            users.append(detail_info)
+            detail_info['begin_time'] = get_datetime_str(detail_info['create_time'])
+            detail_info['ack_time'] = get_datetime_str(detail_info['ack_time'])
+            detail_info['ack_status_text'] = get_ack_status_text(detail_info['ack_status'])
+            items.append(detail_info)
 
-        data['users'] = users
+        data['items'] = items
 
         return {
             "code": 200,
@@ -248,4 +514,172 @@ async def get_event_detail(
     except Exception as e:
         # 处理异常
         traceback.print_exc()
-        raise HTTPException(status_code=500, detail=str(e))
+        raise HTTPException(status_code=500, detail=str(e))
+    
+
+def get_ack_status_text(ack_status: int) -> str:
+    if ack_status == 0:
+        return '未应答'
+    elif ack_status == 1:
+        return '已接通'
+    elif ack_status == 2:
+        return '呼叫中'
+    else:
+        return str(ack_status)
+    
+#应答详情
+@router.get('/list')
+async def get_event_list(
+    request: Request,
+    begin_date: str = Query('', description='开始时间'),
+    end_date: str = Query('', description='结束时间'), 
+    page: int = Query(1, gt=0, description='页码'),
+    page_size: int = Query(10, gt=0, description='pageSize'),
+    db: Session = Depends(get_db)):
+
+    try:
+        where = and_(OnlineRollCallBase.del_flag == '0')
+
+        if begin_date is not None and begin_date != '':
+            begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
+            where = and_(where, OnlineRollCallBase.create_time > begin_date)
+
+        if end_date is not None and end_date != '':
+            end_date = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
+            where = and_(where, OnlineRollCallBase.create_time < end_date)
+
+        print(where)
+        
+        # 计算总条目数
+        q = db.query(func.count(OnlineRollCallBase.id))
+        q = q.filter(where)
+        total = q.scalar()
+        
+        # 执行分页查询
+        q = db.query(OnlineRollCallBase)
+        q = q.filter(where)
+        rows = q.order_by(OnlineRollCallBase.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
+        data = []
+
+        for row in rows:
+            duration_time = ""
+            # 已结束
+            if row.call_status == 2:
+                time_diff = row.end_time - row.create_time
+                # hours,minutes,seconds = str(time_diff).split(':')
+                duration_time = str(time_diff)
+                
+            data.append({
+                "id": row.id,
+                "begin_time": get_datetime_str(row.create_time),
+                "end_time": get_datetime_str(row.end_time),
+                "duration_time": duration_time,
+                "call_status": row.call_status,
+                "ack_count": row.ack_count,
+                "unack_count": row.unack_count
+            })
+
+        # 返回结果
+        return {
+            "code": 200,
+            "msg": "查询成功",
+            "data": data,
+            "total": total
+        }
+
+    except Exception as e:
+        # 处理异常
+        traceback.print_exc()
+        raise HTTPException(status_code=500, detail=str(e))
+    
+
+#应答列表
+@router.get('/ack_list')
+async def get_event_list(
+    request: Request,
+    begin_date: str = Query('', description='开始时间'),
+    end_date: str = Query('', description='结束时间'), 
+    page: int = Query(1, gt=0, description='页码'),
+    page_size: int = Query(10, gt=0, description='pageSize'),
+    db: Session = Depends(get_db)):
+
+    try:
+        where = and_(OnlineRollCallDetail.del_flag == '0')
+
+        if begin_date is not None and begin_date != '':
+            begin_date = datetime.strptime(begin_date, "%Y-%m-%d")
+            where = and_(where, OnlineRollCallDetail.create_time > begin_date)
+
+        if end_date is not None and end_date != '':
+            end_date = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
+            where = and_(where, OnlineRollCallDetail.create_time < end_date)
+
+        print(where)
+        
+        # 计算总条目数
+        q = db.query(func.count(OnlineRollCallDetail.id))
+        q = q.filter(where)
+        total = q.scalar()
+        
+        # 执行分页查询
+        q = db.query(OnlineRollCallDetail)
+        q = q.filter(where)
+        rows = q.order_by(OnlineRollCallDetail.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
+        data = []
+
+        for row in rows:
+            duration_time = ""
+            # 已应答
+            if row.ack_status == 1:
+                time_diff = row.ack_time - row.create_time
+                # hours,minutes,seconds = str(time_diff).split(':')
+                duration_time = str(time_diff)
+                
+            data.append({
+                "id": row.id,
+                "create_time": get_datetime_str(row.create_time),
+                "duration_time": duration_time,
+                "ack_status": row.ack_status
+            })
+
+        # 返回结果
+        return {
+            "code": 200,
+            "msg": "查询成功",
+            "data": data,
+            "total": total
+        }
+
+    except Exception as e:
+        # 处理异常
+        traceback.print_exc()
+        raise HTTPException(status_code=500, detail=str(e))
+    
+
+# 点名统计
+@router.get("/summary")
+async def get_call_summary(request: Request,
+    db: Session = Depends(get_db)):
+
+    try:
+        call_count = db.query(OnlineRollCallBase).filter(OnlineRollCallBase.del_flag == '0').count()
+        ack_count = db.query(OnlineRollCallDetail).filter(and_(OnlineRollCallDetail.del_flag == '0', OnlineRollCallDetail.ack_status == 1)).count()
+        unack_count = db.query(OnlineRollCallDetail).filter(and_(OnlineRollCallDetail.del_flag == '0', OnlineRollCallDetail.ack_status == 0)).count()
+
+        data = {
+            "call_count": call_count,
+            "ack_count": ack_count,
+            "unack_count": unack_count
+        }
+
+        return {
+            "code": 200,
+            "msg": "查询成功",
+            "data": data
+        }
+
+    except Exception as e:
+        # 处理异常
+        traceback.print_exc()
+        raise HTTPException(status_code=500, detail=str(e))
+    

+ 71 - 67
routers/prod_api/system/menu/__init__.py

@@ -329,6 +329,77 @@ async def get_list(
         "msg": "操作成功"
     }
 
+
+
+@router.get('/layer_list')
+async def get_list(
+    # request: Request,
+    menuName: str = Query(None, max_length=100),
+    status: str =  Query(None, max_length=100),
+    db: Session = Depends(get_db),
+    body = Depends(remove_xss_json),
+    user_id = Depends(valid_access_token)
+):
+    query = db.query(SysMenuLayer)
+    query = query.filter(SysMenuLayer.del_flag != '2')
+    if menuName:
+        query = query.filter(SysMenuLayer.menu_name.like(f'%{menuName}%'))
+    if status:
+        query = query.filter(SysMenuLayer.status.like(f'%{status}%'))
+    # 应用查询
+    # menu_list = db.query(SysMenu).filter(
+    #     (SysMenu.menu_name.like(f'%{menu_name}%')) ,
+    #     (SysMenu.status.like(f'%{status}%'))
+    # ).all()
+    menu_list = query.all()
+    # 将模型实例转换为字典
+    menu_list_dict = [{
+            "menuId": menu.menu_id,
+            "menuName": menu.menu_name,
+            "parentId": menu.parent_id,
+            "orderNum": menu.order_num,
+            "path": menu.path,
+            "component": menu.component,
+            "queryParam": menu.query_param,
+            "isFrame": str(menu.is_frame),
+            "isCache": str(menu.is_cache),
+            "menuType": menu.menu_type,
+            "visible": menu.visible,
+            "status": menu.status,
+            "perms": menu.perms,
+            "icon": menu.icon,
+            "createDept": menu.create_dept,
+            "remark": menu.remark,
+            "createTime": menu.create_time.strftime('%Y-%m-%d %H:%M:%S') if menu.create_time else '',
+            "children": []  # 递归调用以获取子菜单
+        } for menu in menu_list]
+
+    # 构建分页响应
+    # pagination_info = {
+    #     "total": total_count,
+    #     "page_num": page_num,
+    #     "page_size": page_size,
+    #     "total_pages": (total_count + page_size - 1) // page_size  # 计算总页数
+    # }
+
+    return {
+        "code": 200,
+        "data": menu_list_dict,
+        # 'pages': page_num,  # 总页数
+        # 'currentPage': page_num,  # 当前页数
+        # # 'current':current,
+        # # 'total' : total,
+        # 'total': total_count,  # 总数据量
+        # # 'size':size,
+        # 'pageSize': page_size,  # 页码
+        #        {
+        #     "items": menu_list_dict,
+        #     "pagination": pagination_info
+        # },
+        "msg": "操作成功"
+    }  
+  
+  
 @router.get('/{menuid}')
 async def get_list(
     # request: Request,
@@ -536,70 +607,3 @@ async def delete(
         raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
 
 
-@router.get('/layer_list')
-async def get_list(
-    # request: Request,
-    menuName: str = Query(None, max_length=100),
-    status: str =  Query(None, max_length=100),
-    db: Session = Depends(get_db),
-    body = Depends(remove_xss_json),
-    user_id = Depends(valid_access_token)
-):
-    query = db.query(SysMenuLayer)
-    query = query.filter(SysMenuLayer   .del_flag != '2')
-    if menuName:
-        query = query.filter(SysMenuLayer.menu_name.like(f'%{menuName}%'))
-    if status:
-        query = query.filter(SysMenuLayer.status.like(f'%{status}%'))
-    # 应用查询
-    # menu_list = db.query(SysMenu).filter(
-    #     (SysMenu.menu_name.like(f'%{menu_name}%')) ,
-    #     (SysMenu.status.like(f'%{status}%'))
-    # ).all()
-    menu_list = query.all()
-    # 将模型实例转换为字典
-    menu_list_dict = [{
-            "menuId": menu.menu_id,
-            "menuName": menu.menu_name,
-            "parentId": menu.parent_id,
-            "orderNum": menu.order_num,
-            "path": menu.path,
-            "component": menu.component,
-            "queryParam": menu.query_param,
-            "isFrame": str(menu.is_frame),
-            "isCache": str(menu.is_cache),
-            "menuType": menu.menu_type,
-            "visible": menu.visible,
-            "status": menu.status,
-            "perms": menu.perms,
-            "icon": menu.icon,
-            "createDept": menu.create_dept,
-            "remark": menu.remark,
-            "createTime": menu.create_time.strftime('%Y-%m-%d %H:%M:%S') if menu.create_time else '',
-            "children": []  # 递归调用以获取子菜单
-        } for menu in menu_list]
-
-    # 构建分页响应
-    # pagination_info = {
-    #     "total": total_count,
-    #     "page_num": page_num,
-    #     "page_size": page_size,
-    #     "total_pages": (total_count + page_size - 1) // page_size  # 计算总页数
-    # }
-
-    return {
-        "code": 200,
-        "data": menu_list_dict,
-        # 'pages': page_num,  # 总页数
-        # 'currentPage': page_num,  # 当前页数
-        # # 'current':current,
-        # # 'total' : total,
-        # 'total': total_count,  # 总数据量
-        # # 'size':size,
-        # 'pageSize': page_size,  # 页码
-        #        {
-        #     "items": menu_list_dict,
-        #     "pagination": pagination_info
-        # },
-        "msg": "操作成功"
-    }

+ 2 - 0
utils/__init__.py

@@ -56,6 +56,8 @@ def from_timestamp2(timestamp: int):
         return dt
 
 def get_datetime_str(val: datetime) -> str:
+    if val is None:
+        return ''
     return val.strftime("%Y-%m-%d %H:%M:%S")