diff --git a/.gitignore b/.gitignore index f6c6f3091722b5f5c7604ecbcf99a8ca64bceea0..bd229c98d4c9b714ba614974a65f1990840475c7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ # Distribution / packaging .Python develop-eggs/ +df_admin/ dist/ downloads/ eggs/ diff --git a/README.md b/README.md index 85cc90cfe403f87dde5a97097fc8897dbbde4b2f..38d2f844416dd8d4a65e709a1ec01df7380d08c7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,17 @@

logo

-

Dash-FastAPI-Admin v1.4.2

+

Dash-FastAPI-Admin v2.0.0

基于Dash+FastAPI前后端分离的纯Python快速开发框架

- + - +

- - - - - - - ## 平台简介 Dash-FastAPI-Admin是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 @@ -219,12 +212,15 @@ python3 app.py --env=prod ## 交流与赞助 如果有对本项目及FastAPI感兴趣的朋友,欢迎加入知识星球一起交流学习,让我们一起变得更强。如果你觉得这个项目帮助到了你,你可以请作者喝杯咖啡表示鼓励☕。扫描下面微信二维码添加微信备注DF-Admin即可进群,也欢迎大家加入dash大神费弗里的知识星球学习更多dash开发知识。 + + + + - + -
zanzhu_wxzanzhu_zfb
zsxqzanzhudashzsxq
wxcodedashzsxq
\ No newline at end of file diff --git a/dash-fastapi-backend/.env.dev b/dash-fastapi-backend/.env.dev index 840a1ab8c0a31c8fe02e33b24872a5357b0ad653..931468f1096f726bd88dbb1a2aa9cabf9c363034 100644 --- a/dash-fastapi-backend/.env.dev +++ b/dash-fastapi-backend/.env.dev @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.4.2' +APP_VERSION= '2.0.0' # 应用是否开启热重载 APP_RELOAD = true # 应用是否开启IP归属区域查询 diff --git a/dash-fastapi-backend/.env.prod b/dash-fastapi-backend/.env.prod index ea8f2b6eb06aa37a9d413839ad0d48342bf04dd9..c812b1de72b24e2da5e5ba58fd280248c85436ae 100644 --- a/dash-fastapi-backend/.env.prod +++ b/dash-fastapi-backend/.env.prod @@ -10,7 +10,7 @@ APP_HOST = '0.0.0.0' # 应用端口 APP_PORT = 9099 # 应用版本 -APP_VERSION= '1.4.2' +APP_VERSION= '2.0.0' # 应用是否开启热重载 APP_RELOAD = false # 应用是否开启IP归属区域查询 @@ -37,7 +37,7 @@ DB_PORT = 3306 # 数据库用户名 DB_USERNAME = 'root' # 数据库密码 -DB_PASSWORD = 'mysqlroot' +DB_PASSWORD = 'root' # 数据库名称 DB_DATABASE = 'dash-fastapi' # 是否开启sqlalchemy日志 diff --git a/dash-fastapi-backend/app.py b/dash-fastapi-backend/app.py index 81d0bce88e71b07a7ae61766a3b5f44d7c4eccfb..1ee7695527ae397af30eec9d905ac7410da05295 100644 --- a/dash-fastapi-backend/app.py +++ b/dash-fastapi-backend/app.py @@ -1,113 +1,12 @@ -from fastapi import FastAPI, Request import uvicorn -from fastapi.exceptions import HTTPException -from fastapi.middleware.cors import CORSMiddleware -from module_admin.controller.login_controller import loginController -from module_admin.controller.captcha_controller import captchaController -from module_admin.controller.user_controller import userController -from module_admin.controller.menu_controller import menuController -from module_admin.controller.dept_controller import deptController -from module_admin.controller.role_controller import roleController -from module_admin.controller.post_controler import postController -from module_admin.controller.dict_controller import dictController -from module_admin.controller.config_controller import configController -from module_admin.controller.notice_controller import noticeController -from module_admin.controller.log_controller import logController -from module_admin.controller.online_controller import onlineController -from module_admin.controller.job_controller import jobController -from module_admin.controller.server_controller import serverController -from module_admin.controller.cache_controller import cacheController -from module_admin.controller.common_controller import commonController -from config.env import AppConfig -from config.get_redis import RedisUtil -from config.get_db import init_create_table -from config.get_scheduler import SchedulerUtil -from utils.response_util import * -from utils.log_util import logger -from utils.common_util import worship +from server import app, AppConfig # noqa: F401 -app = FastAPI( - title=AppConfig.app_name, - description=f'{AppConfig.app_name}接口文档', - version=AppConfig.app_version, - root_path=AppConfig.app_root_path, -) - -# 前端页面url -origins = [ - "http://localhost:8088", - "http://127.0.0.1:8088", -] - -# 后台api允许跨域 -app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -@app.on_event("startup") -async def startup_event(): - logger.info(f"{AppConfig.app_name}开始启动") - worship() - await init_create_table() - app.state.redis = await RedisUtil.create_redis_pool() - await RedisUtil.init_sys_dict(app.state.redis) - await RedisUtil.init_sys_config(app.state.redis) - await SchedulerUtil.init_system_scheduler() - logger.info(f"{AppConfig.app_name}启动成功") - - -@app.on_event("shutdown") -async def shutdown_event(): - await RedisUtil.close_redis_pool(app) - await SchedulerUtil.close_system_scheduler() - - -# 自定义token检验异常 -@app.exception_handler(AuthException) -async def auth_exception_handler(request: Request, exc: AuthException): - return response_401(data=exc.data, message=exc.message) - - -# 自定义权限检验异常 -@app.exception_handler(PermissionException) -async def permission_exception_handler(request: Request, exc: PermissionException): - return response_403(data=exc.data, message=exc.message) - - -@app.exception_handler(HTTPException) -async def http_exception_handler(request: Request, exc: HTTPException): - return JSONResponse( - content=jsonable_encoder({"message": exc.detail, "code": exc.status_code}), - status_code=exc.status_code - ) - - -app.include_router(loginController, prefix="/login", tags=['登录模块']) -app.include_router(captchaController, prefix="/captcha", tags=['验证码模块']) -app.include_router(userController, prefix="/system", tags=['系统管理-用户管理']) -app.include_router(menuController, prefix="/system", tags=['系统管理-菜单管理']) -app.include_router(deptController, prefix="/system", tags=['系统管理-部门管理']) -app.include_router(roleController, prefix="/system", tags=['系统管理-角色管理']) -app.include_router(postController, prefix="/system", tags=['系统管理-岗位管理']) -app.include_router(dictController, prefix="/system", tags=['系统管理-字典管理']) -app.include_router(configController, prefix="/system", tags=['系统管理-参数管理']) -app.include_router(noticeController, prefix="/system", tags=['系统管理-通知公告管理']) -app.include_router(logController, prefix="/system", tags=['系统管理-日志管理']) -app.include_router(onlineController, prefix="/monitor", tags=['系统监控-在线用户']) -app.include_router(jobController, prefix="/monitor", tags=['系统监控-定时任务']) -app.include_router(serverController, prefix="/monitor", tags=['系统监控-服务监控']) -app.include_router(cacheController, prefix="/monitor", tags=['系统监控-缓存监控']) -app.include_router(commonController, prefix="/common", tags=['通用模块']) if __name__ == '__main__': uvicorn.run( app='app:app', host=AppConfig.app_host, port=AppConfig.app_port, - reload=AppConfig.app_reload + root_path=AppConfig.app_root_path, + reload=AppConfig.app_reload, ) diff --git a/dash-fastapi-backend/caches/avatar/admin/admin_avatar.jpeg b/dash-fastapi-backend/caches/avatar/admin/admin_avatar.jpeg deleted file mode 100644 index 6fda0548459e5a43be8be8dfa745bfaabbad6b0c..0000000000000000000000000000000000000000 Binary files a/dash-fastapi-backend/caches/avatar/admin/admin_avatar.jpeg and /dev/null differ diff --git a/dash-fastapi-backend/caches/avatar/ry/ry_avatar.jpeg b/dash-fastapi-backend/caches/avatar/ry/ry_avatar.jpeg deleted file mode 100644 index 7a683af3b9fceb35b19a74c94ce5da212b2b89e9..0000000000000000000000000000000000000000 Binary files a/dash-fastapi-backend/caches/avatar/ry/ry_avatar.jpeg and /dev/null differ diff --git a/dash-fastapi-backend/config/constant.py b/dash-fastapi-backend/config/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..6db32dad38752eb48024d794cecf763008e75453 --- /dev/null +++ b/dash-fastapi-backend/config/constant.py @@ -0,0 +1,152 @@ +class CommonConstant: + """ + 常用常量 + + WWW: www主域 + HTTP: http请求 + HTTPS: https请求 + LOOKUP_RMI: RMI远程方法调用 + LOOKUP_LDAP: LDAP远程方法调用 + LOOKUP_LDAPS: LDAPS远程方法调用 + YES: 是否为系统默认(是) + NO: 是否为系统默认(否) + DEPT_NORMAL: 部门正常状态 + DEPT_DISABLE: 部门停用状态 + UNIQUE: 校验是否唯一的返回标识(是) + NOT_UNIQUE: 校验是否唯一的返回标识(否) + """ + + WWW = 'www.' + HTTP = 'http://' + HTTPS = 'https://' + LOOKUP_RMI = 'rmi:' + LOOKUP_LDAP = 'ldap:' + LOOKUP_LDAPS = 'ldaps:' + YES = 'Y' + NO = 'N' + DEPT_NORMAL = '0' + DEPT_DISABLE = '1' + UNIQUE = True + NOT_UNIQUE = False + + +class HttpStatusConstant: + """ + 返回状态码 + + SUCCESS: 操作成功 + CREATED: 对象创建成功 + ACCEPTED: 请求已经被接受 + NO_CONTENT: 操作已经执行成功,但是没有返回数据 + MOVED_PERM: 资源已被移除 + SEE_OTHER: 重定向 + NOT_MODIFIED: 资源没有被修改 + BAD_REQUEST: 参数列表错误(缺少,格式不匹配) + UNAUTHORIZED: 未授权 + FORBIDDEN: 访问受限,授权过期 + NOT_FOUND: 资源,服务未找到 + BAD_METHOD: 不允许的http方法 + CONFLICT: 资源冲突,或者资源被锁 + UNSUPPORTED_TYPE: 不支持的数据,媒体类型 + ERROR: 系统内部错误 + NOT_IMPLEMENTED: 接口未实现 + WARN: 系统警告消息 + """ + + SUCCESS = 200 + CREATED = 201 + ACCEPTED = 202 + NO_CONTENT = 204 + MOVED_PERM = 301 + SEE_OTHER = 303 + NOT_MODIFIED = 304 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + BAD_METHOD = 405 + CONFLICT = 409 + UNSUPPORTED_TYPE = 415 + ERROR = 500 + NOT_IMPLEMENTED = 501 + WARN = 601 + + +class JobConstant: + """ + 定时任务常量 + + JOB_ERROR_LIST: 定时任务禁止调用模块及违规字符串列表 + JOB_WHITE_LIST: 定时任务允许调用模块列表 + """ + + JOB_ERROR_LIST = [ + 'app', + 'config', + 'exceptions', + 'import ', + 'middlewares', + 'module_admin', + 'open(', + 'os.', + 'server', + 'sub_applications', + 'subprocess.', + 'sys.', + 'utils', + 'while ', + '__import__', + '"', + "'", + ',', + '?', + ':', + ';', + '/', + '|', + '+', + '-', + '=', + '~', + '!', + '#', + '$', + '%', + '^', + '&', + '*', + '<', + '>', + '(', + ')', + '[', + ']', + '{', + '}', + ' ', + ] + JOB_WHITE_LIST = ['module_task'] + + +class MenuConstant: + """ + 菜单常量 + + TYPE_DIR: 菜单类型(目录) + TYPE_MENU: 菜单类型(菜单) + TYPE_BUTTON: 菜单类型(按钮) + YES_FRAME: 是否菜单外链(是) + NO_FRAME: 是否菜单外链(否) + LAYOUT: Layout组件标识 + PARENT_VIEW: ParentView组件标识 + INNER_LINK: InnerLink组件标识 + """ + + TYPE_DIR = 'M' + TYPE_MENU = 'C' + TYPE_BUTTON = 'F' + YES_FRAME = 0 + NO_FRAME = 1 + LAYOUT = 'Layout' + PARENT_VIEW = 'ParentView' + INNER_LINK = 'InnerLink' diff --git a/dash-fastapi-backend/config/database.py b/dash-fastapi-backend/config/database.py index cb871ecb816a3ad308b6ca2758209f92da6d21e4..14c5a80cb8131737f2083116d8fdfe2235cdf9df 100644 --- a/dash-fastapi-backend/config/database.py +++ b/dash-fastapi-backend/config/database.py @@ -1,19 +1,25 @@ -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.ext.asyncio import async_sessionmaker +from sqlalchemy.ext.asyncio import AsyncAttrs +from sqlalchemy.orm import DeclarativeBase from urllib.parse import quote_plus from config.env import DataBaseConfig -SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@" \ - f"{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}" +ASYNC_SQLALCHEMY_DATABASE_URL = ( + f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' +) -engine = create_engine( - SQLALCHEMY_DATABASE_URL, +async_engine = create_async_engine( + ASYNC_SQLALCHEMY_DATABASE_URL, echo=DataBaseConfig.db_echo, max_overflow=DataBaseConfig.db_max_overflow, pool_size=DataBaseConfig.db_pool_size, pool_recycle=DataBaseConfig.db_pool_recycle, - pool_timeout=DataBaseConfig.db_pool_timeout + pool_timeout=DataBaseConfig.db_pool_timeout, ) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() +AsyncSessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=async_engine) + + +class Base(AsyncAttrs, DeclarativeBase): + pass diff --git a/dash-fastapi-backend/config/enums.py b/dash-fastapi-backend/config/enums.py new file mode 100644 index 0000000000000000000000000000000000000000..0df623899fb1ff67e136f329bc8dd793348b3c6a --- /dev/null +++ b/dash-fastapi-backend/config/enums.py @@ -0,0 +1,51 @@ +from enum import Enum + + +class BusinessType(Enum): + """ + 业务操作类型 + + OTHER: 其它 + INSERT: 新增 + UPDATE: 修改 + DELETE: 删除 + GRANT: 授权 + EXPORT: 导出 + IMPORT: 导入 + FORCE: 强退 + GENCODE: 生成代码 + CLEAN: 清空数据 + """ + + OTHER = 0 + INSERT = 1 + UPDATE = 2 + DELETE = 3 + GRANT = 4 + EXPORT = 5 + IMPORT = 6 + FORCE = 7 + GENCODE = 8 + CLEAN = 9 + + +class RedisInitKeyConfig(Enum): + """ + 系统内置Redis键名 + """ + + @property + def key(self): + return self.value.get('key') + + @property + def remark(self): + return self.value.get('remark') + + ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'} + SYS_DICT = {'key': 'sys_dict', 'remark': '数据字典'} + SYS_CONFIG = {'key': 'sys_config', 'remark': '配置信息'} + CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'} + ACCOUNT_LOCK = {'key': 'account_lock', 'remark': '用户锁定'} + PASSWORD_ERROR_COUNT = {'key': 'password_error_count', 'remark': '密码错误次数'} + SMS_CODE = {'key': 'sms_code', 'remark': '短信验证码'} diff --git a/dash-fastapi-backend/config/env.py b/dash-fastapi-backend/config/env.py index 863719a7862e0446eff87a6e91a9cea67f2348f5..e232a1395832114e90ac6def2870da7fa15a2bf5 100644 --- a/dash-fastapi-backend/config/env.py +++ b/dash-fastapi-backend/config/env.py @@ -1,21 +1,22 @@ +import argparse import os import sys -import argparse -from pydantic import BaseSettings -from functools import lru_cache from dotenv import load_dotenv +from functools import lru_cache +from pydantic_settings import BaseSettings class AppSettings(BaseSettings): """ 应用配置 """ + app_env: str = 'dev' app_name: str = 'Dash-FasAPI-Admin' app_root_path: str = '/dev-api' app_host: str = '0.0.0.0' app_port: int = 9099 - app_version: str = '1.4.0' + app_version: str = '2.0.0' app_reload: bool = True app_ip_location_query: bool = True app_same_time_login: bool = True @@ -25,6 +26,7 @@ class JwtSettings(BaseSettings): """ Jwt配置 """ + jwt_secret_key: str = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55' jwt_algorithm: str = 'HS256' jwt_expire_minutes: int = 1440 @@ -35,11 +37,12 @@ class DataBaseSettings(BaseSettings): """ 数据库配置 """ + db_host: str = '127.0.0.1' db_port: int = 3306 db_username: str = 'root' db_password: str = 'mysqlroot' - db_database: str = 'dash-fastapi' + db_database: str = 'ruoyi-fastapi' db_echo: bool = True db_max_overflow: int = 10 db_pool_size: int = 50 @@ -51,6 +54,7 @@ class RedisSettings(BaseSettings): """ Redis配置 """ + redis_host: str = '127.0.0.1' redis_port: int = 6379 redis_username: str = '' @@ -58,25 +62,59 @@ class RedisSettings(BaseSettings): redis_database: int = 2 -class CachePathConfig: +class UploadSettings: """ - 缓存目录配置 + 上传配置 """ - PATH = os.path.join(os.path.abspath(os.getcwd()), 'caches') - PATHSTR = 'caches' + UPLOAD_PREFIX = '/profile' + UPLOAD_PATH = 'df_admin/upload_path' + UPLOAD_MACHINE = 'A' + DEFAULT_ALLOWED_EXTENSION = [ + # 图片 + 'bmp', + 'gif', + 'jpg', + 'jpeg', + 'png', + # word excel powerpoint + 'doc', + 'docx', + 'xls', + 'xlsx', + 'ppt', + 'pptx', + 'html', + 'htm', + 'txt', + # 压缩文件 + 'rar', + 'zip', + 'gz', + 'bz2', + # 视频格式 + 'mp4', + 'avi', + 'rmvb', + # pdf + 'pdf', + ] + DOWNLOAD_PATH = 'df_admin/download_path' -class RedisInitKeyConfig: + def __init__(self): + if not os.path.exists(self.UPLOAD_PATH): + os.makedirs(self.UPLOAD_PATH) + if not os.path.exists(self.DOWNLOAD_PATH): + os.makedirs(self.DOWNLOAD_PATH) + + +class CachePathConfig: """ - 系统内置Redis键名 + 缓存目录配置 """ - ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'} - SYS_DICT = {'key': 'sys_dict', 'remark': '数据字典'} - SYS_CONFIG = {'key': 'sys_config', 'remark': '配置信息'} - CAPTCHA_CODES = {'key': 'captcha_codes', 'remark': '图片验证码'} - ACCOUNT_LOCK = {'key': 'account_lock', 'remark': '用户锁定'} - PASSWORD_ERROR_COUNT = {'key': 'password_error_count', 'remark': '密码错误次数'} - SMS_CODE = {'key': 'sms_code', 'remark': '短信验证码'} + + PATH = os.path.join(os.path.abspath(os.getcwd()), 'caches') + PATHSTR = 'caches' class GetConfig: @@ -119,6 +157,14 @@ class GetConfig: # 实例化Redis配置模型 return RedisSettings() + @lru_cache() + def get_upload_config(self): + """ + 获取数据库配置 + """ + # 实例上传配置 + return UploadSettings() + @staticmethod def parse_cli_args(): """ @@ -156,3 +202,5 @@ JwtConfig = get_config.get_jwt_config() DataBaseConfig = get_config.get_database_config() # Redis配置 RedisConfig = get_config.get_redis_config() +# 上传配置 +UploadConfig = get_config.get_upload_config() diff --git a/dash-fastapi-backend/config/get_db.py b/dash-fastapi-backend/config/get_db.py index 6d3a9cf40ff2f03fc33c327063fdaeb784825071..20986aeebe8dedcfa9327dd569ca535ad0ef3ee2 100644 --- a/dash-fastapi-backend/config/get_db.py +++ b/dash-fastapi-backend/config/get_db.py @@ -1,27 +1,24 @@ -from config.database import * +from config.database import async_engine, AsyncSessionLocal, Base from utils.log_util import logger -def get_db_pro(): +async def get_db(): """ 每一个请求处理完毕后会关闭当前连接,不同的请求使用不同的连接 + :return: """ - current_db = SessionLocal() - try: + async with AsyncSessionLocal() as current_db: yield current_db - finally: - current_db.close() async def init_create_table(): """ 应用启动时初始化数据库连接 + :return: """ - logger.info("初始化数据库连接...") - Base.metadata.create_all(bind=engine) - logger.info("数据库连接成功") - - -get_db = get_db_pro + logger.info('初始化数据库连接...') + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + logger.info('数据库连接成功') diff --git a/dash-fastapi-backend/config/get_redis.py b/dash-fastapi-backend/config/get_redis.py index 4c3ef800f881cae013ac0d3a76590b98d5860ceb..9d78cad0c935cf9fc91a6f3b67aecff132174178 100644 --- a/dash-fastapi-backend/config/get_redis.py +++ b/dash-fastapi-backend/config/get_redis.py @@ -1,9 +1,9 @@ from redis import asyncio as aioredis from redis.exceptions import AuthenticationError, TimeoutError, RedisError -from module_admin.service.dict_service import DictDataService -from module_admin.service.config_service import ConfigService +from config.database import AsyncSessionLocal from config.env import RedisConfig -from config.database import SessionLocal +from module_admin.service.config_service import ConfigService +from module_admin.service.dict_service import DictDataService from utils.log_util import logger @@ -16,62 +16,62 @@ class RedisUtil: async def create_redis_pool(cls) -> aioredis.Redis: """ 应用启动时初始化redis连接 + :return: Redis连接对象 """ - logger.info("开始连接redis...") + logger.info('开始连接redis...') redis = await aioredis.from_url( - url=f"redis://{RedisConfig.redis_host}", + url=f'redis://{RedisConfig.redis_host}', port=RedisConfig.redis_port, username=RedisConfig.redis_username, password=RedisConfig.redis_password, db=RedisConfig.redis_database, - encoding="utf-8", - decode_responses=True + encoding='utf-8', + decode_responses=True, ) try: connection = await redis.ping() if connection: - logger.info("redis连接成功") + logger.info('redis连接成功') else: - logger.error("redis连接失败") + logger.error('redis连接失败') except AuthenticationError as e: - logger.error(f"redis用户名或密码错误,详细错误信息:{e}") + logger.error(f'redis用户名或密码错误,详细错误信息:{e}') except TimeoutError as e: - logger.error(f"redis连接超时,详细错误信息:{e}") + logger.error(f'redis连接超时,详细错误信息:{e}') except RedisError as e: - logger.error(f"redis连接错误,详细错误信息:{e}") + logger.error(f'redis连接错误,详细错误信息:{e}') return redis @classmethod async def close_redis_pool(cls, app): """ 应用关闭时关闭redis连接 + :param app: fastapi对象 :return: """ await app.state.redis.close() - logger.info("关闭redis连接成功") + logger.info('关闭redis连接成功') @classmethod async def init_sys_dict(cls, redis): """ 应用启动时缓存字典表 + :param redis: redis对象 :return: """ - session = SessionLocal() - await DictDataService.init_cache_sys_dict_services(session, redis) - - session.close() + async with AsyncSessionLocal() as session: + await DictDataService.init_cache_sys_dict_services(session, redis) @classmethod async def init_sys_config(cls, redis): """ 应用启动时缓存参数配置表 + :param redis: redis对象 :return: """ - session = SessionLocal() - await ConfigService.init_cache_sys_config_services(session, redis) - - session.close() + async with AsyncSessionLocal() as session: + await ConfigService.init_cache_sys_config_services(session, redis) diff --git a/dash-fastapi-backend/config/get_scheduler.py b/dash-fastapi-backend/config/get_scheduler.py index 2e51af4c7b701cfb2c183c00492e070aa770ddad..f6beeb988932ef7da97daed5535d720eb4e9f0be 100644 --- a/dash-fastapi-backend/config/get_scheduler.py +++ b/dash-fastapi-backend/config/get_scheduler.py @@ -1,24 +1,28 @@ +import json +from apscheduler.events import EVENT_ALL +from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.jobstores.redis import RedisJobStore -from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor +from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.triggers.cron import CronTrigger -from apscheduler.events import EVENT_ALL -import json from datetime import datetime, timedelta -from config.database import engine, SQLALCHEMY_DATABASE_URL, SessionLocal -from config.env import RedisConfig -from module_admin.service.job_log_service import JobLogService, JobLogModel -from module_admin.dao.job_dao import Session, JobDao +from sqlalchemy.engine import create_engine +from sqlalchemy.orm import sessionmaker +from typing import Union +from config.database import AsyncSessionLocal, quote_plus +from config.env import DataBaseConfig, RedisConfig +from module_admin.dao.job_dao import JobDao +from module_admin.entity.vo.job_vo import JobLogModel, JobModel +from module_admin.service.job_log_service import JobLogService from utils.log_util import logger -import module_task +import module_task # noqa: F401 # 重写Cron定时 class MyCronTrigger(CronTrigger): @classmethod - def from_crontab(cls, expr, timezone=None): + def from_crontab(cls, expr: str, timezone=None): values = expr.split() if len(values) != 6 and len(values) != 7: raise ValueError('Wrong number of fields; got {}, expected 6 or 7'.format(len(values))) @@ -46,11 +50,20 @@ class MyCronTrigger(CronTrigger): else: day_of_week = None year = values[6] if len(values) == 7 else None - return cls(second=second, minute=minute, hour=hour, day=day, month=month, week=week, - day_of_week=day_of_week, year=year, timezone=timezone) + return cls( + second=second, + minute=minute, + hour=hour, + day=day, + month=month, + week=week, + day_of_week=day_of_week, + year=year, + timezone=timezone, + ) @classmethod - def __find_recent_workday(cls, day): + def __find_recent_workday(cls, day: int): now = datetime.now() date = datetime(now.year, now.month, day) if date.weekday() < 5: @@ -65,6 +78,19 @@ class MyCronTrigger(CronTrigger): diff += 1 +SQLALCHEMY_DATABASE_URL = ( + f'mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@' + f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}' +) +engine = create_engine( + SQLALCHEMY_DATABASE_URL, + echo=DataBaseConfig.db_echo, + max_overflow=DataBaseConfig.db_max_overflow, + pool_size=DataBaseConfig.db_pool_size, + pool_recycle=DataBaseConfig.db_pool_recycle, + pool_timeout=DataBaseConfig.db_pool_timeout, +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) job_stores = { 'default': MemoryJobStore(), 'sqlalchemy': SQLAlchemyJobStore(url=SQLALCHEMY_DATABASE_URL, engine=engine), @@ -74,18 +100,12 @@ job_stores = { port=RedisConfig.redis_port, username=RedisConfig.redis_username, password=RedisConfig.redis_password, - db=RedisConfig.redis_database + db=RedisConfig.redis_database, ) - ) -} -executors = { - 'default': ThreadPoolExecutor(20), - 'processpool': ProcessPoolExecutor(5) -} -job_defaults = { - 'coalesce': False, - 'max_instance': 1 + ), } +executors = {'default': ThreadPoolExecutor(20), 'processpool': ProcessPoolExecutor(5)} +job_defaults = {'coalesce': False, 'max_instance': 1} scheduler = BackgroundScheduler() scheduler.configure(jobstores=job_stores, executors=executors, job_defaults=job_defaults) @@ -96,36 +116,39 @@ class SchedulerUtil: """ @classmethod - async def init_system_scheduler(cls, result_db: Session = SessionLocal()): + async def init_system_scheduler(cls): """ 应用启动时初始化定时任务 + :return: """ - logger.info("开始启动定时任务...") + logger.info('开始启动定时任务...') scheduler.start() - job_list = JobDao.get_job_list_for_scheduler(result_db) - for item in job_list: - query_job = cls.get_scheduler_job(job_id=str(item.job_id)) - if query_job: - cls.remove_scheduler_job(job_id=str(item.job_id)) - cls.add_scheduler_job(item) - result_db.close() + async with AsyncSessionLocal() as session: + job_list = await JobDao.get_job_list_for_scheduler(session) + for item in job_list: + query_job = cls.get_scheduler_job(job_id=str(item.job_id)) + if query_job: + cls.remove_scheduler_job(job_id=str(item.job_id)) + cls.add_scheduler_job(item) scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL) - logger.info("系统初始定时任务加载成功") + logger.info('系统初始定时任务加载成功') @classmethod async def close_system_scheduler(cls): """ 应用关闭时关闭定时任务 + :return: """ scheduler.shutdown() - logger.info("关闭定时任务成功") + logger.info('关闭定时任务成功') @classmethod - def get_scheduler_job(cls, job_id): + def get_scheduler_job(cls, job_id: Union[str, int]): """ 根据任务id获取任务对象 + :param job_id: 任务id :return: 任务对象 """ @@ -134,9 +157,10 @@ class SchedulerUtil: return query_job @classmethod - def add_scheduler_job(cls, job_info): + def add_scheduler_job(cls, job_info: JobModel): """ 根据输入的任务对象信息添加任务 + :param job_info: 任务对象信息 :return: """ @@ -151,13 +175,14 @@ class SchedulerUtil: coalesce=True if job_info.misfire_policy == '2' else False, max_instances=3 if job_info.concurrent == '0' else 1, jobstore=job_info.job_group, - executor=job_info.job_executor + executor=job_info.job_executor, ) @classmethod - def execute_scheduler_job_once(cls, job_info): + def execute_scheduler_job_once(cls, job_info: JobModel): """ 根据输入的任务对象执行一次任务 + :param job_info: 任务对象信息 :return: """ @@ -173,13 +198,14 @@ class SchedulerUtil: coalesce=True if job_info.misfire_policy == '2' else False, max_instances=3 if job_info.concurrent == '0' else 1, jobstore=job_info.job_group, - executor=job_info.job_executor + executor=job_info.job_executor, ) @classmethod - def remove_scheduler_job(cls, job_id): + def remove_scheduler_job(cls, job_id: Union[str, int]): """ 根据任务id移除任务 + :param job_id: 任务id :return: """ @@ -195,38 +221,40 @@ class SchedulerUtil: if event_type == 'JobExecutionEvent' and event.exception: exception_info = str(event.exception) status = '1' - job_id = event.job_id - query_job = cls.get_scheduler_job(job_id=job_id) - if query_job: - query_job_info = query_job.__getstate__() - # 获取任务名称 - job_name = query_job_info.get('name') - # 获取任务组名 - job_group = query_job._jobstore_alias - # 获取任务执行器 - job_executor = query_job_info.get('executor') - # 获取调用目标字符串 - invoke_target = query_job_info.get('func') - # 获取调用函数位置参数 - job_args = ','.join(query_job_info.get('args')) - # 获取调用函数关键字参数 - job_kwargs = json.dumps(query_job_info.get('kwargs')) - # 获取任务触发器 - job_trigger = str(query_job_info.get('trigger')) - # 构造日志消息 - job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" - job_log = dict( - job_name=job_name, - job_group=job_group, - job_executor=job_executor, - invoke_target=invoke_target, - job_args=job_args, - job_kwargs=job_kwargs, - job_trigger=job_trigger, - job_message=job_message, - status=status, - exception_info=exception_info - ) - session = SessionLocal() - JobLogService.add_job_log_services(session, JobLogModel(**job_log)) - session.close() + if hasattr(event, 'job_id'): + job_id = event.job_id + query_job = cls.get_scheduler_job(job_id=job_id) + if query_job: + query_job_info = query_job.__getstate__() + # 获取任务名称 + job_name = query_job_info.get('name') + # 获取任务组名 + job_group = query_job._jobstore_alias + # 获取任务执行器 + job_executor = query_job_info.get('executor') + # 获取调用目标字符串 + invoke_target = query_job_info.get('func') + # 获取调用函数位置参数 + job_args = ','.join(query_job_info.get('args')) + # 获取调用函数关键字参数 + job_kwargs = json.dumps(query_job_info.get('kwargs')) + # 获取任务触发器 + job_trigger = str(query_job_info.get('trigger')) + # 构造日志消息 + job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + job_log = JobLogModel( + job_name=job_name, + job_group=job_group, + job_executor=job_executor, + invoke_target=invoke_target, + job_args=job_args, + job_kwargs=job_kwargs, + job_trigger=job_trigger, + job_message=job_message, + status=status, + exception_info=exception_info, + create_time=datetime.now(), + ) + session = SessionLocal() + JobLogService.add_job_log_services(session, job_log) + session.close() diff --git a/dash-fastapi-backend/exceptions/exception.py b/dash-fastapi-backend/exceptions/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..b86f50d01aa2282f3dedebdeceb3992930cf4c3c --- /dev/null +++ b/dash-fastapi-backend/exceptions/exception.py @@ -0,0 +1,58 @@ +class LoginException(Exception): + """ + 自定义登录异常LoginException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class AuthException(Exception): + """ + 自定义令牌异常AuthException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class PermissionException(Exception): + """ + 自定义权限异常PermissionException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceException(Exception): + """ + 自定义服务异常ServiceException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceWarning(Exception): + """ + 自定义服务警告ServiceWarning + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ModelValidatorException(Exception): + """ + 自定义模型校验异常ModelValidatorException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message diff --git a/dash-fastapi-backend/exceptions/handle.py b/dash-fastapi-backend/exceptions/handle.py new file mode 100644 index 0000000000000000000000000000000000000000..dec516a7e36683cb55f5a84a9477cca4464ddbe3 --- /dev/null +++ b/dash-fastapi-backend/exceptions/handle.py @@ -0,0 +1,71 @@ +from fastapi import FastAPI, Request +from fastapi.exceptions import HTTPException +from pydantic_validation_decorator import FieldValidationError +from exceptions.exception import ( + AuthException, + LoginException, + ModelValidatorException, + PermissionException, + ServiceException, + ServiceWarning, +) +from utils.log_util import logger +from utils.response_util import jsonable_encoder, JSONResponse, ResponseUtil + + +def handle_exception(app: FastAPI): + """ + 全局异常处理 + """ + + # 自定义token检验异常 + @app.exception_handler(AuthException) + async def auth_exception_handler(request: Request, exc: AuthException): + return ResponseUtil.unauthorized(data=exc.data, msg=exc.message) + + # 自定义登录检验异常 + @app.exception_handler(LoginException) + async def login_exception_handler(request: Request, exc: LoginException): + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 自定义模型检验异常 + @app.exception_handler(ModelValidatorException) + async def model_validator_exception_handler(request: Request, exc: ModelValidatorException): + logger.warning(exc.message) + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 自定义字段检验异常 + @app.exception_handler(FieldValidationError) + async def field_validation_error_handler(request: Request, exc: FieldValidationError): + logger.warning(exc.message) + return ResponseUtil.failure(msg=exc.message) + + # 自定义权限检验异常 + @app.exception_handler(PermissionException) + async def permission_exception_handler(request: Request, exc: PermissionException): + return ResponseUtil.forbidden(data=exc.data, msg=exc.message) + + # 自定义服务异常 + @app.exception_handler(ServiceException) + async def service_exception_handler(request: Request, exc: ServiceException): + logger.error(exc.message) + return ResponseUtil.error(data=exc.data, msg=exc.message) + + # 自定义服务警告 + @app.exception_handler(ServiceWarning) + async def service_warning_handler(request: Request, exc: ServiceWarning): + logger.warning(exc.message) + return ResponseUtil.failure(data=exc.data, msg=exc.message) + + # 处理其他http请求异常 + @app.exception_handler(HTTPException) + async def http_exception_handler(request: Request, exc: HTTPException): + return JSONResponse( + content=jsonable_encoder({'code': exc.status_code, 'msg': exc.detail}), status_code=exc.status_code + ) + + # 处理其他异常 + @app.exception_handler(Exception) + async def exception_handler(request: Request, exc: Exception): + logger.exception(exc) + return ResponseUtil.error(msg=str(exc)) diff --git a/dash-fastapi-backend/middlewares/cors_middleware.py b/dash-fastapi-backend/middlewares/cors_middleware.py new file mode 100644 index 0000000000000000000000000000000000000000..f5f7ef14b2b8a6f87c1bf1fff54121852467a795 --- /dev/null +++ b/dash-fastapi-backend/middlewares/cors_middleware.py @@ -0,0 +1,25 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + + +def add_cors_middleware(app: FastAPI): + """ + 添加跨域中间件 + + :param app: FastAPI对象 + :return: + """ + # 前端页面url + origins = [ + 'http://localhost:8088', + 'http://127.0.0.1:8088', + ] + + # 后台api允许跨域 + app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + ) diff --git a/dash-fastapi-backend/middlewares/gzip_middleware.py b/dash-fastapi-backend/middlewares/gzip_middleware.py new file mode 100644 index 0000000000000000000000000000000000000000..eb371ceabbe0d7094cf26ec4751d90d99c4c5d04 --- /dev/null +++ b/dash-fastapi-backend/middlewares/gzip_middleware.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from starlette.middleware.gzip import GZipMiddleware + + +def add_gzip_middleware(app: FastAPI): + """ + 添加gzip压缩中间件 + + :param app: FastAPI对象 + :return: + """ + app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=9) diff --git a/dash-fastapi-backend/middlewares/handle.py b/dash-fastapi-backend/middlewares/handle.py new file mode 100644 index 0000000000000000000000000000000000000000..ea447d464131150869b6b06f2bcd237333dba53d --- /dev/null +++ b/dash-fastapi-backend/middlewares/handle.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI +from middlewares.cors_middleware import add_cors_middleware +from middlewares.gzip_middleware import add_gzip_middleware + + +def handle_middleware(app: FastAPI): + """ + 全局中间件处理 + """ + # 加载跨域中间件 + add_cors_middleware(app) + # 加载gzip压缩中间件 + add_gzip_middleware(app) diff --git a/dash-fastapi-backend/module_admin/annotation/log_annotation.py b/dash-fastapi-backend/module_admin/annotation/log_annotation.py index 7c1f162699bde2aa2802ce48dacb20330e9bca19..1b36c86303af4f617bff4ef48539945b87f60f80 100644 --- a/dash-fastapi-backend/module_admin/annotation/log_annotation.py +++ b/dash-fastapi-backend/module_admin/annotation/log_annotation.py @@ -1,29 +1,48 @@ -from functools import wraps, lru_cache -from fastapi import Request -from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse import inspect -import os import json +import os +import requests import time from datetime import datetime -import requests +from fastapi import Request +from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse +from functools import lru_cache, wraps +from typing import Literal, Optional from user_agents import parse -from typing import Optional -from module_admin.service.login_service import get_current_user -from module_admin.service.log_service import OperationLogService, LoginLogService -from module_admin.entity.vo.log_vo import OperLogModel, LogininforModel +from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel +from module_admin.service.log_service import LoginLogService, OperationLogService +from module_admin.service.login_service import LoginService +from config.enums import BusinessType from config.env import AppConfig +from exceptions.exception import LoginException, ServiceException, ServiceWarning +from utils.log_util import logger +from utils.response_util import ResponseUtil -def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'operation'): +class Log: """ 日志装饰器 - :param log_type: 日志类型(login表示登录日志,为空表示为操作日志) - :param title: 当前日志装饰器装饰的模块标题 - :param business_type: 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据) - :return: """ - def decorator(func): + + def __init__( + self, + title: str, + business_type: BusinessType, + log_type: Optional[Literal['login', 'operation']] = 'operation', + ): + """ + 日志装饰器 + + :param title: 当前日志装饰器装饰的模块标题 + :param business_type: 业务类型(OTHER其它 INSERT新增 UPDATE修改 DELETE删除 GRANT授权 EXPORT导出 IMPORT导入 FORCE强退 GENCODE生成代码 CLEAN清空数据) + :param log_type: 日志类型(login表示登录日志,operation表示为操作日志) + :return: + """ + self.title = title + self.business_type = business_type.value + self.log_type = log_type + + def __call__(self, func): @wraps(func) async def wrapper(*args, **kwargs): start_time = time.time() @@ -42,34 +61,47 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope request_method = request.method operator_type = 0 user_agent = request.headers.get('User-Agent') - if "Windows" in user_agent or "Macintosh" in user_agent or "Linux" in user_agent: + if 'Windows' in user_agent or 'Macintosh' in user_agent or 'Linux' in user_agent: operator_type = 1 - if "Mobile" in user_agent or "Android" in user_agent or "iPhone" in user_agent: + if 'Mobile' in user_agent or 'Android' in user_agent or 'iPhone' in user_agent: operator_type = 2 # 获取请求的url oper_url = request.url.path # 获取请求的ip及ip归属区域 - oper_ip = request.headers.get('remote_addr') if request.headers.get('is_browser') == 'no' else request.headers.get('X-Forwarded-For') + oper_ip = ( + request.headers.get('remote_addr') + if request.headers.get('is_browser') == 'no' + else request.headers.get('X-Forwarded-For') + ) oper_location = '内网IP' if AppConfig.app_ip_location_query: oper_location = get_ip_location(oper_ip) # 根据不同的请求类型使用不同的方法获取请求参数 - content_type = request.headers.get("Content-Type") - if content_type and ("multipart/form-data" in content_type or 'application/x-www-form-urlencoded' in content_type): + content_type = request.headers.get('Content-Type') + if content_type and ( + 'multipart/form-data' in content_type or 'application/x-www-form-urlencoded' in content_type + ): payload = await request.form() - oper_param = "\n".join([f"{key}: {value}" for key, value in payload.items()]) + oper_param = '\n'.join([f'{key}: {value}' for key, value in payload.items()]) else: payload = await request.body() - oper_param = json.dumps(json.loads(str(payload, 'utf-8')), ensure_ascii=False) + # 通过 request.path_params 直接访问路径参数 + path_params = request.path_params + oper_param = {} + if payload: + oper_param.update(json.loads(str(payload, 'utf-8'))) + if path_params: + oper_param.update(path_params) + oper_param = json.dumps(oper_param, ensure_ascii=False) # 日志表请求参数字段长度最大为2000,因此在此处判断长度 if len(oper_param) > 2000: oper_param = '请求参数过长' # 获取操作时间 - oper_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + oper_time = datetime.now() # 此处在登录之前向原始函数传递一些登录信息,用于监测在线用户的相关信息 login_log = {} - if log_type == 'login': + if self.log_type == 'login': user_agent_info = parse(user_agent) browser = f'{user_agent_info.browser.family}' system_os = f'{user_agent_info.os.family}' @@ -82,18 +114,36 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope login_location=oper_location, browser=browser, os=system_os, - login_time=oper_time + login_time=oper_time.strftime('%Y-%m-%d %H:%M:%S'), ) kwargs['form_data'].login_info = login_log - # 调用原始函数 - result = await func(*args, **kwargs) + try: + # 调用原始函数 + result = await func(*args, **kwargs) + except (LoginException, ServiceWarning) as e: + logger.warning(e.message) + result = ResponseUtil.failure(data=e.data, msg=e.message) + except ServiceException as e: + logger.error(e.message) + result = ResponseUtil.error(data=e.data, msg=e.message) + except Exception as e: + logger.exception(e) + result = ResponseUtil.error(msg=str(e)) # 获取请求耗时 cost_time = float(time.time() - start_time) * 100 # 判断请求是否来自api文档 - request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False - request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + request_from_swagger = ( + request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + ) + request_from_redoc = ( + request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + ) # 根据响应结果的类型使用不同的方法获取响应结果参数 - if isinstance(result, JSONResponse) or isinstance(result, ORJSONResponse) or isinstance(result, UJSONResponse): + if ( + isinstance(result, JSONResponse) + or isinstance(result, ORJSONResponse) + or isinstance(result, UJSONResponse) + ): result_dict = json.loads(str(result.body, 'utf-8')) else: if request_from_swagger or request_from_redoc: @@ -103,34 +153,35 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope result_dict = {'code': result.status_code, 'message': '获取成功'} else: result_dict = {'code': result.status_code, 'message': '获取失败'} - json_result = json.dumps(dict(code=result_dict.get('code'), message=result_dict.get('message')), ensure_ascii=False) + json_result = json.dumps(result_dict, ensure_ascii=False) # 根据响应结果获取响应状态及异常信息 status = 1 error_msg = '' if result_dict.get('code') == 200: status = 0 else: - error_msg = result_dict.get('message') + error_msg = result_dict.get('msg') # 根据日志类型向对应的日志表插入数据 - if log_type == 'login': + if self.log_type == 'login': # 登录请求来自于api文档时不记录登录日志,其余情况则记录 if request_from_swagger or request_from_redoc: pass else: user = kwargs.get('form_data') user_name = user.username + login_log['login_time'] = oper_time login_log['user_name'] = user_name login_log['status'] = str(status) - login_log['msg'] = result_dict.get('message') + login_log['msg'] = result_dict.get('msg') - LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log)) + await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log)) else: - current_user = await get_current_user(request, token, query_db) + current_user = await LoginService.get_current_user(request, token, query_db) oper_name = current_user.user.user_name - dept_name = current_user.dept.dept_name if current_user.dept else None - operation_log = dict( - title=title, - business_type=business_type, + dept_name = current_user.user.dept.dept_name if current_user.user.dept else None + operation_log = OperLogModel( + title=self.title, + business_type=self.business_type, method=func_path, request_method=request_method, operator_type=operator_type, @@ -144,21 +195,20 @@ def log_decorator(title: str, business_type: int, log_type: Optional[str] = 'ope status=status, error_msg=error_msg, oper_time=oper_time, - cost_time=cost_time + cost_time=int(cost_time), ) - OperationLogService.add_operation_log_services(query_db, OperLogModel(**operation_log)) + await OperationLogService.add_operation_log_services(query_db, operation_log) return result return wrapper - return decorator - @lru_cache() def get_ip_location(oper_ip: str): """ 查询ip归属区域 + :param oper_ip: 需要查询的ip :return: ip归属区域 """ diff --git a/dash-fastapi-backend/module_admin/annotation/pydantic_annotation.py b/dash-fastapi-backend/module_admin/annotation/pydantic_annotation.py new file mode 100644 index 0000000000000000000000000000000000000000..521a13e7ae407e9a505b821b021473abc9e46e0a --- /dev/null +++ b/dash-fastapi-backend/module_admin/annotation/pydantic_annotation.py @@ -0,0 +1,81 @@ +import inspect +from fastapi import Form, Query +from pydantic import BaseModel +from pydantic.fields import FieldInfo +from typing import Type + + +def as_query(cls: Type[BaseModel]): + """ + pydantic模型查询参数装饰器,将pydantic模型用于接收查询参数 + """ + new_parameters = [] + + for field_name, model_field in cls.model_fields.items(): + model_field: FieldInfo # type: ignore + + if not model_field.is_required(): + new_parameters.append( + inspect.Parameter( + field_name, + inspect.Parameter.POSITIONAL_ONLY, + default=Query(default=model_field.default, description=model_field.description), + annotation=model_field.annotation, + ) + ) + else: + new_parameters.append( + inspect.Parameter( + field_name, + inspect.Parameter.POSITIONAL_ONLY, + default=Query(..., description=model_field.description), + annotation=model_field.annotation, + ) + ) + + async def as_query_func(**data): + return cls(**data) + + sig = inspect.signature(as_query_func) + sig = sig.replace(parameters=new_parameters) + as_query_func.__signature__ = sig # type: ignore + setattr(cls, 'as_query', as_query_func) + return cls + + +def as_form(cls: Type[BaseModel]): + """ + pydantic模型表单参数装饰器,将pydantic模型用于接收表单参数 + """ + new_parameters = [] + + for field_name, model_field in cls.model_fields.items(): + model_field: FieldInfo # type: ignore + + if not model_field.is_required(): + new_parameters.append( + inspect.Parameter( + field_name, + inspect.Parameter.POSITIONAL_ONLY, + default=Form(default=model_field.default, description=model_field.description), + annotation=model_field.annotation, + ) + ) + else: + new_parameters.append( + inspect.Parameter( + field_name, + inspect.Parameter.POSITIONAL_ONLY, + default=Form(..., description=model_field.description), + annotation=model_field.annotation, + ) + ) + + async def as_form_func(**data): + return cls(**data) + + sig = inspect.signature(as_form_func) + sig = sig.replace(parameters=new_parameters) + as_form_func.__signature__ = sig # type: ignore + setattr(cls, 'as_form', as_form_func) + return cls diff --git a/dash-fastapi-backend/module_admin/aspect/data_scope.py b/dash-fastapi-backend/module_admin/aspect/data_scope.py index 24f45258e6d410ae92a2f9ab8d94ceb00ec05e4b..5a7afbb6d78f3695b3a1dfac9517d1f29ded957e 100644 --- a/dash-fastapi-backend/module_admin/aspect/data_scope.py +++ b/dash-fastapi-backend/module_admin/aspect/data_scope.py @@ -1,37 +1,75 @@ from fastapi import Depends -from module_admin.entity.vo.user_vo import CurrentUserInfoServiceResponse -from module_admin.service.login_service import get_current_user from typing import Optional +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService class GetDataScope: """ 获取当前用户数据权限对应的查询sql语句 """ - def __init__(self, query_alias: Optional[str] = '', db_alias: Optional[str] = 'db', user_alias: Optional[str] = 'user_id', dept_alias: Optional[str] = 'dept_id'): + + DATA_SCOPE_ALL = '1' + DATA_SCOPE_CUSTOM = '2' + DATA_SCOPE_DEPT = '3' + DATA_SCOPE_DEPT_AND_CHILD = '4' + DATA_SCOPE_SELF = '5' + + def __init__( + self, + query_alias: Optional[str] = '', + db_alias: Optional[str] = 'db', + user_alias: Optional[str] = 'user_id', + dept_alias: Optional[str] = 'dept_id', + ): + """ + 获取当前用户数据权限对应的查询sql语句 + + :param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为'' + :param db_alias: orm对象别名,默认为'db' + :param user_alias: 用户id字段别名,默认为'user_id' + :param dept_alias: 部门id字段别名,默认为'dept_id' + """ self.query_alias = query_alias self.db_alias = db_alias self.user_alias = user_alias self.dept_alias = dept_alias - def __call__(self, current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): user_id = current_user.user.user_id dept_id = current_user.user.dept_id - role_datascope_list = [dict(role_id=item.role_id, data_scope=int(item.data_scope)) for item in current_user.role] - max_data_scope_dict = min(role_datascope_list, key=lambda x: x['data_scope']) - max_role_id = max_data_scope_dict['role_id'] - max_data_scope = max_data_scope_dict['data_scope'] - if self.query_alias == '' or max_data_scope == 1 or user_id == 1: - param_sql = '1 == 1' - elif max_data_scope == 2: - param_sql = f"{self.query_alias}.{self.dept_alias}.in_({self.db_alias}.query(SysRoleDept.dept_id).filter(SysRoleDept.role_id == {max_role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 1" - elif max_data_scope == 3: - param_sql = f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 1" - elif max_data_scope == 4: - param_sql = f"{self.query_alias}.{self.dept_alias}.in_({self.db_alias}.query(SysDept.dept_id).filter(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 1" - elif max_data_scope == 5: - param_sql = f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 1" - else: - param_sql = '1 == 0' + custom_data_scope_role_id_list = [ + item.role_id for item in current_user.user.role if item.data_scope == self.DATA_SCOPE_CUSTOM + ] + param_sql_list = [] + for role in current_user.user.role: + if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL: + param_sql_list = ['1 == 1'] + break + elif role.data_scope == self.DATA_SCOPE_CUSTOM: + if len(custom_data_scope_role_id_list) > 1: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_({custom_data_scope_role_id_list}))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + else: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id == {role.role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_DEPT: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD: + param_sql_list.append( + f"{self.query_alias}.{self.dept_alias}.in_(select(SysDept.dept_id).where(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0" + ) + elif role.data_scope == self.DATA_SCOPE_SELF: + param_sql_list.append( + f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 0" + ) + else: + param_sql_list.append('1 == 0') + param_sql_list = list(dict.fromkeys(param_sql_list)) + param_sql = f"or_({', '.join(param_sql_list)})" return param_sql diff --git a/dash-fastapi-backend/module_admin/aspect/interface_auth.py b/dash-fastapi-backend/module_admin/aspect/interface_auth.py index e17f324cd26c56ae006d8f785bc44285bfad04bb..8f8349dee84dfee54fcb5e4bab471df2ce5f5ffb 100644 --- a/dash-fastapi-backend/module_admin/aspect/interface_auth.py +++ b/dash-fastapi-backend/module_admin/aspect/interface_auth.py @@ -1,23 +1,29 @@ from fastapi import Depends -from typing import Union, List -from module_admin.entity.vo.user_vo import CurrentUserInfoServiceResponse -from module_admin.service.login_service import get_current_user -from utils.response_util import PermissionException +from typing import List, Union +from exceptions.exception import PermissionException +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService class CheckUserInterfaceAuth: """ 校验当前用户是否具有相应的接口权限 - :param perm: 权限标识 - :param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过 """ + def __init__(self, perm: Union[str, List], is_strict: bool = False): + """ + 校验当前用户是否具有相应的接口权限 + + :param perm: 权限标识 + :param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过 + """ self.perm = perm self.is_strict = is_strict - def __call__(self, current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - user_auth_list = [item.perms for item in current_user.menu] - user_auth_list.append('common') + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_auth_list = current_user.permissions + if '*:*:*' in user_auth_list: + return True if isinstance(self.perm, str): if self.perm in user_auth_list: return True @@ -28,21 +34,26 @@ class CheckUserInterfaceAuth: else: if any([perm_str in user_auth_list for perm_str in self.perm]): return True - raise PermissionException(data="", message="该用户无此接口权限") + raise PermissionException(data='', message='该用户无此接口权限') class CheckRoleInterfaceAuth: """ 根据角色校验当前用户是否具有相应的接口权限 - :param role_key: 角色标识 - :param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过 """ + def __init__(self, role_key: Union[str, List], is_strict: bool = False): + """ + 根据角色校验当前用户是否具有相应的接口权限 + + :param role_key: 角色标识 + :param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过 + """ self.role_key = role_key self.is_strict = is_strict - def __call__(self, current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - user_role_list = current_user.role + def __call__(self, current_user: CurrentUserModel = Depends(LoginService.get_current_user)): + user_role_list = current_user.user.role user_role_key_list = [role.role_key for role in user_role_list] if isinstance(self.role_key, str): if self.role_key in user_role_key_list: @@ -54,4 +65,4 @@ class CheckRoleInterfaceAuth: else: if any([role_key_str in user_role_key_list for role_key_str in self.role_key]): return True - raise PermissionException(data="", message="该用户无此接口权限") + raise PermissionException(data='', message='该用户无此接口权限') diff --git a/dash-fastapi-backend/module_admin/controller/cache_controller.py b/dash-fastapi-backend/module_admin/controller/cache_controller.py index 76426a58e1949c2877cdc4786ebc7ad1f51c1997..9e72713e1f41b938919eb3e66f1e0ecc39302a74 100644 --- a/dash-fastapi-backend/module_admin/controller/cache_controller.py +++ b/dash-fastapi-backend/module_admin/controller/cache_controller.py @@ -1,94 +1,89 @@ -from fastapi import APIRouter -from fastapi import Depends -from module_admin.service.login_service import get_current_user -from module_admin.service.cache_service import * -from utils.response_util import * -from utils.log_util import * +from fastapi import APIRouter, Depends, Request +from typing import List from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel +from module_admin.service.cache_service import CacheService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil -cacheController = APIRouter(prefix='/cache', dependencies=[Depends(get_current_user)]) +cacheController = APIRouter(prefix='/monitor/cache', dependencies=[Depends(LoginService.get_current_user)]) -@cacheController.post("/statisticalInfo", response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +@cacheController.get( + '', response_model=CacheMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) async def get_monitor_cache_info(request: Request): - try: - # 获取全量数据 - cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request) - logger.info('获取成功') - return response_200(data=cache_info_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + # 获取全量数据 + cache_info_query_result = await CacheService.get_cache_monitor_statistical_info_services(request) + logger.info('获取成功') + return ResponseUtil.success(data=cache_info_query_result) -@cacheController.post("/getNames", response_model=List[CacheInfoModel], dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) + +@cacheController.get( + '/getNames', + response_model=List[CacheInfoModel], + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) async def get_monitor_cache_name(request: Request): - try: - # 获取全量数据 - cache_name_list_result = CacheService.get_cache_monitor_cache_name_services() - logger.info('获取成功') - return response_200(data=cache_name_list_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + # 获取全量数据 + cache_name_list_result = await CacheService.get_cache_monitor_cache_name_services() + logger.info('获取成功') + + return ResponseUtil.success(data=cache_name_list_result) -@cacheController.post("/getKeys/{cache_name}", response_model=List[str], dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +@cacheController.get( + '/getKeys/{cache_name}', + response_model=List[str], + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) async def get_monitor_cache_key(request: Request, cache_name: str): - try: - # 获取全量数据 - cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name) - logger.info('获取成功') - return response_200(data=cache_key_list_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + # 获取全量数据 + cache_key_list_result = await CacheService.get_cache_monitor_cache_key_services(request, cache_name) + logger.info('获取成功') + + return ResponseUtil.success(data=cache_key_list_result) -@cacheController.post("/getValue/{cache_name}/{cache_key}", response_model=CacheInfoModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) +@cacheController.get( + '/getValue/{cache_name}/{cache_key}', + response_model=CacheInfoModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))], +) async def get_monitor_cache_value(request: Request, cache_name: str, cache_key: str): - try: - # 获取全量数据 - cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key) - logger.info('获取成功') - return response_200(data=cache_value_list_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + # 获取全量数据 + cache_value_list_result = await CacheService.get_cache_monitor_cache_value_services(request, cache_name, cache_key) + logger.info('获取成功') + return ResponseUtil.success(data=cache_value_list_result) -@cacheController.post("/clearCacheName/{cache_name}", response_model=CrudCacheResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) + +@cacheController.delete( + '/clearCacheName/{cache_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) async def clear_monitor_cache_name(request: Request, cache_name: str): - try: - clear_cache_name_result = await CacheService.clear_cache_monitor_cache_name_services(request, cache_name) - if clear_cache_name_result.is_success: - logger.info(clear_cache_name_result.message) - return response_200(data=clear_cache_name_result, message=clear_cache_name_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@cacheController.post("/clearCacheKey/{cache_name}/{cache_key}", response_model=CrudCacheResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) -async def clear_monitor_cache_key(request: Request, cache_name: str, cache_key: str): - try: - clear_cache_key_result = await CacheService.clear_cache_monitor_cache_key_services(request, cache_name, cache_key) - if clear_cache_key_result.is_success: - logger.info(clear_cache_key_result.message) - return response_200(data=clear_cache_key_result, message=clear_cache_key_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@cacheController.post("/clearCacheAll", response_model=CrudCacheResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) + clear_cache_name_result = await CacheService.clear_cache_monitor_cache_name_services(request, cache_name) + logger.info(clear_cache_name_result.message) + + return ResponseUtil.success(msg=clear_cache_name_result.message) + + +@cacheController.delete( + '/clearCacheKey/{cache_key}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))] +) +async def clear_monitor_cache_key(request: Request, cache_key: str): + clear_cache_key_result = await CacheService.clear_cache_monitor_cache_key_services(request, cache_key) + logger.info(clear_cache_key_result.message) + + return ResponseUtil.success(msg=clear_cache_key_result.message) + + +@cacheController.delete('/clearCacheAll', dependencies=[Depends(CheckUserInterfaceAuth('monitor:cache:list'))]) async def clear_monitor_cache_all(request: Request): - try: - clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request) - if clear_cache_all_result.is_success: - logger.info(clear_cache_all_result.message) - return response_200(data=clear_cache_all_result, message=clear_cache_all_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + clear_cache_all_result = await CacheService.clear_cache_monitor_all_services(request) + logger.info(clear_cache_all_result.message) + + return ResponseUtil.success(msg=clear_cache_all_result.message) diff --git a/dash-fastapi-backend/module_admin/controller/captcha_controller.py b/dash-fastapi-backend/module_admin/controller/captcha_controller.py index 22cbe06ecb15c8eebe09a5cbf62ec9796e60f09b..55b14f2bb508a6e5c7a6e52c7c97ae60e17c3b42 100644 --- a/dash-fastapi-backend/module_admin/controller/captcha_controller.py +++ b/dash-fastapi-backend/module_admin/controller/captcha_controller.py @@ -1,25 +1,49 @@ import uuid -from fastapi import APIRouter, Request -from config.env import RedisInitKeyConfig -from module_admin.service.captcha_service import * -from utils.response_util import * -from utils.log_util import * from datetime import timedelta +from fastapi import APIRouter, Request +from config.enums import RedisInitKeyConfig +from module_admin.entity.vo.login_vo import CaptchaCode +from module_admin.service.captcha_service import CaptchaService +from utils.response_util import ResponseUtil +from utils.log_util import logger captchaController = APIRouter() -@captchaController.post("/captchaImage") +@captchaController.get('/captchaImage') async def get_captcha_image(request: Request): - try: - session_id = str(uuid.uuid4()) - captcha_result = CaptchaService.create_captcha_image_service() - image = captcha_result[0] - computed_result = captcha_result[1] - await request.app.state.redis.set(f"{RedisInitKeyConfig.CAPTCHA_CODES.get('key')}:{session_id}", computed_result, ex=timedelta(minutes=2)) - logger.info(f'编号为{session_id}的会话获取图片验证码成功') - return response_200(data={'image': image, 'session_id': session_id}, message='获取验证码成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) + forget_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.forgetUser') == 'true' + else False + ) + register_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') == 'true' + else False + ) + session_id = str(uuid.uuid4()) + captcha_result = await CaptchaService.create_captcha_image_service() + image = captcha_result[0] + computed_result = captcha_result[1] + await request.app.state.redis.set( + f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{session_id}', computed_result, ex=timedelta(minutes=2) + ) + logger.info(f'编号为{session_id}的会话获取图片验证码成功') + + return ResponseUtil.success( + model_content=CaptchaCode( + captcha_enabled=captcha_enabled, + forget_enabled=forget_enabled, + register_enabled=register_enabled, + img=image, + uuid=session_id, + ) + ) diff --git a/dash-fastapi-backend/module_admin/controller/common_controller.py b/dash-fastapi-backend/module_admin/controller/common_controller.py index adec809ddf7cd76c0d47de8f0ea098507a988cab..33ff820eaf78d4f257baf5ba199ee48f7314fb29 100644 --- a/dash-fastapi-backend/module_admin/controller/common_controller.py +++ b/dash-fastapi-backend/module_admin/controller/common_controller.py @@ -1,93 +1,65 @@ -from fastapi import APIRouter, Request -from fastapi import Depends, File, Form -from sqlalchemy.orm import Session -from config.env import CachePathConfig -from config.get_db import get_db -from module_admin.service.login_service import get_current_user -from module_admin.service.common_service import * -from module_admin.service.config_service import ConfigService -from utils.response_util import * -from utils.log_util import * -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from typing import Optional +from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, Query, Request, status, UploadFile +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse +from module_admin.service.common_service import CommonService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil +commonController = APIRouter(prefix='/common', dependencies=[Depends(LoginService.get_current_user)]) -commonController = APIRouter() +@commonController.post('/upload') +async def common_upload(request: Request, file: UploadFile = File(...)): + upload_result = await CommonService.upload_service(request, file) + logger.info('上传成功') -@commonController.post("/upload", dependencies=[Depends(get_current_user), Depends(CheckUserInterfaceAuth('common'))]) -async def common_upload(request: Request, taskPath: str = Form(), uploadId: str = Form(), file: UploadFile = File(...)): - try: - try: - os.makedirs(os.path.join(CachePathConfig.PATH, taskPath, uploadId)) - except FileExistsError: - pass - CommonService.upload_service(CachePathConfig.PATH, taskPath, uploadId, file) - logger.info('上传成功') - return response_200(data={'filename': file.filename, 'path': f'/common/{CachePathConfig.PATHSTR}?taskPath={taskPath}&taskId={uploadId}&filename={file.filename}'}, message="上传成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + return ResponseUtil.success(model_content=upload_result.result) -@commonController.post("/uploadForEditor", dependencies=[Depends(get_current_user), Depends(CheckUserInterfaceAuth('common'))]) -async def editor_upload(request: Request, baseUrl: str = Form(), uploadId: str = Form(), taskPath: str = Form(), file: UploadFile = File(...)): +@commonController.post('/uploadForEditor', dependencies=[Depends(LoginService.get_current_user)]) +async def editor_upload(request: Request, base_url: str = Form(), file: UploadFile = File(...)): try: - try: - os.makedirs(os.path.join(CachePathConfig.PATH, taskPath, uploadId)) - except FileExistsError: - pass - CommonService.upload_service(CachePathConfig.PATH, taskPath, uploadId, file) + upload_result = await CommonService.upload_service(request, file) logger.info('上传成功') return JSONResponse( status_code=status.HTTP_200_OK, content=jsonable_encoder( { 'errno': 0, - 'data': { - 'url': f'{baseUrl}/common/{CachePathConfig.PATHSTR}?taskPath={taskPath}&taskId={uploadId}&filename={file.filename}' - }, + 'data': {'url': f'{base_url}{upload_result.result.file_name}'}, } - ) + ), ) except Exception as e: logger.exception(e) return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + status_code=status.HTTP_200_OK, content=jsonable_encoder( { 'errno': 1, 'message': str(e), } - ) + ), ) -@commonController.get(f"/{CachePathConfig.PATHSTR}") -async def common_download(request: Request, taskPath: str, taskId: str, filename: str, token: Optional[str] = None, query_db: Session = Depends(get_db)): - try: - def generate_file(): - with open(os.path.join(CachePathConfig.PATH, taskPath, taskId, filename), 'rb') as response_file: - yield from response_file - if taskPath not in ['notice']: - current_user = await get_current_user(request, token, query_db) - if current_user: - logger.info('获取成功') - return streaming_response_200(data=generate_file()) - logger.info('获取成功') - return streaming_response_200(data=generate_file()) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +@commonController.get('/download') +async def common_download( + request: Request, + background_tasks: BackgroundTasks, + file_name: str = Query(alias='fileName'), + delete: bool = Query(), +): + download_result = await CommonService.download_services(background_tasks, file_name, delete) + logger.info(download_result.message) + return ResponseUtil.streaming(data=download_result.result) -@commonController.get("/config/query/{config_key}") -async def query_system_config(request: Request, config_key: str): - try: - # 获取全量数据 - config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key) - logger.info('获取成功') - return response_200(data=config_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + +@commonController.get('/download/resource') +async def common_download_resource(request: Request, resource: str = Query()): + download_resource_result = await CommonService.download_resource_services(resource) + logger.info(download_resource_result.message) + + return ResponseUtil.streaming(data=download_resource_result.result) diff --git a/dash-fastapi-backend/module_admin/controller/config_controller.py b/dash-fastapi-backend/module_admin/controller/config_controller.py index a146fcfde90707faf8cd26cf56bb435f38759001..2871a4a2eefa6c2657d43fba45d3ad7b56d0525c 100644 --- a/dash-fastapi-backend/module_admin/controller/config_controller.py +++ b/dash-fastapi-backend/module_admin/controller/config_controller.py @@ -1,125 +1,123 @@ -from fastapi import APIRouter -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.config_service import * -from module_admin.entity.vo.config_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -configController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@configController.post("/config/get", response_model=ConfigPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:config:list'))]) -async def get_system_config_list(request: Request, config_page_query: ConfigPageObject, query_db: Session = Depends(get_db)): - try: - config_query = ConfigQueryModel(**config_page_query.dict()) - # 获取全量数据 - config_query_result = ConfigService.get_config_list_services(query_db, config_query) - # 分页操作 - config_page_query_result = get_page_obj(config_query_result, config_page_query.page_num, config_page_query.page_size) - logger.info('获取成功') - return response_200(data=config_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@configController.post("/config/add", response_model=CrudConfigResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:config:add'))]) -@log_decorator(title='参数管理', business_type=1) -async def add_system_config(request: Request, add_config: ConfigModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_config.create_by = current_user.user.user_name - add_config.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_config.update_by = current_user.user.user_name - add_config.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_config_result = await ConfigService.add_config_services(request, query_db, add_config) - if add_config_result.is_success: - logger.info(add_config_result.message) - return response_200(data=add_config_result, message=add_config_result.message) - else: - logger.warning(add_config_result.message) - return response_400(data="", message=add_config_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@configController.patch("/config/edit", response_model=CrudConfigResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))]) -@log_decorator(title='参数管理', business_type=2) -async def edit_system_config(request: Request, edit_config: ConfigModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_config.update_by = current_user.user.user_name - edit_config.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_config_result = await ConfigService.edit_config_services(request, query_db, edit_config) - if edit_config_result.is_success: - logger.info(edit_config_result.message) - return response_200(data=edit_config_result, message=edit_config_result.message) - else: - logger.warning(edit_config_result.message) - return response_400(data="", message=edit_config_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@configController.post("/config/delete", response_model=CrudConfigResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) -@log_decorator(title='参数管理', business_type=3) -async def delete_system_config(request: Request, delete_config: DeleteConfigModel, query_db: Session = Depends(get_db)): - try: - delete_config_result = await ConfigService.delete_config_services(request, query_db, delete_config) - if delete_config_result.is_success: - logger.info(delete_config_result.message) - return response_200(data=delete_config_result, message=delete_config_result.message) - else: - logger.warning(delete_config_result.message) - return response_400(data="", message=delete_config_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@configController.get("/config/{config_id}", response_model=ConfigModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:query'))]) -async def query_detail_system_config(request: Request, config_id: int, query_db: Session = Depends(get_db)): - try: - detail_config_result = ConfigService.detail_config_services(query_db, config_id) - logger.info(f'获取config_id为{config_id}的信息成功') - return response_200(data=detail_config_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@configController.post("/config/export", dependencies=[Depends(CheckUserInterfaceAuth('system:config:export'))]) -@log_decorator(title='参数管理', business_type=5) -async def export_system_config_list(request: Request, config_query: ConfigQueryModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - config_query_result = ConfigService.get_config_list_services(query_db, config_query) - config_export_result = ConfigService.export_config_list_services(config_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(config_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@configController.post("/config/refresh", response_model=CrudConfigResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))]) -@log_decorator(title='参数管理', business_type=2) -async def refresh_system_config(request: Request, query_db: Session = Depends(get_db)): - try: - refresh_config_result = await ConfigService.refresh_sys_config_services(request, query_db) - if refresh_config_result.is_success: - logger.info(refresh_config_result.message) - return response_200(data=refresh_config_result, message=refresh_config_result.message) - else: - logger.warning(refresh_config_result.message) - return response_400(data="", message=refresh_config_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.config_service import ConfigService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +configController = APIRouter(prefix='/system/config', dependencies=[Depends(LoginService.get_current_user)]) + + +@configController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:list'))] +) +async def get_system_config_list( + request: Request, + config_page_query: ConfigPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + config_page_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=config_page_query_result) + + +@configController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:config:add'))]) +@ValidateFields(validate_model='add_config') +@Log(title='参数管理', business_type=BusinessType.INSERT) +async def add_system_config( + request: Request, + add_config: ConfigModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_config.create_by = current_user.user.user_name + add_config.create_time = datetime.now() + add_config.update_by = current_user.user.user_name + add_config.update_time = datetime.now() + add_config_result = await ConfigService.add_config_services(request, query_db, add_config) + logger.info(add_config_result.message) + + return ResponseUtil.success(msg=add_config_result.message) + + +@configController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:config:edit'))]) +@ValidateFields(validate_model='edit_config') +@Log(title='参数管理', business_type=BusinessType.UPDATE) +async def edit_system_config( + request: Request, + edit_config: ConfigModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_config.update_by = current_user.user.user_name + edit_config.update_time = datetime.now() + edit_config_result = await ConfigService.edit_config_services(request, query_db, edit_config) + logger.info(edit_config_result.message) + + return ResponseUtil.success(msg=edit_config_result.message) + + +@configController.delete('/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) +@Log(title='参数管理', business_type=BusinessType.UPDATE) +async def refresh_system_config(request: Request, query_db: AsyncSession = Depends(get_db)): + refresh_config_result = await ConfigService.refresh_sys_config_services(request, query_db) + logger.info(refresh_config_result.message) + + return ResponseUtil.success(msg=refresh_config_result.message) + + +@configController.delete('/{config_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:config:remove'))]) +@Log(title='参数管理', business_type=BusinessType.DELETE) +async def delete_system_config(request: Request, config_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_config = DeleteConfigModel(config_ids=config_ids) + delete_config_result = await ConfigService.delete_config_services(request, query_db, delete_config) + logger.info(delete_config_result.message) + + return ResponseUtil.success(msg=delete_config_result.message) + + +@configController.get( + '/{config_id}', response_model=ConfigModel, dependencies=[Depends(CheckUserInterfaceAuth('system:config:query'))] +) +async def query_detail_system_config(request: Request, config_id: int, query_db: AsyncSession = Depends(get_db)): + config_detail_result = await ConfigService.config_detail_services(query_db, config_id) + logger.info(f'获取config_id为{config_id}的信息成功') + + return ResponseUtil.success(data=config_detail_result) + + +@configController.get('/configKey/{config_key}') +async def query_system_config(request: Request, config_key: str): + # 获取全量数据 + config_query_result = await ConfigService.query_config_list_from_cache_services(request.app.state.redis, config_key) + logger.info('获取成功') + + return ResponseUtil.success(msg=config_query_result) + + +@configController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:config:export'))]) +@Log(title='参数管理', business_type=BusinessType.EXPORT) +async def export_system_config_list( + request: Request, + config_page_query: ConfigPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + config_query_result = await ConfigService.get_config_list_services(query_db, config_page_query, is_page=False) + config_export_result = await ConfigService.export_config_list_services(config_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(config_export_result)) diff --git a/dash-fastapi-backend/module_admin/controller/dept_controller.py b/dash-fastapi-backend/module_admin/controller/dept_controller.py index 6b0171fc3e4239768eed2352fc2d353561d0d157..4ff03da7524e40f2b66ae5717ab770c312c122bf 100644 --- a/dash-fastapi-backend/module_admin/controller/dept_controller.py +++ b/dash-fastapi-backend/module_admin/controller/dept_controller.py @@ -1,115 +1,132 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.dept_service import * -from module_admin.entity.vo.dept_vo import * -from module_admin.dao.dept_dao import * -from utils.response_util import * -from utils.log_util import * -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.annotation.log_annotation import Log from module_admin.aspect.data_scope import GetDataScope -from module_admin.annotation.log_annotation import log_decorator - - -deptController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@deptController.post("/dept/tree", response_model=DeptTree, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_dept_tree(request: Request, dept_query: DeptModel, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): - try: - dept_query_result = DeptService.get_dept_tree_services(query_db, dept_query, data_scope_sql) - logger.info('获取成功') - return response_200(data=dept_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@deptController.post("/dept/forEditOption", response_model=DeptTree, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_dept_tree_for_edit_option(request: Request, dept_query: DeptModel, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): - try: - dept_query_result = DeptService.get_dept_tree_for_edit_option_services(query_db, dept_query, data_scope_sql) - logger.info('获取成功') - return response_200(data=dept_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@deptController.post("/dept/get", response_model=DeptResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))]) -async def get_system_dept_list(request: Request, dept_query: DeptModel, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept'))): - try: - dept_query_result = DeptService.get_dept_list_services(query_db, dept_query, data_scope_sql) - logger.info('获取成功') - return response_200(data=dept_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@deptController.post("/dept/add", response_model=CrudDeptResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:add'))]) -@log_decorator(title='部门管理', business_type=1) -async def add_system_dept(request: Request, add_dept: DeptModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_dept.create_by = current_user.user.user_name - add_dept.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_dept.update_by = current_user.user.user_name - add_dept.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_dept_result = DeptService.add_dept_services(query_db, add_dept) - if add_dept_result.is_success: - logger.info(add_dept_result.message) - return response_200(data=add_dept_result, message=add_dept_result.message) - else: - logger.warning(add_dept_result.message) - return response_400(data="", message=add_dept_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@deptController.patch("/dept/edit", response_model=CrudDeptResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:edit'))]) -@log_decorator(title='部门管理', business_type=2) -async def edit_system_dept(request: Request, edit_dept: DeptModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_dept.update_by = current_user.user.user_name - edit_dept.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_dept_result = DeptService.edit_dept_services(query_db, edit_dept) - if edit_dept_result.is_success: - logger.info(edit_dept_result.message) - return response_200(data=edit_dept_result, message=edit_dept_result.message) - else: - logger.warning(edit_dept_result.message) - return response_400(data="", message=edit_dept_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@deptController.post("/dept/delete", response_model=CrudDeptResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:remove'))]) -@log_decorator(title='部门管理', business_type=3) -async def delete_system_dept(request: Request, delete_dept: DeleteDeptModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - delete_dept.update_by = current_user.user.user_name - delete_dept.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - delete_dept_result = DeptService.delete_dept_services(query_db, delete_dept) - if delete_dept_result.is_success: - logger.info(delete_dept_result.message) - return response_200(data=delete_dept_result, message=delete_dept_result.message) - else: - logger.warning(delete_dept_result.message) - return response_400(data="", message=delete_dept_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@deptController.get("/dept/{dept_id}", response_model=DeptModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:query'))]) -async def query_detail_system_dept(request: Request, dept_id: int, query_db: Session = Depends(get_db)): - try: - detail_dept_result = DeptService.detail_dept_services(query_db, dept_id) - logger.info(f'获取dept_id为{dept_id}的信息成功') - return response_200(data=detail_dept_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel, DeptQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.dept_service import DeptService +from module_admin.service.login_service import LoginService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +deptController = APIRouter(prefix='/system/dept', dependencies=[Depends(LoginService.get_current_user)]) + + +@deptController.get( + '/list/exclude/{dept_id}', + response_model=List[DeptModel], + dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))], +) +async def get_system_dept_tree_for_edit_option( + request: Request, + dept_id: int, + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_query = DeptModel(dept_id=dept_id) + dept_query_result = await DeptService.get_dept_for_edit_option_services(query_db, dept_query, data_scope_sql) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@deptController.get( + '/list', response_model=List[DeptModel], dependencies=[Depends(CheckUserInterfaceAuth('system:dept:list'))] +) +async def get_system_dept_list( + request: Request, + dept_query: DeptQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_query_result = await DeptService.get_dept_list_services(query_db, dept_query, data_scope_sql) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@deptController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:add'))]) +@ValidateFields(validate_model='add_dept') +@Log(title='部门管理', business_type=BusinessType.INSERT) +async def add_system_dept( + request: Request, + add_dept: DeptModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_dept.create_by = current_user.user.user_name + add_dept.create_time = datetime.now() + add_dept.update_by = current_user.user.user_name + add_dept.update_time = datetime.now() + add_dept_result = await DeptService.add_dept_services(query_db, add_dept) + logger.info(add_dept_result.message) + + return ResponseUtil.success(data=add_dept_result) + + +@deptController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:edit'))]) +@ValidateFields(validate_model='edit_dept') +@Log(title='部门管理', business_type=BusinessType.UPDATE) +async def edit_system_dept( + request: Request, + edit_dept: DeptModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, edit_dept.dept_id, data_scope_sql) + edit_dept.update_by = current_user.user.user_name + edit_dept.update_time = datetime.now() + edit_dept_result = await DeptService.edit_dept_services(query_db, edit_dept) + logger.info(edit_dept_result.message) + + return ResponseUtil.success(msg=edit_dept_result.message) + + +@deptController.delete('/{dept_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:dept:remove'))]) +@Log(title='部门管理', business_type=BusinessType.DELETE) +async def delete_system_dept( + request: Request, + dept_ids: str, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_id_list = dept_ids.split(',') if dept_ids else [] + if dept_id_list: + for dept_id in dept_id_list: + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, int(dept_id), data_scope_sql) + delete_dept = DeleteDeptModel(dept_ids=dept_ids) + delete_dept.update_by = current_user.user.user_name + delete_dept.update_time = datetime.now() + delete_dept_result = await DeptService.delete_dept_services(query_db, delete_dept) + logger.info(delete_dept_result.message) + + return ResponseUtil.success(msg=delete_dept_result.message) + + +@deptController.get( + '/{dept_id}', response_model=DeptModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dept:query'))] +) +async def query_detail_system_dept( + request: Request, + dept_id: int, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, dept_id, data_scope_sql) + detail_dept_result = await DeptService.dept_detail_services(query_db, dept_id) + logger.info(f'获取dept_id为{dept_id}的信息成功') + + return ResponseUtil.success(data=detail_dept_result) diff --git a/dash-fastapi-backend/module_admin/controller/dict_controller.py b/dash-fastapi-backend/module_admin/controller/dict_controller.py index 293f8e353d537ab0d2e2b0a4515fa166603944e2..58c6a74781ab5abf597f485a84a98c5ec1216e5a 100644 --- a/dash-fastapi-backend/module_admin/controller/dict_controller.py +++ b/dash-fastapi-backend/module_admin/controller/dict_controller.py @@ -1,242 +1,239 @@ -from fastapi import APIRouter -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.dict_service import * -from module_admin.entity.vo.dict_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -dictController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@dictController.post("/dictType/get", response_model=DictTypePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]) -async def get_system_dict_type_list(request: Request, dict_type_page_query: DictTypePageObject, query_db: Session = Depends(get_db)): - try: - dict_type_query = DictTypeQueryModel(**dict_type_page_query.dict()) - # 获取全量数据 - dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, dict_type_query) - # 分页操作 - dict_type_page_query_result = get_page_obj(dict_type_query_result, dict_type_page_query.page_num, dict_type_page_query.page_size) - logger.info('获取成功') - return response_200(data=dict_type_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictType/all", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]) -async def get_system_all_dict_type(request: Request, dict_type_query: DictTypeQueryModel, query_db: Session = Depends(get_db)): - try: - dict_type_query_result = DictTypeService.get_all_dict_type_services(query_db) - logger.info('获取成功') - return response_200(data=dict_type_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictType/add", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) -@log_decorator(title='字典管理', business_type=1) -async def add_system_dict_type(request: Request, add_dict_type: DictTypeModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_dict_type.create_by = current_user.user.user_name - add_dict_type.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_dict_type.update_by = current_user.user.user_name - add_dict_type.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type) - if add_dict_type_result.is_success: - logger.info(add_dict_type_result.message) - return response_200(data=add_dict_type_result, message=add_dict_type_result.message) - else: - logger.warning(add_dict_type_result.message) - return response_400(data="", message=add_dict_type_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.patch("/dictType/edit", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) -@log_decorator(title='字典管理', business_type=2) -async def edit_system_dict_type(request: Request, edit_dict_type: DictTypeModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_dict_type.update_by = current_user.user.user_name - edit_dict_type.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_dict_type_result = await DictTypeService.edit_dict_type_services(request, query_db, edit_dict_type) - if edit_dict_type_result.is_success: - logger.info(edit_dict_type_result.message) - return response_200(data=edit_dict_type_result, message=edit_dict_type_result.message) - else: - logger.warning(edit_dict_type_result.message) - return response_400(data="", message=edit_dict_type_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictType/delete", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) -@log_decorator(title='字典管理', business_type=3) -async def delete_system_dict_type(request: Request, delete_dict_type: DeleteDictTypeModel, query_db: Session = Depends(get_db)): - try: - delete_dict_type_result = await DictTypeService.delete_dict_type_services(request, query_db, delete_dict_type) - if delete_dict_type_result.is_success: - logger.info(delete_dict_type_result.message) - return response_200(data=delete_dict_type_result, message=delete_dict_type_result.message) - else: - logger.warning(delete_dict_type_result.message) - return response_400(data="", message=delete_dict_type_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.get("/dictType/{dict_id}", response_model=DictTypeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))]) -async def query_detail_system_dict_type(request: Request, dict_id: int, query_db: Session = Depends(get_db)): - try: - detail_dict_type_result = DictTypeService.detail_dict_type_services(query_db, dict_id) - logger.info(f'获取dict_id为{dict_id}的信息成功') - return response_200(data=detail_dict_type_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictType/export", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) -@log_decorator(title='字典管理', business_type=5) -async def export_system_dict_type_list(request: Request, dict_type_query: DictTypeQueryModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - dict_type_query_result = DictTypeService.get_dict_type_list_services(query_db, dict_type_query) - dict_type_export_result = DictTypeService.export_dict_type_list_services(dict_type_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(dict_type_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictType/refresh", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) -@log_decorator(title='字典管理', business_type=2) -async def refresh_system_dict(request: Request, query_db: Session = Depends(get_db)): - try: - refresh_dict_result = await DictTypeService.refresh_sys_dict_services(request, query_db) - if refresh_dict_result.is_success: - logger.info(refresh_dict_result.message) - return response_200(data=refresh_dict_result, message=refresh_dict_result.message) - else: - logger.warning(refresh_dict_result.message) - return response_400(data="", message=refresh_dict_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictData/get", response_model=DictDataPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]) -async def get_system_dict_data_list(request: Request, dict_data_page_query: DictDataPageObject, query_db: Session = Depends(get_db)): - try: - dict_data_query = DictDataModel(**dict_data_page_query.dict()) - # 获取全量数据 - dict_data_query_result = DictDataService.get_dict_data_list_services(query_db, dict_data_query) - # 分页操作 - dict_data_page_query_result = get_page_obj(dict_data_query_result, dict_data_page_query.page_num, dict_data_page_query.page_size) - logger.info('获取成功') - return response_200(data=dict_data_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.get("/dictData/query/{dict_type}", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))]) -async def query_system_dict_data_list(request: Request, dict_type: str, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services(request.app.state.redis, dict_type) - logger.info('获取成功') - return response_200(data=dict_data_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictData/add", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) -@log_decorator(title='字典管理', business_type=1) -async def add_system_dict_data(request: Request, add_dict_data: DictDataModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_dict_data.create_by = current_user.user.user_name - add_dict_data.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_dict_data.update_by = current_user.user.user_name - add_dict_data.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data) - if add_dict_data_result.is_success: - logger.info(add_dict_data_result.message) - return response_200(data=add_dict_data_result, message=add_dict_data_result.message) - else: - logger.warning(add_dict_data_result.message) - return response_400(data="", message=add_dict_data_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.patch("/dictData/edit", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) -@log_decorator(title='字典管理', business_type=2) -async def edit_system_dict_data(request: Request, edit_dict_data: DictDataModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_dict_data.update_by = current_user.user.user_name - edit_dict_data.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_dict_data_result = await DictDataService.edit_dict_data_services(request, query_db, edit_dict_data) - if edit_dict_data_result.is_success: - logger.info(edit_dict_data_result.message) - return response_200(data=edit_dict_data_result, message=edit_dict_data_result.message) - else: - logger.warning(edit_dict_data_result.message) - return response_400(data="", message=edit_dict_data_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictData/delete", response_model=CrudDictResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) -@log_decorator(title='字典管理', business_type=3) -async def delete_system_dict_data(request: Request, delete_dict_data: DeleteDictDataModel, query_db: Session = Depends(get_db)): - try: - delete_dict_data_result = await DictDataService.delete_dict_data_services(request, query_db, delete_dict_data) - if delete_dict_data_result.is_success: - logger.info(delete_dict_data_result.message) - return response_200(data=delete_dict_data_result, message=delete_dict_data_result.message) - else: - logger.warning(delete_dict_data_result.message) - return response_400(data="", message=delete_dict_data_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.get("/dictData/{dict_code}", response_model=DictDataModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))]) -async def query_detail_system_dict_data(request: Request, dict_code: int, query_db: Session = Depends(get_db)): - try: - detail_dict_data_result = DictDataService.detail_dict_data_services(query_db, dict_code) - logger.info(f'获取dict_code为{dict_code}的信息成功') - return response_200(data=detail_dict_data_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@dictController.post("/dictData/export", dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) -@log_decorator(title='字典管理', business_type=5) -async def export_system_dict_data_list(request: Request, dict_data_query: DictDataModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - dict_data_query_result = DictDataService.get_dict_data_list_services(query_db, dict_data_query) - dict_data_export_result = DictDataService.export_dict_data_list_services(dict_data_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(dict_data_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.dict_vo import ( + DeleteDictDataModel, + DeleteDictTypeModel, + DictDataModel, + DictDataPageQueryModel, + DictTypeModel, + DictTypePageQueryModel, +) +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.dict_service import DictDataService, DictTypeService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +dictController = APIRouter(prefix='/system/dict', dependencies=[Depends(LoginService.get_current_user)]) + + +@dictController.get( + '/type/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))] +) +async def get_system_dict_type_list( + request: Request, + dict_type_page_query: DictTypePageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + dict_type_page_query_result = await DictTypeService.get_dict_type_list_services( + query_db, dict_type_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=dict_type_page_query_result) + + +@dictController.post('/type', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) +@ValidateFields(validate_model='add_dict_type') +@Log(title='字典类型', business_type=BusinessType.INSERT) +async def add_system_dict_type( + request: Request, + add_dict_type: DictTypeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_dict_type.create_by = current_user.user.user_name + add_dict_type.create_time = datetime.now() + add_dict_type.update_by = current_user.user.user_name + add_dict_type.update_time = datetime.now() + add_dict_type_result = await DictTypeService.add_dict_type_services(request, query_db, add_dict_type) + logger.info(add_dict_type_result.message) + + return ResponseUtil.success(msg=add_dict_type_result.message) + + +@dictController.put('/type', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) +@ValidateFields(validate_model='edit_dict_type') +@Log(title='字典类型', business_type=BusinessType.UPDATE) +async def edit_system_dict_type( + request: Request, + edit_dict_type: DictTypeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_dict_type.update_by = current_user.user.user_name + edit_dict_type.update_time = datetime.now() + edit_dict_type_result = await DictTypeService.edit_dict_type_services(request, query_db, edit_dict_type) + logger.info(edit_dict_type_result.message) + + return ResponseUtil.success(msg=edit_dict_type_result.message) + + +@dictController.delete('/type/refreshCache', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典类型', business_type=BusinessType.UPDATE) +async def refresh_system_dict(request: Request, query_db: AsyncSession = Depends(get_db)): + refresh_dict_result = await DictTypeService.refresh_sys_dict_services(request, query_db) + logger.info(refresh_dict_result.message) + + return ResponseUtil.success(msg=refresh_dict_result.message) + + +@dictController.delete('/type/{dict_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典类型', business_type=BusinessType.DELETE) +async def delete_system_dict_type(request: Request, dict_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_dict_type = DeleteDictTypeModel(dict_ids=dict_ids) + delete_dict_type_result = await DictTypeService.delete_dict_type_services(request, query_db, delete_dict_type) + logger.info(delete_dict_type_result.message) + + return ResponseUtil.success(msg=delete_dict_type_result.message) + + +@dictController.get('/type/optionselect', response_model=List[DictTypeModel]) +async def query_system_dict_type_options(request: Request, query_db: AsyncSession = Depends(get_db)): + dict_type_query_result = await DictTypeService.get_dict_type_list_services( + query_db, DictTypePageQueryModel(**dict()), is_page=False + ) + logger.info('获取成功') + + return ResponseUtil.success(data=dict_type_query_result) + + +@dictController.get( + '/type/{dict_id}', response_model=DictTypeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))] +) +async def query_detail_system_dict_type(request: Request, dict_id: int, query_db: AsyncSession = Depends(get_db)): + dict_type_detail_result = await DictTypeService.dict_type_detail_services(query_db, dict_id) + logger.info(f'获取dict_id为{dict_id}的信息成功') + + return ResponseUtil.success(data=dict_type_detail_result) + + +@dictController.post('/type/export', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) +@Log(title='字典类型', business_type=BusinessType.EXPORT) +async def export_system_dict_type_list( + request: Request, + dict_type_page_query: DictTypePageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + dict_type_query_result = await DictTypeService.get_dict_type_list_services( + query_db, dict_type_page_query, is_page=False + ) + dict_type_export_result = await DictTypeService.export_dict_type_list_services(dict_type_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(dict_type_export_result)) + + +@dictController.get('/data/type/{dict_type}') +async def query_system_dict_type_data(request: Request, dict_type: str, query_db: AsyncSession = Depends(get_db)): + # 获取全量数据 + dict_data_query_result = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type + ) + logger.info('获取成功') + + return ResponseUtil.success(data=dict_data_query_result) + + +@dictController.get( + '/data/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:dict:list'))] +) +async def get_system_dict_data_list( + request: Request, + dict_data_page_query: DictDataPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + dict_data_page_query_result = await DictDataService.get_dict_data_list_services( + query_db, dict_data_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=dict_data_page_query_result) + + +@dictController.post('/data', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:add'))]) +@ValidateFields(validate_model='add_dict_data') +@Log(title='字典数据', business_type=BusinessType.INSERT) +async def add_system_dict_data( + request: Request, + add_dict_data: DictDataModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_dict_data.create_by = current_user.user.user_name + add_dict_data.create_time = datetime.now() + add_dict_data.update_by = current_user.user.user_name + add_dict_data.update_time = datetime.now() + add_dict_data_result = await DictDataService.add_dict_data_services(request, query_db, add_dict_data) + logger.info(add_dict_data_result.message) + + return ResponseUtil.success(msg=add_dict_data_result.message) + + +@dictController.put('/data', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:edit'))]) +@ValidateFields(validate_model='edit_dict_data') +@Log(title='字典数据', business_type=BusinessType.UPDATE) +async def edit_system_dict_data( + request: Request, + edit_dict_data: DictDataModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_dict_data.update_by = current_user.user.user_name + edit_dict_data.update_time = datetime.now() + edit_dict_data_result = await DictDataService.edit_dict_data_services(request, query_db, edit_dict_data) + logger.info(edit_dict_data_result.message) + + return ResponseUtil.success(msg=edit_dict_data_result.message) + + +@dictController.delete('/data/{dict_codes}', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:remove'))]) +@Log(title='字典数据', business_type=BusinessType.DELETE) +async def delete_system_dict_data(request: Request, dict_codes: str, query_db: AsyncSession = Depends(get_db)): + delete_dict_data = DeleteDictDataModel(dict_codes=dict_codes) + delete_dict_data_result = await DictDataService.delete_dict_data_services(request, query_db, delete_dict_data) + logger.info(delete_dict_data_result.message) + + return ResponseUtil.success(msg=delete_dict_data_result.message) + + +@dictController.get( + '/data/{dict_code}', + response_model=DictDataModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:dict:query'))], +) +async def query_detail_system_dict_data(request: Request, dict_code: int, query_db: AsyncSession = Depends(get_db)): + detail_dict_data_result = await DictDataService.dict_data_detail_services(query_db, dict_code) + logger.info(f'获取dict_code为{dict_code}的信息成功') + + return ResponseUtil.success(data=detail_dict_data_result) + + +@dictController.post('/data/export', dependencies=[Depends(CheckUserInterfaceAuth('system:dict:export'))]) +@Log(title='字典数据', business_type=BusinessType.EXPORT) +async def export_system_dict_data_list( + request: Request, + dict_data_page_query: DictDataPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + dict_data_query_result = await DictDataService.get_dict_data_list_services( + query_db, dict_data_page_query, is_page=False + ) + dict_data_export_result = await DictDataService.export_dict_data_list_services(dict_data_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(dict_data_export_result)) diff --git a/dash-fastapi-backend/module_admin/controller/job_controller.py b/dash-fastapi-backend/module_admin/controller/job_controller.py index a2dc90cd919962ce2f38f7e11fa0c313706cd498..cbff19a2381dc582d76121b9b93b47f5a6e121b2 100644 --- a/dash-fastapi-backend/module_admin/controller/job_controller.py +++ b/dash-fastapi-backend/module_admin/controller/job_controller.py @@ -1,198 +1,194 @@ -from fastapi import APIRouter -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.job_service import * -from module_admin.service.job_log_service import * -from module_admin.entity.vo.job_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -jobController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@jobController.post("/job/get", response_model=JobPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))]) -async def get_system_job_list(request: Request, job_page_query: JobPageObject, query_db: Session = Depends(get_db)): - try: - job_query = JobModel(**job_page_query.dict()) - # 获取全量数据 - job_query_result = JobService.get_job_list_services(query_db, job_query) - # 分页操作 - notice_page_query_result = get_page_obj(job_query_result, job_page_query.page_num, job_page_query.page_size) - logger.info('获取成功') - return response_200(data=notice_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/job/add", response_model=CrudJobResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))]) -@log_decorator(title='定时任务管理', business_type=1) -async def add_system_job(request: Request, add_job: JobModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_job.create_by = current_user.user.user_name - add_job.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_job.update_by = current_user.user.user_name - add_job.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_job_result = JobService.add_job_services(query_db, add_job) - if add_job_result.is_success: - logger.info(add_job_result.message) - return response_200(data=add_job_result, message=add_job_result.message) - else: - logger.warning(add_job_result.message) - return response_400(data="", message=add_job_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.patch("/job/edit", response_model=CrudJobResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))]) -@log_decorator(title='定时任务管理', business_type=2) -async def edit_system_job(request: Request, edit_job: EditJobModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_job.update_by = current_user.user.user_name - edit_job.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_job_result = JobService.edit_job_services(query_db, edit_job) - if edit_job_result.is_success: - logger.info(edit_job_result.message) - return response_200(data=edit_job_result, message=edit_job_result.message) - else: - logger.warning(edit_job_result.message) - return response_400(data="", message=edit_job_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/job/changeStatus", response_model=CrudJobResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) -@log_decorator(title='定时任务管理', business_type=2) -async def execute_system_job(request: Request, execute_job: JobModel, query_db: Session = Depends(get_db)): - try: - execute_job_result = JobService.execute_job_once_services(query_db, execute_job) - if execute_job_result.is_success: - logger.info(execute_job_result.message) - return response_200(data=execute_job_result, message=execute_job_result.message) - else: - logger.warning(execute_job_result.message) - return response_400(data="", message=execute_job_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/job/delete", response_model=CrudJobResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) -@log_decorator(title='定时任务管理', business_type=3) -async def delete_system_job(request: Request, delete_job: DeleteJobModel, query_db: Session = Depends(get_db)): - try: - delete_job_result = JobService.delete_job_services(query_db, delete_job) - if delete_job_result.is_success: - logger.info(delete_job_result.message) - return response_200(data=delete_job_result, message=delete_job_result.message) - else: - logger.warning(delete_job_result.message) - return response_400(data="", message=delete_job_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.get("/job/{job_id}", response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))]) -async def query_detail_system_job(request: Request, job_id: int, query_db: Session = Depends(get_db)): - try: - detail_job_result = JobService.detail_job_services(query_db, job_id) - logger.info(f'获取job_id为{job_id}的信息成功') - return response_200(data=detail_job_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/job/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) -@log_decorator(title='定时任务管理', business_type=5) -async def export_system_job_list(request: Request, job_query: JobModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - job_query_result = JobService.get_job_list_services(query_db, job_query) - job_export_result = await JobService.export_job_list_services(request, job_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(job_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/jobLog/get", response_model=JobLogPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))]) -async def get_system_job_log_list(request: Request, job_log_page_query: JobLogPageObject, query_db: Session = Depends(get_db)): - try: - job_log_query = JobLogQueryModel(**job_log_page_query.dict()) - # 获取全量数据 - job_log_query_result = JobLogService.get_job_log_list_services(query_db, job_log_query) - # 分页操作 - notice_page_query_result = get_page_obj(job_log_query_result, job_log_page_query.page_num, job_log_page_query.page_size) - logger.info('获取成功') - return response_200(data=notice_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/jobLog/delete", response_model=CrudJobResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) -@log_decorator(title='定时任务日志管理', business_type=3) -async def delete_system_job_log(request: Request, delete_job_log: DeleteJobLogModel, query_db: Session = Depends(get_db)): - try: - delete_job_log_result = JobLogService.delete_job_log_services(query_db, delete_job_log) - if delete_job_log_result.is_success: - logger.info(delete_job_log_result.message) - return response_200(data=delete_job_log_result, message=delete_job_log_result.message) - else: - logger.warning(delete_job_log_result.message) - return response_400(data="", message=delete_job_log_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/jobLog/clear", response_model=CrudJobResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) -@log_decorator(title='定时任务日志管理', business_type=9) -async def clear_system_job_log(request: Request, clear_job_log: ClearJobLogModel, query_db: Session = Depends(get_db)): - try: - clear_job_log_result = JobLogService.clear_job_log_services(query_db, clear_job_log) - if clear_job_log_result.is_success: - logger.info(clear_job_log_result.message) - return response_200(data=clear_job_log_result, message=clear_job_log_result.message) - else: - logger.warning(clear_job_log_result.message) - return response_400(data="", message=clear_job_log_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.get("/jobLog/{job_log_id}", response_model=JobLogModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))]) -async def query_detail_system_job_log(request: Request, job_log_id: int, query_db: Session = Depends(get_db)): - try: - detail_job_log_result = JobLogService.detail_job_log_services(query_db, job_log_id) - logger.info(f'获取job_log_id为{job_log_id}的信息成功') - return response_200(data=detail_job_log_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@jobController.post("/jobLog/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) -@log_decorator(title='定时任务日志管理', business_type=5) -async def export_system_job_log_list(request: Request, job_log_query: JobLogQueryModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - job_log_query_result = JobLogService.get_job_log_list_services(query_db, job_log_query) - job_log_export_result = JobLogService.export_job_log_list_services(query_db, job_log_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(job_log_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.job_vo import ( + DeleteJobLogModel, + DeleteJobModel, + EditJobModel, + JobLogPageQueryModel, + JobModel, + JobPageQueryModel, +) +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.job_log_service import JobLogService +from module_admin.service.job_service import JobService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +jobController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.get_current_user)]) + + +@jobController.get( + '/job/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))] +) +async def get_system_job_list( + request: Request, + job_page_query: JobPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + notice_page_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=notice_page_query_result) + + +@jobController.post('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:add'))]) +@ValidateFields(validate_model='add_job') +@Log(title='定时任务', business_type=BusinessType.INSERT) +async def add_system_job( + request: Request, + add_job: JobModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_job.create_by = current_user.user.user_name + add_job.create_time = datetime.now() + add_job.update_by = current_user.user.user_name + add_job.update_time = datetime.now() + add_job_result = await JobService.add_job_services(query_db, add_job) + logger.info(add_job_result.message) + + return ResponseUtil.success(msg=add_job_result.message) + + +@jobController.put('/job', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:edit'))]) +@ValidateFields(validate_model='edit_job') +@Log(title='定时任务', business_type=BusinessType.UPDATE) +async def edit_system_job( + request: Request, + edit_job: EditJobModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_job.update_by = current_user.user.user_name + edit_job.update_time = datetime.now() + edit_job_result = await JobService.edit_job_services(query_db, edit_job) + logger.info(edit_job_result.message) + + return ResponseUtil.success(msg=edit_job_result.message) + + +@jobController.put('/job/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) +@Log(title='定时任务', business_type=BusinessType.UPDATE) +async def change_system_job_status( + request: Request, + change_job: EditJobModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_job = EditJobModel( + job_id=change_job.job_id, + status=change_job.status, + update_by=current_user.user.user_name, + update_time=datetime.now(), + type='status', + ) + edit_job_result = await JobService.edit_job_services(query_db, edit_job) + logger.info(edit_job_result.message) + + return ResponseUtil.success(msg=edit_job_result.message) + + +@jobController.put('/job/run', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:changeStatus'))]) +@Log(title='定时任务', business_type=BusinessType.UPDATE) +async def execute_system_job(request: Request, execute_job: JobModel, query_db: AsyncSession = Depends(get_db)): + execute_job_result = await JobService.execute_job_once_services(query_db, execute_job) + logger.info(execute_job_result.message) + + return ResponseUtil.success(msg=execute_job_result.message) + + +@jobController.delete('/job/{job_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务', business_type=BusinessType.DELETE) +async def delete_system_job(request: Request, job_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_job = DeleteJobModel(job_ids=job_ids) + delete_job_result = await JobService.delete_job_services(query_db, delete_job) + logger.info(delete_job_result.message) + + return ResponseUtil.success(msg=delete_job_result.message) + + +@jobController.get( + '/job/{job_id}', response_model=JobModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:query'))] +) +async def query_detail_system_job(request: Request, job_id: int, query_db: AsyncSession = Depends(get_db)): + job_detail_result = await JobService.job_detail_services(query_db, job_id) + logger.info(f'获取job_id为{job_id}的信息成功') + + return ResponseUtil.success(data=job_detail_result) + + +@jobController.post('/job/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) +@Log(title='定时任务', business_type=BusinessType.EXPORT) +async def export_system_job_list( + request: Request, + job_page_query: JobPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + job_query_result = await JobService.get_job_list_services(query_db, job_page_query, is_page=False) + job_export_result = await JobService.export_job_list_services(request, job_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(job_export_result)) + + +@jobController.get( + '/jobLog/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:list'))] +) +async def get_system_job_log_list( + request: Request, + job_log_page_query: JobLogPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + job_log_page_query_result = await JobLogService.get_job_log_list_services( + query_db, job_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=job_log_page_query_result) + + +@jobController.delete('/jobLog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.CLEAN) +async def clear_system_job_log(request: Request, query_db: AsyncSession = Depends(get_db)): + clear_job_log_result = await JobLogService.clear_job_log_services(query_db) + logger.info(clear_job_log_result.message) + + return ResponseUtil.success(msg=clear_job_log_result.message) + + +@jobController.delete('/jobLog/{job_log_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:remove'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.DELETE) +async def delete_system_job_log(request: Request, job_log_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_job_log = DeleteJobLogModel(job_log_ids=job_log_ids) + delete_job_log_result = await JobLogService.delete_job_log_services(query_db, delete_job_log) + logger.info(delete_job_log_result.message) + + return ResponseUtil.success(msg=delete_job_log_result.message) + + +@jobController.post('/jobLog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:job:export'))]) +@Log(title='定时任务调度日志', business_type=BusinessType.EXPORT) +async def export_system_job_log_list( + request: Request, + job_log_page_query: JobLogPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + job_log_query_result = await JobLogService.get_job_log_list_services(query_db, job_log_page_query, is_page=False) + job_log_export_result = await JobLogService.export_job_log_list_services(request, job_log_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(job_log_export_result)) diff --git a/dash-fastapi-backend/module_admin/controller/log_controller.py b/dash-fastapi-backend/module_admin/controller/log_controller.py index 3e7175c29dd1ce07989751641964376f52f9423c..0013c557ba9809171504764bb34c253c54c2d013 100644 --- a/dash-fastapi-backend/module_admin/controller/log_controller.py +++ b/dash-fastapi-backend/module_admin/controller/log_controller.py @@ -1,164 +1,150 @@ -from fastapi import APIRouter -from fastapi import Depends +from fastapi import APIRouter, Depends, Form, Query, Request +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user -from module_admin.service.log_service import * -from module_admin.entity.vo.log_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -logController = APIRouter(prefix='/log', dependencies=[Depends(get_current_user)]) - - -@logController.post("/operation/get", response_model=OperLogPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:list'))]) -async def get_system_operation_log_list(request: Request, operation_log_page_query: OperLogPageObject, query_db: Session = Depends(get_db)): - try: - operation_log_query = OperLogQueryModel(**operation_log_page_query.dict()) - # 获取全量数据 - operation_log_query_result = OperationLogService.get_operation_log_list_services(query_db, operation_log_query) - # 分页操作 - operation_log_page_query_result = get_page_obj(operation_log_query_result, operation_log_page_query.page_num, operation_log_page_query.page_size) - logger.info('获取成功') - return response_200(data=operation_log_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/operation/delete", response_model=CrudLogResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) -@log_decorator(title='操作日志管理', business_type=3) -async def delete_system_operation_log(request: Request, delete_operation_log: DeleteOperLogModel, query_db: Session = Depends(get_db)): - try: - delete_operation_log_result = OperationLogService.delete_operation_log_services(query_db, delete_operation_log) - if delete_operation_log_result.is_success: - logger.info(delete_operation_log_result.message) - return response_200(data=delete_operation_log_result, message=delete_operation_log_result.message) - else: - logger.warning(delete_operation_log_result.message) - return response_400(data="", message=delete_operation_log_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/operation/clear", response_model=CrudLogResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) -@log_decorator(title='操作日志管理', business_type=9) -async def clear_system_operation_log(request: Request, clear_operation_log: ClearOperLogModel, query_db: Session = Depends(get_db)): - try: - clear_operation_log_result = OperationLogService.clear_operation_log_services(query_db, clear_operation_log) - if clear_operation_log_result.is_success: - logger.info(clear_operation_log_result.message) - return response_200(data=clear_operation_log_result, message=clear_operation_log_result.message) - else: - logger.warning(clear_operation_log_result.message) - return response_400(data="", message=clear_operation_log_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.get("/operation/{oper_id}", response_model=OperLogModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:query'))]) -async def query_detail_system_operation_log(request: Request, oper_id: int, query_db: Session = Depends(get_db)): - try: - detail_operation_log_result = OperationLogService.detail_operation_log_services(query_db, oper_id) - logger.info(f'获取oper_id为{oper_id}的信息成功') - return response_200(data=detail_operation_log_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/operation/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:export'))]) -@log_decorator(title='操作日志管理', business_type=5) -async def export_system_operation_log_list(request: Request, operation_log_query: OperLogQueryModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - operation_log_query_result = OperationLogService.get_operation_log_list_services(query_db, operation_log_query) - operation_log_export_result = await OperationLogService.export_operation_log_list_services(request, operation_log_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(operation_log_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/login/get", response_model=LoginLogPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:list'))]) -async def get_system_login_log_list(request: Request, login_log_page_query: LoginLogPageObject, query_db: Session = Depends(get_db)): - try: - login_log_query = LoginLogQueryModel(**login_log_page_query.dict()) - # 获取全量数据 - login_log_query_result = LoginLogService.get_login_log_list_services(query_db, login_log_query) - # 分页操作 - login_log_page_query_result = get_page_obj(login_log_query_result, login_log_page_query.page_num, login_log_page_query.page_size) - logger.info('获取成功') - return response_200(data=login_log_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/login/delete", response_model=CrudLogResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) -@log_decorator(title='登录日志管理', business_type=3) -async def delete_system_login_log(request: Request, delete_login_log: DeleteLoginLogModel, query_db: Session = Depends(get_db)): - try: - delete_login_log_result = LoginLogService.delete_login_log_services(query_db, delete_login_log) - if delete_login_log_result.is_success: - logger.info(delete_login_log_result.message) - return response_200(data=delete_login_log_result, message=delete_login_log_result.message) - else: - logger.warning(delete_login_log_result.message) - return response_400(data="", message=delete_login_log_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/login/clear", response_model=CrudLogResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) -@log_decorator(title='登录日志管理', business_type=9) -async def clear_system_login_log(request: Request, clear_login_log: ClearLoginLogModel, query_db: Session = Depends(get_db)): - try: - clear_login_log_result = LoginLogService.clear_login_log_services(query_db, clear_login_log) - if clear_login_log_result.is_success: - logger.info(clear_login_log_result.message) - return response_200(data=clear_login_log_result, message=clear_login_log_result.message) - else: - logger.warning(clear_login_log_result.message) - return response_400(data="", message=clear_login_log_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/login/unlock", response_model=CrudLogResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:unlock'))]) -@log_decorator(title='登录日志管理', business_type=0) -async def clear_system_login_log(request: Request, unlock_user: UnlockUser, query_db: Session = Depends(get_db)): - try: - unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user) - if unlock_user_result.is_success: - logger.info(unlock_user_result.message) - return response_200(data=unlock_user_result, message=unlock_user_result.message) - else: - logger.warning(unlock_user_result.message) - return response_400(data="", message=unlock_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@logController.post("/login/export", dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:export'))]) -@log_decorator(title='登录日志管理', business_type=5) -async def export_system_login_log_list(request: Request, login_log_query: LoginLogQueryModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - login_log_query_result = LoginLogService.get_login_log_list_services(query_db, login_log_query) - login_log_export_result = LoginLogService.export_login_log_list_services(login_log_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(login_log_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.log_vo import ( + DeleteLoginLogModel, + DeleteOperLogModel, + LoginLogPageQueryModel, + OperLogPageQueryModel, + UnlockUser, +) +from module_admin.service.log_service import LoginLogService, OperationLogService +from module_admin.service.login_service import LoginService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +logController = APIRouter(prefix='/monitor', dependencies=[Depends(LoginService.get_current_user)]) + + +@logController.get( + '/operlog/list', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:list'))], +) +async def get_system_operation_log_list( + request: Request, + operation_log_page_query: OperLogPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + operation_log_page_query_result = await OperationLogService.get_operation_log_list_services( + query_db, operation_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=operation_log_page_query_result) + + +@logController.delete('/operlog/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) +@Log(title='操作日志', business_type=BusinessType.CLEAN) +async def clear_system_operation_log(request: Request, query_db: AsyncSession = Depends(get_db)): + clear_operation_log_result = await OperationLogService.clear_operation_log_services(query_db) + logger.info(clear_operation_log_result.message) + + return ResponseUtil.success(msg=clear_operation_log_result.message) + + +@logController.delete('/operlog/{oper_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:remove'))]) +@Log(title='操作日志', business_type=BusinessType.DELETE) +async def delete_system_operation_log(request: Request, oper_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_operation_log = DeleteOperLogModel(oper_ids=oper_ids) + delete_operation_log_result = await OperationLogService.delete_operation_log_services( + query_db, delete_operation_log + ) + logger.info(delete_operation_log_result.message) + + return ResponseUtil.success(msg=delete_operation_log_result.message) + + +@logController.post('/operlog/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:operlog:export'))]) +@Log(title='操作日志', business_type=BusinessType.EXPORT) +async def export_system_operation_log_list( + request: Request, + operation_log_page_query: OperLogPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + operation_log_query_result = await OperationLogService.get_operation_log_list_services( + query_db, operation_log_page_query, is_page=False + ) + operation_log_export_result = await OperationLogService.export_operation_log_list_services( + request, operation_log_query_result + ) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(operation_log_export_result)) + + +@logController.get( + '/logininfor/list', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:list'))], +) +async def get_system_login_log_list( + request: Request, + login_log_page_query: LoginLogPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + login_log_page_query_result = await LoginLogService.get_login_log_list_services( + query_db, login_log_page_query, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=login_log_page_query_result) + + +@logController.delete('/logininfor/clean', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))]) +@Log(title='登录日志', business_type=BusinessType.CLEAN) +async def clear_system_login_log(request: Request, query_db: AsyncSession = Depends(get_db)): + clear_login_log_result = await LoginLogService.clear_login_log_services(query_db) + logger.info(clear_login_log_result.message) + + return ResponseUtil.success(msg=clear_login_log_result.message) + + +@logController.delete( + '/logininfor/{info_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:remove'))] +) +@Log(title='登录日志', business_type=BusinessType.DELETE) +async def delete_system_login_log(request: Request, info_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_login_log = DeleteLoginLogModel(info_ids=info_ids) + delete_login_log_result = await LoginLogService.delete_login_log_services(query_db, delete_login_log) + logger.info(delete_login_log_result.message) + + return ResponseUtil.success(msg=delete_login_log_result.message) + + +@logController.get( + '/logininfor/unlock/{user_name}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:unlock'))] +) +@Log(title='账户解锁', business_type=BusinessType.OTHER) +async def unlock_system_user(request: Request, user_name: str, query_db: AsyncSession = Depends(get_db)): + unlock_user = UnlockUser(user_name=user_name) + unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user) + logger.info(unlock_user_result.message) + + return ResponseUtil.success(msg=unlock_user_result.message) + + +@logController.post('/logininfor/export', dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:export'))]) +@Log(title='登录日志', business_type=BusinessType.EXPORT) +async def export_system_login_log_list( + request: Request, + login_log_page_query: LoginLogPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + login_log_query_result = await LoginLogService.get_login_log_list_services( + query_db, login_log_page_query, is_page=False + ) + login_log_export_result = await LoginLogService.export_login_log_list_services(login_log_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(login_log_export_result)) diff --git a/dash-fastapi-backend/module_admin/controller/login_controller.py b/dash-fastapi-backend/module_admin/controller/login_controller.py index 23f7b94e0f0c29ab0319a439e3e21b772a626e84..06a050d4e8e069d6577045eaf09a0e3e75622e0a 100644 --- a/dash-fastapi-backend/module_admin/controller/login_controller.py +++ b/dash-fastapi-backend/module_admin/controller/login_controller.py @@ -1,119 +1,134 @@ -from fastapi import APIRouter -from module_admin.service.login_service import * -from module_admin.entity.vo.login_vo import * -from module_admin.dao.login_dao import * -from config.env import JwtConfig, RedisInitKeyConfig -from utils.response_util import * -from utils.log_util import * -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator -from datetime import timedelta +import jwt +import uuid +from datetime import datetime, timedelta +from fastapi import APIRouter, Depends, Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional +from config.enums import BusinessType, RedisInitKeyConfig +from config.env import AppConfig, JwtConfig +from config.get_db import get_db +from module_admin.annotation.log_annotation import Log +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.login_vo import UserLogin, UserRegister, SmsCode, Token +from module_admin.entity.vo.user_vo import CurrentUserModel, EditUserModel, ResetUserModel +from module_admin.service.login_service import CustomOAuth2PasswordRequestForm, LoginService, oauth2_scheme +from module_admin.service.user_service import UserService +from utils.log_util import logger +from utils.response_util import ResponseUtil loginController = APIRouter() -@loginController.post("/loginByAccount", response_model=Token) -@log_decorator(title='用户登录', business_type=0, log_type='login') -async def login(request: Request, form_data: CustomOAuth2PasswordRequestForm = Depends(), query_db: Session = Depends(get_db)): - captcha_enabled = True if await request.app.state.redis.get(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.account.captchaEnabled") == 'true' else False +@loginController.post('/login', response_model=Token) +@Log(title='用户登录', business_type=BusinessType.OTHER, log_type='login') +async def login( + request: Request, form_data: CustomOAuth2PasswordRequestForm = Depends(), query_db: AsyncSession = Depends(get_db) +): + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) user = UserLogin( - **dict( - user_name=form_data.username, - password=form_data.password, - captcha=form_data.captcha, - session_id=form_data.session_id, - login_info=form_data.login_info, - captcha_enabled=captcha_enabled - ) + user_name=form_data.username, + password=form_data.password, + code=form_data.code, + uuid=form_data.uuid, + login_info=form_data.login_info, + captcha_enabled=captcha_enabled, + ) + result = await LoginService.authenticate_user(request, query_db, user) + access_token_expires = timedelta(minutes=JwtConfig.jwt_expire_minutes) + session_id = str(uuid.uuid4()) + access_token = await LoginService.create_access_token( + data={ + 'user_id': str(result[0].user_id), + 'user_name': result[0].user_name, + 'dept_name': result[1].dept_name if result[1] else None, + 'session_id': session_id, + 'login_info': user.login_info, + }, + expires_delta=access_token_expires, ) - try: - result = await authenticate_user(request, query_db, user) - except LoginException as e: - return response_400(data="", message=e.message) - try: - access_token_expires = timedelta(minutes=JwtConfig.jwt_expire_minutes) - session_id = str(uuid.uuid4()) - access_token = create_access_token( - data={ - "user_id": str(result[0].user_id), - "user_name": result[0].user_name, - "dept_name": result[1].dept_name if result[1] else None, - "session_id": session_id, - "login_info": user.login_info - }, - expires_delta=access_token_expires + if AppConfig.app_same_time_login: + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}', + access_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), ) - if AppConfig.app_same_time_login: - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", access_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) - else: - # 此方法可实现同一账号同一时间只能登录一次 - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{result[0].user_id}", access_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) - logger.info('登录成功') - # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug - request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False - request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False - if request_from_swagger or request_from_redoc: - return {'access_token': access_token, 'token_type': 'Bearer'} - return response_200( - data={'access_token': access_token, 'token_type': 'Bearer'}, - message='登录成功' + else: + # 此方法可实现同一账号同一时间只能登录一次 + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{result[0].user_id}', + access_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), ) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@loginController.post("/getSmsCode", response_model=SmsCode) -async def get_sms_code(request: Request, user: ResetUserModel, query_db: Session = Depends(get_db)): - try: - sms_result = await get_sms_code_services(request, query_db, user) - if sms_result.is_success: - logger.info('获取成功') - return response_200(data=sms_result, message='获取成功') - else: - logger.warning(sms_result.message) - return response_400(data='', message=sms_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@loginController.post("/forgetPwd", response_model=CrudUserResponse) -async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: Session = Depends(get_db)): - try: - forget_user_result = await forget_user_services(request, query_db, forget_user) - if forget_user_result.is_success: - logger.info(forget_user_result.message) - return response_200(data=forget_user_result, message=forget_user_result.message) - else: - logger.warning(forget_user_result.message) - return response_400(data="", message=forget_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@loginController.post("/getLoginUserInfo", response_model=CurrentUserInfoServiceResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_login_user_info(request: Request, current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - logger.info('获取成功') - return response_200(data=current_user, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@loginController.post("/logout", dependencies=[Depends(get_current_user), Depends(CheckUserInterfaceAuth('common'))]) -async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme), query_db: Session = Depends(get_db)): - try: - payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) - session_id: str = payload.get("session_id") - await logout_services(request, session_id) - logger.info('退出成功') - return response_200(data="", message="退出成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + await UserService.edit_user_services( + query_db, EditUserModel(user_id=result[0].user_id, login_date=datetime.now(), type='status') + ) + logger.info('登录成功') + # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug + request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + if request_from_swagger or request_from_redoc: + return {'access_token': access_token, 'token_type': 'Bearer'} + return ResponseUtil.success(msg='登录成功', dict_content={'token': access_token}) + + +@loginController.get('/getInfo', response_model=CurrentUserModel) +async def get_login_user_info( + request: Request, current_user: CurrentUserModel = Depends(LoginService.get_current_user) +): + logger.info('获取成功') + + return ResponseUtil.success(model_content=current_user) + + +@loginController.get('/getRouters') +async def get_login_user_routers( + request: Request, + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + query_db: AsyncSession = Depends(get_db), +): + logger.info('获取成功') + user_routers = await LoginService.get_current_user_routers(current_user.user.user_id, query_db) + + return ResponseUtil.success(data=user_routers) + + +@loginController.post('/register', response_model=CrudResponseModel) +async def register_user(request: Request, user_register: UserRegister, query_db: AsyncSession = Depends(get_db)): + user_register_result = await LoginService.register_user_services(request, query_db, user_register) + logger.info(user_register_result.message) + + return ResponseUtil.success(data=user_register_result, msg=user_register_result.message) + + +@loginController.post('/getSmsCode', response_model=SmsCode) +async def get_sms_code(request: Request, user: ResetUserModel, query_db: AsyncSession = Depends(get_db)): + sms_result = await LoginService.get_sms_code_services(request, query_db, user) + logger.info('获取成功') + + return ResponseUtil.success(data=sms_result) + + +@loginController.post('/forgetPwd', response_model=CrudResponseModel) +async def forget_user_pwd(request: Request, forget_user: ResetUserModel, query_db: AsyncSession = Depends(get_db)): + forget_user_result = await LoginService.forget_user_services(request, query_db, forget_user) + logger.info(forget_user_result.message) + + return ResponseUtil.success(data=forget_user_result, msg=forget_user_result.message) + + +@loginController.post('/logout') +async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme)): + payload = jwt.decode( + token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm], options={'verify_exp': False} + ) + session_id: str = payload.get('session_id') + await LoginService.logout_services(request, session_id) + logger.info('退出成功') + + return ResponseUtil.success(msg='退出成功') diff --git a/dash-fastapi-backend/module_admin/controller/menu_controller.py b/dash-fastapi-backend/module_admin/controller/menu_controller.py index c2630d8dc079a94900670ab4ff194c8ae2d70045..ed3a5a7fddc29d55cae73014d8c5167b2d0db608 100644 --- a/dash-fastapi-backend/module_admin/controller/menu_controller.py +++ b/dash-fastapi-backend/module_admin/controller/menu_controller.py @@ -1,112 +1,114 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user -from module_admin.service.menu_service import * -from module_admin.entity.vo.menu_vo import * -from module_admin.dao.menu_dao import * -from utils.response_util import * -from utils.log_util import * +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -menuController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@menuController.post("/menu/tree", response_model=MenuTree, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_menu_tree(request: Request, menu_query: MenuTreeModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - menu_query_result = MenuService.get_menu_tree_services(query_db, menu_query, current_user) - logger.info('获取成功') - return response_200(data=menu_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@menuController.post("/menu/forEditOption", response_model=MenuTree, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_menu_tree_for_edit_option(request: Request, menu_query: MenuModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - menu_query_result = MenuService.get_menu_tree_for_edit_option_services(query_db, menu_query, current_user) - logger.info('获取成功') - return response_200(data=menu_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@menuController.post("/menu/get", response_model=MenuResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:list'))]) -async def get_system_menu_list(request: Request, menu_query: MenuModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - menu_query_result = MenuService.get_menu_list_services(query_db, menu_query, current_user) - logger.info('获取成功') - return response_200(data=menu_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@menuController.post("/menu/add", response_model=CrudMenuResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:add'))]) -@log_decorator(title='菜单管理', business_type=1) -async def add_system_menu(request: Request, add_menu: MenuModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_menu.create_by = current_user.user.user_name - add_menu.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_menu.update_by = current_user.user.user_name - add_menu.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_menu_result = MenuService.add_menu_services(query_db, add_menu) - if add_menu_result.is_success: - logger.info(add_menu_result.message) - return response_200(data=add_menu_result, message=add_menu_result.message) - else: - logger.warning(add_menu_result.message) - return response_400(data="", message=add_menu_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@menuController.patch("/menu/edit", response_model=CrudMenuResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:edit'))]) -@log_decorator(title='菜单管理', business_type=2) -async def edit_system_menu(request: Request, edit_menu: MenuModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_menu.update_by = current_user.user.user_name - edit_menu.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_menu_result = MenuService.edit_menu_services(query_db, edit_menu) - if edit_menu_result.is_success: - logger.info(edit_menu_result.message) - return response_200(data=edit_menu_result, message=edit_menu_result.message) - else: - logger.warning(edit_menu_result.message) - return response_400(data="", message=edit_menu_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@menuController.post("/menu/delete", response_model=CrudMenuResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:remove'))]) -@log_decorator(title='菜单管理', business_type=3) -async def delete_system_menu(request: Request, delete_menu: DeleteMenuModel, query_db: Session = Depends(get_db)): - try: - delete_menu_result = MenuService.delete_menu_services(query_db, delete_menu) - if delete_menu_result.is_success: - logger.info(delete_menu_result.message) - return response_200(data=delete_menu_result, message=delete_menu_result.message) - else: - logger.warning(delete_menu_result.message) - return response_400(data="", message=delete_menu_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@menuController.get("/menu/{menu_id}", response_model=MenuModel, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:query'))]) -async def query_detail_system_menu(request: Request, menu_id: int, query_db: Session = Depends(get_db)): - try: - detail_menu_result = MenuService.detail_menu_services(query_db, menu_id) - logger.info(f'获取menu_id为{menu_id}的信息成功') - return response_200(data=detail_menu_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuModel, MenuQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.menu_service import MenuService +from utils.log_util import logger +from utils.response_util import ResponseUtil + + +menuController = APIRouter(prefix='/system/menu', dependencies=[Depends(LoginService.get_current_user)]) + + +@menuController.get('/treeselect') +async def get_system_menu_tree( + request: Request, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + menu_query_result = await MenuService.get_menu_tree_services(query_db, current_user) + logger.info('获取成功') + + return ResponseUtil.success(data=menu_query_result) + + +@menuController.get('/roleMenuTreeselect/{role_id}') +async def get_system_role_menu_tree( + request: Request, + role_id: int, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + role_menu_query_result = await MenuService.get_role_menu_tree_services(query_db, role_id, current_user) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_menu_query_result) + + +@menuController.get( + '/list', response_model=List[MenuModel], dependencies=[Depends(CheckUserInterfaceAuth('system:menu:list'))] +) +async def get_system_menu_list( + request: Request, + menu_query: MenuQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + menu_query_result = await MenuService.get_menu_list_services(query_db, menu_query, current_user) + logger.info('获取成功') + + return ResponseUtil.success(data=menu_query_result) + + +@menuController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:add'))]) +@ValidateFields(validate_model='add_menu') +@Log(title='菜单管理', business_type=BusinessType.INSERT) +async def add_system_menu( + request: Request, + add_menu: MenuModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_menu.create_by = current_user.user.user_name + add_menu.create_time = datetime.now() + add_menu.update_by = current_user.user.user_name + add_menu.update_time = datetime.now() + add_menu_result = await MenuService.add_menu_services(query_db, add_menu) + logger.info(add_menu_result.message) + + return ResponseUtil.success(msg=add_menu_result.message) + + +@menuController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:edit'))]) +@ValidateFields(validate_model='edit_menu') +@Log(title='菜单管理', business_type=BusinessType.UPDATE) +async def edit_system_menu( + request: Request, + edit_menu: MenuModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_menu.update_by = current_user.user.user_name + edit_menu.update_time = datetime.now() + edit_menu_result = await MenuService.edit_menu_services(query_db, edit_menu) + logger.info(edit_menu_result.message) + + return ResponseUtil.success(msg=edit_menu_result.message) + + +@menuController.delete('/{menu_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:menu:remove'))]) +@Log(title='菜单管理', business_type=BusinessType.DELETE) +async def delete_system_menu(request: Request, menu_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_menu = DeleteMenuModel(menu_ids=menu_ids) + delete_menu_result = await MenuService.delete_menu_services(query_db, delete_menu) + logger.info(delete_menu_result.message) + + return ResponseUtil.success(msg=delete_menu_result.message) + + +@menuController.get( + '/{menu_id}', response_model=MenuModel, dependencies=[Depends(CheckUserInterfaceAuth('system:menu:query'))] +) +async def query_detail_system_menu(request: Request, menu_id: int, query_db: AsyncSession = Depends(get_db)): + menu_detail_result = await MenuService.menu_detail_services(query_db, menu_id) + logger.info(f'获取menu_id为{menu_id}的信息成功') + + return ResponseUtil.success(data=menu_detail_result) diff --git a/dash-fastapi-backend/module_admin/controller/notice_controller.py b/dash-fastapi-backend/module_admin/controller/notice_controller.py index f23c625eae59a8f9227cb7780f1546b8b9b6671a..7e634e9608c99c67bff235c5591a1f10bb9e1f3e 100644 --- a/dash-fastapi-backend/module_admin/controller/notice_controller.py +++ b/dash-fastapi-backend/module_admin/controller/notice_controller.py @@ -1,94 +1,89 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.notice_service import * -from module_admin.entity.vo.notice_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -noticeController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@noticeController.post("/notice/get", response_model=NoticePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:list'))]) -async def get_system_notice_list(request: Request, notice_page_query: NoticePageObject, query_db: Session = Depends(get_db)): - try: - notice_query = NoticeQueryModel(**notice_page_query.dict()) - # 获取全量数据 - notice_query_result = NoticeService.get_notice_list_services(query_db, notice_query) - # 分页操作 - notice_page_query_result = get_page_obj(notice_query_result, notice_page_query.page_num, notice_page_query.page_size) - logger.info('获取成功') - return response_200(data=notice_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@noticeController.post("/notice/add", response_model=CrudNoticeResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:add'))]) -@log_decorator(title='通知公告管理', business_type=1) -async def add_system_notice(request: Request, add_notice: NoticeModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_notice.create_by = current_user.user.user_name - add_notice.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_notice.update_by = current_user.user.user_name - add_notice.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_notice_result = NoticeService.add_notice_services(query_db, add_notice) - if add_notice_result.is_success: - logger.info(add_notice_result.message) - return response_200(data=add_notice_result, message=add_notice_result.message) - else: - logger.warning(add_notice_result.message) - return response_400(data="", message=add_notice_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@noticeController.patch("/notice/edit", response_model=CrudNoticeResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:edit'))]) -@log_decorator(title='通知公告管理', business_type=2) -async def edit_system_notice(request: Request, edit_notice: NoticeModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_notice.update_by = current_user.user.user_name - edit_notice.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_notice_result = NoticeService.edit_notice_services(query_db, edit_notice) - if edit_notice_result.is_success: - logger.info(edit_notice_result.message) - return response_200(data=edit_notice_result, message=edit_notice_result.message) - else: - logger.warning(edit_notice_result.message) - return response_400(data="", message=edit_notice_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@noticeController.post("/notice/delete", response_model=CrudNoticeResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:remove'))]) -@log_decorator(title='通知公告管理', business_type=3) -async def delete_system_notice(request: Request, delete_notice: DeleteNoticeModel, query_db: Session = Depends(get_db)): - try: - delete_notice_result = NoticeService.delete_notice_services(query_db, delete_notice) - if delete_notice_result.is_success: - logger.info(delete_notice_result.message) - return response_200(data=delete_notice_result, message=delete_notice_result.message) - else: - logger.warning(delete_notice_result.message) - return response_400(data="", message=delete_notice_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@noticeController.get("/notice/{notice_id}", response_model=NoticeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:query'))]) -async def query_detail_system_post(request: Request, notice_id: int, query_db: Session = Depends(get_db)): - try: - detail_notice_result = NoticeService.detail_notice_services(query_db, notice_id) - logger.info(f'获取notice_id为{notice_id}的信息成功') - return response_200(data=detail_notice_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from module_admin.service.login_service import LoginService +from module_admin.service.notice_service import NoticeService +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +noticeController = APIRouter(prefix='/system/notice', dependencies=[Depends(LoginService.get_current_user)]) + + +@noticeController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:list'))] +) +async def get_system_notice_list( + request: Request, + notice_page_query: NoticePageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + notice_page_query_result = await NoticeService.get_notice_list_services(query_db, notice_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=notice_page_query_result) + + +@noticeController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:add'))]) +@ValidateFields(validate_model='add_notice') +@Log(title='通知公告', business_type=BusinessType.INSERT) +async def add_system_notice( + request: Request, + add_notice: NoticeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_notice.create_by = current_user.user.user_name + add_notice.create_time = datetime.now() + add_notice.update_by = current_user.user.user_name + add_notice.update_time = datetime.now() + add_notice_result = await NoticeService.add_notice_services(query_db, add_notice) + logger.info(add_notice_result.message) + + return ResponseUtil.success(msg=add_notice_result.message) + + +@noticeController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:edit'))]) +@ValidateFields(validate_model='edit_notice') +@Log(title='通知公告', business_type=BusinessType.UPDATE) +async def edit_system_notice( + request: Request, + edit_notice: NoticeModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_notice.update_by = current_user.user.user_name + edit_notice.update_time = datetime.now() + edit_notice_result = await NoticeService.edit_notice_services(query_db, edit_notice) + logger.info(edit_notice_result.message) + + return ResponseUtil.success(msg=edit_notice_result.message) + + +@noticeController.delete('/{notice_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:notice:remove'))]) +@Log(title='通知公告', business_type=BusinessType.DELETE) +async def delete_system_notice(request: Request, notice_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_notice = DeleteNoticeModel(notice_ids=notice_ids) + delete_notice_result = await NoticeService.delete_notice_services(query_db, delete_notice) + logger.info(delete_notice_result.message) + + return ResponseUtil.success(msg=delete_notice_result.message) + + +@noticeController.get( + '/{notice_id}', response_model=NoticeModel, dependencies=[Depends(CheckUserInterfaceAuth('system:notice:query'))] +) +async def query_detail_system_post(request: Request, notice_id: int, query_db: AsyncSession = Depends(get_db)): + notice_detail_result = await NoticeService.notice_detail_services(query_db, notice_id) + logger.info(f'获取notice_id为{notice_id}的信息成功') + + return ResponseUtil.success(data=notice_detail_result) diff --git a/dash-fastapi-backend/module_admin/controller/online_controller.py b/dash-fastapi-backend/module_admin/controller/online_controller.py index 9390c8b0e15e12cfc88fe10047a72b41b0fe4276..c6a781bf2fff6923dda7f536f4513fcf51996311 100644 --- a/dash-fastapi-backend/module_admin/controller/online_controller.py +++ b/dash-fastapi-backend/module_admin/controller/online_controller.py @@ -1,59 +1,56 @@ -from fastapi import APIRouter -from fastapi import Depends +from fastapi import APIRouter, Depends, Query, Request +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, Session -from module_admin.service.online_service import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -onlineController = APIRouter(prefix='/online', dependencies=[Depends(get_current_user)]) - - -@onlineController.post("/get", response_model=OnlinePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))]) -async def get_monitor_online_list(request: Request, online_page_query: OnlinePageObject): - try: - # 获取全量数据 - online_query_result = await OnlineService.get_online_list_services(request, online_page_query) - # 分页操作 - online_page_query_result = get_page_obj(online_query_result, online_page_query.page_num, online_page_query.page_size) - logger.info('获取成功') - return response_200(data=online_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@onlineController.post("/forceLogout", response_model=CrudOnlineResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:forceLogout'))]) -@log_decorator(title='在线用户', business_type=7) -async def delete_monitor_online(request: Request, delete_online: DeleteOnlineModel, query_db: Session = Depends(get_db)): - try: - delete_online_result = await OnlineService.delete_online_services(request, delete_online) - if delete_online_result.is_success: - logger.info(delete_online_result.message) - return response_200(data=delete_online_result, message=delete_online_result.message) - else: - logger.warning(delete_online_result.message) - return response_400(data="", message=delete_online_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@onlineController.post("/batchLogout", response_model=CrudOnlineResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:batchLogout'))]) -@log_decorator(title='在线用户', business_type=7) -async def delete_monitor_online(request: Request, delete_online: DeleteOnlineModel, query_db: Session = Depends(get_db)): - try: - delete_online_result = await OnlineService.delete_online_services(request, delete_online) - if delete_online_result.is_success: - logger.info(delete_online_result.message) - return response_200(data=delete_online_result, message=delete_online_result.message) - else: - logger.warning(delete_online_result.message) - return response_400(data="", message=delete_online_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlinePageQueryModel, OnlineQueryModel +from module_admin.service.login_service import LoginService +from module_admin.service.online_service import OnlineService +from utils.log_util import logger +from utils.page_util import PageResponseModel, PageUtil +from utils.response_util import ResponseUtil + + +onlineController = APIRouter(prefix='/monitor/online', dependencies=[Depends(LoginService.get_current_user)]) + + +@onlineController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))] +) +async def get_monitor_online_list(request: Request, online_page_query: OnlineQueryModel = Query()): + # 获取全量数据 + online_query_result = await OnlineService.get_online_list_services(request, online_page_query) + logger.info('获取成功') + + return ResponseUtil.success( + model_content=PageResponseModel(rows=online_query_result, total=len(online_query_result)) + ) + + +@onlineController.get( + '/list/page', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:list'))], +) +async def get_monitor_online_page_list(request: Request, online_page_query: OnlinePageQueryModel = Query()): + online_query = OnlineQueryModel(**online_page_query.model_dump(by_alias=True)) + # 获取全量数据 + online_query_result = await OnlineService.get_online_list_services(request, online_query) + # 获取分页数据 + online_page_query_result = PageUtil.get_page_obj( + online_query_result, online_page_query.page_num, online_page_query.page_size + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=online_page_query_result) + + +@onlineController.delete('/{token_ids}', dependencies=[Depends(CheckUserInterfaceAuth('monitor:online:forceLogout'))]) +@Log(title='在线用户', business_type=BusinessType.FORCE) +async def delete_monitor_online(request: Request, token_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_online = DeleteOnlineModel(token_ids=token_ids) + delete_online_result = await OnlineService.delete_online_services(request, delete_online) + logger.info(delete_online_result.message) + + return ResponseUtil.success(msg=delete_online_result.message) diff --git a/dash-fastapi-backend/module_admin/controller/post_controler.py b/dash-fastapi-backend/module_admin/controller/post_controler.py index 33180db87beee2bea9bb0d10d3682389df3efd7d..42d8654582d0ecbeb1fb107898a6b820fdd7b9d1 100644 --- a/dash-fastapi-backend/module_admin/controller/post_controler.py +++ b/dash-fastapi-backend/module_admin/controller/post_controler.py @@ -1,120 +1,105 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.post_service import * -from module_admin.entity.vo.post_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -postController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@postController.post("/post/forSelectOption", response_model=PostSelectOptionResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_post_select(request: Request, query_db: Session = Depends(get_db)): - try: - role_query_result = PostService.get_post_select_option_services(query_db) - logger.info('获取成功') - return response_200(data=role_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@postController.post("/post/get", response_model=PostPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))]) -async def get_system_post_list(request: Request, post_page_query: PostPageObject, query_db: Session = Depends(get_db)): - try: - post_query = PostModel(**post_page_query.dict()) - # 获取全量数据 - post_query_result = PostService.get_post_list_services(query_db, post_query) - # 分页操作 - post_page_query_result = get_page_obj(post_query_result, post_page_query.page_num, post_page_query.page_size) - logger.info('获取成功') - return response_200(data=post_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@postController.post("/post/add", response_model=CrudPostResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))]) -@log_decorator(title='岗位管理', business_type=1) -async def add_system_post(request: Request, add_post: PostModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_post.create_by = current_user.user.user_name - add_post.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_post.update_by = current_user.user.user_name - add_post.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_post_result = PostService.add_post_services(query_db, add_post) - if add_post_result.is_success: - logger.info(add_post_result.message) - return response_200(data=add_post_result, message=add_post_result.message) - else: - logger.warning(add_post_result.message) - return response_400(data="", message=add_post_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@postController.patch("/post/edit", response_model=CrudPostResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))]) -@log_decorator(title='岗位管理', business_type=2) -async def edit_system_post(request: Request, edit_post: PostModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_post.update_by = current_user.user.user_name - edit_post.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_post_result = PostService.edit_post_services(query_db, edit_post) - if edit_post_result.is_success: - logger.info(edit_post_result.message) - return response_200(data=edit_post_result, message=edit_post_result.message) - else: - logger.warning(edit_post_result.message) - return response_400(data="", message=edit_post_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@postController.post("/post/delete", response_model=CrudPostResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))]) -@log_decorator(title='岗位管理', business_type=3) -async def delete_system_post(request: Request, delete_post: DeletePostModel, query_db: Session = Depends(get_db)): - try: - delete_post_result = PostService.delete_post_services(query_db, delete_post) - if delete_post_result.is_success: - logger.info(delete_post_result.message) - return response_200(data=delete_post_result, message=delete_post_result.message) - else: - logger.warning(delete_post_result.message) - return response_400(data="", message=delete_post_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@postController.get("/post/{post_id}", response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))]) -async def query_detail_system_post(request: Request, post_id: int, query_db: Session = Depends(get_db)): - try: - detail_post_result = PostService.detail_post_services(query_db, post_id) - logger.info(f'获取post_id为{post_id}的信息成功') - return response_200(data=detail_post_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@postController.post("/post/export", dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))]) -@log_decorator(title='岗位管理', business_type=5) -async def export_system_post_list(request: Request, post_query: PostModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - post_query_result = PostService.get_post_list_services(query_db, post_query) - post_export_result = PostService.export_post_list_services(post_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(post_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.service.login_service import LoginService +from module_admin.service.post_service import PostService +from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +postController = APIRouter(prefix='/system/post', dependencies=[Depends(LoginService.get_current_user)]) + + +@postController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:list'))] +) +async def get_system_post_list( + request: Request, + post_page_query: PostPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + # 获取分页数据 + post_page_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=True) + logger.info('获取成功') + + return ResponseUtil.success(model_content=post_page_query_result) + + +@postController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:add'))]) +@ValidateFields(validate_model='add_post') +@Log(title='岗位管理', business_type=BusinessType.INSERT) +async def add_system_post( + request: Request, + add_post: PostModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_post.create_by = current_user.user.user_name + add_post.create_time = datetime.now() + add_post.update_by = current_user.user.user_name + add_post.update_time = datetime.now() + add_post_result = await PostService.add_post_services(query_db, add_post) + logger.info(add_post_result.message) + + return ResponseUtil.success(msg=add_post_result.message) + + +@postController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:post:edit'))]) +@ValidateFields(validate_model='edit_post') +@Log(title='岗位管理', business_type=BusinessType.UPDATE) +async def edit_system_post( + request: Request, + edit_post: PostModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_post.update_by = current_user.user.user_name + edit_post.update_time = datetime.now() + edit_post_result = await PostService.edit_post_services(query_db, edit_post) + logger.info(edit_post_result.message) + + return ResponseUtil.success(msg=edit_post_result.message) + + +@postController.delete('/{post_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:post:remove'))]) +@Log(title='岗位管理', business_type=BusinessType.DELETE) +async def delete_system_post(request: Request, post_ids: str, query_db: AsyncSession = Depends(get_db)): + delete_post = DeletePostModel(post_ids=post_ids) + delete_post_result = await PostService.delete_post_services(query_db, delete_post) + logger.info(delete_post_result.message) + + return ResponseUtil.success(msg=delete_post_result.message) + + +@postController.get( + '/{post_id}', response_model=PostModel, dependencies=[Depends(CheckUserInterfaceAuth('system:post:query'))] +) +async def query_detail_system_post(request: Request, post_id: int, query_db: AsyncSession = Depends(get_db)): + post_detail_result = await PostService.post_detail_services(query_db, post_id) + logger.info(f'获取post_id为{post_id}的信息成功') + + return ResponseUtil.success(data=post_detail_result) + + +@postController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:post:export'))]) +@Log(title='岗位管理', business_type=BusinessType.EXPORT) +async def export_system_post_list( + request: Request, + post_page_query: PostPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + # 获取全量数据 + post_query_result = await PostService.get_post_list_services(query_db, post_page_query, is_page=False) + post_export_result = await PostService.export_post_list_services(post_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(post_export_result)) diff --git a/dash-fastapi-backend/module_admin/controller/role_controller.py b/dash-fastapi-backend/module_admin/controller/role_controller.py index e43e841bfca6d49e9f3ff3a2ee441c8f484cbfdf..2cf5bd219f06555e01848bada84f8f73afc21fbd 100644 --- a/dash-fastapi-backend/module_admin/controller/role_controller.py +++ b/dash-fastapi-backend/module_admin/controller/role_controller.py @@ -1,200 +1,283 @@ -from fastapi import APIRouter, Request -from fastapi import Depends +from datetime import datetime +from fastapi import APIRouter, Depends, Form, Query, Request +from pydantic_validation_decorator import ValidateFields +from sqlalchemy.ext.asyncio import AsyncSession +from config.enums import BusinessType from config.get_db import get_db -from module_admin.service.login_service import get_current_user, CurrentUserInfoServiceResponse -from module_admin.service.role_service import * -from module_admin.service.user_service import UserService, UserRoleQueryModel, UserRolePageObject, UserRolePageObjectResponse, CrudUserRoleModel -from module_admin.entity.vo.role_vo import * -from utils.response_util import * -from utils.log_util import * -from utils.page_util import get_page_obj -from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log +from module_admin.aspect.data_scope import GetDataScope from module_admin.aspect.interface_auth import CheckUserInterfaceAuth -from module_admin.annotation.log_annotation import log_decorator - - -roleController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@roleController.post("/role/forSelectOption", response_model=RoleSelectOptionResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_role_select(request: Request, query_db: Session = Depends(get_db)): - try: - role_query_result = RoleService.get_role_select_option_services(query_db) - logger.info('获取成功') - return response_200(data=role_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/get", response_model=RolePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))]) -async def get_system_role_list(request: Request, role_page_query: RolePageObject, query_db: Session = Depends(get_db)): - try: - role_query = RoleQueryModel(**role_page_query.dict()) - role_query_result = RoleService.get_role_list_services(query_db, role_query) - # 分页操作 - role_page_query_result = get_page_obj(role_query_result, role_page_query.page_num, role_page_query.page_size) - logger.info('获取成功') - return response_200(data=role_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/add", response_model=CrudRoleResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:add'))]) -@log_decorator(title='角色管理', business_type=1) -async def add_system_role(request: Request, add_role: AddRoleModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_role.create_by = current_user.user.user_name - add_role.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_role.update_by = current_user.user.user_name - add_role.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_role_result = RoleService.add_role_services(query_db, add_role) - if add_role_result.is_success: - logger.info(add_role_result.message) - return response_200(data=add_role_result, message=add_role_result.message) - else: - logger.warning(add_role_result.message) - return response_400(data="", message=add_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.patch("/role/edit", response_model=CrudRoleResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=2) -async def edit_system_role(request: Request, edit_role: AddRoleModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_role.update_by = current_user.user.user_name - edit_role.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_role_result = RoleService.edit_role_services(query_db, edit_role) - if edit_role_result.is_success: - logger.info(edit_role_result.message) - return response_200(data=edit_role_result, message=edit_role_result.message) - else: - logger.warning(edit_role_result.message) - return response_400(data="", message=edit_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.patch("/role/dataScope", response_model=CrudRoleResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -async def edit_system_role_datascope(request: Request, role_data_scope: RoleDataScopeModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - role_data_scope.update_by = current_user.user.user_name - role_data_scope.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - role_data_scope_result = RoleService.role_datascope_services(query_db, role_data_scope) - if role_data_scope_result.is_success: - logger.info(role_data_scope_result.message) - return response_200(data=role_data_scope_result, message=role_data_scope_result.message) - else: - logger.warning(role_data_scope_result.message) - return response_400(data="", message=role_data_scope_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/delete", response_model=CrudRoleResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:remove'))]) -@log_decorator(title='角色管理', business_type=3) -async def delete_system_role(request: Request, delete_role: DeleteRoleModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - delete_role.update_by = current_user.user.user_name - delete_role.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - delete_role_result = RoleService.delete_role_services(query_db, delete_role) - if delete_role_result.is_success: - logger.info(delete_role_result.message) - return response_200(data=delete_role_result, message=delete_role_result.message) - else: - logger.warning(delete_role_result.message) - return response_400(data="", message=delete_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.get("/role/{role_id}", response_model=RoleDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))]) -async def query_detail_system_role(request: Request, role_id: int, query_db: Session = Depends(get_db)): - try: - delete_role_result = RoleService.detail_role_services(query_db, role_id) - logger.info(f'获取role_id为{role_id}的信息成功') - return response_200(data=delete_role_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/export", dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))]) -@log_decorator(title='角色管理', business_type=5) -async def export_system_role_list(request: Request, role_query: RoleQueryModel, query_db: Session = Depends(get_db)): - try: - # 获取全量数据 - role_query_result = RoleService.get_role_list_services(query_db, role_query) - role_export_result = RoleService.export_role_list_services(role_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(role_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/authUser/allocatedList", response_model=UserRolePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_allocated_user_list(request: Request, user_role: UserRolePageObject, query_db: Session = Depends(get_db)): - try: - user_role_query = UserRoleQueryModel(**user_role.dict()) - user_role_allocated_query_result = UserService.get_user_role_allocated_list_services(query_db, user_role_query) - # 分页操作 - user_role_allocated_page_query_result = get_page_obj(user_role_allocated_query_result, user_role.page_num, user_role.page_size) - logger.info('获取成功') - return response_200(data=user_role_allocated_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/authUser/unallocatedList", response_model=UserRolePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_unallocated_user_list(request: Request, user_role: UserRolePageObject, query_db: Session = Depends(get_db)): - try: - user_role_query = UserRoleQueryModel(**user_role.dict()) - user_role_unallocated_query_result = UserService.get_user_role_unallocated_list_services(query_db, user_role_query) - # 分页操作 - user_role_unallocated_page_query_result = get_page_obj(user_role_unallocated_query_result, user_role.page_num, user_role.page_size) - logger.info('获取成功') - return response_200(data=user_role_unallocated_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/authUser/selectAll", response_model=CrudRoleResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -async def add_system_role_user(request: Request, add_user_role: CrudUserRoleModel, query_db: Session = Depends(get_db)): - try: - add_user_role_result = UserService.add_user_role_services(query_db, add_user_role) - if add_user_role_result.is_success: - logger.info(add_user_role_result.message) - return response_200(data=add_user_role_result, message=add_user_role_result.message) - else: - logger.warning(add_user_role_result.message) - return response_400(data="", message=add_user_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@roleController.post("/role/authUser/cancel", response_model=CrudRoleResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) -@log_decorator(title='角色管理', business_type=4) -async def cancel_system_role_user(request: Request, cancel_user_role: CrudUserRoleModel, query_db: Session = Depends(get_db)): - try: - cancel_user_role_result = UserService.delete_user_role_services(query_db, cancel_user_role) - if cancel_user_role_result.is_success: - logger.info(cancel_user_role_result.message) - return response_200(data=cancel_user_role_result, message=cancel_user_role_result.message) - else: - logger.warning(cancel_user_role_result.message) - return response_400(data="", message=cancel_user_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) +from module_admin.entity.vo.dept_vo import DeptModel +from module_admin.entity.vo.role_vo import AddRoleModel, DeleteRoleModel, RoleModel, RolePageQueryModel +from module_admin.entity.vo.user_vo import CrudUserRoleModel, CurrentUserModel, UserRolePageQueryModel +from module_admin.service.dept_service import DeptService +from module_admin.service.login_service import LoginService +from module_admin.service.role_service import RoleService +from module_admin.service.user_service import UserService +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil + + +roleController = APIRouter(prefix='/system/role', dependencies=[Depends(LoginService.get_current_user)]) + + +@roleController.get('/deptTree/{role_id}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))]) +async def get_system_role_dept_tree( + request: Request, + role_id: int, + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) + role_dept_query_result = await RoleService.get_role_dept_tree_services(query_db, role_id) + role_dept_query_result.depts = dept_query_result + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_dept_query_result) + + +@roleController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))] +) +async def get_system_role_list( + request: Request, + role_page_query: RolePageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + role_page_query_result = await RoleService.get_role_list_services( + query_db, role_page_query, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_page_query_result) + + +@roleController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:add'))]) +@ValidateFields(validate_model='add_role') +@Log(title='角色管理', business_type=BusinessType.INSERT) +async def add_system_role( + request: Request, + add_role: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + add_role.create_by = current_user.user.user_name + add_role.create_time = datetime.now() + add_role.update_by = current_user.user.user_name + add_role.update_time = datetime.now() + add_role_result = await RoleService.add_role_services(query_db, add_role) + logger.info(add_role_result.message) + + return ResponseUtil.success(msg=add_role_result.message) + + +@roleController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@ValidateFields(validate_model='edit_role') +@Log(title='角色管理', business_type=BusinessType.UPDATE) +async def edit_system_role( + request: Request, + edit_role: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await RoleService.check_role_allowed_services(edit_role) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(edit_role.role_id), data_scope_sql) + edit_role.update_by = current_user.user.user_name + edit_role.update_time = datetime.now() + edit_role_result = await RoleService.edit_role_services(query_db, edit_role) + logger.info(edit_role_result.message) + + return ResponseUtil.success(msg=edit_role_result.message) + + +@roleController.put('/dataScope', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def edit_system_role_datascope( + request: Request, + role_data_scope: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await RoleService.check_role_allowed_services(role_data_scope) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(role_data_scope.role_id), data_scope_sql) + edit_role = AddRoleModel( + role_id=role_data_scope.role_id, + data_scope=role_data_scope.data_scope, + dept_ids=role_data_scope.dept_ids, + dept_check_strictly=role_data_scope.dept_check_strictly, + update_by=current_user.user.user_name, + update_time=datetime.now(), + ) + role_data_scope_result = await RoleService.role_datascope_services(query_db, edit_role) + logger.info(role_data_scope_result.message) + + return ResponseUtil.success(msg=role_data_scope_result.message) + + +@roleController.delete('/{role_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:role:remove'))]) +@Log(title='角色管理', business_type=BusinessType.DELETE) +async def delete_system_role( + request: Request, + role_ids: str, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + role_id_list = role_ids.split(',') if role_ids else [] + if role_id_list: + for role_id in role_id_list: + await RoleService.check_role_allowed_services(RoleModel(role_id=int(role_id))) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, role_id, data_scope_sql) + delete_role = DeleteRoleModel(role_ids=role_ids, update_by=current_user.user.user_name, update_time=datetime.now()) + delete_role_result = await RoleService.delete_role_services(query_db, delete_role) + logger.info(delete_role_result.message) + + return ResponseUtil.success(msg=delete_role_result.message) + + +@roleController.get( + '/{role_id}', response_model=RoleModel, dependencies=[Depends(CheckUserInterfaceAuth('system:role:query'))] +) +async def query_detail_system_role( + request: Request, + role_id: int, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(role_id), data_scope_sql) + role_detail_result = await RoleService.role_detail_services(query_db, role_id) + logger.info(f'获取role_id为{role_id}的信息成功') + + return ResponseUtil.success(data=role_detail_result.model_dump()) + + +@roleController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:role:export'))]) +@Log(title='角色管理', business_type=BusinessType.EXPORT) +async def export_system_role_list( + request: Request, + role_page_query: RolePageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + # 获取全量数据 + role_query_result = await RoleService.get_role_list_services( + query_db, role_page_query, data_scope_sql, is_page=False + ) + role_export_result = await RoleService.export_role_list_services(role_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(role_export_result)) + + +@roleController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.UPDATE) +async def reset_system_role_status( + request: Request, + change_role: AddRoleModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await RoleService.check_role_allowed_services(change_role) + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(change_role.role_id), data_scope_sql) + edit_role = AddRoleModel( + role_id=change_role.role_id, + status=change_role.status, + update_by=current_user.user.user_name, + update_time=datetime.now(), + type='status', + ) + edit_role_result = await RoleService.edit_role_services(query_db, edit_role) + logger.info(edit_role_result.message) + + return ResponseUtil.success(msg=edit_role_result.message) + + +@roleController.get( + '/authUser/allocatedList', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))], +) +async def get_system_allocated_user_list( + request: Request, + user_role: UserRolePageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + role_user_allocated_page_query_result = await RoleService.get_role_user_allocated_list_services( + query_db, user_role, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_user_allocated_page_query_result) + + +@roleController.get( + '/authUser/unallocatedList', + response_model=PageResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:role:list'))], +) +async def get_system_unallocated_user_list( + request: Request, + user_role: UserRolePageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + role_user_unallocated_page_query_result = await RoleService.get_role_user_unallocated_list_services( + query_db, user_role, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=role_user_unallocated_page_query_result) + + +@roleController.put('/authUser/selectAll', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def add_system_role_user( + request: Request, + add_role_user: CrudUserRoleModel = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await RoleService.check_role_data_scope_services(query_db, str(add_role_user.role_id), data_scope_sql) + add_role_user_result = await UserService.add_user_role_services(query_db, add_role_user) + logger.info(add_role_user_result.message) + + return ResponseUtil.success(msg=add_role_user_result.message) + + +@roleController.put('/authUser/cancel', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def cancel_system_role_user( + request: Request, cancel_user_role: CrudUserRoleModel, query_db: AsyncSession = Depends(get_db) +): + cancel_user_role_result = await UserService.delete_user_role_services(query_db, cancel_user_role) + logger.info(cancel_user_role_result.message) + + return ResponseUtil.success(msg=cancel_user_role_result.message) + + +@roleController.put('/authUser/cancelAll', dependencies=[Depends(CheckUserInterfaceAuth('system:role:edit'))]) +@Log(title='角色管理', business_type=BusinessType.GRANT) +async def batch_cancel_system_role_user( + request: Request, + batch_cancel_user_role: CrudUserRoleModel = Query(), + query_db: AsyncSession = Depends(get_db), +): + batch_cancel_user_role_result = await UserService.delete_user_role_services(query_db, batch_cancel_user_role) + logger.info(batch_cancel_user_role_result.message) + + return ResponseUtil.success(msg=batch_cancel_user_role_result.message) diff --git a/dash-fastapi-backend/module_admin/controller/server_controller.py b/dash-fastapi-backend/module_admin/controller/server_controller.py index 0119f9fb44e00fed9248b5303e1bbba2fc255810..f63fdf8010e1bd16dc228c5e3ad4a306100b5d97 100644 --- a/dash-fastapi-backend/module_admin/controller/server_controller.py +++ b/dash-fastapi-backend/module_admin/controller/server_controller.py @@ -1,22 +1,21 @@ -from fastapi import APIRouter, Request -from fastapi import Depends -from module_admin.service.login_service import get_current_user -from module_admin.service.server_service import * -from utils.response_util import * -from utils.log_util import * +from fastapi import APIRouter, Depends, Request from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.server_vo import ServerMonitorModel +from module_admin.service.login_service import LoginService +from module_admin.service.server_service import ServerService +from utils.response_util import ResponseUtil +from utils.log_util import logger -serverController = APIRouter(prefix='/server', dependencies=[Depends(get_current_user)]) +serverController = APIRouter(prefix='/monitor/server', dependencies=[Depends(LoginService.get_current_user)]) -@serverController.post("/statisticalInfo", response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))]) +@serverController.get( + '', response_model=ServerMonitorModel, dependencies=[Depends(CheckUserInterfaceAuth('monitor:server:list'))] +) async def get_monitor_server_info(request: Request): - try: - # 获取全量数据 - server_info_query_result = ServerService.get_server_monitor_info() - logger.info('获取成功') - return response_200(data=server_info_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + # 获取全量数据 + server_info_query_result = await ServerService.get_server_monitor_info() + logger.info('获取成功') + + return ResponseUtil.success(data=server_info_query_result) diff --git a/dash-fastapi-backend/module_admin/controller/user_controller.py b/dash-fastapi-backend/module_admin/controller/user_controller.py index c14d4d21bb95fa9d9817f43966e9b8ec49ff63cb..eda93a698b8f30a27cacf0dbe04b24a82b0fcec7 100644 --- a/dash-fastapi-backend/module_admin/controller/user_controller.py +++ b/dash-fastapi-backend/module_admin/controller/user_controller.py @@ -1,275 +1,399 @@ -from fastapi import APIRouter, Request -from fastapi import Depends -import base64 +import os +from datetime import datetime +from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional, Union +from pydantic_validation_decorator import ValidateFields from config.get_db import get_db -from module_admin.service.login_service import get_current_user -from module_admin.service.user_service import * -from module_admin.entity.vo.user_vo import * -from module_admin.dao.user_dao import * -from utils.page_util import get_page_obj -from utils.response_util import * -from utils.log_util import * -from utils.common_util import bytes2file_response -from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from config.env import UploadConfig +from module_admin.annotation.log_annotation import Log from module_admin.aspect.data_scope import GetDataScope -from module_admin.annotation.log_annotation import log_decorator - - -userController = APIRouter(dependencies=[Depends(get_current_user)]) - - -@userController.post("/user/get", response_model=UserPageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))]) -async def get_system_user_list(request: Request, user_page_query: UserPageObject, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysUser'))): - try: - user_query = UserQueryModel(**user_page_query.dict()) - # 获取全量数据 - user_query_result = UserService.get_user_list_services(query_db, user_query, data_scope_sql) - # 分页操作 - user_page_query_result = get_page_obj(user_query_result, user_page_query.page_num, user_page_query.page_size) - logger.info('获取成功') - return response_200(data=user_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/add", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:user:add'))]) -@log_decorator(title='用户管理', business_type=1) -async def add_system_user(request: Request, add_user: AddUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - add_user.password = PwdUtil.get_password_hash(add_user.password) - add_user.create_by = current_user.user.user_name - add_user.create_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_user.update_by = current_user.user.user_name - add_user.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - add_user_result = UserService.add_user_services(query_db, add_user) - if add_user_result.is_success: - logger.info(add_user_result.message) - return response_200(data=add_user_result, message=add_user_result.message) - else: - logger.warning(add_user_result.message) - return response_400(data="", message=add_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.patch("/user/edit", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) -@log_decorator(title='用户管理', business_type=2) -async def edit_system_user(request: Request, edit_user: AddUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_user_result = UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return response_200(data=edit_user_result, message=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return response_400(data="", message=edit_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/delete", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:user:remove'))]) -@log_decorator(title='用户管理', business_type=3) -async def delete_system_user(request: Request, delete_user: DeleteUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - delete_user.update_by = current_user.user.user_name - delete_user.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - delete_user_result = UserService.delete_user_services(query_db, delete_user) - if delete_user_result.is_success: - logger.info(delete_user_result.message) - return response_200(data=delete_user_result, message=delete_user_result.message) - else: - logger.warning(delete_user_result.message) - return response_400(data="", message=delete_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.get("/user/{user_id}", response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))]) -async def query_detail_system_user(request: Request, user_id: int, query_db: Session = Depends(get_db)): - try: - delete_user_result = UserService.detail_user_services(query_db, user_id) - logger.info(f'获取user_id为{user_id}的信息成功') - return response_200(data=delete_user_result, message='获取成功') - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.patch("/user/profile/changeAvatar", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -@log_decorator(title='个人信息', business_type=2) -async def change_system_user_profile_avatar(request: Request, edit_user: AddUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - avatar = edit_user.avatar - # 去除 base64 字符串中的头部信息(data:image/jpeg;base64, 等等) - base64_string = avatar.split(',', 1)[1] - # 解码 base64 字符串 - file_data = base64.b64decode(base64_string) - dir_path = os.path.join(CachePathConfig.PATH, 'avatar', current_user.user.user_name) +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.entity.vo.dept_vo import DeptModel +from module_admin.entity.vo.user_vo import ( + AddUserModel, + CrudUserRoleModel, + CurrentUserModel, + DeleteUserModel, + EditUserModel, + ResetPasswordModel, + ResetUserModel, + UserDetailModel, + UserInfoModel, + UserModel, + UserPageQueryModel, + UserProfileModel, + UserRoleQueryModel, + UserRoleResponseModel, +) +from module_admin.service.login_service import LoginService +from module_admin.service.user_service import UserService +from module_admin.service.role_service import RoleService +from module_admin.service.dept_service import DeptService +from config.enums import BusinessType +from utils.common_util import bytes2file_response +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.pwd_util import PwdUtil +from utils.response_util import ResponseUtil +from utils.upload_util import UploadUtil + + +userController = APIRouter(prefix='/system/user', dependencies=[Depends(LoginService.get_current_user)]) + + +@userController.get('/deptTree', dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))]) +async def get_system_dept_tree( + request: Request, query_db: AsyncSession = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysDept')) +): + dept_query_result = await DeptService.get_dept_tree_services(query_db, DeptModel(**{}), data_scope_sql) + logger.info('获取成功') + + return ResponseUtil.success(data=dept_query_result) + + +@userController.get( + '/list', response_model=PageResponseModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:list'))] +) +async def get_system_user_list( + request: Request, + user_page_query: UserPageQueryModel = Query(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + # 获取分页数据 + user_page_query_result = await UserService.get_user_list_services( + query_db, user_page_query, data_scope_sql, is_page=True + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=user_page_query_result) + + +@userController.post('', dependencies=[Depends(CheckUserInterfaceAuth('system:user:add'))]) +@ValidateFields(validate_model='add_user') +@Log(title='用户管理', business_type=BusinessType.INSERT) +async def add_system_user( + request: Request, + add_user: AddUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + dept_data_scope_sql: str = Depends(GetDataScope('SysDept')), + role_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services(query_db, add_user.dept_id, dept_data_scope_sql) + await RoleService.check_role_data_scope_services( + query_db, ','.join([str(item) for item in add_user.role_ids]), role_data_scope_sql + ) + add_user.password = PwdUtil.get_password_hash(add_user.password) + add_user.create_by = current_user.user.user_name + add_user.create_time = datetime.now() + add_user.update_by = current_user.user.user_name + add_user.update_time = datetime.now() + add_user_result = await UserService.add_user_services(query_db, add_user) + logger.info(add_user_result.message) + + return ResponseUtil.success(msg=add_user_result.message) + + +@userController.put('', dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) +@ValidateFields(validate_model='edit_user') +@Log(title='用户管理', business_type=BusinessType.UPDATE) +async def edit_system_user( + request: Request, + edit_user: EditUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + user_data_scope_sql: str = Depends(GetDataScope('SysUser')), + dept_data_scope_sql: str = Depends(GetDataScope('SysDept')), + role_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + await UserService.check_user_allowed_services(edit_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, edit_user.user_id, user_data_scope_sql) + await DeptService.check_dept_data_scope_services(query_db, edit_user.dept_id, dept_data_scope_sql) + await RoleService.check_role_data_scope_services( + query_db, ','.join([str(item) for item in edit_user.role_ids]), role_data_scope_sql + ) + edit_user.update_by = current_user.user.user_name + edit_user.update_time = datetime.now() + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.delete('/{user_ids}', dependencies=[Depends(CheckUserInterfaceAuth('system:user:remove'))]) +@Log(title='用户管理', business_type=BusinessType.DELETE) +async def delete_system_user( + request: Request, + user_ids: str, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + user_id_list = user_ids.split(',') if user_ids else [] + if user_id_list: + if current_user.user.user_id in user_id_list: + logger.warning('当前登录用户不能删除') + + return ResponseUtil.failure(msg='当前登录用户不能删除') + for user_id in user_id_list: + await UserService.check_user_allowed_services(UserModel(user_id=int(user_id))) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, int(user_id), data_scope_sql) + delete_user = DeleteUserModel(user_ids=user_ids, update_by=current_user.user.user_name, update_time=datetime.now()) + delete_user_result = await UserService.delete_user_services(query_db, delete_user) + logger.info(delete_user_result.message) + + return ResponseUtil.success(msg=delete_user_result.message) + + +@userController.put('/resetPwd', dependencies=[Depends(CheckUserInterfaceAuth('system:user:resetPwd'))]) +@Log(title='用户管理', business_type=BusinessType.UPDATE) +async def reset_system_user_pwd( + request: Request, + reset_user: EditUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + await UserService.check_user_allowed_services(reset_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, reset_user.user_id, data_scope_sql) + edit_user = EditUserModel( + user_id=reset_user.user_id, + password=PwdUtil.get_password_hash(reset_user.password), + update_by=current_user.user.user_name, + update_time=datetime.now(), + type='pwd', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.put('/changeStatus', dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) +@Log(title='用户管理', business_type=BusinessType.UPDATE) +async def change_system_user_status( + request: Request, + change_user: EditUserModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + await UserService.check_user_allowed_services(change_user) + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, change_user.user_id, data_scope_sql) + edit_user = EditUserModel( + user_id=change_user.user_id, + status=change_user.status, + update_by=current_user.user.user_name, + update_time=datetime.now(), + type='status', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.get('/profile', response_model=UserProfileModel) +async def query_detail_system_user_profile( + request: Request, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + profile_user_result = await UserService.user_profile_services(query_db, current_user.user.user_id) + logger.info(f'获取user_id为{current_user.user.user_id}的信息成功') + + return ResponseUtil.success(model_content=profile_user_result) + + +@userController.get( + '/{user_id}', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))] +) +@userController.get( + '/', response_model=UserDetailModel, dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))] +) +async def query_detail_system_user( + request: Request, + user_id: Optional[Union[int, str]] = '', + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + if user_id and not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, user_id, data_scope_sql) + detail_user_result = await UserService.user_detail_services(query_db, user_id) + logger.info(f'获取user_id为{user_id}的信息成功') + + return ResponseUtil.success(model_content=detail_user_result) + + +@userController.post('/profile/avatar') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def change_system_user_profile_avatar( + request: Request, + avatarfile: bytes = File(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + if avatarfile: + relative_path = ( + f'avatar/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' + ) + dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path) try: os.makedirs(dir_path) except FileExistsError: pass - filepath = os.path.join(dir_path, f'{current_user.user.user_name}_avatar.jpeg') - with open(filepath, 'wb') as f: - f.write(file_data) - edit_user.user_id = current_user.user.user_id - edit_user.avatar = f'/common/{CachePathConfig.PATHSTR}?taskPath=avatar&taskId={current_user.user.user_name}&filename={current_user.user.user_name}_avatar.jpeg' - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_user_result = UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return response_200(data=edit_user_result, message=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return response_400(data="", message=edit_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.patch("/user/profile/changeInfo", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -@log_decorator(title='个人信息', business_type=2) -async def change_system_user_profile_info(request: Request, edit_user: AddUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - edit_user.user_id = current_user.user.user_id - edit_user.update_by = current_user.user.user_name - edit_user.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - edit_user_result = UserService.edit_user_services(query_db, edit_user) - if edit_user_result.is_success: - logger.info(edit_user_result.message) - return response_200(data=edit_user_result, message=edit_user_result.message) - else: - logger.warning(edit_user_result.message) - return response_400(data="", message=edit_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.patch("/user/profile/resetPwd", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -@log_decorator(title='个人信息', business_type=2) -async def reset_system_user_password(request: Request, reset_user: ResetUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - if not reset_user.user_id and reset_user.old_password: - reset_user.user_id = current_user.user.user_id - reset_user.password = PwdUtil.get_password_hash(reset_user.password) - reset_user.update_by = current_user.user.user_name - reset_user.update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - reset_user_result = UserService.reset_user_services(query_db, reset_user) - if reset_user_result.is_success: - logger.info(reset_user_result.message) - return response_200(data=reset_user_result, message=reset_user_result.message) - else: - logger.warning(reset_user_result.message) - return response_400(data="", message=reset_user_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/importData", dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) -@log_decorator(title='用户管理', business_type=6) -async def batch_import_system_user(request: Request, user_import: ImportUserModel, query_db: Session = Depends(get_db), current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)): - try: - batch_import_result = UserService.batch_import_user_services(query_db, user_import, current_user) - if batch_import_result.is_success: - logger.info(batch_import_result.message) - return response_200(data=batch_import_result, message=batch_import_result.message) - else: - logger.warning(batch_import_result.message) - return response_400(data="", message=batch_import_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/importTemplate", dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) -async def export_system_user_template(request: Request, query_db: Session = Depends(get_db)): - try: - user_import_template_result = UserService.get_user_import_template_services() - logger.info('获取成功') - return streaming_response_200(data=bytes2file_response(user_import_template_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/export", dependencies=[Depends(CheckUserInterfaceAuth('system:user:export'))]) -@log_decorator(title='用户管理', business_type=5) -async def export_system_user_list(request: Request, user_query: UserQueryModel, query_db: Session = Depends(get_db), data_scope_sql: str = Depends(GetDataScope('SysUser'))): - try: - # 获取全量数据 - user_query_result = UserService.get_user_list_services(query_db, user_query, data_scope_sql) - user_export_result = UserService.export_user_list_services(user_query_result) - logger.info('导出成功') - return streaming_response_200(data=bytes2file_response(user_export_result)) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/authRole/allocatedList", response_model=UserRolePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_allocated_role_list(request: Request, user_role: UserRolePageObject, query_db: Session = Depends(get_db)): - try: - user_role_query = UserRoleQueryModel(**user_role.dict()) - user_role_allocated_query_result = UserService.get_user_role_allocated_list_services(query_db, user_role_query) - # 分页操作 - user_role_allocated_page_query_result = get_page_obj(user_role_allocated_query_result, user_role.page_num, user_role.page_size) - logger.info('获取成功') - return response_200(data=user_role_allocated_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/authRole/unallocatedList", response_model=UserRolePageObjectResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))]) -async def get_system_unallocated_role_list(request: Request, user_role: UserRolePageObject, query_db: Session = Depends(get_db)): - try: - user_role_query = UserRoleQueryModel(**user_role.dict()) - user_role_unallocated_query_result = UserService.get_user_role_unallocated_list_services(query_db, user_role_query) - # 分页操作 - user_role_unallocated_page_query_result = get_page_obj(user_role_unallocated_query_result, user_role.page_num, user_role.page_size) - logger.info('获取成功') - return response_200(data=user_role_unallocated_page_query_result, message="获取成功") - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/authRole/selectAll", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) -@log_decorator(title='用户管理', business_type=4) -async def add_system_role_user(request: Request, add_user_role: CrudUserRoleModel, query_db: Session = Depends(get_db)): - try: - add_user_role_result = UserService.add_user_role_services(query_db, add_user_role) - if add_user_role_result.is_success: - logger.info(add_user_role_result.message) - return response_200(data=add_user_role_result, message=add_user_role_result.message) - else: - logger.warning(add_user_role_result.message) - return response_400(data="", message=add_user_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) - - -@userController.post("/user/authRole/cancel", response_model=CrudUserResponse, dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))]) -@log_decorator(title='用户管理', business_type=4) -async def cancel_system_role_user(request: Request, cancel_user_role: CrudUserRoleModel, query_db: Session = Depends(get_db)): - try: - cancel_user_role_result = UserService.delete_user_role_services(query_db, cancel_user_role) - if cancel_user_role_result.is_success: - logger.info(cancel_user_role_result.message) - return response_200(data=cancel_user_role_result, message=cancel_user_role_result.message) - else: - logger.warning(cancel_user_role_result.message) - return response_400(data="", message=cancel_user_role_result.message) - except Exception as e: - logger.exception(e) - return response_500(data="", message=str(e)) + avatar_name = f'avatar_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.png' + avatar_path = os.path.join(dir_path, avatar_name) + with open(avatar_path, 'wb') as f: + f.write(avatarfile) + edit_user = EditUserModel( + user_id=current_user.user.user_id, + avatar=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{avatar_name}', + update_by=current_user.user.user_name, + update_time=datetime.now(), + type='avatar', + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(dict_content={'imgUrl': edit_user.avatar}, msg=edit_user_result.message) + return ResponseUtil.failure(msg='上传图片异常,请联系管理员') + + +@userController.put('/profile') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def change_system_user_profile_info( + request: Request, + user_info: UserInfoModel, + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + edit_user = EditUserModel( + **user_info.model_dump(exclude_unset=True, exclude={'role_ids', 'post_ids'}), + user_id=current_user.user.user_id, + user_name=current_user.user.user_name, + update_by=current_user.user.user_name, + update_time=datetime.now(), + role_ids=current_user.user.role_ids.split(',') if current_user.user.role_ids else [], + post_ids=current_user.user.post_ids.split(',') if current_user.user.post_ids else [], + role=current_user.user.role, + ) + edit_user_result = await UserService.edit_user_services(query_db, edit_user) + logger.info(edit_user_result.message) + + return ResponseUtil.success(msg=edit_user_result.message) + + +@userController.put('/profile/updatePwd') +@Log(title='个人信息', business_type=BusinessType.UPDATE) +async def reset_system_user_password( + request: Request, + reset_password: ResetPasswordModel = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), +): + reset_user = ResetUserModel( + user_id=current_user.user.user_id, + old_password=reset_password.old_password, + password=reset_password.new_password, + update_by=current_user.user.user_name, + update_time=datetime.now(), + ) + reset_user_result = await UserService.reset_user_services(query_db, reset_user) + logger.info(reset_user_result.message) + + return ResponseUtil.success(msg=reset_user_result.message) + + +@userController.post('/importData', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) +@Log(title='用户管理', business_type=BusinessType.IMPORT) +async def batch_import_system_user( + request: Request, + file: UploadFile = File(...), + update_support: bool = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + user_data_scope_sql: str = Depends(GetDataScope('SysUser')), + dept_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + batch_import_result = await UserService.batch_import_user_services( + request, query_db, file, update_support, current_user, user_data_scope_sql, dept_data_scope_sql + ) + logger.info(batch_import_result.message) + + return ResponseUtil.success(msg=batch_import_result.message) + + +@userController.post('/importTemplate', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))]) +async def export_system_user_template(request: Request, query_db: AsyncSession = Depends(get_db)): + user_import_template_result = await UserService.get_user_import_template_services() + logger.info('获取成功') + + return ResponseUtil.streaming(data=bytes2file_response(user_import_template_result)) + + +@userController.post('/export', dependencies=[Depends(CheckUserInterfaceAuth('system:user:export'))]) +@Log(title='用户管理', business_type=BusinessType.EXPORT) +async def export_system_user_list( + request: Request, + user_page_query: UserPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), + data_scope_sql: str = Depends(GetDataScope('SysUser')), +): + # 获取全量数据 + user_query_result = await UserService.get_user_list_services( + query_db, user_page_query, data_scope_sql, is_page=False + ) + user_export_result = await UserService.export_user_list_services(user_query_result) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(user_export_result)) + + +@userController.get( + '/authRole/{user_id}', + response_model=UserRoleResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:user:query'))], +) +async def get_system_allocated_role_list(request: Request, user_id: int, query_db: AsyncSession = Depends(get_db)): + user_role_query = UserRoleQueryModel(user_id=user_id) + user_role_allocated_query_result = await UserService.get_user_role_allocated_list_services( + query_db, user_role_query + ) + logger.info('获取成功') + + return ResponseUtil.success(model_content=user_role_allocated_query_result) + + +@userController.put( + '/authRole', + response_model=UserRoleResponseModel, + dependencies=[Depends(CheckUserInterfaceAuth('system:user:edit'))], +) +@Log(title='用户管理', business_type=BusinessType.GRANT) +async def update_system_role_user( + request: Request, + user_id: int = Query(), + role_ids: str = Query(), + query_db: AsyncSession = Depends(get_db), + current_user: CurrentUserModel = Depends(LoginService.get_current_user), + user_data_scope_sql: str = Depends(GetDataScope('SysUser')), + role_data_scope_sql: str = Depends(GetDataScope('SysDept')), +): + if not current_user.user.admin: + await UserService.check_user_data_scope_services(query_db, user_id, user_data_scope_sql) + await RoleService.check_role_data_scope_services(query_db, role_ids, role_data_scope_sql) + add_user_role_result = await UserService.add_user_role_services( + query_db, CrudUserRoleModel(user_id=user_id, role_ids=role_ids) + ) + logger.info(add_user_role_result.message) + + return ResponseUtil.success(msg=add_user_role_result.message) diff --git a/dash-fastapi-backend/module_admin/dao/config_dao.py b/dash-fastapi-backend/module_admin/dao/config_dao.py index a7fb473c973a48c23950f69279020b62f5f5b19c..7b3c067847d85abd4cf3ba05e084dc3973c12c6c 100644 --- a/dash-fastapi-backend/module_admin/dao/config_dao.py +++ b/dash-fastapi-backend/module_admin/dao/config_dao.py @@ -1,8 +1,9 @@ -from sqlalchemy.orm import Session -from module_admin.entity.do.config_do import SysConfig -from module_admin.entity.vo.config_vo import ConfigModel, ConfigQueryModel -from utils.time_format_util import list_format_datetime from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.config_do import SysConfig +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel +from utils.page_util import PageUtil class ConfigDao: @@ -11,100 +12,104 @@ class ConfigDao: """ @classmethod - def get_config_detail_by_id(cls, db: Session, config_id: int): + async def get_config_detail_by_id(cls, db: AsyncSession, config_id: int): """ 根据参数配置id获取参数配置详细信息 + :param db: orm对象 :param config_id: 参数配置id :return: 参数配置信息对象 """ - config_info = db.query(SysConfig) \ - .filter(SysConfig.config_id == config_id) \ - .first() + config_info = (await db.execute(select(SysConfig).where(SysConfig.config_id == config_id))).scalars().first() return config_info @classmethod - def get_config_detail_by_info(cls, db: Session, config: ConfigModel): + async def get_config_detail_by_info(cls, db: AsyncSession, config: ConfigModel): """ 根据参数配置参数获取参数配置信息 + :param db: orm对象 :param config: 参数配置参数对象 :return: 参数配置信息对象 """ - config_info = db.query(SysConfig) \ - .filter(SysConfig.config_key == config.config_key if config.config_key else True, - SysConfig.config_value == config.config_value if config.config_value else True) \ + config_info = ( + ( + await db.execute( + select(SysConfig).where( + SysConfig.config_key == config.config_key if config.config_key else True, + SysConfig.config_value == config.config_value if config.config_value else True, + ) + ) + ) + .scalars() .first() + ) return config_info @classmethod - def get_all_config(cls, db: Session): - """ - 获取所有的参数配置信息 - :param db: orm对象 - :return: 参数配置信息列表对象 - """ - config_info = db.query(SysConfig).all() - - return list_format_datetime(config_info) - - @classmethod - def get_config_list(cls, db: Session, query_object: ConfigQueryModel): + async def get_config_list(cls, db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False): """ 根据查询参数获取参数配置列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 参数配置列表信息对象 """ - config_list = db.query(SysConfig) \ - .filter(SysConfig.config_name.like(f'%{query_object.config_name}%') if query_object.config_name else True, - SysConfig.config_key.like(f'%{query_object.config_key}%') if query_object.config_key else True, - SysConfig.config_type == query_object.config_type if query_object.config_type else True, - SysConfig.create_time.between( - datetime.combine(datetime.strptime(query_object.create_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.create_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.create_time_start and query_object.create_time_end else True - ) \ - .distinct().all() - - return list_format_datetime(config_list) + query = ( + select(SysConfig) + .where( + SysConfig.config_name.like(f'%{query_object.config_name}%') if query_object.config_name else True, + SysConfig.config_key.like(f'%{query_object.config_key}%') if query_object.config_key else True, + SysConfig.config_type == query_object.config_type if query_object.config_type else True, + SysConfig.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + ) + config_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return config_list @classmethod - def add_config_dao(cls, db: Session, config: ConfigModel): + async def add_config_dao(cls, db: AsyncSession, config: ConfigModel): """ 新增参数配置数据库操作 + :param db: orm对象 :param config: 参数配置对象 :return: """ - db_config = SysConfig(**config.dict()) + db_config = SysConfig(**config.model_dump()) db.add(db_config) - db.flush() + await db.flush() return db_config @classmethod - def edit_config_dao(cls, db: Session, config: dict): + async def edit_config_dao(cls, db: AsyncSession, config: dict): """ 编辑参数配置数据库操作 + :param db: orm对象 :param config: 需要更新的参数配置字典 :return: """ - db.query(SysConfig) \ - .filter(SysConfig.config_id == config.get('config_id')) \ - .update(config) + await db.execute(update(SysConfig), [config]) @classmethod - def delete_config_dao(cls, db: Session, config: ConfigModel): + async def delete_config_dao(cls, db: AsyncSession, config: ConfigModel): """ 删除参数配置数据库操作 + :param db: orm对象 :param config: 参数配置对象 :return: """ - db.query(SysConfig) \ - .filter(SysConfig.config_id == config.config_id) \ - .delete() + await db.execute(delete(SysConfig).where(SysConfig.config_id.in_([config.config_id]))) diff --git a/dash-fastapi-backend/module_admin/dao/dept_dao.py b/dash-fastapi-backend/module_admin/dao/dept_dao.py index 32d85abb410a2a184664217c01d8bf15cb9af136..f45021008ebde852a92f9caf055957183409e6b8 100644 --- a/dash-fastapi-backend/module_admin/dao/dept_dao.py +++ b/dash-fastapi-backend/module_admin/dao/dept_dao.py @@ -1,9 +1,11 @@ -from sqlalchemy import or_, func -from sqlalchemy.orm import Session -from module_admin.entity.do.role_do import SysRoleDept +from sqlalchemy import bindparam, func, or_, select, update # noqa: F401 +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.util import immutabledict +from typing import List from module_admin.entity.do.dept_do import SysDept -from module_admin.entity.vo.dept_vo import DeptModel, DeptResponse, CrudDeptResponse -from utils.time_format_util import list_format_datetime +from module_admin.entity.do.role_do import SysRoleDept # noqa: F401 +from module_admin.entity.do.user_do import SysUser +from module_admin.entity.vo.dept_vo import DeptModel class DeptDao: @@ -12,189 +14,294 @@ class DeptDao: """ @classmethod - def get_dept_by_id(cls, db: Session, dept_id: int): + async def get_dept_by_id(cls, db: AsyncSession, dept_id: int): """ 根据部门id获取在用部门信息 - :param db: orm对象 - :param dept_id: 部门id - :return: 在用部门信息对象 - """ - dept_info = db.query(SysDept) \ - .filter(SysDept.dept_id == dept_id, - SysDept.status == 0, - SysDept.del_flag == 0) \ - .first() - return dept_info - - @classmethod - def get_dept_by_id_for_list(cls, db: Session, dept_id: int): - """ - 用于获取部门列表的工具方法 :param db: orm对象 :param dept_id: 部门id - :return: 部门id对应的信息对象 + :return: 在用部门信息对象 """ - dept_info = db.query(SysDept) \ - .filter(SysDept.dept_id == dept_id, - SysDept.del_flag == 0) \ - .first() + dept_info = (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id))).scalars().first() return dept_info @classmethod - def get_dept_detail_by_id(cls, db: Session, dept_id: int): + async def get_dept_detail_by_id(cls, db: AsyncSession, dept_id: int): """ 根据部门id获取部门详细信息 + :param db: orm对象 :param dept_id: 部门id :return: 部门信息对象 """ - dept_info = db.query(SysDept) \ - .filter(SysDept.dept_id == dept_id, - SysDept.del_flag == 0) \ + dept_info = ( + (await db.execute(select(SysDept).where(SysDept.dept_id == dept_id, SysDept.del_flag == '0'))) + .scalars() .first() + ) return dept_info @classmethod - def get_dept_detail_by_info(cls, db: Session, dept: DeptModel): + async def get_dept_detail_by_info(cls, db: AsyncSession, dept: DeptModel): """ 根据部门参数获取部门信息 + :param db: orm对象 :param dept: 部门参数对象 :return: 部门信息对象 """ - dept_info = db.query(SysDept) \ - .filter(SysDept.parent_id == dept.parent_id if dept.parent_id else True, - SysDept.dept_name == dept.dept_name if dept.dept_name else True) \ + dept_info = ( + ( + await db.execute( + select(SysDept).where( + SysDept.parent_id == dept.parent_id if dept.parent_id else True, + SysDept.dept_name == dept.dept_name if dept.dept_name else True, + ) + ) + ) + .scalars() .first() + ) return dept_info @classmethod - def get_dept_info_for_edit_option(cls, db: Session, dept_info: DeptModel, data_scope_sql: str): + async def get_dept_info_for_edit_option(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str): """ 获取部门编辑对应的在用部门列表信息 + :param db: orm对象 :param dept_info: 部门对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门列表信息 """ - dept_result = db.query(SysDept) \ - .filter(SysDept.dept_id != dept_info.dept_id, - SysDept.parent_id != dept_info.dept_id, - SysDept.del_flag == 0, SysDept.status == 0, - eval(data_scope_sql)) \ - .order_by(SysDept.order_num) \ - .distinct().all() + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.dept_id != dept_info.dept_id, + ~SysDept.dept_id.in_( + select(SysDept.dept_id).where(func.find_in_set(dept_info.dept_id, SysDept.ancestors)) + ), + SysDept.del_flag == '0', + SysDept.status == '0', + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) - return list_format_datetime(dept_result) + return dept_result @classmethod - def get_children_dept(cls, db: Session, dept_id: int): + async def get_children_dept_dao(cls, db: AsyncSession, dept_id: int): """ 根据部门id查询当前部门的子部门列表信息 + :param db: orm对象 :param dept_id: 部门id :return: 子部门信息列表 """ - dept_result = db.query(SysDept) \ - .filter(SysDept.parent_id == dept_id, - SysDept.del_flag == 0) \ - .all() - - return list_format_datetime(dept_result) - - @classmethod - def get_dept_all_ancestors(cls, db: Session): - """ - 获取所有部门的ancestors信息 - :param db: orm对象 - :return: ancestors信息列表 - """ - ancestors = db.query(SysDept.ancestors)\ - .filter(SysDept.del_flag == 0)\ - .all() + dept_result = ( + (await db.execute(select(SysDept).where(func.find_in_set(dept_id, SysDept.ancestors)))).scalars().all() + ) - return ancestors + return dept_result @classmethod - def get_dept_list_for_tree(cls, db: Session, dept_info: DeptModel, data_scope_sql: str): + async def get_dept_list_for_tree(cls, db: AsyncSession, dept_info: DeptModel, data_scope_sql: str): """ 获取所有在用部门列表信息 + :param db: orm对象 :param dept_info: 部门对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 在用部门列表信息 """ - dept_result = db.query(SysDept) \ - .filter(SysDept.status == 0, - SysDept.del_flag == 0, - SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True, - eval(data_scope_sql)) \ - .order_by(SysDept.order_num) \ - .distinct().all() + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.status == '0', + SysDept.del_flag == '0', + SysDept.dept_name.like(f'%{dept_info.dept_name}%') if dept_info.dept_name else True, + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) - return list_format_datetime(dept_result) + return dept_result @classmethod - def get_dept_list(cls, db: Session, page_object: DeptModel, data_scope_sql: str): + async def get_dept_list(cls, db: AsyncSession, page_object: DeptModel, data_scope_sql: str): """ 根据查询参数获取部门列表信息 + :param db: orm对象 :param page_object: 不分页查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门列表信息对象 """ - dept_result = db.query(SysDept) \ - .filter(SysDept.del_flag == 0, - SysDept.status == page_object.status if page_object.status else True, - SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True, - eval(data_scope_sql)) \ - .order_by(SysDept.order_num) \ - .distinct().all() - - result = dict( - rows=list_format_datetime(dept_result), + dept_result = ( + ( + await db.execute( + select(SysDept) + .where( + SysDept.del_flag == '0', + SysDept.dept_id == page_object.dept_id if page_object.dept_id is not None else True, + SysDept.status == page_object.status if page_object.status else True, + SysDept.dept_name.like(f'%{page_object.dept_name}%') if page_object.dept_name else True, + eval(data_scope_sql), + ) + .order_by(SysDept.order_num) + .distinct() + ) + ) + .scalars() + .all() ) - return DeptResponse(**result) + return dept_result @classmethod - def add_dept_dao(cls, db: Session, dept: DeptModel): + async def add_dept_dao(cls, db: AsyncSession, dept: DeptModel): """ 新增部门数据库操作 + :param db: orm对象 :param dept: 部门对象 :return: 新增校验结果 """ - db_dept = SysDept(**dept.dict()) + db_dept = SysDept(**dept.model_dump()) db.add(db_dept) - db.flush() + await db.flush() return db_dept @classmethod - def edit_dept_dao(cls, db: Session, dept: dict): + async def edit_dept_dao(cls, db: AsyncSession, dept: dict): """ 编辑部门数据库操作 + :param db: orm对象 :param dept: 需要更新的部门字典 :return: 编辑校验结果 """ - db.query(SysDept) \ - .filter(SysDept.dept_id == dept.get('dept_id')) \ - .update(dept) + await db.execute(update(SysDept), [dept]) @classmethod - def delete_dept_dao(cls, db: Session, dept: DeptModel): + async def update_dept_children_dao(cls, db: AsyncSession, update_dept: List): + """ + 更新子部门信息 + + :param db: orm对象 + :param update_dept: 需要更新的部门列表 + :return: + """ + await db.execute( + update(SysDept) + .where(SysDept.dept_id == bindparam('dept_id')) + .values( + { + 'dept_id': bindparam('dept_id'), + 'ancestors': bindparam('ancestors'), + } + ), + update_dept, + execution_options=immutabledict({'synchronize_session': None}), + ) + + @classmethod + async def update_dept_status_normal_dao(cls, db: AsyncSession, dept_id_list: List): + """ + 批量更新部门状态为正常 + + :param db: orm对象 + :param dept_id_list: 部门id列表 + :return: + """ + await db.execute(update(SysDept).where(SysDept.dept_id.in_(dept_id_list)).values(status='0')) + + @classmethod + async def delete_dept_dao(cls, db: AsyncSession, dept: DeptModel): """ 删除部门数据库操作 + :param db: orm对象 :param dept: 部门对象 :return: """ - db.query(SysDept) \ - .filter(SysDept.dept_id == dept.dept_id) \ - .update({SysDept.del_flag: '2', SysDept.update_by: dept.update_by, SysDept.update_time: dept.update_time}) + await db.execute( + update(SysDept) + .where(SysDept.dept_id == dept.dept_id) + .values(del_flag='2', update_by=dept.update_by, update_time=dept.update_time) + ) + + @classmethod + async def count_normal_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询所有子部门(正常状态)的数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 所有子部门(正常状态)的数量 + """ + normal_children_dept_count = ( + await db.execute( + select(func.count('*')) + .select_from(SysDept) + .where(SysDept.status == '0', SysDept.del_flag == '0', func.find_in_set(dept_id, SysDept.ancestors)) + ) + ).scalar() + + return normal_children_dept_count + + @classmethod + async def count_children_dept_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询所有子部门(所有状态)的数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 所有子部门(所有状态)的数量 + """ + children_dept_count = ( + await db.execute( + select(func.count('*')) + .select_from(SysDept) + .where(SysDept.del_flag == '0', SysDept.parent_id == dept_id) + .limit(1) + ) + ).scalar() + + return children_dept_count + + @classmethod + async def count_dept_user_dao(cls, db: AsyncSession, dept_id: int): + """ + 根据部门id查询查询部门下的用户数量 + + :param db: orm对象 + :param dept_id: 部门id + :return: 部门下的用户数量 + """ + dept_user_count = ( + await db.execute( + select(func.count('*')).select_from(SysUser).where(SysUser.dept_id == dept_id, SysUser.del_flag == '0') + ) + ).scalar() + + return dept_user_count diff --git a/dash-fastapi-backend/module_admin/dao/dict_dao.py b/dash-fastapi-backend/module_admin/dao/dict_dao.py index 30312fd9dad7fa09ef858a208500d09f0472ccd8..8f4aab221714345d545dee6edd4f981be81631c1 100644 --- a/dash-fastapi-backend/module_admin/dao/dict_dao.py +++ b/dash-fastapi-backend/module_admin/dao/dict_dao.py @@ -1,9 +1,10 @@ -from sqlalchemy import and_ -from sqlalchemy.orm import Session +from datetime import datetime, time +from sqlalchemy import and_, delete, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.dict_do import SysDictType, SysDictData -from module_admin.entity.vo.dict_vo import DictTypeModel, DictTypeQueryModel, DictDataModel +from module_admin.entity.vo.dict_vo import DictDataModel, DictDataPageQueryModel, DictTypeModel, DictTypePageQueryModel +from utils.page_util import PageUtil from utils.time_format_util import list_format_datetime -from datetime import datetime, time class DictTypeDao: @@ -12,103 +13,119 @@ class DictTypeDao: """ @classmethod - def get_dict_type_detail_by_id(cls, db: Session, dict_id: int): + async def get_dict_type_detail_by_id(cls, db: AsyncSession, dict_id: int): """ 根据字典类型id获取字典类型详细信息 + :param db: orm对象 :param dict_id: 字典类型id :return: 字典类型信息对象 """ - dict_type_info = db.query(SysDictType) \ - .filter(SysDictType.dict_id == dict_id) \ - .first() + dict_type_info = (await db.execute(select(SysDictType).where(SysDictType.dict_id == dict_id))).scalars().first() return dict_type_info @classmethod - def get_dict_type_detail_by_info(cls, db: Session, dict_type: DictTypeModel): + async def get_dict_type_detail_by_info(cls, db: AsyncSession, dict_type: DictTypeModel): """ 根据字典类型参数获取字典类型信息 + :param db: orm对象 :param dict_type: 字典类型参数对象 :return: 字典类型信息对象 """ - dict_type_info = db.query(SysDictType) \ - .filter(SysDictType.dict_type == dict_type.dict_type if dict_type.dict_type else True, - SysDictType.dict_name == dict_type.dict_name if dict_type.dict_name else True) \ + dict_type_info = ( + ( + await db.execute( + select(SysDictType).where( + SysDictType.dict_type == dict_type.dict_type if dict_type.dict_type else True, + SysDictType.dict_name == dict_type.dict_name if dict_type.dict_name else True, + ) + ) + ) + .scalars() .first() + ) return dict_type_info @classmethod - def get_all_dict_type(cls, db: Session): + async def get_all_dict_type(cls, db: AsyncSession): """ 获取所有的字典类型信息 + :param db: orm对象 :return: 字典类型信息列表对象 """ - dict_type_info = db.query(SysDictType).all() + dict_type_info = (await db.execute(select(SysDictType))).scalars().all() return list_format_datetime(dict_type_info) @classmethod - def get_dict_type_list(cls, db: Session, query_object: DictTypeQueryModel): + async def get_dict_type_list(cls, db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False): """ 根据查询参数获取字典类型列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 字典类型列表信息对象 """ - dict_type_list = db.query(SysDictType) \ - .filter(SysDictType.dict_name.like(f'%{query_object.dict_name}%') if query_object.dict_name else True, - SysDictType.dict_type.like(f'%{query_object.dict_type}%') if query_object.dict_type else True, - SysDictType.status == query_object.status if query_object.status else True, - SysDictType.create_time.between( - datetime.combine(datetime.strptime(query_object.create_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.create_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.create_time_start and query_object.create_time_end else True - ) \ - .distinct().all() + query = ( + select(SysDictType) + .where( + SysDictType.dict_name.like(f'%{query_object.dict_name}%') if query_object.dict_name else True, + SysDictType.dict_type.like(f'%{query_object.dict_type}%') if query_object.dict_type else True, + SysDictType.status == query_object.status if query_object.status else True, + SysDictType.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + ) + dict_type_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(dict_type_list) + return dict_type_list @classmethod - def add_dict_type_dao(cls, db: Session, dict_type: DictTypeModel): + async def add_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel): """ 新增字典类型数据库操作 + :param db: orm对象 :param dict_type: 字典类型对象 :return: """ - db_dict_type = SysDictType(**dict_type.dict()) + db_dict_type = SysDictType(**dict_type.model_dump()) db.add(db_dict_type) - db.flush() + await db.flush() return db_dict_type @classmethod - def edit_dict_type_dao(cls, db: Session, dict_type: dict): + async def edit_dict_type_dao(cls, db: AsyncSession, dict_type: dict): """ 编辑字典类型数据库操作 + :param db: orm对象 :param dict_type: 需要更新的字典类型字典 :return: """ - db.query(SysDictType) \ - .filter(SysDictType.dict_id == dict_type.get('dict_id')) \ - .update(dict_type) + await db.execute(update(SysDictType), [dict_type]) @classmethod - def delete_dict_type_dao(cls, db: Session, dict_type: DictTypeModel): + async def delete_dict_type_dao(cls, db: AsyncSession, dict_type: DictTypeModel): """ 删除字典类型数据库操作 + :param db: orm对象 :param dict_type: 字典类型对象 :return: """ - db.query(SysDictType) \ - .filter(SysDictType.dict_id == dict_type.dict_id) \ - .delete() + await db.execute(delete(SysDictType).where(SysDictType.dict_id.in_([dict_type.dict_id]))) class DictDataDao: @@ -117,103 +134,147 @@ class DictDataDao: """ @classmethod - def get_dict_data_detail_by_id(cls, db: Session, dict_code: int): + async def get_dict_data_detail_by_id(cls, db: AsyncSession, dict_code: int): """ 根据字典数据id获取字典数据详细信息 + :param db: orm对象 :param dict_code: 字典数据id :return: 字典数据信息对象 """ - dict_data_info = db.query(SysDictData) \ - .filter(SysDictData.dict_code == dict_code) \ - .first() + dict_data_info = ( + (await db.execute(select(SysDictData).where(SysDictData.dict_code == dict_code))).scalars().first() + ) return dict_data_info @classmethod - def get_dict_data_detail_by_info(cls, db: Session, dict_data: DictDataModel): + async def get_dict_data_detail_by_info(cls, db: AsyncSession, dict_data: DictDataModel): """ 根据字典数据参数获取字典数据信息 + :param db: orm对象 :param dict_data: 字典数据参数对象 :return: 字典数据信息对象 """ - dict_data_info = db.query(SysDictData) \ - .filter(SysDictData.dict_type == dict_data.dict_type if dict_data.dict_type else True, - SysDictData.dict_label == dict_data.dict_label if dict_data.dict_label else True, - SysDictData.dict_value == dict_data.dict_value if dict_data.dict_value else True) \ + dict_data_info = ( + ( + await db.execute( + select(SysDictData).where( + SysDictData.dict_type == dict_data.dict_type, + SysDictData.dict_label == dict_data.dict_label, + SysDictData.dict_value == dict_data.dict_value, + ) + ) + ) + .scalars() .first() + ) return dict_data_info @classmethod - def get_dict_data_list(cls, db: Session, query_object: DictDataModel): + async def get_dict_data_list(cls, db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False): """ 根据查询参数获取字典数据列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 字典数据列表信息对象 """ - dict_data_list = db.query(SysDictData) \ - .filter(SysDictData.dict_type == query_object.dict_type if query_object.dict_type else True, - SysDictData.dict_label.like(f'%{query_object.dict_label}%') if query_object.dict_label else True, - SysDictData.status == query_object.status if query_object.status else True - ) \ - .order_by(SysDictData.dict_sort) \ - .distinct().all() + query = ( + select(SysDictData) + .where( + SysDictData.dict_type == query_object.dict_type if query_object.dict_type else True, + SysDictData.dict_label.like(f'%{query_object.dict_label}%') if query_object.dict_label else True, + SysDictData.status == query_object.status if query_object.status else True, + ) + .order_by(SysDictData.dict_sort) + .distinct() + ) + dict_data_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(dict_data_list) + return dict_data_list @classmethod - def query_dict_data_list(cls, db: Session, dict_type: str): + async def query_dict_data_list(cls, db: AsyncSession, dict_type: str): """ 根据查询参数获取字典数据列表信息 + :param db: orm对象 :param dict_type: 字典类型 :return: 字典数据列表信息对象 """ - dict_data_list = db.query(SysDictData).select_from(SysDictType) \ - .filter(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == 0) \ - .outerjoin(SysDictData, and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == 0)) \ - .order_by(SysDictData.dict_sort) \ - .distinct().all() + dict_data_list = ( + ( + await db.execute( + select(SysDictData) + .select_from(SysDictType) + .where(SysDictType.dict_type == dict_type if dict_type else True, SysDictType.status == '0') + .join( + SysDictData, + and_(SysDictType.dict_type == SysDictData.dict_type, SysDictData.status == '0'), + isouter=True, + ) + .order_by(SysDictData.dict_sort) + .distinct() + ) + ) + .scalars() + .all() + ) - return list_format_datetime(dict_data_list) + return dict_data_list @classmethod - def add_dict_data_dao(cls, db: Session, dict_data: DictDataModel): + async def add_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel): """ 新增字典数据数据库操作 + :param db: orm对象 :param dict_data: 字典数据对象 :return: """ - db_data_type = SysDictData(**dict_data.dict()) + db_data_type = SysDictData(**dict_data.model_dump()) db.add(db_data_type) - db.flush() + await db.flush() return db_data_type @classmethod - def edit_dict_data_dao(cls, db: Session, dict_data: dict): + async def edit_dict_data_dao(cls, db: AsyncSession, dict_data: dict): """ 编辑字典数据数据库操作 + :param db: orm对象 :param dict_data: 需要更新的字典数据字典 :return: """ - db.query(SysDictData) \ - .filter(SysDictData.dict_code == dict_data.get('dict_code')) \ - .update(dict_data) + await db.execute(update(SysDictData), [dict_data]) @classmethod - def delete_dict_data_dao(cls, db: Session, dict_data: DictDataModel): + async def delete_dict_data_dao(cls, db: AsyncSession, dict_data: DictDataModel): """ 删除字典数据数据库操作 + :param db: orm对象 :param dict_data: 字典数据对象 :return: """ - db.query(SysDictData) \ - .filter(SysDictData.dict_code == dict_data.dict_code) \ - .delete() + await db.execute(delete(SysDictData).where(SysDictData.dict_code.in_([dict_data.dict_code]))) + + @classmethod + async def count_dict_data_dao(cls, db: AsyncSession, dict_type: str): + """ + 根据字典类型查询字典类型关联的字典数据数量 + + :param db: orm对象 + :param dict_type: 字典类型 + :return: 字典类型关联的字典数据数量 + """ + dict_data_count = ( + await db.execute(select(func.count('*')).select_from(SysDictData).where(SysDictData.dict_type == dict_type)) + ).scalar() + + return dict_data_count diff --git a/dash-fastapi-backend/module_admin/dao/job_dao.py b/dash-fastapi-backend/module_admin/dao/job_dao.py index ca318c8c0eb942f591a14a608ba1cffbc8a62c2f..7f4f4a34e99bbd84b08c2ae32bad9f7fc2eee3a7 100644 --- a/dash-fastapi-backend/module_admin/dao/job_dao.py +++ b/dash-fastapi-backend/module_admin/dao/job_dao.py @@ -1,7 +1,8 @@ -from sqlalchemy.orm import Session +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.job_do import SysJob -from module_admin.entity.vo.job_vo import JobModel -from utils.time_format_util import list_format_datetime, object_format_datetime +from module_admin.entity.vo.job_vo import JobModel, JobPageQueryModel +from utils.page_util import PageUtil class JobDao: @@ -10,100 +11,115 @@ class JobDao: """ @classmethod - def get_job_detail_by_id(cls, db: Session, job_id: int): + async def get_job_detail_by_id(cls, db: AsyncSession, job_id: int): """ 根据定时任务id获取定时任务详细信息 + :param db: orm对象 :param job_id: 定时任务id :return: 定时任务信息对象 """ - job_info = db.query(SysJob) \ - .filter(SysJob.job_id == job_id) \ - .first() + job_info = (await db.execute(select(SysJob).where(SysJob.job_id == job_id))).scalars().first() - return object_format_datetime(job_info) + return job_info @classmethod - def get_job_detail_by_info(cls, db: Session, job: JobModel): + async def get_job_detail_by_info(cls, db: AsyncSession, job: JobModel): """ 根据定时任务参数获取定时任务信息 + :param db: orm对象 :param job: 定时任务参数对象 :return: 定时任务信息对象 """ - job_info = db.query(SysJob) \ - .filter(SysJob.job_name == job.job_name if job.job_name else True, - SysJob.job_group == job.job_group if job.job_group else True, - SysJob.invoke_target == job.invoke_target if job.invoke_target else True, - SysJob.cron_expression == job.cron_expression if job.cron_expression else True) \ + job_info = ( + ( + await db.execute( + select(SysJob).where( + SysJob.job_name == job.job_name, + SysJob.job_group == job.job_group, + SysJob.job_executor == job.job_executor, + SysJob.invoke_target == job.invoke_target, + SysJob.job_args == job.job_args, + SysJob.job_kwargs == job.job_kwargs, + SysJob.cron_expression == job.cron_expression, + ) + ) + ) + .scalars() .first() + ) return job_info @classmethod - def get_job_list(cls, db: Session, query_object: JobModel): + async def get_job_list(cls, db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False): """ 根据查询参数获取定时任务列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 定时任务列表信息对象 """ - job_list = db.query(SysJob) \ - .filter(SysJob.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, - SysJob.job_group == query_object.job_group if query_object.job_group else True, - SysJob.status == query_object.status if query_object.status else True - ) \ - .distinct().all() + query = ( + select(SysJob) + .where( + SysJob.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, + SysJob.job_group == query_object.job_group if query_object.job_group else True, + SysJob.status == query_object.status if query_object.status else True, + ) + .distinct() + ) + job_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(job_list) + return job_list @classmethod - def get_job_list_for_scheduler(cls, db: Session): + async def get_job_list_for_scheduler(cls, db: AsyncSession): """ 获取定时任务列表信息 + :param db: orm对象 :return: 定时任务列表信息对象 """ - job_list = db.query(SysJob) \ - .filter(SysJob.status == 0) \ - .distinct().all() + job_list = (await db.execute(select(SysJob).where(SysJob.status == '0').distinct())).scalars().all() - return list_format_datetime(job_list) + return job_list @classmethod - def add_job_dao(cls, db: Session, job: JobModel): + async def add_job_dao(cls, db: AsyncSession, job: JobModel): """ 新增定时任务数据库操作 + :param db: orm对象 :param job: 定时任务对象 :return: """ - db_job = SysJob(**job.dict()) + db_job = SysJob(**job.model_dump()) db.add(db_job) - db.flush() + await db.flush() return db_job @classmethod - def edit_job_dao(cls, db: Session, job: dict): + async def edit_job_dao(cls, db: AsyncSession, job: dict): """ 编辑定时任务数据库操作 + :param db: orm对象 :param job: 需要更新的定时任务字典 :return: """ - db.query(SysJob) \ - .filter(SysJob.job_id == job.get('job_id')) \ - .update(job) + await db.execute(update(SysJob), [job]) @classmethod - def delete_job_dao(cls, db: Session, job: JobModel): + async def delete_job_dao(cls, db: AsyncSession, job: JobModel): """ 删除定时任务数据库操作 + :param db: orm对象 :param job: 定时任务对象 :return: """ - db.query(SysJob) \ - .filter(SysJob.job_id == job.job_id) \ - .delete() + await db.execute(delete(SysJob).where(SysJob.job_id.in_([job.job_id]))) diff --git a/dash-fastapi-backend/module_admin/dao/job_log_dao.py b/dash-fastapi-backend/module_admin/dao/job_log_dao.py index b3cd75c830f72448039639f1e240c9e060c27ef1..730be5aa285daae3aef29dade1bee41315cec3e8 100644 --- a/dash-fastapi-backend/module_admin/dao/job_log_dao.py +++ b/dash-fastapi-backend/module_admin/dao/job_log_dao.py @@ -1,8 +1,10 @@ +from datetime import datetime, time +from sqlalchemy import delete, select +from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session from module_admin.entity.do.job_do import SysJobLog -from module_admin.entity.vo.job_vo import JobLogModel, JobLogQueryModel -from utils.time_format_util import list_format_datetime, object_format_datetime -from datetime import datetime, time +from module_admin.entity.vo.job_vo import JobLogModel, JobLogPageQueryModel +from utils.page_util import PageUtil class JobLogDao: @@ -11,72 +13,66 @@ class JobLogDao: """ @classmethod - def get_job_log_detail_by_id(cls, db: Session, job_log_id: int): - """ - 根据定时任务日志id获取定时任务日志详细信息 - :param db: orm对象 - :param job_log_id: 定时任务日志id - :return: 定时任务日志信息对象 - """ - job_log_info = db.query(SysJobLog) \ - .filter(SysJobLog.job_log_id == job_log_id) \ - .first() - - return object_format_datetime(job_log_info) - - @classmethod - def get_job_log_list(cls, db: Session, query_object: JobLogQueryModel): + async def get_job_log_list(cls, db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False): """ 根据查询参数获取定时任务日志列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 定时任务日志列表信息对象 """ - job_log_list = db.query(SysJobLog) \ - .filter(SysJobLog.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, - SysJobLog.job_group == query_object.job_group if query_object.job_group else True, - SysJobLog.status == query_object.status if query_object.status else True, - SysJobLog.create_time.between( - datetime.combine(datetime.strptime(query_object.create_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.create_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.create_time_start and query_object.create_time_end else True - ) \ - .distinct().all() + query = ( + select(SysJobLog) + .where( + SysJobLog.job_name.like(f'%{query_object.job_name}%') if query_object.job_name else True, + SysJobLog.job_group == query_object.job_group if query_object.job_group else True, + SysJobLog.status == query_object.status if query_object.status else True, + SysJobLog.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + ) + job_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(job_log_list) + return job_log_list @classmethod def add_job_log_dao(cls, db: Session, job_log: JobLogModel): """ 新增定时任务日志数据库操作 + :param db: orm对象 :param job_log: 定时任务日志对象 :return: """ - db_job_log = SysJobLog(**job_log.dict()) + db_job_log = SysJobLog(**job_log.model_dump()) db.add(db_job_log) db.flush() return db_job_log @classmethod - def delete_job_log_dao(cls, db: Session, job_log: JobLogModel): + async def delete_job_log_dao(cls, db: AsyncSession, job_log: JobLogModel): """ 删除定时任务日志数据库操作 + :param db: orm对象 :param job_log: 定时任务日志对象 :return: """ - db.query(SysJobLog) \ - .filter(SysJobLog.job_log_id == job_log.job_log_id) \ - .delete() + await db.execute(delete(SysJobLog).where(SysJobLog.job_log_id.in_([job_log.job_log_id]))) @classmethod - def clear_job_log_dao(cls, db: Session): + async def clear_job_log_dao(cls, db: AsyncSession): """ 清除定时任务日志数据库操作 + :param db: orm对象 :return: """ - db.query(SysJobLog) \ - .delete() + await db.execute(delete(SysJobLog)) diff --git a/dash-fastapi-backend/module_admin/dao/log_dao.py b/dash-fastapi-backend/module_admin/dao/log_dao.py index c5ee91b234e4c9621038086943bb79ef643b9fd4..1e4ad41eb21dd11f40422a0759d6f6ea21e9bd97 100644 --- a/dash-fastapi-backend/module_admin/dao/log_dao.py +++ b/dash-fastapi-backend/module_admin/dao/log_dao.py @@ -1,9 +1,10 @@ -from sqlalchemy import asc, desc -from sqlalchemy.orm import Session -from module_admin.entity.do.log_do import SysOperLog, SysLogininfor -from module_admin.entity.vo.log_vo import OperLogModel, LogininforModel, OperLogQueryModel, LoginLogQueryModel -from utils.time_format_util import object_format_datetime, list_format_datetime from datetime import datetime, time +from sqlalchemy import asc, delete, desc, select +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.log_do import SysLogininfor, SysOperLog +from module_admin.entity.vo.log_vo import LogininforModel, LoginLogPageQueryModel, OperLogModel, OperLogPageQueryModel +from utils.common_util import SnakeCaseUtil +from utils.page_util import PageUtil class OperationLogDao: @@ -12,82 +13,79 @@ class OperationLogDao: """ @classmethod - def get_operation_log_detail_by_id(cls, db: Session, oper_id: int): - """ - 根据操作日志id获取操作日志详细信息 - :param db: orm对象 - :param oper_id: 操作日志id - :return: 操作日志信息对象 - """ - operation_log_info = db.query(SysOperLog) \ - .filter(SysOperLog.oper_id == oper_id) \ - .first() - - return object_format_datetime(operation_log_info) - - @classmethod - def get_operation_log_list(cls, db: Session, query_object: OperLogQueryModel): + async def get_operation_log_list(cls, db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False): """ 根据查询参数获取操作日志列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 操作日志列表信息对象 """ - if query_object.is_asc == 'ascend': - order_by_column = asc(getattr(SysOperLog, query_object.order_by_column, None)) - elif query_object.is_asc == 'descend': - order_by_column = desc(getattr(SysOperLog, query_object.order_by_column, None)) + if query_object.is_asc == 'ascending': + order_by_column = asc(getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None)) + elif query_object.is_asc == 'descending': + order_by_column = desc( + getattr(SysOperLog, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) else: - order_by_column = asc(SysOperLog.oper_time) - operation_log_list = db.query(SysOperLog) \ - .filter(SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True, - SysOperLog.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True, - SysOperLog.business_type == query_object.business_type if query_object.business_type else True, - SysOperLog.status == query_object.status if query_object.status else True, - SysOperLog.oper_time.between( - datetime.combine(datetime.strptime(query_object.oper_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.oper_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.oper_time_start and query_object.oper_time_end else True - )\ - .distinct().order_by(order_by_column).all() - - return list_format_datetime(operation_log_list) + order_by_column = desc(SysOperLog.oper_time) + query = ( + select(SysOperLog) + .where( + SysOperLog.title.like(f'%{query_object.title}%') if query_object.title else True, + SysOperLog.oper_name.like(f'%{query_object.oper_name}%') if query_object.oper_name else True, + SysOperLog.business_type == query_object.business_type if query_object.business_type else True, + SysOperLog.status == query_object.status if query_object.status else True, + SysOperLog.oper_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + .order_by(order_by_column) + ) + operation_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return operation_log_list @classmethod - def add_operation_log_dao(cls, db: Session, operation_log: OperLogModel): + async def add_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel): """ 新增操作日志数据库操作 + :param db: orm对象 :param operation_log: 操作日志对象 :return: 新增校验结果 """ - db_operation_log = SysOperLog(**operation_log.dict()) + db_operation_log = SysOperLog(**operation_log.model_dump()) db.add(db_operation_log) - db.flush() + await db.flush() return db_operation_log @classmethod - def delete_operation_log_dao(cls, db: Session, operation_log: OperLogModel): + async def delete_operation_log_dao(cls, db: AsyncSession, operation_log: OperLogModel): """ 删除操作日志数据库操作 + :param db: orm对象 :param operation_log: 操作日志对象 :return: """ - db.query(SysOperLog) \ - .filter(SysOperLog.oper_id == operation_log.oper_id) \ - .delete() + await db.execute(delete(SysOperLog).where(SysOperLog.oper_id.in_([operation_log.oper_id]))) @classmethod - def clear_operation_log_dao(cls, db: Session): + async def clear_operation_log_dao(cls, db: AsyncSession): """ 清除操作日志数据库操作 + :param db: orm对象 :return: """ - db.query(SysOperLog) \ - .delete() + await db.execute(delete(SysOperLog)) class LoginLogDao: @@ -96,64 +94,77 @@ class LoginLogDao: """ @classmethod - def get_login_log_list(cls, db: Session, query_object: LoginLogQueryModel): + async def get_login_log_list(cls, db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False): """ 根据查询参数获取登录日志列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 登录日志列表信息对象 """ - if query_object.is_asc == 'ascend': - order_by_column = asc(getattr(SysLogininfor, query_object.order_by_column, None)) - elif query_object.is_asc == 'descend': - order_by_column = desc(getattr(SysLogininfor, query_object.order_by_column, None)) + if query_object.is_asc == 'ascending': + order_by_column = asc( + getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) + elif query_object.is_asc == 'descending': + order_by_column = desc( + getattr(SysLogininfor, SnakeCaseUtil.camel_to_snake(query_object.order_by_column), None) + ) else: - order_by_column = asc(SysLogininfor.login_time) - login_log_list = db.query(SysLogininfor) \ - .filter(SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True, - SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, - SysLogininfor.status == query_object.status if query_object.status else True, - SysLogininfor.login_time.between( - datetime.combine(datetime.strptime(query_object.login_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.login_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.login_time_start and query_object.login_time_end else True - )\ - .distinct().order_by(order_by_column).all() - - return list_format_datetime(login_log_list) + order_by_column = desc(SysLogininfor.login_time) + query = ( + select(SysLogininfor) + .where( + SysLogininfor.ipaddr.like(f'%{query_object.ipaddr}%') if query_object.ipaddr else True, + SysLogininfor.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, + SysLogininfor.status == query_object.status if query_object.status else True, + SysLogininfor.login_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + .order_by(order_by_column) + ) + login_log_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return login_log_list @classmethod - def add_login_log_dao(cls, db: Session, login_log: LogininforModel): + async def add_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel): """ 新增登录日志数据库操作 + :param db: orm对象 :param login_log: 登录日志对象 :return: 新增校验结果 """ - db_login_log = SysLogininfor(**login_log.dict()) + db_login_log = SysLogininfor(**login_log.model_dump()) db.add(db_login_log) - db.flush() + await db.flush() return db_login_log @classmethod - def delete_login_log_dao(cls, db: Session, login_log: LogininforModel): + async def delete_login_log_dao(cls, db: AsyncSession, login_log: LogininforModel): """ 删除登录日志数据库操作 + :param db: orm对象 :param login_log: 登录日志对象 :return: """ - db.query(SysLogininfor) \ - .filter(SysLogininfor.info_id == login_log.info_id) \ - .delete() + await db.execute(delete(SysLogininfor).where(SysLogininfor.info_id.in_([login_log.info_id]))) @classmethod - def clear_login_log_dao(cls, db: Session): + async def clear_login_log_dao(cls, db: AsyncSession): """ 清除登录日志数据库操作 + :param db: orm对象 :return: """ - db.query(SysLogininfor) \ - .delete() + await db.execute(delete(SysLogininfor)) diff --git a/dash-fastapi-backend/module_admin/dao/login_dao.py b/dash-fastapi-backend/module_admin/dao/login_dao.py index b932bc1793139f0174cd40bf89a29252acf89248..9764a4a0ef1d5ca901ec58f347697f932943a3e9 100644 --- a/dash-fastapi-backend/module_admin/dao/login_dao.py +++ b/dash-fastapi-backend/module_admin/dao/login_dao.py @@ -1,20 +1,28 @@ -from sqlalchemy.orm import Session -from sqlalchemy import and_ -from module_admin.entity.do.user_do import SysUser +from sqlalchemy import and_, select +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.dept_do import SysDept +from module_admin.entity.do.user_do import SysUser -def login_by_account(db: Session, user_name: str): +async def login_by_account(db: AsyncSession, user_name: str): """ 根据用户名查询用户信息 + :param db: orm对象 :param user_name: 用户名 :return: 用户对象 """ - user = db.query(SysUser, SysDept)\ - .filter(SysUser.user_name == user_name, SysUser.del_flag == '0') \ - .outerjoin(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ - .distinct() \ - .first() + user = ( + await db.execute( + select(SysUser, SysDept) + .where(SysUser.user_name == user_name, SysUser.del_flag == '0') + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + isouter=True, + ) + .distinct() + ) + ).first() return user diff --git a/dash-fastapi-backend/module_admin/dao/menu_dao.py b/dash-fastapi-backend/module_admin/dao/menu_dao.py index 6409572fbde17b9895ca204a51c56123100f9d22..19831e015255114a46b390330591ebfa6c11e613 100644 --- a/dash-fastapi-backend/module_admin/dao/menu_dao.py +++ b/dash-fastapi-backend/module_admin/dao/menu_dao.py @@ -1,10 +1,9 @@ -from sqlalchemy import and_ -from sqlalchemy.orm import Session +from sqlalchemy import and_, delete, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.menu_do import SysMenu -from module_admin.entity.do.user_do import SysUser, SysUserRole from module_admin.entity.do.role_do import SysRole, SysRoleMenu -from module_admin.entity.vo.menu_vo import MenuModel, MenuResponse -from utils.time_format_util import list_format_datetime +from module_admin.entity.do.user_do import SysUser, SysUserRole +from module_admin.entity.vo.menu_vo import MenuModel, MenuQueryModel class MenuDao: @@ -13,104 +12,92 @@ class MenuDao: """ @classmethod - def get_menu_detail_by_id(cls, db: Session, menu_id: int): + async def get_menu_detail_by_id(cls, db: AsyncSession, menu_id: int): """ 根据菜单id获取菜单详细信息 + :param db: orm对象 :param menu_id: 菜单id :return: 菜单信息对象 """ - menu_info = db.query(SysMenu) \ - .filter(SysMenu.menu_id == menu_id) \ - .first() + menu_info = (await db.execute(select(SysMenu).where(SysMenu.menu_id == menu_id))).scalars().first() return menu_info @classmethod - def get_menu_detail_by_info(cls, db: Session, menu: MenuModel): + async def get_menu_detail_by_info(cls, db: AsyncSession, menu: MenuModel): """ 根据菜单参数获取菜单信息 + :param db: orm对象 :param menu: 菜单参数对象 :return: 菜单信息对象 """ - menu_info = db.query(SysMenu) \ - .filter(SysMenu.parent_id == menu.parent_id if menu.parent_id else True, - SysMenu.menu_name == menu.menu_name if menu.menu_name else True, - SysMenu.menu_type == menu.menu_type if menu.menu_type else True) \ + menu_info = ( + ( + await db.execute( + select(SysMenu).where( + SysMenu.parent_id == menu.parent_id if menu.parent_id else True, + SysMenu.menu_name == menu.menu_name if menu.menu_name else True, + SysMenu.menu_type == menu.menu_type if menu.menu_type else True, + ) + ) + ) + .scalars() .first() + ) return menu_info @classmethod - def get_menu_info_for_edit_option(cls, db: Session, menu_info: MenuModel, user_id: int, role: list): + async def get_menu_list_for_tree(cls, db: AsyncSession, user_id: int, role: list): """ - 根据角色信息获取菜单编辑对应的在用菜单列表信息 + 根据角色信息获取所有在用菜单列表信息 + :param db: orm对象 - :param menu_info: 菜单对象 :param user_id: 用户id :param role: 用户角色列表信息 :return: 菜单列表信息 """ role_id_list = [item.role_id for item in role] if 1 in role_id_list: - menu_result = db.query(SysMenu) \ - .filter(SysMenu.menu_id != menu_info.menu_id, SysMenu.parent_id != menu_info.menu_id, - SysMenu.status == 0) \ + menu_query_all = ( + (await db.execute(select(SysMenu).where(SysMenu.status == '0').order_by(SysMenu.order_num).distinct())) + .scalars() .all() + ) else: - menu_result = db.query(SysMenu).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .outerjoin(SysRole, - and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, - SysMenu.menu_id != menu_info.menu_id, - SysMenu.parent_id != menu_info.menu_id, - SysMenu.status == 0)) \ - .order_by(SysMenu.order_num) \ - .distinct().all() - - return list_format_datetime(menu_result) + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) - @classmethod - def get_menu_list_for_tree(cls, db: Session, menu_info: MenuModel, user_id: int, role: list): - """ - 根据角色信息获取所有在用菜单列表信息 - :param db: orm对象 - :param menu_info: 菜单对象 - :param user_id: 用户id - :param role: 用户角色列表信息 - :return: 菜单列表信息 - """ - role_id_list = [item.role_id for item in role] - if 1 in role_id_list: - menu_query_all = db.query(SysMenu) \ - .filter(SysMenu.status == 0, - SysMenu.menu_name.like(f'%{menu_info.menu_name}%') if menu_info.menu_name else True) \ - .order_by(SysMenu.order_num) \ - .distinct().all() - else: - menu_query_all = db.query(SysMenu).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .outerjoin(SysRole, - and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, - SysMenu.status == 0, - SysMenu.menu_name.like( - f'%{menu_info.menu_name}%') if menu_info.menu_name else True)) \ - .order_by(SysMenu.order_num) \ - .distinct().all() - - return list_format_datetime(menu_query_all) + return menu_query_all @classmethod - def get_menu_list(cls, db: Session, page_object: MenuModel, user_id: int, role: list): + async def get_menu_list(cls, db: AsyncSession, page_object: MenuQueryModel, user_id: int, role: list): """ 根据查询参数获取菜单列表信息 + :param db: orm对象 :param page_object: 不分页查询参数对象 :param user_id: 用户id @@ -119,66 +106,118 @@ class MenuDao: """ role_id_list = [item.role_id for item in role] if 1 in role_id_list: - menu_query_all = db.query(SysMenu) \ - .filter(SysMenu.status == page_object.status if page_object.status else True, - SysMenu.menu_name.like( - f'%{page_object.menu_name}%') if page_object.menu_name else True) \ - .order_by(SysMenu.order_num) \ - .distinct().all() + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .where( + SysMenu.status == page_object.status if page_object.status else True, + SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True, + ) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) else: - menu_query_all = db.query(SysMenu).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .outerjoin(SysRole, - and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, - SysMenu.status == page_object.status if page_object.status else True, - SysMenu.menu_name.like( - f'%{page_object.menu_name}%') if page_object.menu_name else True)) \ - .order_by(SysMenu.order_num) \ - .distinct().all() - - result = dict( - rows=list_format_datetime(menu_query_all), - ) + menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join( + SysMenu, + and_( + SysRoleMenu.menu_id == SysMenu.menu_id, + SysMenu.status == page_object.status if page_object.status else True, + SysMenu.menu_name.like(f'%{page_object.menu_name}%') if page_object.menu_name else True, + ), + ) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) - return MenuResponse(**result) + return menu_query_all @classmethod - def add_menu_dao(cls, db: Session, menu: MenuModel): + async def add_menu_dao(cls, db: AsyncSession, menu: MenuModel): """ 新增菜单数据库操作 + :param db: orm对象 :param menu: 菜单对象 :return: """ - db_menu = SysMenu(**menu.dict()) + db_menu = SysMenu(**menu.model_dump()) db.add(db_menu) - db.flush() + await db.flush() return db_menu @classmethod - def edit_menu_dao(cls, db: Session, menu: dict): + async def edit_menu_dao(cls, db: AsyncSession, menu: dict): """ 编辑菜单数据库操作 + :param db: orm对象 :param menu: 需要更新的菜单字典 :return: """ - db.query(SysMenu) \ - .filter(SysMenu.menu_id == menu.get('menu_id')) \ - .update(menu) + await db.execute(update(SysMenu), [menu]) @classmethod - def delete_menu_dao(cls, db: Session, menu: MenuModel): + async def delete_menu_dao(cls, db: AsyncSession, menu: MenuModel): """ 删除菜单数据库操作 + :param db: orm对象 :param menu: 菜单对象 :return: """ - db.query(SysMenu) \ - .filter(SysMenu.menu_id == menu.menu_id) \ - .delete() + await db.execute(delete(SysMenu).where(SysMenu.menu_id.in_([menu.menu_id]))) + + @classmethod + async def has_child_by_menu_id_dao(cls, db: AsyncSession, menu_id: int): + """ + 根据菜单id查询菜单关联子菜单的数量 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单关联子菜单的数量 + """ + menu_count = ( + await db.execute(select(func.count('*')).select_from(SysMenu).where(SysMenu.parent_id == menu_id)) + ).scalar() + + return menu_count + + @classmethod + async def check_menu_exist_role_dao(cls, db: AsyncSession, menu_id: int): + """ + 根据菜单id查询菜单关联角色数量 + + :param db: orm对象 + :param menu_id: 菜单id + :return: 菜单关联角色数量 + """ + role_count = ( + await db.execute(select(func.count('*')).select_from(SysRoleMenu).where(SysRoleMenu.menu_id == menu_id)) + ).scalar() + + return role_count diff --git a/dash-fastapi-backend/module_admin/dao/notice_dao.py b/dash-fastapi-backend/module_admin/dao/notice_dao.py index b72d59f28e20b1e237a19bc72581c6da20e32621..961a9921c160a00ce5a1f4084b525018fc33bf49 100644 --- a/dash-fastapi-backend/module_admin/dao/notice_dao.py +++ b/dash-fastapi-backend/module_admin/dao/notice_dao.py @@ -1,8 +1,9 @@ -from sqlalchemy.orm import Session -from module_admin.entity.do.notice_do import SysNotice -from module_admin.entity.vo.notice_vo import NoticeModel, NoticeQueryModel, CrudNoticeResponse -from utils.time_format_util import list_format_datetime, object_format_datetime from datetime import datetime, time +from sqlalchemy import delete, select, update +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.notice_do import SysNotice +from module_admin.entity.vo.notice_vo import NoticeModel, NoticePageQueryModel +from utils.page_util import PageUtil class NoticeDao: @@ -11,90 +12,105 @@ class NoticeDao: """ @classmethod - def get_notice_detail_by_id(cls, db: Session, notice_id: int): + async def get_notice_detail_by_id(cls, db: AsyncSession, notice_id: int): """ 根据通知公告id获取通知公告详细信息 + :param db: orm对象 :param notice_id: 通知公告id :return: 通知公告信息对象 """ - notice_info = db.query(SysNotice) \ - .filter(SysNotice.notice_id == notice_id) \ - .first() + notice_info = (await db.execute(select(SysNotice).where(SysNotice.notice_id == notice_id))).scalars().first() - return object_format_datetime(notice_info) + return notice_info @classmethod - def get_notice_detail_by_info(cls, db: Session, notice: NoticeModel): + async def get_notice_detail_by_info(cls, db: AsyncSession, notice: NoticeModel): """ 根据通知公告参数获取通知公告信息 + :param db: orm对象 :param notice: 通知公告参数对象 :return: 通知公告信息对象 """ - notice_info = db.query(SysNotice) \ - .filter(SysNotice.notice_title == notice.notice_title if notice.notice_title else True, - SysNotice.notice_type == notice.notice_type if notice.notice_type else True, - SysNotice.notice_content == notice.notice_content if notice.notice_content else True) \ + notice_info = ( + ( + await db.execute( + select(SysNotice).where( + SysNotice.notice_title == notice.notice_title, + SysNotice.notice_type == notice.notice_type, + SysNotice.notice_content == notice.notice_content, + ) + ) + ) + .scalars() .first() + ) return notice_info @classmethod - def get_notice_list(cls, db: Session, query_object: NoticeQueryModel): + async def get_notice_list(cls, db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = False): """ 根据查询参数获取通知公告列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 通知公告列表信息对象 """ - notice_list = db.query(SysNotice) \ - .filter(SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, - SysNotice.update_by.like(f'%{query_object.update_by}%') if query_object.update_by else True, - SysNotice.notice_type == query_object.notice_type if query_object.notice_type else True, - SysNotice.create_time.between( - datetime.combine(datetime.strptime(query_object.create_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.create_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.create_time_start and query_object.create_time_end else True - ) \ - .distinct().all() - - return list_format_datetime(notice_list) + query = ( + select(SysNotice) + .where( + SysNotice.notice_title.like(f'%{query_object.notice_title}%') if query_object.notice_title else True, + SysNotice.create_by.like(f'%{query_object.create_by}%') if query_object.create_by else True, + SysNotice.notice_type == query_object.notice_type if query_object.notice_type else True, + SysNotice.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + ) + .distinct() + ) + notice_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + + return notice_list @classmethod - def add_notice_dao(cls, db: Session, notice: NoticeModel): + async def add_notice_dao(cls, db: AsyncSession, notice: NoticeModel): """ 新增通知公告数据库操作 + :param db: orm对象 :param notice: 通知公告对象 :return: """ - db_notice = SysNotice(**notice.dict()) + db_notice = SysNotice(**notice.model_dump()) db.add(db_notice) - db.flush() + await db.flush() return db_notice @classmethod - def edit_notice_dao(cls, db: Session, notice: dict): + async def edit_notice_dao(cls, db: AsyncSession, notice: dict): """ 编辑通知公告数据库操作 + :param db: orm对象 :param notice: 需要更新的通知公告字典 :return: """ - db.query(SysNotice) \ - .filter(SysNotice.notice_id == notice.get('notice_id')) \ - .update(notice) + await db.execute(update(SysNotice), [notice]) @classmethod - def delete_notice_dao(cls, db: Session, notice: NoticeModel): + async def delete_notice_dao(cls, db: AsyncSession, notice: NoticeModel): """ 删除通知公告数据库操作 + :param db: orm对象 :param notice: 通知公告对象 :return: """ - db.query(SysNotice) \ - .filter(SysNotice.notice_id == notice.notice_id) \ - .delete() + await db.execute(delete(SysNotice).where(SysNotice.notice_id.in_([notice.notice_id]))) diff --git a/dash-fastapi-backend/module_admin/dao/post_dao.py b/dash-fastapi-backend/module_admin/dao/post_dao.py index 11fbdc1592ff5b34a472e23830846df06f3e87d9..7d90088b607b63c48ea292b534a7f6703c296b8a 100644 --- a/dash-fastapi-backend/module_admin/dao/post_dao.py +++ b/dash-fastapi-backend/module_admin/dao/post_dao.py @@ -1,7 +1,9 @@ -from sqlalchemy.orm import Session +from sqlalchemy import delete, func, select, update +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.post_do import SysPost -from module_admin.entity.vo.post_vo import PostModel -from utils.time_format_util import list_format_datetime +from module_admin.entity.do.user_do import SysUserPost +from module_admin.entity.vo.post_vo import PostModel, PostPageQueryModel +from utils.page_util import PageUtil class PostDao: @@ -10,115 +12,132 @@ class PostDao: """ @classmethod - def get_post_by_id(cls, db: Session, post_id: int): + async def get_post_by_id(cls, db: AsyncSession, post_id: int): """ 根据岗位id获取在用岗位详细信息 + :param db: orm对象 :param post_id: 岗位id :return: 在用岗位信息对象 """ - post_info = db.query(SysPost) \ - .filter(SysPost.post_id == post_id, - SysPost.status == 0) \ + post_info = ( + (await db.execute(select(SysPost).where(SysPost.post_id == post_id, SysPost.status == '0'))) + .scalars() .first() + ) return post_info @classmethod - def get_post_detail_by_id(cls, db: Session, post_id: int): + async def get_post_detail_by_id(cls, db: AsyncSession, post_id: int): """ 根据岗位id获取岗位详细信息 + :param db: orm对象 :param post_id: 岗位id :return: 岗位信息对象 """ - post_info = db.query(SysPost) \ - .filter(SysPost.post_id == post_id) \ - .first() + post_info = (await db.execute(select(SysPost).where(SysPost.post_id == post_id))).scalars().first() return post_info @classmethod - def get_post_detail_by_info(cls, db: Session, post: PostModel): + async def get_post_detail_by_info(cls, db: AsyncSession, post: PostModel): """ 根据岗位参数获取岗位信息 + :param db: orm对象 :param post: 岗位参数对象 :return: 岗位信息对象 """ - post_info = db.query(SysPost) \ - .filter(SysPost.post_name == post.post_name if post.post_name else True, - SysPost.post_code == post.post_code if post.post_code else True, - SysPost.post_sort == post.post_sort if post.post_sort else True) \ + post_info = ( + ( + await db.execute( + select(SysPost).where( + SysPost.post_name == post.post_name if post.post_name else True, + SysPost.post_code == post.post_code if post.post_code else True, + SysPost.post_sort == post.post_sort if post.post_sort else True, + ) + ) + ) + .scalars() .first() + ) return post_info @classmethod - def get_post_select_option_dao(cls, db: Session): - """ - 获取所有在用岗位信息 - :param db: orm对象 - :return: 在用岗位信息列表 - """ - post_info = db.query(SysPost) \ - .filter(SysPost.status == 0) \ - .all() - - return post_info - - @classmethod - def get_post_list(cls, db: Session, query_object: PostModel): + async def get_post_list(cls, db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False): """ 根据查询参数获取岗位列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 岗位列表信息对象 """ - post_list = db.query(SysPost) \ - .filter(SysPost.post_code.like(f'%{query_object.post_code}%') if query_object.post_code else True, - SysPost.post_name.like(f'%{query_object.post_name}%') if query_object.post_name else True, - SysPost.status == query_object.status if query_object.status else True - ) \ - .order_by(SysPost.post_sort) \ - .distinct().all() + query = ( + select(SysPost) + .where( + SysPost.post_code.like(f'%{query_object.post_code}%') if query_object.post_code else True, + SysPost.post_name.like(f'%{query_object.post_name}%') if query_object.post_name else True, + SysPost.status == query_object.status if query_object.status else True, + ) + .order_by(SysPost.post_sort) + .distinct() + ) + post_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(post_list) + return post_list @classmethod - def add_post_dao(cls, db: Session, post: PostModel): + async def add_post_dao(cls, db: AsyncSession, post: PostModel): """ 新增岗位数据库操作 + :param db: orm对象 :param post: 岗位对象 :return: """ - db_post = SysPost(**post.dict()) + db_post = SysPost(**post.model_dump()) db.add(db_post) - db.flush() + await db.flush() return db_post @classmethod - def edit_post_dao(cls, db: Session, post: dict): + async def edit_post_dao(cls, db: AsyncSession, post: dict): """ 编辑岗位数据库操作 + :param db: orm对象 :param post: 需要更新的岗位字典 :return: """ - db.query(SysPost) \ - .filter(SysPost.post_id == post.get('post_id')) \ - .update(post) + await db.execute(update(SysPost), [post]) @classmethod - def delete_post_dao(cls, db: Session, post: PostModel): + async def delete_post_dao(cls, db: AsyncSession, post: PostModel): """ 删除岗位数据库操作 + :param db: orm对象 :param post: 岗位对象 :return: """ - db.query(SysPost) \ - .filter(SysPost.post_id == post.post_id) \ - .delete() + await db.execute(delete(SysPost).where(SysPost.post_id.in_([post.post_id]))) + + @classmethod + async def count_user_post_dao(cls, db: AsyncSession, post_id: int): + """ + 根据岗位id查询岗位关联的用户数量 + + :param db: orm对象 + :param post_id: 岗位id + :return: 岗位关联的用户数量 + """ + user_post_count = ( + await db.execute(select(func.count('*')).select_from(SysUserPost).where(SysUserPost.post_id == post_id)) + ).scalar() + + return user_post_count diff --git a/dash-fastapi-backend/module_admin/dao/role_dao.py b/dash-fastapi-backend/module_admin/dao/role_dao.py index 7e2624f328c85ed8df3c0a3f929c8f6451ee869b..534c7f34bd28a537e2c273083ed6b2f211d72547 100644 --- a/dash-fastapi-backend/module_admin/dao/role_dao.py +++ b/dash-fastapi-backend/module_admin/dao/role_dao.py @@ -1,11 +1,12 @@ -from sqlalchemy import and_, desc -from sqlalchemy.orm import Session -from module_admin.entity.do.role_do import SysRole, SysRoleMenu, SysRoleDept +from datetime import datetime, time +from sqlalchemy import and_, delete, desc, func, or_, select, update # noqa: F401 +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.dept_do import SysDept from module_admin.entity.do.menu_do import SysMenu -from module_admin.entity.vo.role_vo import RoleModel, RoleMenuModel, RoleDeptModel, RoleQueryModel, RoleDetailModel -from utils.time_format_util import list_format_datetime, object_format_datetime -from datetime import datetime, time +from module_admin.entity.do.role_do import SysRole, SysRoleMenu, SysRoleDept +from module_admin.entity.do.user_do import SysUser, SysUserRole +from module_admin.entity.vo.role_vo import RoleDeptModel, RoleMenuModel, RoleModel, RolePageQueryModel +from utils.page_util import PageUtil class RoleDao: @@ -14,181 +15,295 @@ class RoleDao: """ @classmethod - def get_role_by_name(cls, db: Session, role_name: str): + async def get_role_by_name(cls, db: AsyncSession, role_name: str): """ 根据角色名获取在用角色信息 + :param db: orm对象 :param role_name: 角色名 :return: 当前角色名的角色信息对象 """ - query_role_info = db.query(SysRole) \ - .filter(SysRole.status == 0, SysRole.del_flag == 0, SysRole.role_name == role_name) \ - .order_by(desc(SysRole.create_time)).distinct().first() + query_role_info = ( + ( + await db.execute( + select(SysRole) + .where(SysRole.status == '0', SysRole.del_flag == '0', SysRole.role_name == role_name) + .order_by(desc(SysRole.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_role_info @classmethod - def get_role_by_info(cls, db: Session, role: RoleModel): + async def get_role_by_info(cls, db: AsyncSession, role: RoleModel): """ 根据角色参数获取角色信息 + :param db: orm对象 :param role: 角色参数 :return: 当前角色参数的角色信息对象 """ - query_role_info = db.query(SysRole) \ - .filter(SysRole.del_flag == 0, - SysRole.role_name == role.role_name if role.role_name else True, - SysRole.role_key == role.role_key if role.role_key else True) \ - .order_by(desc(SysRole.create_time)).distinct().first() + query_role_info = ( + ( + await db.execute( + select(SysRole) + .where( + SysRole.del_flag == '0', + SysRole.role_name == role.role_name if role.role_name else True, + SysRole.role_key == role.role_key if role.role_key else True, + ) + .order_by(desc(SysRole.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_role_info @classmethod - def get_role_by_id(cls, db: Session, role_id: int): + async def get_role_by_id(cls, db: AsyncSession, role_id: int): """ 根据角色id获取在用角色信息 + :param db: orm对象 :param role_id: 角色id :return: 当前角色id的角色信息对象 """ - role_info = db.query(SysRole) \ - .filter(SysRole.role_id == role_id, - SysRole.status == 0, - SysRole.del_flag == 0) \ + role_info = ( + ( + await db.execute( + select(SysRole).where(SysRole.role_id == role_id, SysRole.status == '0', SysRole.del_flag == '0') + ) + ) + .scalars() .first() + ) return role_info @classmethod - def get_role_detail_by_id(cls, db: Session, role_id: int): + async def get_role_detail_by_id(cls, db: AsyncSession, role_id: int): """ 根据role_id获取角色详细信息 + :param db: orm对象 :param role_id: 角色id :return: 当前role_id的角色信息对象 """ - query_role_basic_info = db.query(SysRole) \ - .filter(SysRole.del_flag == 0, SysRole.role_id == role_id) \ - .distinct().first() - query_role_menu_info = db.query(SysMenu).select_from(SysRole) \ - .filter(SysRole.del_flag == 0, SysRole.role_id == role_id) \ - .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) \ - .distinct().all() - query_role_dept_info = db.query(SysDept).select_from(SysRole) \ - .filter(SysRole.del_flag == 0, SysRole.role_id == role_id) \ - .outerjoin(SysRoleDept, SysRole.role_id == SysRoleDept.role_id) \ - .join(SysDept, and_(SysRoleDept.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ - .distinct().all() - results = dict( - role=object_format_datetime(query_role_basic_info), - menu=list_format_datetime(query_role_menu_info), - dept=list_format_datetime(query_role_dept_info), + query_role_info = ( + (await db.execute(select(SysRole).where(SysRole.del_flag == '0', SysRole.role_id == role_id).distinct())) + .scalars() + .first() ) - return RoleDetailModel(**results) + return query_role_info @classmethod - def get_role_select_option_dao(cls, db: Session): + async def get_role_select_option_dao(cls, db: AsyncSession): """ 获取编辑页面对应的在用角色列表信息 + :param db: orm对象 :return: 角色列表信息 """ - role_info = db.query(SysRole) \ - .filter(SysRole.role_id != 1, SysRole.status == 0, SysRole.del_flag == 0) \ + role_info = ( + ( + await db.execute( + select(SysRole).where(SysRole.role_id != 1, SysRole.status == '0', SysRole.del_flag == '0') + ) + ) + .scalars() .all() + ) return role_info @classmethod - def get_role_list(cls, db: Session, query_object: RoleQueryModel): + async def get_role_list( + cls, db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据查询参数获取角色列表信息 + :param db: orm对象 :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 :return: 角色列表信息对象 """ - role_list = db.query(SysRole) \ - .filter(SysRole.del_flag == 0, - SysRole.role_name.like(f'%{query_object.role_name}%') if query_object.role_name else True, - SysRole.role_key.like(f'%{query_object.role_key}%') if query_object.role_key else True, - SysRole.status == query_object.status if query_object.status else True, - SysRole.create_time.between( - datetime.combine(datetime.strptime(query_object.create_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.create_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.create_time_start and query_object.create_time_end else True - ) \ - .order_by(SysRole.role_sort) \ - .distinct().all() + query = ( + select(SysRole) + .join(SysUserRole, SysUserRole.role_id == SysRole.role_id, isouter=True) + .join(SysUser, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .where( + SysRole.del_flag == '0', + SysRole.role_id == query_object.role_id if query_object.role_id is not None else True, + SysRole.role_name.like(f'%{query_object.role_name}%') if query_object.role_name else True, + SysRole.role_key.like(f'%{query_object.role_key}%') if query_object.role_key else True, + SysRole.status == query_object.status if query_object.status else True, + SysRole.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), + ) + if query_object.begin_time and query_object.end_time + else True, + eval(data_scope_sql), + ) + .order_by(SysRole.role_sort) + .distinct() + ) + role_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(role_list) + return role_list @classmethod - def add_role_dao(cls, db: Session, role: RoleModel): + async def add_role_dao(cls, db: AsyncSession, role: RoleModel): """ 新增角色数据库操作 + :param db: orm对象 :param role: 角色对象 :return: """ - db_role = SysRole(**role.dict()) + db_role = SysRole(**role.model_dump(exclude={'admin'})) db.add(db_role) - db.flush() + await db.flush() return db_role @classmethod - def edit_role_dao(cls, db: Session, role: dict): + async def edit_role_dao(cls, db: AsyncSession, role: dict): """ 编辑角色数据库操作 + :param db: orm对象 :param role: 需要更新的角色字典 :return: """ - db.query(SysRole) \ - .filter(SysRole.role_id == role.get('role_id')) \ - .update(role) + await db.execute(update(SysRole), [role]) @classmethod - def delete_role_dao(cls, db: Session, role: RoleModel): + async def delete_role_dao(cls, db: AsyncSession, role: RoleModel): """ 删除角色数据库操作 + :param db: orm对象 :param role: 角色对象 :return: """ - db.query(SysRole) \ - .filter(SysRole.role_id == role.role_id) \ - .update({SysRole.del_flag: '2', SysRole.update_by: role.update_by, SysRole.update_time: role.update_time}) + await db.execute( + update(SysRole) + .where(SysRole.role_id == role.role_id) + .values(del_flag='2', update_by=role.update_by, update_time=role.update_time) + ) @classmethod - def add_role_menu_dao(cls, db: Session, role_menu: RoleMenuModel): + async def get_role_menu_dao(cls, db: AsyncSession, role: RoleModel): + """ + 根据角色id获取角色菜单关联列表信息 + + :param db: orm对象 + :param role: 角色对象 + :return: 角色菜单关联列表信息 + """ + role_menu_query_all = ( + ( + await db.execute( + select(SysMenu) + .join(SysRoleMenu, SysRoleMenu.menu_id == SysMenu.menu_id) + .where( + SysRoleMenu.role_id == role.role_id, + ~SysMenu.menu_id.in_( + select(SysMenu.parent_id) + .select_from(SysMenu) + .join( + SysRoleMenu, + and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysRoleMenu.role_id == role.role_id), + ) + ) + if role.menu_check_strictly + else True, + ) + .order_by(SysMenu.parent_id, SysMenu.order_num) + ) + ) + .scalars() + .all() + ) + + return role_menu_query_all + + @classmethod + async def add_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel): """ 新增角色菜单关联信息数据库操作 + :param db: orm对象 :param role_menu: 用户角色菜单关联对象 :return: """ - db_role_menu = SysRoleMenu(**role_menu.dict()) + db_role_menu = SysRoleMenu(**role_menu.model_dump()) db.add(db_role_menu) @classmethod - def delete_role_menu_dao(cls, db: Session, role_menu: RoleMenuModel): + async def delete_role_menu_dao(cls, db: AsyncSession, role_menu: RoleMenuModel): """ 删除角色菜单关联信息数据库操作 + :param db: orm对象 :param role_menu: 角色菜单关联对象 :return: """ - db.query(SysRoleMenu) \ - .filter(SysRoleMenu.role_id == role_menu.role_id) \ - .delete() + await db.execute(delete(SysRoleMenu).where(SysRoleMenu.role_id.in_([role_menu.role_id]))) @classmethod - def add_role_dept_dao(cls, db: Session, role_dept: RoleDeptModel): + async def get_role_dept_dao(cls, db: AsyncSession, role: RoleModel): + """ + 根据角色id获取角色部门关联列表信息 + + :param db: orm对象 + :param role: 角色对象 + :return: 角色部门关联列表信息 + """ + role_dept_query_all = ( + ( + await db.execute( + select(SysDept) + .join(SysRoleDept, SysRoleDept.dept_id == SysDept.dept_id) + .where( + SysRoleDept.role_id == role.role_id, + ~SysDept.dept_id.in_( + select(SysDept.parent_id) + .select_from(SysDept) + .join( + SysRoleDept, + and_(SysRoleDept.dept_id == SysDept.dept_id, SysRoleDept.role_id == role.role_id), + ) + ) + if role.dept_check_strictly + else True, + ) + .order_by(SysDept.parent_id, SysDept.order_num) + ) + ) + .scalars() + .all() + ) + + return role_dept_query_all + + @classmethod + async def add_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel): """ 新增角色部门关联信息数据库操作 + :param db: orm对象 :param role_dept: 用户角色部门关联对象 :return: @@ -197,13 +312,27 @@ class RoleDao: db.add(db_role_dept) @classmethod - def delete_role_dept_dao(cls, db: Session, role_dept: RoleDeptModel): + async def delete_role_dept_dao(cls, db: AsyncSession, role_dept: RoleDeptModel): """ 删除角色部门关联信息数据库操作 + :param db: orm对象 :param role_dept: 角色部门关联对象 :return: """ - db.query(SysRoleDept) \ - .filter(SysRoleDept.role_id == role_dept.role_id) \ - .delete() + await db.execute(delete(SysRoleDept).where(SysRoleDept.role_id.in_([role_dept.role_id]))) + + @classmethod + async def count_user_role_dao(cls, db: AsyncSession, role_id: int): + """ + 根据角色id查询角色关联用户数量 + + :param db: orm对象 + :param role_id: 角色id + :return: 角色关联用户数量 + """ + user_count = ( + await db.execute(select(func.count('*')).select_from(SysUserRole).where(SysUserRole.role_id == role_id)) + ).scalar() + + return user_count diff --git a/dash-fastapi-backend/module_admin/dao/user_dao.py b/dash-fastapi-backend/module_admin/dao/user_dao.py index c68b5dba0026100d242f88a3e9cd82639599c994..52a088c8fa4c43e9e201cc50c613ddc8d7fe8c2d 100644 --- a/dash-fastapi-backend/module_admin/dao/user_dao.py +++ b/dash-fastapi-backend/module_admin/dao/user_dao.py @@ -1,14 +1,20 @@ -from sqlalchemy import and_, or_, desc, func -from sqlalchemy.orm import Session -from module_admin.entity.do.user_do import SysUser, SysUserRole, SysUserPost -from module_admin.entity.do.role_do import SysRole, SysRoleMenu, SysRoleDept +from datetime import datetime, time +from sqlalchemy import and_, delete, desc, func, or_, select, update +from sqlalchemy.ext.asyncio import AsyncSession from module_admin.entity.do.dept_do import SysDept -from module_admin.entity.do.post_do import SysPost from module_admin.entity.do.menu_do import SysMenu -from module_admin.entity.vo.user_vo import UserModel, UserRoleModel, UserPostModel, CurrentUserInfo, UserQueryModel, UserRoleQueryModel -from utils.time_format_util import object_format_datetime, list_format_datetime, format_datetime_dict_list -from datetime import datetime, time -from typing import Union, List +from module_admin.entity.do.post_do import SysPost +from module_admin.entity.do.role_do import SysRole, SysRoleDept, SysRoleMenu # noqa: F401 +from module_admin.entity.do.user_do import SysUser, SysUserPost, SysUserRole +from module_admin.entity.vo.user_vo import ( + UserModel, + UserPageQueryModel, + UserPostModel, + UserRoleModel, + UserRolePageQueryModel, + UserRoleQueryModel, +) +from utils.page_util import PageUtil class UserDao: @@ -17,381 +23,545 @@ class UserDao: """ @classmethod - def get_user_by_name(cls, db: Session, user_name: str): + async def get_user_by_name(cls, db: AsyncSession, user_name: str): """ 根据用户名获取用户信息 + :param db: orm对象 :param user_name: 用户名 :return: 当前用户名的用户信息对象 """ - query_user_info = db.query(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_name == user_name) \ - .order_by(desc(SysUser.create_time)).distinct().first() + query_user_info = ( + ( + await db.execute( + select(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_name == user_name) + .order_by(desc(SysUser.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_user_info @classmethod - def get_user_by_info(cls, db: Session, user: UserModel): + async def get_user_by_info(cls, db: AsyncSession, user: UserModel): """ 根据用户参数获取用户信息 + :param db: orm对象 :param user: 用户参数 :return: 当前用户参数的用户信息对象 """ - query_user_info = db.query(SysUser) \ - .filter(SysUser.del_flag == 0, - SysUser.user_name == user.user_name) \ - .order_by(desc(SysUser.create_time)).distinct().first() + query_user_info = ( + ( + await db.execute( + select(SysUser) + .where( + SysUser.del_flag == '0', + SysUser.user_name == user.user_name if user.user_name else True, + SysUser.phonenumber == user.phonenumber if user.phonenumber else True, + SysUser.email == user.email if user.email else True, + ) + .order_by(desc(SysUser.create_time)) + .distinct() + ) + ) + .scalars() + .first() + ) return query_user_info @classmethod - def get_user_by_id(cls, db: Session, user_id: int): + async def get_user_by_id(cls, db: AsyncSession, user_id: int): """ 根据user_id获取用户信息 + :param db: orm对象 :param user_id: 用户id :return: 当前user_id的用户信息对象 """ - query_user_basic_info = db.query(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .distinct().first() - query_user_dept_info = db.query(SysDept).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .join(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ - .distinct().first() - query_user_role_info = db.query(SysRole).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .distinct().all() - query_user_post_info = db.query(SysPost).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserPost, SysUser.user_id == SysUserPost.user_id) \ - .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == 0)) \ - .distinct().all() + query_user_basic_info = ( + ( + await db.execute( + select(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_dept_info = ( + ( + await db.execute( + select(SysDept) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_role_info = ( + ( + await db.execute( + select(SysRole) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_post_info = ( + ( + await db.execute( + select(SysPost) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) role_id_list = [item.role_id for item in query_user_role_info] if 1 in role_id_list: - query_user_menu_info = db.query(SysMenu) \ - .filter(SysMenu.status == 0) \ - .distinct().all() + query_user_menu_info = ( + (await db.execute(select(SysMenu).where(SysMenu.status == '0').distinct())).scalars().all() + ) else: - query_user_menu_info = db.query(SysMenu).select_from(SysUser) \ - .filter(SysUser.status == 0, SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .outerjoin(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) \ - .order_by(SysMenu.order_num) \ - .distinct().all() + query_user_menu_info = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.status == '0', SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_( + SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0' + ), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .order_by(SysMenu.order_num) + .distinct() + ) + ) + .scalars() + .all() + ) + results = dict( - user_basic_info=object_format_datetime(query_user_basic_info), - user_dept_info=object_format_datetime(query_user_dept_info), - user_role_info=list_format_datetime(query_user_role_info), - user_post_info=list_format_datetime(query_user_post_info), - user_menu_info=list_format_datetime(query_user_menu_info) + user_basic_info=query_user_basic_info, + user_dept_info=query_user_dept_info, + user_role_info=query_user_role_info, + user_post_info=query_user_post_info, + user_menu_info=query_user_menu_info, ) - return CurrentUserInfo(**results) + return results @classmethod - def get_user_detail_by_id(cls, db: Session, user_id: int): + async def get_user_detail_by_id(cls, db: AsyncSession, user_id: int): """ 根据user_id获取用户详细信息 + :param db: orm对象 :param user_id: 用户id :return: 当前user_id的用户信息对象 """ - query_user_basic_info = db.query(SysUser) \ - .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .distinct().first() - query_user_dept_info = db.query(SysDept).select_from(SysUser) \ - .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .join(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ - .distinct().first() - query_user_role_info = db.query(SysRole).select_from(SysUser) \ - .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .join(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .distinct().all() - query_user_post_info = db.query(SysPost).select_from(SysUser) \ - .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserPost, SysUser.user_id == SysUserPost.user_id) \ - .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == 0)) \ - .distinct().all() - query_user_menu_info = db.query(SysMenu).select_from(SysUser) \ - .filter(SysUser.del_flag == 0, SysUser.user_id == user_id) \ - .outerjoin(SysUserRole, SysUser.user_id == SysUserRole.user_id) \ - .outerjoin(SysRole, and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == 0, SysRole.del_flag == 0)) \ - .outerjoin(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id) \ - .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == 0)) \ - .distinct().all() + query_user_basic_info = ( + (await db.execute(select(SysUser).where(SysUser.del_flag == '0', SysUser.user_id == user_id).distinct())) + .scalars() + .first() + ) + query_user_dept_info = ( + ( + await db.execute( + select(SysDept) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .first() + ) + query_user_role_info = ( + ( + await db.execute( + select(SysRole) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + ) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_post_info = ( + ( + await db.execute( + select(SysPost) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserPost, SysUser.user_id == SysUserPost.user_id, isouter=True) + .join(SysPost, and_(SysUserPost.post_id == SysPost.post_id, SysPost.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) + query_user_menu_info = ( + ( + await db.execute( + select(SysMenu) + .select_from(SysUser) + .where(SysUser.del_flag == '0', SysUser.user_id == user_id) + .join(SysUserRole, SysUser.user_id == SysUserRole.user_id, isouter=True) + .join( + SysRole, + and_(SysUserRole.role_id == SysRole.role_id, SysRole.status == '0', SysRole.del_flag == '0'), + isouter=True, + ) + .join(SysRoleMenu, SysRole.role_id == SysRoleMenu.role_id, isouter=True) + .join(SysMenu, and_(SysRoleMenu.menu_id == SysMenu.menu_id, SysMenu.status == '0')) + .distinct() + ) + ) + .scalars() + .all() + ) results = dict( - user_basic_info=object_format_datetime(query_user_basic_info), - user_dept_info=object_format_datetime(query_user_dept_info), - user_role_info=list_format_datetime(query_user_role_info), - user_post_info=list_format_datetime(query_user_post_info), - user_menu_info=list_format_datetime(query_user_menu_info) + user_basic_info=query_user_basic_info, + user_dept_info=query_user_dept_info, + user_role_info=query_user_role_info, + user_post_info=query_user_post_info, + user_menu_info=query_user_menu_info, ) - return CurrentUserInfo(**results) + return results @classmethod - def get_user_list(cls, db: Session, query_object: UserQueryModel, data_scope_sql: str): + async def get_user_list( + cls, db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据查询参数获取用户列表信息 + :param db: orm对象 :param query_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 :return: 用户列表信息对象 """ - user_list = db.query(SysUser, SysDept) \ - .filter(SysUser.del_flag == 0, - or_(SysUser.dept_id == query_object.dept_id, SysUser.dept_id.in_( - db.query(SysDept.dept_id).filter(func.find_in_set(query_object.dept_id, SysDept.ancestors)) - )) if query_object.dept_id else True, - SysUser.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, - SysUser.nick_name.like(f'%{query_object.nick_name}%') if query_object.nick_name else True, - SysUser.email.like(f'%{query_object.email}%') if query_object.email else True, - SysUser.phonenumber.like(f'%{query_object.phonenumber}%') if query_object.phonenumber else True, - SysUser.status == query_object.status if query_object.status else True, - SysUser.sex == query_object.sex if query_object.sex else True, - SysUser.create_time.between( - datetime.combine(datetime.strptime(query_object.create_time_start, '%Y-%m-%d'), time(00, 00, 00)), - datetime.combine(datetime.strptime(query_object.create_time_end, '%Y-%m-%d'), time(23, 59, 59))) - if query_object.create_time_start and query_object.create_time_end else True, - eval(data_scope_sql) - ) \ - .outerjoin(SysDept, and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == 0, SysDept.del_flag == 0)) \ - .distinct().all() - - result_list: List[Union[dict, None]] = [] - if user_list: - for item in user_list: - obj = dict( - user_id=item[0].user_id, - dept_id=item[0].dept_id, - dept_name=item[1].dept_name if item[1] else '', - user_name=item[0].user_name, - nick_name=item[0].nick_name, - user_type=item[0].user_type, - email=item[0].email, - phonenumber=item[0].phonenumber, - sex=item[0].sex, - avatar=item[0].avatar, - status=item[0].status, - del_flag=item[0].del_flag, - login_ip=item[0].login_ip, - login_date=item[0].login_date, - create_by=item[0].create_by, - create_time=item[0].create_time, - update_by=item[0].update_by, - update_time=item[0].update_time, - remark=item[0].remark + query = ( + select(SysUser, SysDept) + .where( + SysUser.del_flag == '0', + or_( + SysUser.dept_id == query_object.dept_id, + SysUser.dept_id.in_( + select(SysDept.dept_id).where(func.find_in_set(query_object.dept_id, SysDept.ancestors)) + ), + ) + if query_object.dept_id + else True, + SysUser.user_id == query_object.user_id if query_object.user_id is not None else True, + SysUser.user_name.like(f'%{query_object.user_name}%') if query_object.user_name else True, + SysUser.nick_name.like(f'%{query_object.nick_name}%') if query_object.nick_name else True, + SysUser.email.like(f'%{query_object.email}%') if query_object.email else True, + SysUser.phonenumber.like(f'%{query_object.phonenumber}%') if query_object.phonenumber else True, + SysUser.status == query_object.status if query_object.status else True, + SysUser.sex == query_object.sex if query_object.sex else True, + SysUser.create_time.between( + datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)), + datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)), ) - result_list.append(obj) + if query_object.begin_time and query_object.end_time + else True, + eval(data_scope_sql), + ) + .join( + SysDept, + and_(SysUser.dept_id == SysDept.dept_id, SysDept.status == '0', SysDept.del_flag == '0'), + isouter=True, + ) + .distinct() + ) + user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return format_datetime_dict_list(result_list) + return user_list @classmethod - def add_user_dao(cls, db: Session, user: UserModel): + async def add_user_dao(cls, db: AsyncSession, user: UserModel): """ 新增用户数据库操作 + :param db: orm对象 :param user: 用户对象 :return: 新增校验结果 """ - db_user = SysUser(**user.dict()) + db_user = SysUser(**user.model_dump(exclude={'admin'})) db.add(db_user) - db.flush() + await db.flush() return db_user @classmethod - def edit_user_dao(cls, db: Session, user: dict): + async def edit_user_dao(cls, db: AsyncSession, user: dict): """ 编辑用户数据库操作 + :param db: orm对象 :param user: 需要更新的用户字典 :return: 编辑校验结果 """ - db.query(SysUser) \ - .filter(SysUser.user_id == user.get('user_id')) \ - .update(user) + await db.execute(update(SysUser), [user]) @classmethod - def delete_user_dao(cls, db: Session, user: UserModel): + async def delete_user_dao(cls, db: AsyncSession, user: UserModel): """ 删除用户数据库操作 + :param db: orm对象 :param user: 用户对象 :return: """ - db.query(SysUser) \ - .filter(SysUser.user_id == user.user_id) \ - .update({SysUser.del_flag: '2', SysUser.update_by: user.update_by, SysUser.update_time: user.update_time}) + await db.execute( + update(SysUser) + .where(SysUser.user_id == user.user_id) + .values(del_flag='2', update_by=user.update_by, update_time=user.update_time) + ) @classmethod - def get_user_role_allocated_list_by_user_id(cls, db: Session, query_object: UserRoleQueryModel): + async def get_user_role_allocated_list_by_user_id(cls, db: AsyncSession, query_object: UserRoleQueryModel): """ 根据用户id获取用户已分配的角色列表信息数据库操作 + :param db: orm对象 :param query_object: 用户角色查询对象 :return: 用户已分配的角色列表信息 """ - allocated_role_list = db.query(SysRole) \ - .filter( - SysRole.del_flag == 0, - SysRole.role_id != 1, - SysRole.role_name == query_object.role_name if query_object.role_name else True, - SysRole.role_key == query_object.role_key if query_object.role_key else True, - SysRole.role_id.in_( - db.query(SysUserRole.role_id).filter(SysUserRole.user_id == query_object.user_id) - ) - ).distinct().all() - - return list_format_datetime(allocated_role_list) - - @classmethod - def get_user_role_unallocated_list_by_user_id(cls, db: Session, query_object: UserRoleQueryModel): - """ - 根据用户id获取用户未分配的角色列表信息数据库操作 - :param db: orm对象 - :param query_object: 用户角色查询对象 - :return: 用户未分配的角色列表信息 - """ - unallocated_role_list = db.query(SysRole) \ - .filter( - SysRole.del_flag == 0, - SysRole.role_id != 1, - SysRole.role_name == query_object.role_name if query_object.role_name else True, - SysRole.role_key == query_object.role_key if query_object.role_key else True, - ~SysRole.role_id.in_( - db.query(SysUserRole.role_id).filter(SysUserRole.user_id == query_object.user_id) + allocated_role_list = ( + ( + await db.execute( + select(SysRole) + .where( + SysRole.del_flag == '0', + SysRole.role_id != 1, + SysRole.role_name == query_object.role_name if query_object.role_name else True, + SysRole.role_key == query_object.role_key if query_object.role_key else True, + SysRole.role_id.in_( + select(SysUserRole.role_id).where(SysUserRole.user_id == query_object.user_id) + ), + ) + .distinct() + ) ) - ).distinct().all() + .scalars() + .all() + ) - return list_format_datetime(unallocated_role_list) + return allocated_role_list @classmethod - def get_user_role_allocated_list_by_role_id(cls, db: Session, query_object: UserRoleQueryModel): + async def get_user_role_allocated_list_by_role_id( + cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据角色id获取已分配的用户列表信息 + :param db: orm对象 :param query_object: 用户角色查询对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 :return: 角色已分配的用户列表信息 """ - allocated_user_list = db.query(SysUser) \ - .filter( - SysUser.del_flag == 0, - SysUser.user_id != 1, - SysUser.user_name == query_object.user_name if query_object.user_name else True, - SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, - SysUser.user_id.in_( - db.query(SysUserRole.user_id).filter(SysUserRole.role_id == query_object.role_id) + query = ( + select(SysUser) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True) + .join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True) + .where( + SysUser.del_flag == '0', + SysUser.user_name == query_object.user_name if query_object.user_name else True, + SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, + SysRole.role_id == query_object.role_id, + eval(data_scope_sql), ) - ).distinct().all() + .distinct() + ) + allocated_user_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) - return list_format_datetime(allocated_user_list) + return allocated_user_list @classmethod - def get_user_role_unallocated_list_by_role_id(cls, db: Session, query_object: UserRoleQueryModel): + async def get_user_role_unallocated_list_by_role_id( + cls, db: AsyncSession, query_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 根据角色id获取未分配的用户列表信息 + :param db: orm对象 :param query_object: 用户角色查询对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 :return: 角色未分配的用户列表信息 """ - unallocated_user_list = db.query(SysUser) \ - .filter( - SysUser.del_flag == 0, - SysUser.user_id != 1, - SysUser.user_name == query_object.user_name if query_object.user_name else True, - SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, - ~SysUser.user_id.in_( - db.query(SysUserRole.user_id).filter(SysUserRole.role_id == query_object.role_id) + query = ( + select(SysUser) + .join(SysDept, SysDept.dept_id == SysUser.dept_id, isouter=True) + .join(SysUserRole, SysUserRole.user_id == SysUser.user_id, isouter=True) + .join(SysRole, SysRole.role_id == SysUserRole.role_id, isouter=True) + .where( + SysUser.del_flag == '0', + SysUser.user_name == query_object.user_name if query_object.user_name else True, + SysUser.phonenumber == query_object.phonenumber if query_object.phonenumber else True, + or_(SysRole.role_id != query_object.role_id, SysRole.role_id.is_(None)), + ~SysUser.user_id.in_( + select(SysUser.user_id) + .select_from(SysUser) + .join( + SysUserRole, + and_(SysUserRole.user_id == SysUser.user_id, SysUserRole.role_id == query_object.role_id), + ) + ), + eval(data_scope_sql), ) - ).distinct().all() + .distinct() + ) + unallocated_user_list = await PageUtil.paginate( + db, query, query_object.page_num, query_object.page_size, is_page + ) - return list_format_datetime(unallocated_user_list) + return unallocated_user_list @classmethod - def add_user_role_dao(cls, db: Session, user_role: UserRoleModel): + async def add_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): """ 新增用户角色关联信息数据库操作 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: """ - db_user_role = SysUserRole(**user_role.dict()) + db_user_role = SysUserRole(**user_role.model_dump()) db.add(db_user_role) @classmethod - def delete_user_role_dao(cls, db: Session, user_role: UserRoleModel): + async def delete_user_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): """ 删除用户角色关联信息数据库操作 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: """ - db.query(SysUserRole) \ - .filter(SysUserRole.user_id == user_role.user_id) \ - .delete() + await db.execute(delete(SysUserRole).where(SysUserRole.user_id.in_([user_role.user_id]))) @classmethod - def delete_user_role_by_user_and_role_dao(cls, db: Session, user_role: UserRoleModel): + async def delete_user_role_by_user_and_role_dao(cls, db: AsyncSession, user_role: UserRoleModel): """ 根据用户id及角色id删除用户角色关联信息数据库操作 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: """ - db.query(SysUserRole) \ - .filter(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) \ - .delete() + await db.execute( + delete(SysUserRole).where( + SysUserRole.user_id == user_role.user_id if user_role.user_id else True, + SysUserRole.role_id == user_role.role_id if user_role.role_id else True, + ) + ) @classmethod - def get_user_role_detail(cls, db: Session, user_role: UserRoleModel): + async def get_user_role_detail(cls, db: AsyncSession, user_role: UserRoleModel): """ 根据用户角色关联获取用户角色关联详细信息 + :param db: orm对象 :param user_role: 用户角色关联对象 :return: 用户角色关联信息 """ - user_role_info = db.query(SysUserRole) \ - .filter(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) \ - .distinct().first() + user_role_info = ( + ( + await db.execute( + select(SysUserRole) + .where(SysUserRole.user_id == user_role.user_id, SysUserRole.role_id == user_role.role_id) + .distinct() + ) + ) + .scalars() + .first() + ) return user_role_info @classmethod - def add_user_post_dao(cls, db: Session, user_post: UserPostModel): + async def add_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel): """ 新增用户岗位关联信息数据库操作 + :param db: orm对象 :param user_post: 用户岗位关联对象 :return: """ - db_user_post = SysUserPost(**user_post.dict()) + db_user_post = SysUserPost(**user_post.model_dump()) db.add(db_user_post) @classmethod - def delete_user_post_dao(cls, db: Session, user_post: UserPostModel): + async def delete_user_post_dao(cls, db: AsyncSession, user_post: UserPostModel): """ 删除用户岗位关联信息数据库操作 + :param db: orm对象 :param user_post: 用户岗位关联对象 :return: """ - db.query(SysUserPost) \ - .filter(SysUserPost.user_id == user_post.user_id) \ - .delete() + await db.execute(delete(SysUserPost).where(SysUserPost.user_id.in_([user_post.user_id]))) @classmethod - def get_user_dept_info(cls, db: Session, dept_id: int): - dept_basic_info = db.query(SysDept) \ - .filter(SysDept.dept_id == dept_id, - SysDept.status == 0, - SysDept.del_flag == 0) \ + async def get_user_dept_info(cls, db: AsyncSession, dept_id: int): + dept_basic_info = ( + ( + await db.execute( + select(SysDept).where(SysDept.dept_id == dept_id, SysDept.status == '0', SysDept.del_flag == '0') + ) + ) + .scalars() .first() + ) return dept_basic_info diff --git a/dash-fastapi-backend/module_admin/entity/do/config_do.py b/dash-fastapi-backend/module_admin/entity/do/config_do.py index 5fb6476005cd3f45aeed01c695e30fc8f5609fd6..32af3b0f737766a5f1f37da5e8d7c415beb88cb0 100644 --- a/dash-fastapi-backend/module_admin/entity/do/config_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/config_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysConfig(Base): """ 参数配置表 """ + __tablename__ = 'sys_config' config_id = Column(Integer, primary_key=True, autoincrement=True, comment='参数主键') @@ -18,4 +19,4 @@ class SysConfig(Base): create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') update_by = Column(String(64), nullable=True, default='', comment='更新者') update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') - remark = Column(String(500), nullable=True, default='', comment='备注') \ No newline at end of file + remark = Column(String(500), nullable=True, default='', comment='备注') diff --git a/dash-fastapi-backend/module_admin/entity/do/dept_do.py b/dash-fastapi-backend/module_admin/entity/do/dept_do.py index 05729270974100678e662fbb1d7689309677e0bc..96ac8daf6e999a8de60e9f654ef6b77899ba9371 100644 --- a/dash-fastapi-backend/module_admin/entity/do/dept_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/dept_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysDept(Base): """ 部门表 """ + __tablename__ = 'sys_dept' dept_id = Column(Integer, primary_key=True, autoincrement=True, comment='部门id') diff --git a/dash-fastapi-backend/module_admin/entity/do/dict_do.py b/dash-fastapi-backend/module_admin/entity/do/dict_do.py index a7d630efad527c3748133ba961f4bc9027805280..061c88f2b2352a233a275c36700603f7bf6b1ae1 100644 --- a/dash-fastapi-backend/module_admin/entity/do/dict_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/dict_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime, UniqueConstraint -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint +from config.database import Base class SysDictType(Base): """ 字典类型表 """ + __tablename__ = 'sys_dict_type' dict_id = Column(Integer, primary_key=True, autoincrement=True, comment='字典主键') @@ -19,15 +20,14 @@ class SysDictType(Base): update_time = Column(DateTime, nullable=True, default=datetime.now(), comment='更新时间') remark = Column(String(500), nullable=True, default='', comment='备注') - __table_args__ = ( - UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'), - ) + __table_args__ = (UniqueConstraint('dict_type', name='uq_sys_dict_type_dict_type'),) class SysDictData(Base): """ 字典数据表 """ + __tablename__ = 'sys_dict_data' dict_code = Column(Integer, primary_key=True, autoincrement=True, comment='字典编码') diff --git a/dash-fastapi-backend/module_admin/entity/do/job_do.py b/dash-fastapi-backend/module_admin/entity/do/job_do.py index aef168faca0943a40797944c9c9b1c63e7fc8f11..c6d671b18a27ca86e13edc729b23f417602bf112 100644 --- a/dash-fastapi-backend/module_admin/entity/do/job_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/job_do.py @@ -1,24 +1,36 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysJob(Base): """ 定时任务调度表 """ + __tablename__ = 'sys_job' job_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务ID') job_name = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务名称') job_group = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务组名') - job_executor = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器') + job_executor = Column( + String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器' + ) invoke_target = Column(String(500, collation='utf8_general_ci'), nullable=False, comment='调用目标字符串') job_args = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='位置参数') job_kwargs = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='关键字参数') - cron_expression = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='cron执行表达式') - misfire_policy = Column(String(20, collation='utf8_general_ci'), nullable=True, default='3', comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)') - concurrent = Column(String(1, collation='utf8_general_ci'), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)') + cron_expression = Column( + String(255, collation='utf8_general_ci'), nullable=True, default='', comment='cron执行表达式' + ) + misfire_policy = Column( + String(20, collation='utf8_general_ci'), + nullable=True, + default='3', + comment='计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + ) + concurrent = Column( + String(1, collation='utf8_general_ci'), nullable=True, default='1', comment='是否并发执行(0允许 1禁止)' + ) status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='状态(0正常 1暂停)') create_by = Column(String(64, collation='utf8_general_ci'), nullable=True, default='', comment='创建者') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') @@ -31,17 +43,22 @@ class SysJobLog(Base): """ 定时任务调度日志表 """ + __tablename__ = 'sys_job_log' job_log_id = Column(Integer, primary_key=True, autoincrement=True, comment='任务日志ID') job_name = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务名称') job_group = Column(String(64, collation='utf8_general_ci'), nullable=False, comment='任务组名') - job_executor = Column(String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器') + job_executor = Column( + String(64, collation='utf8_general_ci'), nullable=False, default='default', comment='任务执行器' + ) invoke_target = Column(String(500, collation='utf8_general_ci'), nullable=False, comment='调用目标字符串') job_args = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='位置参数') job_kwargs = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='关键字参数') job_trigger = Column(String(255, collation='utf8_general_ci'), nullable=True, comment='任务触发器') job_message = Column(String(500, collation='utf8_general_ci'), nullable=True, default='', comment='日志信息') - status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='执行状态(0正常 1失败)') + status = Column( + String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='执行状态(0正常 1失败)' + ) exception_info = Column(String(2000, collation='utf8_general_ci'), nullable=True, default='', comment='异常信息') create_time = Column(DateTime, nullable=True, default=datetime.now(), comment='创建时间') diff --git a/dash-fastapi-backend/module_admin/entity/do/log_do.py b/dash-fastapi-backend/module_admin/entity/do/log_do.py index 19d2b187d8c579777fec80b996a29ab51ec3714a..f915207cf984f4c663ea4cb818fa5c99f9a8610d 100644 --- a/dash-fastapi-backend/module_admin/entity/do/log_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/log_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime, Text, BigInteger, Index -from config.database import Base from datetime import datetime +from sqlalchemy import BigInteger, Column, DateTime, Index, Integer, String +from config.database import Base class SysLogininfor(Base): """ 系统访问记录 """ + __tablename__ = 'sys_logininfor' info_id = Column(Integer, primary_key=True, autoincrement=True, comment='访问ID') @@ -15,7 +16,9 @@ class SysLogininfor(Base): login_location = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='登录地点') browser = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='浏览器类型') os = Column(String(50, collation='utf8_general_ci'), nullable=True, default='', comment='操作系统') - status = Column(String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='登录状态(0成功 1失败)') + status = Column( + String(1, collation='utf8_general_ci'), nullable=True, default='0', comment='登录状态(0成功 1失败)' + ) msg = Column(String(255, collation='utf8_general_ci'), nullable=True, default='', comment='提示消息') login_time = Column(DateTime, nullable=True, default=datetime.now(), comment='访问时间') @@ -27,6 +30,7 @@ class SysOperLog(Base): """ 操作日志记录 """ + __tablename__ = 'sys_oper_log' oper_id = Column(BigInteger, primary_key=True, autoincrement=True, comment='日志主键') diff --git a/dash-fastapi-backend/module_admin/entity/do/menu_do.py b/dash-fastapi-backend/module_admin/entity/do/menu_do.py index 8f98be48e0b75542fe209893747cce903506503e..d3ccffa5537b8b31ae73c7977dd6d444f49c2844 100644 --- a/dash-fastapi-backend/module_admin/entity/do/menu_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/menu_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysMenu(Base): """ 菜单权限表 """ + __tablename__ = 'sys_menu' menu_id = Column(Integer, primary_key=True, autoincrement=True, comment='菜单ID') @@ -16,6 +17,7 @@ class SysMenu(Base): path = Column(String(200), nullable=True, default='', comment='路由地址') component = Column(String(255), nullable=True, default=None, comment='组件路径') query = Column(String(255), nullable=True, default=None, comment='路由参数') + route_name = Column(String(50), nullable=True, default='', comment='路由名称') is_frame = Column(Integer, default=1, comment='是否为外链(0是 1否)') is_cache = Column(Integer, default=0, comment='是否缓存(0缓存 1不缓存)') menu_type = Column(String(1), nullable=True, default='', comment='菜单类型(M目录 C菜单 F按钮)') diff --git a/dash-fastapi-backend/module_admin/entity/do/notice_do.py b/dash-fastapi-backend/module_admin/entity/do/notice_do.py index 38566584178aaa0a3105e497bc7159ea3935c839..125a40a8ef38766307ccd210e00bd1ad9fa5e9c1 100644 --- a/dash-fastapi-backend/module_admin/entity/do/notice_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/notice_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime, LargeBinary -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, LargeBinary, String +from config.database import Base class SysNotice(Base): """ 通知公告表 """ + __tablename__ = 'sys_notice' notice_id = Column(Integer, primary_key=True, autoincrement=True, comment='公告ID') diff --git a/dash-fastapi-backend/module_admin/entity/do/post_do.py b/dash-fastapi-backend/module_admin/entity/do/post_do.py index c6b189b6cd9fe1d5adcf6a8e02e30979a85c3cb4..54ab38dcb0d3ee77b500286f6e7d731ac092dd78 100644 --- a/dash-fastapi-backend/module_admin/entity/do/post_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/post_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysPost(Base): """ 岗位信息表 """ + __tablename__ = 'sys_post' post_id = Column(Integer, primary_key=True, autoincrement=True, comment='岗位ID') diff --git a/dash-fastapi-backend/module_admin/entity/do/role_do.py b/dash-fastapi-backend/module_admin/entity/do/role_do.py index db29244e11f24e330683b8e10d992b9c4318593c..fc2e34d593ac8eb624f9288a9a2be32a7d8c3303 100644 --- a/dash-fastapi-backend/module_admin/entity/do/role_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/role_do.py @@ -1,19 +1,24 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysRole(Base): """ 角色信息表 """ + __tablename__ = 'sys_role' role_id = Column(Integer, primary_key=True, autoincrement=True, comment='角色ID') role_name = Column(String(30, collation='utf8_general_ci'), nullable=False, comment='角色名称') role_key = Column(String(100, collation='utf8_general_ci'), nullable=False, comment='角色权限字符串') role_sort = Column(Integer, nullable=False, comment='显示顺序') - data_scope = Column(String(1, collation='utf8_general_ci'), default='1', comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)') + data_scope = Column( + String(1, collation='utf8_general_ci'), + default='1', + comment='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + ) menu_check_strictly = Column(Integer, default=1, comment='菜单树选择项是否关联显示') dept_check_strictly = Column(Integer, default=1, comment='部门树选择项是否关联显示') status = Column(String(1, collation='utf8_general_ci'), nullable=False, comment='角色状态(0正常 1停用)') @@ -29,6 +34,7 @@ class SysRoleDept(Base): """ 角色和部门关联表 """ + __tablename__ = 'sys_role_dept' role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') @@ -39,6 +45,7 @@ class SysRoleMenu(Base): """ 角色和菜单关联表 """ + __tablename__ = 'sys_role_menu' role_id = Column(Integer, primary_key=True, nullable=False, comment='角色ID') diff --git a/dash-fastapi-backend/module_admin/entity/do/user_do.py b/dash-fastapi-backend/module_admin/entity/do/user_do.py index 93515472f905e58d79b28ccc3f0aa9ffa24779ed..21bba84a7bdeab34378dd39d70439859c4cc8d1d 100644 --- a/dash-fastapi-backend/module_admin/entity/do/user_do.py +++ b/dash-fastapi-backend/module_admin/entity/do/user_do.py @@ -1,12 +1,13 @@ -from sqlalchemy import Column, Integer, String, DateTime -from config.database import Base from datetime import datetime +from sqlalchemy import Column, DateTime, Integer, String +from config.database import Base class SysUser(Base): """ 用户信息表 """ + __tablename__ = 'sys_user' user_id = Column(Integer, primary_key=True, autoincrement=True, comment='用户ID') @@ -34,6 +35,7 @@ class SysUserRole(Base): """ 用户和角色关联表 """ + __tablename__ = 'sys_user_role' user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') @@ -44,6 +46,7 @@ class SysUserPost(Base): """ 用户与岗位关联表 """ + __tablename__ = 'sys_user_post' user_id = Column(Integer, primary_key=True, nullable=False, comment='用户ID') diff --git a/dash-fastapi-backend/module_admin/entity/vo/cache_vo.py b/dash-fastapi-backend/module_admin/entity/vo/cache_vo.py index 18c23572c6b05359a19412d7d24881a78f282936..6d5889626679833c65f68f841654f21d32c4fa02 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/cache_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/cache_vo.py @@ -1,29 +1,23 @@ -from pydantic import BaseModel -from typing import Optional, List, Any +from pydantic import BaseModel, Field +from typing import Any, List, Optional class CacheMonitorModel(BaseModel): """ 缓存监控信息对应pydantic模型 """ - command_stats: Optional[List] - db_size: Optional[int] - info: Optional[dict] + + command_stats: Optional[List] = Field(default=[], description='命令统计') + db_size: Optional[int] = Field(default=None, description='Key数量') + info: Optional[dict] = Field(default={}, description='Redis信息') class CacheInfoModel(BaseModel): """ 缓存监控对象对应pydantic模型 """ - cache_key: Optional[str] - cache_name: Optional[str] - cache_value: Optional[Any] - remark: Optional[str] - -class CrudCacheResponse(BaseModel): - """ - 操作缓存响应模型 - """ - is_success: bool - message: str + cache_key: Optional[str] = Field(default=None, description='缓存键名') + cache_name: Optional[str] = Field(default=None, description='缓存名称') + cache_value: Optional[Any] = Field(default=None, description='缓存内容') + remark: Optional[str] = Field(default=None, description='备注') diff --git a/dash-fastapi-backend/module_admin/entity/vo/common_vo.py b/dash-fastapi-backend/module_admin/entity/vo/common_vo.py new file mode 100644 index 0000000000000000000000000000000000000000..d25bf1c39bc32f231b411f575133b966c8ed60ff --- /dev/null +++ b/dash-fastapi-backend/module_admin/entity/vo/common_vo.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel, Field +from typing import Any, Optional + + +class CrudResponseModel(BaseModel): + """ + 操作响应模型 + """ + + is_success: bool = Field(description='操作是否成功') + message: str = Field(description='响应信息') + result: Optional[Any] = Field(default=None, description='响应结果') + + +class UploadResponseModel(BaseModel): + """ + 上传响应模型 + """ + + file_name: Optional[str] = Field(default=None, description='新文件映射路径') + new_file_name: Optional[str] = Field(default=None, description='新文件名称') + original_filename: Optional[str] = Field(default=None, description='原文件名称') + url: Optional[str] = Field(default=None, description='新文件url') diff --git a/dash-fastapi-backend/module_admin/entity/vo/config_vo.py b/dash-fastapi-backend/module_admin/entity/vo/config_vo.py index 6ad9c95bec38e8aa4c6c8c226679a21cc04cf448..4bfabb0930cc4ebb8a7143678ee2c4c3f5478c33 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/config_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/config_vo.py @@ -1,63 +1,69 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional class ConfigModel(BaseModel): """ 参数配置表对应pydantic模型 """ - config_id: Optional[int] - config_name: Optional[str] - config_key: Optional[str] - config_value: Optional[str] - config_type: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + config_id: Optional[int] = Field(default=None, description='参数主键') + config_name: Optional[str] = Field(default=None, description='参数名称') + config_key: Optional[str] = Field(default=None, description='参数键名') + config_value: Optional[str] = Field(default=None, description='参数键值') + config_type: Optional[Literal['Y', 'N']] = Field(default=None, description='系统内置(Y是 N否)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='config_key', message='参数名称不能为空') + @Size(field_name='config_key', min_length=0, max_length=100, message='参数名称长度不能超过100个字符') + def get_config_key(self): + return self.config_key + + @NotBlank(field_name='config_name', message='参数键名不能为空') + @Size(field_name='config_name', min_length=0, max_length=100, message='参数键名长度不能超过100个字符') + def get_config_name(self): + return self.config_name + + @NotBlank(field_name='config_value', message='参数键值不能为空') + @Size(field_name='config_value', min_length=0, max_length=500, message='参数键值长度不能超过500个字符') + def get_config_value(self): + return self.config_value + + def validate_fields(self): + self.get_config_key() + self.get_config_name() + self.get_config_value() class ConfigQueryModel(ConfigModel): """ 参数配置管理不分页查询模型 """ - create_time_start: Optional[str] - create_time_end: Optional[str] + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class ConfigPageObject(ConfigQueryModel): +class ConfigPageQueryModel(ConfigQueryModel): """ 参数配置管理分页查询模型 """ - page_num: int - page_size: int - -class ConfigPageObjectResponse(BaseModel): - """ - 参数配置管理列表分页查询返回模型 - """ - rows: List[Union[ConfigModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteConfigModel(BaseModel): """ 删除参数配置模型 """ - config_ids: str - -class CrudConfigResponse(BaseModel): - """ - 操作参数配置响应模型 - """ - is_success: bool - message: str + config_ids: str = Field(description='需要删除的参数主键') diff --git a/dash-fastapi-backend/module_admin/entity/vo/dept_vo.py b/dash-fastapi-backend/module_admin/entity/vo/dept_vo.py index a2cf22879287f8c1ab65c2ed4fec3516c5fc4c81..4d89ebdd84f480c0f9ff478d19a5660a8a035c3e 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/dept_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/dept_vo.py @@ -1,53 +1,70 @@ -from pydantic import BaseModel -from typing import Union, Optional, List -from module_admin.entity.vo.user_vo import DeptModel +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import Network, NotBlank, Size +from typing import Literal, Optional -class DeptPageObject(DeptModel): +class DeptModel(BaseModel): """ - 部门管理分页查询模型 + 部门表对应pydantic模型 """ - page_num: int - page_size: int + model_config = ConfigDict(from_attributes=True) -class DeptPageObjectResponse(BaseModel): - """ - 用户管理列表分页查询返回模型 - """ - rows: List[Union[DeptModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + dept_id: Optional[int] = Field(default=None, description='部门id') + parent_id: Optional[int] = Field(default=None, description='父部门id') + ancestors: Optional[str] = Field(default=None, description='祖级列表') + dept_name: Optional[str] = Field(default=None, description='部门名称') + order_num: Optional[int] = Field(default=None, description='显示顺序') + leader: Optional[str] = Field(default=None, description='负责人') + phone: Optional[str] = Field(default=None, description='联系电话') + email: Optional[str] = Field(default=None, description='邮箱') + status: Optional[Literal['0', '1']] = Field(default=None, description='部门状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + @NotBlank(field_name='dept_name', message='部门名称不能为空') + @Size(field_name='dept_name', min_length=0, max_length=30, message='部门名称长度不能超过30个字符') + def get_dept_name(self): + return self.dept_name -class DeptResponse(BaseModel): - """ - 用户管理列表不分页查询返回模型 - """ - rows: List[Union[DeptModel, None]] = [] + @NotBlank(field_name='order_num', message='显示顺序不能为空') + def get_order_num(self): + return self.order_num + @Size(field_name='phone', min_length=0, max_length=11, message='联系电话长度不能超过11个字符') + def get_phone(self): + return self.phone -class DeptTree(BaseModel): - """ - 部门树响应模型 - """ - dept_tree: Union[List, None] + @Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确') + @Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符') + def get_email(self): + return self.email + + def validate_fields(self): + self.get_dept_name() + self.get_order_num() + self.get_phone() + self.get_email() -class CrudDeptResponse(BaseModel): +class DeptQueryModel(DeptModel): """ - 操作部门响应模型 + 部门管理不分页查询模型 """ - is_success: bool - message: str + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') class DeleteDeptModel(BaseModel): """ 删除部门模型 """ - dept_ids: str - update_by: Optional[str] - update_time: Optional[str] + + dept_ids: str = Field(default=None, description='需要删除的部门id') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[str] = Field(default=None, description='更新时间') diff --git a/dash-fastapi-backend/module_admin/entity/vo/dict_vo.py b/dash-fastapi-backend/module_admin/entity/vo/dict_vo.py index 6de3d7431dae20c886afa0fad9b08ab916af17cf..8c9cef3f37e5544dd59eb0613e74e576aff96d30 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/dict_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/dict_vo.py @@ -1,111 +1,141 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import NotBlank, Pattern, Size +from typing import Literal, Optional class DictTypeModel(BaseModel): """ 字典类型表对应pydantic模型 """ - dict_id: Optional[int] - dict_name: Optional[str] - dict_type: Optional[str] - status: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + dict_id: Optional[int] = Field(default=None, description='字典主键') + dict_name: Optional[str] = Field(default=None, description='字典名称') + dict_type: Optional[str] = Field(default=None, description='字典类型') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='dict_name', message='字典名称不能为空') + @Size(field_name='dict_name', min_length=0, max_length=100, message='字典类型名称长度不能超过100个字符') + def get_dict_name(self): + return self.dict_name + + @NotBlank(field_name='dict_type', message='字典类型不能为空') + @Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型类型长度不能超过100个字符') + @Pattern( + field_name='dict_type', + regexp='^[a-z][a-z0-9_]*$', + message='字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)', + ) + def get_dict_type(self): + return self.dict_type + + def validate_fields(self): + self.get_dict_name() + self.get_dict_type() class DictDataModel(BaseModel): """ 字典数据表对应pydantic模型 """ - dict_code: Optional[int] - dict_sort: Optional[int] - dict_label: Optional[str] - dict_value: Optional[str] - dict_type: Optional[str] - css_class: Optional[str] - list_class: Optional[str] - is_default: Optional[str] - status: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - - class Config: - orm_mode = True + + model_config = ConfigDict(from_attributes=True) + + dict_code: Optional[int] = Field(default=None, description='字典编码') + dict_sort: Optional[int] = Field(default=None, description='字典排序') + dict_label: Optional[str] = Field(default=None, description='字典标签') + dict_value: Optional[str] = Field(default=None, description='字典键值') + dict_type: Optional[str] = Field(default=None, description='字典类型') + css_class: Optional[str] = Field(default=None, description='样式属性(其他样式扩展)') + list_class: Optional[str] = Field(default=None, description='表格回显样式') + is_default: Optional[Literal['Y', 'N']] = Field(default=None, description='是否默认(Y是 N否)') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='dict_label', message='字典标签不能为空') + @Size(field_name='dict_label', min_length=0, max_length=100, message='字典标签长度不能超过100个字符') + def get_dict_label(self): + return self.dict_label + + @NotBlank(field_name='dict_value', message='字典键值不能为空') + @Size(field_name='dict_value', min_length=0, max_length=100, message='字典键值长度不能超过100个字符') + def get_dict_value(self): + return self.dict_value + + @NotBlank(field_name='dict_type', message='字典类型不能为空') + @Size(field_name='dict_type', min_length=0, max_length=100, message='字典类型长度不能超过100个字符') + def get_dict_type(self): + return self.dict_type + + @Size(field_name='css_class', min_length=0, max_length=100, message='样式属性长度不能超过100个字符') + def get_css_class(self): + return self.css_class + + def validate_fields(self): + self.get_dict_label() + self.get_dict_value() + self.get_dict_type() + self.get_css_class() class DictTypeQueryModel(DictTypeModel): """ 字典类型管理不分页查询模型 """ - create_time_start: Optional[str] - create_time_end: Optional[str] + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class DictTypePageObject(DictTypeQueryModel): + +class DictTypePageQueryModel(DictTypeQueryModel): """ 字典类型管理分页查询模型 """ - page_num: int - page_size: int - -class DictTypePageObjectResponse(BaseModel): - """ - 字典类型管理列表分页查询返回模型 - """ - rows: List[Union[DictTypeModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteDictTypeModel(BaseModel): """ 删除字典类型模型 """ - dict_ids: str + dict_ids: str = Field(description='需要删除的字典主键') -class DictDataPageObject(DictDataModel): + +class DictDataQueryModel(DictDataModel): """ - 字典数据管理分页查询模型 + 字典数据管理不分页查询模型 """ - page_num: int - page_size: int + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class DictDataPageObjectResponse(BaseModel): + +class DictDataPageQueryModel(DictDataQueryModel): """ - 字典数据管理列表分页查询返回模型 + 字典数据管理分页查询模型 """ - rows: List[Union[DictDataModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteDictDataModel(BaseModel): """ 删除字典数据模型 """ - dict_codes: str - -class CrudDictResponse(BaseModel): - """ - 操作字典响应模型 - """ - is_success: bool - message: str + dict_codes: str = Field(description='需要删除的字典编码') diff --git a/dash-fastapi-backend/module_admin/entity/vo/job_vo.py b/dash-fastapi-backend/module_admin/entity/vo/job_vo.py index 0283b1fe0cd3f8cfbd2dd36fdf86216729d4f0ed..24e6dd1c34c84f3b378b0fe481eafab13c414064 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/job_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/job_vo.py @@ -1,130 +1,126 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional class JobModel(BaseModel): """ 定时任务调度表对应pydantic模型 """ - job_id: Optional[int] - job_name: Optional[str] - job_group: Optional[str] - job_executor: Optional[str] - invoke_target: Optional[str] - job_args: Optional[str] - job_kwargs: Optional[str] - cron_expression: Optional[str] - misfire_policy: Optional[str] - concurrent: Optional[str] - status: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - - class Config: - orm_mode = True + + model_config = ConfigDict(from_attributes=True) + + job_id: Optional[int] = Field(default=None, description='任务ID') + job_name: Optional[str] = Field(default=None, description='任务名称') + job_group: Optional[str] = Field(default=None, description='任务组名') + job_executor: Optional[str] = Field(default=None, description='任务执行器') + invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') + job_args: Optional[str] = Field(default=None, description='位置参数') + job_kwargs: Optional[str] = Field(default=None, description='关键字参数') + cron_expression: Optional[str] = Field(default=None, description='cron执行表达式') + misfire_policy: Optional[Literal['1', '2', '3']] = Field( + default=None, description='计划执行错误策略(1立即执行 2执行一次 3放弃执行)' + ) + concurrent: Optional[Literal['0', '1']] = Field(default=None, description='是否并发执行(0允许 1禁止)') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1暂停)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注信息') + + @NotBlank(field_name='invoke_target', message='调用目标字符串不能为空') + @Size(field_name='invoke_target', min_length=0, max_length=500, message='调用目标字符串长度不能超过500个字符') + def get_invoke_target(self): + return self.invoke_target + + @NotBlank(field_name='cron_expression', message='Cron执行表达式不能为空') + @Size(field_name='cron_expression', min_length=0, max_length=255, message='Cron执行表达式不能超过255个字符') + def get_cron_expression(self): + return self.cron_expression + + def validate_fields(self): + self.get_invoke_target() + self.get_cron_expression() class JobLogModel(BaseModel): """ 定时任务调度日志表对应pydantic模型 """ - job_log_id: Optional[int] - job_name: Optional[str] - job_group: Optional[str] - job_executor: Optional[str] - invoke_target: Optional[str] - job_args: Optional[str] - job_kwargs: Optional[str] - job_trigger: Optional[str] - job_message: Optional[str] - status: Optional[str] - exception_info: Optional[str] - create_time: Optional[str] - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) -class JobPageObject(JobModel): - """ - 定时任务管理分页查询模型 - """ - page_num: int - page_size: int + job_log_id: Optional[int] = Field(default=None, description='任务日志ID') + job_name: Optional[str] = Field(default=None, description='任务名称') + job_group: Optional[str] = Field(default=None, description='任务组名') + job_executor: Optional[str] = Field(default=None, description='任务执行器') + invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') + job_args: Optional[str] = Field(default=None, description='位置参数') + job_kwargs: Optional[str] = Field(default=None, description='关键字参数') + job_trigger: Optional[str] = Field(default=None, description='任务触发器') + job_message: Optional[str] = Field(default=None, description='日志信息') + status: Optional[Literal['0', '1']] = Field(default=None, description='执行状态(0正常 1失败)') + exception_info: Optional[str] = Field(default=None, description='异常信息') + create_time: Optional[datetime] = Field(default=None, description='创建时间') -class JobPageObjectResponse(BaseModel): +class JobQueryModel(JobModel): """ - 定时任务管理列表分页查询返回模型 + 定时任务管理不分页查询模型 """ - rows: List[Union[JobModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class CrudJobResponse(BaseModel): + +class JobPageQueryModel(JobQueryModel): """ - 操作定时任务及日志响应模型 + 定时任务管理分页查询模型 """ - is_success: bool - message: str + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class EditJobModel(JobModel): """ 编辑定时任务模型 """ - type: Optional[str] + + type: Optional[str] = Field(default=None, description='操作类型') class DeleteJobModel(BaseModel): """ 删除定时任务模型 """ - job_ids: str + + job_ids: str = Field(description='需要删除的定时任务ID') class JobLogQueryModel(JobLogModel): """ 定时任务日志不分页查询模型 """ - create_time_start: Optional[str] - create_time_end: Optional[str] + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class JobLogPageObject(JobLogQueryModel): +class JobLogPageQueryModel(JobLogQueryModel): """ 定时任务日志管理分页查询模型 """ - page_num: int - page_size: int - -class JobLogPageObjectResponse(BaseModel): - """ - 定时任务日志管理列表分页查询返回模型 - """ - rows: List[Union[JobLogModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteJobLogModel(BaseModel): """ 删除定时任务日志模型 """ - job_log_ids: str - -class ClearJobLogModel(BaseModel): - """ - 清除定时任务日志模型 - """ - oper_type: str + job_log_ids: str = Field(description='需要删除的定时任务日志ID') diff --git a/dash-fastapi-backend/module_admin/entity/vo/log_vo.py b/dash-fastapi-backend/module_admin/entity/vo/log_vo.py index a8486e230cf196e63f270bfdbd9894815596d764..1fa2b4a7844297cdeed214e0f9ff46faeab21be4 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/log_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/log_vo.py @@ -1,147 +1,121 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from typing import Literal, Optional class OperLogModel(BaseModel): """ 操作日志表对应pydantic模型 """ - oper_id: Optional[int] - title: Optional[str] - business_type: Optional[int] - method: Optional[str] - request_method: Optional[str] - operator_type: Optional[int] - oper_name: Optional[str] - dept_name: Optional[str] - oper_url: Optional[str] - oper_ip: Optional[str] - oper_location: Optional[str] - oper_param: Optional[str] - json_result: Optional[str] - status: Optional[int] - error_msg: Optional[str] - oper_time: Optional[str] - cost_time: Optional[int] - - class Config: - orm_mode = True + + model_config = ConfigDict(from_attributes=True) + + oper_id: Optional[int] = Field(default=None, description='日志主键') + title: Optional[str] = Field(default=None, description='模块标题') + business_type: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']] = ( + Field( + default=None, description='业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8生成代码 9清空数据)' + ) + ) + method: Optional[str] = Field(default=None, description='方法名称') + request_method: Optional[str] = Field(default=None, description='请求方式') + operator_type: Optional[Literal[0, 1, 2]] = Field( + default=None, description='操作类别(0其它 1后台用户 2手机端用户)' + ) + oper_name: Optional[str] = Field(default=None, description='操作人员') + dept_name: Optional[str] = Field(default=None, description='部门名称') + oper_url: Optional[str] = Field(default=None, description='请求URL') + oper_ip: Optional[str] = Field(default=None, description='主机地址') + oper_location: Optional[str] = Field(default=None, description='操作地点') + oper_param: Optional[str] = Field(default=None, description='请求参数') + json_result: Optional[str] = Field(default=None, description='返回参数') + status: Optional[Literal[0, 1, '0', '1']] = Field(default=None, description='操作状态(0正常 1异常)') + error_msg: Optional[str] = Field(default=None, description='错误消息') + oper_time: Optional[datetime] = Field(default=None, description='操作时间') + cost_time: Optional[int] = Field(default=None, description='消耗时间') class LogininforModel(BaseModel): """ 登录日志表对应pydantic模型 """ - info_id: Optional[int] - user_name: Optional[str] - ipaddr: Optional[str] - login_location: Optional[str] - browser: Optional[str] - os: Optional[str] - status: Optional[str] - msg: Optional[str] - login_time: Optional[str] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + info_id: Optional[int] = Field(default=None, description='访问ID') + user_name: Optional[str] = Field(default=None, description='用户账号') + ipaddr: Optional[str] = Field(default=None, description='登录IP地址') + login_location: Optional[str] = Field(default=None, description='登录地点') + browser: Optional[str] = Field(default=None, description='浏览器类型') + os: Optional[str] = Field(default=None, description='操作系统') + status: Optional[Literal['0', '1']] = Field(default=None, description='登录状态(0成功 1失败)') + msg: Optional[str] = Field(default=None, description='提示消息') + login_time: Optional[datetime] = Field(default=None, description='访问时间') class OperLogQueryModel(OperLogModel): """ 操作日志管理不分页查询模型 """ - order_by_column: Optional[str] - is_asc: Optional[str] - oper_time_start: Optional[str] - oper_time_end: Optional[str] + + order_by_column: Optional[str] = Field(default=None, description='排序的字段名称') + is_asc: Optional[Literal['ascending', 'descending']] = Field( + default=None, description='排序方式(ascending升序 descending降序)' + ) + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class OperLogPageObject(OperLogQueryModel): +class OperLogPageQueryModel(OperLogQueryModel): """ 操作日志管理分页查询模型 """ - page_num: int - page_size: int - -class OperLogPageObjectResponse(BaseModel): - """ - 操作日志列表分页查询返回模型 - """ - rows: List[Union[OperLogModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteOperLogModel(BaseModel): """ 删除操作日志模型 """ - oper_ids: str - -class ClearOperLogModel(BaseModel): - """ - 清除操作日志模型 - """ - oper_type: str + oper_ids: str = Field(description='需要删除的日志主键') class LoginLogQueryModel(LogininforModel): """ 登录日志管理不分页查询模型 """ - order_by_column: Optional[str] - is_asc: Optional[str] - login_time_start: Optional[str] - login_time_end: Optional[str] + + order_by_column: Optional[str] = Field(default=None, description='排序的字段名称') + is_asc: Optional[Literal['ascending', 'descending']] = Field( + default=None, description='排序方式(ascending升序 descending降序)' + ) + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class LoginLogPageObject(LoginLogQueryModel): +class LoginLogPageQueryModel(LoginLogQueryModel): """ 登录日志管理分页查询模型 """ - page_num: int - page_size: int - -class LoginLogPageObjectResponse(BaseModel): - """ - 登录日志列表分页查询返回模型 - """ - rows: List[Union[LogininforModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteLoginLogModel(BaseModel): """ 删除登录日志模型 """ - info_ids: str - -class ClearLoginLogModel(BaseModel): - """ - 清除登录日志模型 - """ - oper_type: str + info_ids: str = Field(description='需要删除的访问ID') class UnlockUser(BaseModel): """ 解锁用户模型 """ - user_name: str - -class CrudLogResponse(BaseModel): - """ - 操作各类日志响应模型 - """ - is_success: bool - message: str + user_name: str = Field(description='用户名称') diff --git a/dash-fastapi-backend/module_admin/entity/vo/login_vo.py b/dash-fastapi-backend/module_admin/entity/vo/login_vo.py index 528b3b2680362cc73a4f92d72608eccf1b2c2499..f948ce607770a076c2b470c809c168bc8d3b9a81 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/login_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/login_vo.py @@ -1,23 +1,77 @@ -from pydantic import BaseModel -from typing import Optional +import re +from pydantic import BaseModel, Field, model_validator +from typing import List, Optional, Union +from exceptions.exception import ModelValidatorException +from module_admin.entity.vo.menu_vo import MenuModel class UserLogin(BaseModel): - user_name: str - password: str - captcha: Optional[str] - session_id: Optional[str] - login_info: Optional[dict] - captcha_enabled: Optional[bool] + user_name: str = Field(description='用户名称') + password: str = Field(description='用户密码') + code: Optional[str] = Field(default=None, description='验证码') + uuid: Optional[str] = Field(default=None, description='会话编号') + login_info: Optional[dict] = Field(default=None, description='登录信息,前端无需传递') + captcha_enabled: Optional[bool] = Field(default=None, description='是否启用验证码,前端无需传递') + + +class UserRegister(BaseModel): + username: str = Field(description='用户名称') + password: str = Field(description='用户密码') + confirm_password: str = Field(description='用户二次确认密码') + code: Optional[str] = Field(default=None, description='验证码') + uuid: Optional[str] = Field(default=None, description='会话编号') + + @model_validator(mode='after') + def check_password(self) -> 'UserRegister': + pattern = r"""^[^<>"'|\\]+$""" + if self.password is None or re.match(pattern, self.password): + return self + else: + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') class Token(BaseModel): - access_token: str - token_type: str + access_token: str = Field(description='token信息') + token_type: str = Field(description='token类型') + + +class CaptchaCode(BaseModel): + captcha_enabled: bool = Field(description='是否启用验证码') + forget_enabled: bool = Field(description='是否启用忘记密码') + register_enabled: bool = Field(description='是否启用注册') + img: str = Field(description='验证码图片') + uuid: str = Field(description='会话编号') class SmsCode(BaseModel): - is_success: Optional[bool] - sms_code: str - session_id: str - message: Optional[str] + is_success: Optional[bool] = Field(default=None, description='操作是否成功') + sms_code: str = Field(description='短信验证码') + session_id: str = Field(description='会话编号') + message: Optional[str] = Field(default=None, description='响应信息') + + +class MenuTreeModel(MenuModel): + children: Optional[Union[List['MenuTreeModel'], None]] = Field(default=None, description='子菜单') + + +class MetaModel(BaseModel): + title: Optional[str] = Field(default=None, description='设置路由在侧边栏和面包屑中展示的名字') + icon: Optional[str] = Field(default=None, description='设置路由的图标') + no_cache: Optional[bool] = Field(default=None, description='设置为true,则不会被 缓存') + link: Optional[str] = Field(default=None, description='内链地址(http(s)://开头)') + + +class RouterModel(BaseModel): + name: Optional[str] = Field(default=None, description='路由名称') + path: Optional[str] = Field(default=None, description='路由地址') + hidden: Optional[bool] = Field(default=None, description='是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现') + redirect: Optional[str] = Field( + default=None, description='重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击' + ) + component: Optional[str] = Field(default=None, description='组件地址') + query: Optional[str] = Field(default=None, description='路由参数:如 {"id": 1, "name": "ry"}') + always_show: Optional[bool] = Field( + default=None, description='当你一个路由下面的children声明的路由大于1个时,自动会变成嵌套的模式--如组件页面' + ) + meta: Optional[MetaModel] = Field(default=None, description='其他元素') + children: Optional[Union[List['RouterModel'], None]] = Field(default=None, description='子路由') diff --git a/dash-fastapi-backend/module_admin/entity/vo/menu_vo.py b/dash-fastapi-backend/module_admin/entity/vo/menu_vo.py index c0aa5c2074514f4506dd3e882b77a5abc8fb1d74..db528e788dea9ea80466f90eef68413e8970feb5 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/menu_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/menu_vo.py @@ -1,85 +1,83 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional class MenuModel(BaseModel): """ 菜单表对应pydantic模型 """ - menu_id: Optional[int] - menu_name: Optional[str] - parent_id: Optional[int] - order_num: Optional[int] - path: Optional[str] - component: Optional[str] - query: Optional[str] - is_frame: Optional[int] - is_cache: Optional[int] - menu_type: Optional[str] - visible: Optional[str] - status: Optional[str] - perms: Optional[str] - icon: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - - class Config: - orm_mode = True - - -class MenuTreeModel(MenuModel): - """ - 菜单树查询模型 - """ - type: Optional[str] + model_config = ConfigDict(from_attributes=True) -class MenuPageObject(MenuModel): - """ - 菜单管理分页查询模型 - """ - page_num: int - page_size: int + menu_id: Optional[int] = Field(default=None, description='菜单ID') + menu_name: Optional[str] = Field(default=None, description='菜单名称') + parent_id: Optional[int] = Field(default=None, description='父菜单ID') + order_num: Optional[int] = Field(default=None, description='显示顺序') + path: Optional[str] = Field(default=None, description='路由地址') + component: Optional[str] = Field(default=None, description='组件路径') + query: Optional[str] = Field(default=None, description='路由参数') + route_name: Optional[str] = Field(default=None, description='路由名称') + is_frame: Optional[Literal[0, 1]] = Field(default=None, description='是否为外链(0是 1否)') + is_cache: Optional[Literal[0, 1]] = Field(default=None, description='是否缓存(0缓存 1不缓存)') + menu_type: Optional[Literal['M', 'C', 'F']] = Field(default=None, description='菜单类型(M目录 C菜单 F按钮)') + visible: Optional[Literal['0', '1']] = Field(default=None, description='菜单状态(0显示 1隐藏)') + status: Optional[Literal['0', '1']] = Field(default=None, description='菜单状态(0正常 1停用)') + perms: Optional[str] = Field(default=None, description='权限标识') + icon: Optional[str] = Field(default=None, description='菜单图标') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + @NotBlank(field_name='menu_name', message='菜单名称不能为空') + @Size(field_name='menu_name', min_length=0, max_length=50, message='菜单名称长度不能超过50个字符') + def get_menu_name(self): + return self.menu_name -class MenuPageObjectResponse(BaseModel): - """ - 菜单管理列表分页查询返回模型 - """ - rows: List[Union[MenuModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + @NotBlank(field_name='order_num', message='显示顺序不能为空') + def get_order_num(self): + return self.order_num + @Size(field_name='path', min_length=0, max_length=200, message='路由地址长度不能超过200个字符') + def get_path(self): + return self.path -class MenuResponse(BaseModel): - """ - 菜单管理列表不分页查询返回模型 - """ - rows: List[Union[MenuModel, None]] = [] + @Size(field_name='component', min_length=0, max_length=255, message='组件路径长度不能超过255个字符') + def get_component(self): + return self.component + @NotBlank(field_name='menu_type', message='菜单类型不能为空') + def get_menu_type(self): + return self.menu_type -class MenuTree(BaseModel): - """ - 菜单树响应模型 - """ - menu_tree: Union[List, None] + @Size(field_name='perms', min_length=0, max_length=100, message='权限标识长度不能超过100个字符') + def get_perms(self): + return self.perms + def validate_fields(self): + self.get_menu_name() + self.get_order_num() + self.get_path() + self.get_component() + self.get_menu_type() + self.get_perms() -class CrudMenuResponse(BaseModel): + +class MenuQueryModel(MenuModel): """ - 操作菜单响应模型 + 菜单管理不分页查询模型 """ - is_success: bool - message: str + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') class DeleteMenuModel(BaseModel): """ 删除菜单模型 """ - menu_ids: str + + menu_ids: str = Field(description='需要删除的菜单ID') diff --git a/dash-fastapi-backend/module_admin/entity/vo/notice_vo.py b/dash-fastapi-backend/module_admin/entity/vo/notice_vo.py index 7abb8c8ca0ebc071298f16c11d25af0653c38b9e..ed1f05b897582f92dafa3342c0fe85cadc1194f0 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/notice_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/notice_vo.py @@ -1,63 +1,58 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import NotBlank, Size, Xss +from typing import Literal, Optional class NoticeModel(BaseModel): """ 通知公告表对应pydantic模型 """ - notice_id: Optional[int] - notice_title: Optional[str] - notice_type: Optional[str] - notice_content: Optional[bytes] - status: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + notice_id: Optional[int] = Field(default=None, description='公告ID') + notice_title: Optional[str] = Field(default=None, description='公告标题') + notice_type: Optional[Literal['1', '2']] = Field(default=None, description='公告类型(1通知 2公告)') + notice_content: Optional[bytes] = Field(default=None, description='公告内容') + status: Optional[Literal['0', '1']] = Field(default=None, description='公告状态(0正常 1关闭)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @Xss(field_name='notice_title', message='公告标题不能包含脚本字符') + @NotBlank(field_name='notice_title', message='公告标题不能为空') + @Size(field_name='notice_title', min_length=0, max_length=50, message='公告标题不能超过50个字符') + def get_notice_title(self): + return self.notice_title + + def validate_fields(self): + self.get_notice_title() class NoticeQueryModel(NoticeModel): """ 通知公告管理不分页查询模型 """ - create_time_start: Optional[str] - create_time_end: Optional[str] - -class NoticePageObject(NoticeQueryModel): - """ - 通知公告管理分页查询模型 - """ - page_num: int - page_size: int + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class NoticePageObjectResponse(BaseModel): +class NoticePageQueryModel(NoticeQueryModel): """ - 通知公告管理列表分页查询返回模型 + 通知公告管理分页查询模型 """ - rows: List[Union[NoticeModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool - -class CrudNoticeResponse(BaseModel): - """ - 操作通知公告响应模型 - """ - is_success: bool - message: str + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteNoticeModel(BaseModel): """ 删除通知公告模型 """ - notice_ids: str + + notice_ids: str = Field(description='需要删除的公告ID') diff --git a/dash-fastapi-backend/module_admin/entity/vo/online_vo.py b/dash-fastapi-backend/module_admin/entity/vo/online_vo.py index eeef4d371d5c26fe88de1b802ccc0c29bdc01d6d..fd5c140addeffaa536110864fc867e3c00ab8837 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/online_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/online_vo.py @@ -1,50 +1,44 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +from datetime import datetime +from pydantic import BaseModel, Field +from typing import Optional class OnlineModel(BaseModel): """ 在线用户对应pydantic模型 """ - session_id: Optional[str] - user_name: Optional[str] - dept_name: Optional[str] - ipaddr: Optional[str] - login_location: Optional[str] - browser: Optional[str] - os: Optional[str] - login_time: Optional[str] - -class OnlinePageObject(OnlineModel): - """ - 在线用户分页查询模型 - """ - page_num: int - page_size: int + token_id: Optional[str] = Field(default=None, description='会话编号') + user_name: Optional[str] = Field(default=None, description='部门名称') + dept_name: Optional[str] = Field(default=None, description='用户名称') + ipaddr: Optional[str] = Field(default=None, description='登录IP地址') + login_location: Optional[str] = Field(default=None, description='登录地址') + browser: Optional[str] = Field(default=None, description='浏览器类型') + os: Optional[str] = Field(default=None, description='操作系统') + login_time: Optional[datetime] = Field(default=None, description='登录时间') -class OnlinePageObjectResponse(BaseModel): +class OnlineQueryModel(OnlineModel): """ - 在线用户列表分页查询返回模型 + 在线用户不分页查询模型 """ - rows: List[Union[OnlineModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class CrudOnlineResponse(BaseModel): + +class OnlinePageQueryModel(OnlineQueryModel): """ - 操作在线用户响应模型 + 在线用户分页查询模型 """ - is_success: bool - message: str + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeleteOnlineModel(BaseModel): """ 强退在线用户模型 """ - session_ids: str + + token_ids: str = Field(description='需要强退的会话编号') diff --git a/dash-fastapi-backend/module_admin/entity/vo/post_vo.py b/dash-fastapi-backend/module_admin/entity/vo/post_vo.py index a7a7abecc7990a26c527e0779039eecf8027fb9d..5e4d9fae51038614b349afb532f607a27b55c492 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/post_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/post_vo.py @@ -1,44 +1,68 @@ -from pydantic import BaseModel -from typing import Union, List -from module_admin.entity.vo.user_vo import PostModel +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic_validation_decorator import NotBlank, Size +from typing import Literal, Optional -class PostPageObject(PostModel): +class PostModel(BaseModel): """ - 岗位管理分页查询模型 + 岗位信息表对应pydantic模型 """ - page_num: int - page_size: int + model_config = ConfigDict(from_attributes=True) -class PostPageObjectResponse(BaseModel): - """ - 岗位管理列表分页查询返回模型 - """ - rows: List[Union[PostModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + post_id: Optional[int] = Field(default=None, description='岗位ID') + post_code: Optional[str] = Field(default=None, description='岗位编码') + post_name: Optional[str] = Field(default=None, description='岗位名称') + post_sort: Optional[int] = Field(default=None, description='显示顺序') + status: Optional[Literal['0', '1']] = Field(default=None, description='状态(0正常 1停用)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + + @NotBlank(field_name='post_code', message='岗位编码不能为空') + @Size(field_name='post_code', min_length=0, max_length=64, message='岗位编码长度不能超过64个字符') + def get_post_code(self): + return self.post_code + + @NotBlank(field_name='post_name', message='岗位名称不能为空') + @Size(field_name='post_name', min_length=0, max_length=50, message='岗位名称长度不能超过50个字符') + def get_post_name(self): + return self.post_name + + @NotBlank(field_name='post_sort', message='显示顺序不能为空') + def get_post_sort(self): + return self.post_sort + def validate_fields(self): + self.get_post_code() + self.get_post_name() + self.get_post_sort() -class PostSelectOptionResponseModel(BaseModel): + +class PostQueryModel(PostModel): """ 岗位管理不分页查询模型 """ - post: List[Union[PostModel, None]] + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class CrudPostResponse(BaseModel): +class PostPageQueryModel(PostQueryModel): """ - 操作岗位响应模型 + 岗位管理分页查询模型 """ - is_success: bool - message: str + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') class DeletePostModel(BaseModel): """ 删除岗位模型 """ - post_ids: str + + post_ids: str = Field(description='需要删除的岗位ID') diff --git a/dash-fastapi-backend/module_admin/entity/vo/role_vo.py b/dash-fastapi-backend/module_admin/entity/vo/role_vo.py index 0f94d43f91b07e326afb8b54dbbc8867a4290a3b..1c91517c1dfdecd82b22b201fb51493772300386 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/role_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/role_vo.py @@ -1,102 +1,149 @@ -from pydantic import BaseModel -from typing import Union, Optional, List -from module_admin.entity.vo.user_vo import RoleModel -from module_admin.entity.vo.dept_vo import DeptModel -from module_admin.entity.vo.menu_vo import MenuModel +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from pydantic_validation_decorator import NotBlank, Size +from typing import List, Literal, Optional, Union + + +class RoleModel(BaseModel): + """ + 角色表对应pydantic模型 + """ + + model_config = ConfigDict(from_attributes=True) + + role_id: Optional[int] = Field(default=None, description='角色ID') + role_name: Optional[str] = Field(default=None, description='角色名称') + role_key: Optional[str] = Field(default=None, description='角色权限字符串') + role_sort: Optional[int] = Field(default=None, description='显示顺序') + data_scope: Optional[Literal['1', '2', '3', '4', '5']] = Field( + default=None, + description='数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限)', + ) + menu_check_strictly: Optional[Union[int, bool]] = Field(default=None, description='菜单树选择项是否关联显示') + dept_check_strictly: Optional[Union[int, bool]] = Field(default=None, description='部门树选择项是否关联显示') + status: Optional[Literal['0', '1']] = Field(default=None, description='角色状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + admin: Optional[bool] = Field(default=False, description='是否为admin') + + @field_validator('menu_check_strictly', 'dept_check_strictly') + @classmethod + def check_filed_mapping(cls, v: Union[int, bool]) -> Union[int, bool]: + if v == 1: + v = True + elif v == 0: + v = False + elif v is True: + v = 1 + elif v is False: + v = 0 + return v + + @model_validator(mode='after') + def check_admin(self) -> 'RoleModel': + if self.role_id == 1: + self.admin = True + else: + self.admin = False + return self + + @NotBlank(field_name='role_name', message='角色名称不能为空') + @Size(field_name='role_name', min_length=0, max_length=30, message='角色名称长度不能超过30个字符') + def get_role_name(self): + return self.role_name + + @NotBlank(field_name='role_key', message='权限字符不能为空') + @Size(field_name='role_key', min_length=0, max_length=100, message='权限字符长度不能超过100个字符') + def get_role_key(self): + return self.role_key + + @NotBlank(field_name='role_sort', message='显示顺序不能为空') + def get_role_sort(self): + return self.role_sort + + def validate_fields(self): + self.get_role_name() + self.get_role_key() + self.get_role_sort() class RoleMenuModel(BaseModel): """ 角色和菜单关联表对应pydantic模型 """ - role_id: Optional[int] - menu_id: Optional[int] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + role_id: Optional[int] = Field(default=None, description='角色ID') + menu_id: Optional[int] = Field(default=None, description='菜单ID') class RoleDeptModel(BaseModel): """ 角色和部门关联表对应pydantic模型 """ - role_id: Optional[int] - dept_id: Optional[int] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + role_id: Optional[int] = Field(default=None, description='角色ID') + dept_id: Optional[int] = Field(default=None, description='部门ID') class RoleQueryModel(RoleModel): """ 角色管理不分页查询模型 """ - create_time_start: Optional[str] - create_time_end: Optional[str] + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class RolePageObject(RoleQueryModel): +class RolePageQueryModel(RoleQueryModel): """ 角色管理分页查询模型 """ - page_num: int - page_size: int - - -class RolePageObjectResponse(BaseModel): - """ - 角色管理列表分页查询返回模型 - """ - rows: List[Union[RoleModel, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') -class RoleSelectOptionResponseModel(BaseModel): + +class RoleMenuQueryModel(BaseModel): """ - 角色管理不分页查询模型 + 角色菜单查询模型 """ - role: List[Union[RoleModel, None]] - - -class CrudRoleResponse(BaseModel): + + menus: List = Field(default=[], description='菜单信息') + checked_keys: List[int] = Field(default=[], description='已选择的菜单ID信息') + + +class RoleDeptQueryModel(BaseModel): """ - 操作角色响应模型 + 角色部门查询模型 """ - is_success: bool - message: str - - + + depts: List = Field(default=[], description='部门信息') + checked_keys: List[int] = Field(default=[], description='已选择的部门ID信息') + + class AddRoleModel(RoleModel): """ 新增角色模型 """ - menu_id: Optional[str] - type: Optional[str] - -class RoleDataScopeModel(RoleModel): - """ - 角色数据权限模型 - """ - dept_id: Optional[str] + dept_ids: List = Field(default=[], description='部门ID信息') + menu_ids: List = Field(default=[], description='菜单ID信息') + type: Optional[str] = Field(default=None, description='操作类型') class DeleteRoleModel(BaseModel): """ 删除角色模型 """ - role_ids: str - update_by: Optional[str] - update_time: Optional[str] - - -class RoleDetailModel(BaseModel): - """ - 获取角色详情信息响应模型 - """ - role: Union[RoleModel, None] - menu: List[Union[MenuModel, None]] - dept: List[Union[DeptModel, None]] + + role_ids: str = Field(description='需要删除的菜单ID') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') diff --git a/dash-fastapi-backend/module_admin/entity/vo/server_vo.py b/dash-fastapi-backend/module_admin/entity/vo/server_vo.py index 36715968510aff174737cf82a97844bc62efb505..9a3e914bdabbafad91b745ad97341d0b6fb491fc 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/server_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/server_vo.py @@ -1,53 +1,54 @@ -from pydantic import BaseModel -from typing import Optional, List +from pydantic import BaseModel, Field +from typing import List, Optional class CpuInfo(BaseModel): - cpu_num: Optional[int] - used: Optional[str] - sys: Optional[str] - free: Optional[str] + cpu_num: Optional[int] = Field(default=None, description='核心数') + used: Optional[float] = Field(default=None, description='CPU用户使用率') + sys: Optional[float] = Field(default=None, description='CPU系统使用率') + free: Optional[float] = Field(default=None, description='CPU当前空闲率') class MemoryInfo(BaseModel): - total: Optional[str] - used: Optional[str] - free: Optional[str] - usage: Optional[str] + total: Optional[str] = Field(default=None, description='内存总量') + used: Optional[str] = Field(default=None, description='已用内存') + free: Optional[str] = Field(default=None, description='剩余内存') + usage: Optional[float] = Field(default=None, description='使用率') class SysInfo(BaseModel): - computer_ip: Optional[str] - computer_name: Optional[str] - os_arch: Optional[str] - os_name: Optional[str] + computer_ip: Optional[str] = Field(default=None, description='服务器IP') + computer_name: Optional[str] = Field(default=None, description='服务器名称') + os_arch: Optional[str] = Field(default=None, description='系统架构') + os_name: Optional[str] = Field(default=None, description='操作系统') + user_dir: Optional[str] = Field(default=None, description='项目路径') -class PyInfo(BaseModel): - name: Optional[str] - version: Optional[str] - start_time: Optional[str] - run_time: Optional[str] - home: Optional[str] - project_dir: Optional[str] +class PyInfo(MemoryInfo): + name: Optional[str] = Field(default=None, description='Python名称') + version: Optional[str] = Field(default=None, description='Python版本') + start_time: Optional[str] = Field(default=None, description='启动时间') + run_time: Optional[str] = Field(default=None, description='运行时长') + home: Optional[str] = Field(default=None, description='安装路径') class SysFiles(BaseModel): - dir_name: Optional[str] - sys_type_name: Optional[str] - disk_name: Optional[str] - total: Optional[str] - used: Optional[str] - free: Optional[str] - usage: Optional[str] + dir_name: Optional[str] = Field(default=None, description='盘符路径') + sys_type_name: Optional[str] = Field(default=None, description='盘符类型') + type_name: Optional[str] = Field(default=None, description='文件类型') + total: Optional[str] = Field(default=None, description='总大小') + used: Optional[str] = Field(default=None, description='已经使用量') + free: Optional[str] = Field(default=None, description='剩余大小') + usage: Optional[str] = Field(default=None, description='资源的使用率') class ServerMonitorModel(BaseModel): """ 服务监控对应pydantic模型 """ - cpu: Optional[CpuInfo] - py: Optional[PyInfo] - mem: Optional[MemoryInfo] - sys: Optional[SysInfo] - sys_files: Optional[List[SysFiles]] + + cpu: Optional[CpuInfo] = Field(description='CPU相关信息') + py: Optional[PyInfo] = Field(description='Python相关信息') + mem: Optional[MemoryInfo] = Field(description='內存相关信息') + sys: Optional[SysInfo] = Field(description='服务器相关信息') + sys_files: Optional[List[SysFiles]] = Field(description='磁盘相关信息') diff --git a/dash-fastapi-backend/module_admin/entity/vo/user_vo.py b/dash-fastapi-backend/module_admin/entity/vo/user_vo.py index ba79c0d54c732a4024968804fac5bfb599ce8472..b0284e9362ae6622040abff04d3b92c569caabe6 100644 --- a/dash-fastapi-backend/module_admin/entity/vo/user_vo.py +++ b/dash-fastapi-backend/module_admin/entity/vo/user_vo.py @@ -1,289 +1,263 @@ -from pydantic import BaseModel -from typing import Union, Optional, List +import re +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic_validation_decorator import Network, NotBlank, Size, Xss +from typing import List, Literal, Optional, Union +from exceptions.exception import ModelValidatorException +from module_admin.entity.vo.dept_vo import DeptModel +from module_admin.entity.vo.post_vo import PostModel +from module_admin.entity.vo.role_vo import RoleModel class TokenData(BaseModel): """ token解析结果 """ - user_id: Union[int, None] = None + + user_id: Union[int, None] = Field(default=None, description='用户ID') class UserModel(BaseModel): """ 用户表对应pydantic模型 """ - user_id: Optional[int] - dept_id: Optional[int] - user_name: Optional[str] - nick_name: Optional[str] - user_type: Optional[str] - email: Optional[str] - phonenumber: Optional[str] - sex: Optional[str] - avatar: Optional[str] - password: Optional[str] - status: Optional[str] - del_flag: Optional[str] - login_ip: Optional[str] - login_date: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - - class Config: - orm_mode = True + + model_config = ConfigDict(from_attributes=True) + + user_id: Optional[int] = Field(default=None, description='用户ID') + dept_id: Optional[int] = Field(default=None, description='部门ID') + user_name: Optional[str] = Field(default=None, description='用户账号') + nick_name: Optional[str] = Field(default=None, description='用户昵称') + user_type: Optional[str] = Field(default=None, description='用户类型(00系统用户)') + email: Optional[str] = Field(default=None, description='用户邮箱') + phonenumber: Optional[str] = Field(default=None, description='手机号码') + sex: Optional[Literal['0', '1', '2']] = Field(default=None, description='用户性别(0男 1女 2未知)') + avatar: Optional[str] = Field(default=None, description='头像地址') + password: Optional[str] = Field(default=None, description='密码') + status: Optional[Literal['0', '1']] = Field(default=None, description='帐号状态(0正常 1停用)') + del_flag: Optional[Literal['0', '2']] = Field(default=None, description='删除标志(0代表存在 2代表删除)') + login_ip: Optional[str] = Field(default=None, description='最后登录IP') + login_date: Optional[datetime] = Field(default=None, description='最后登录时间') + create_by: Optional[str] = Field(default=None, description='创建者') + create_time: Optional[datetime] = Field(default=None, description='创建时间') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') + remark: Optional[str] = Field(default=None, description='备注') + admin: Optional[bool] = Field(default=False, description='是否为admin') + + @model_validator(mode='after') + def check_password(self) -> 'UserModel': + pattern = r"""^[^<>"'|\\]+$""" + if self.password is None or re.match(pattern, self.password): + return self + else: + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') + + @model_validator(mode='after') + def check_admin(self) -> 'UserModel': + if self.user_id == 1: + self.admin = True + else: + self.admin = False + return self + + @Xss(field_name='user_name', message='用户账号不能包含脚本字符') + @NotBlank(field_name='user_name', message='用户账号不能为空') + @Size(field_name='user_name', min_length=0, max_length=30, message='用户账号长度不能超过30个字符') + def get_user_name(self): + return self.user_name + + @Xss(field_name='nick_name', message='用户昵称不能包含脚本字符') + @Size(field_name='nick_name', min_length=0, max_length=30, message='用户昵称长度不能超过30个字符') + def get_nick_name(self): + return self.nick_name + + @Network(field_name='email', field_type='EmailStr', message='邮箱格式不正确') + @Size(field_name='email', min_length=0, max_length=50, message='邮箱长度不能超过50个字符') + def get_email(self): + return self.email + + @Size(field_name='phonenumber', min_length=0, max_length=11, message='手机号码长度不能超过11个字符') + def get_phonenumber(self): + return self.phonenumber + + def validate_fields(self): + self.get_user_name() + self.get_nick_name() + self.get_email() + self.get_phonenumber() class UserRoleModel(BaseModel): """ 用户和角色关联表对应pydantic模型 """ - user_id: Optional[int] - role_id: Optional[int] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + + user_id: Optional[int] = Field(default=None, description='用户ID') + role_id: Optional[int] = Field(default=None, description='角色ID') class UserPostModel(BaseModel): """ 用户与岗位关联表对应pydantic模型 """ - user_id: Optional[int] - post_id: Optional[int] - - class Config: - orm_mode = True - - -class DeptModel(BaseModel): - """ - 部门表对应pydantic模型 - """ - dept_id: Optional[int] - parent_id: Optional[int] - ancestors: Optional[str] - dept_name: Optional[str] - order_num: Optional[int] - leader: Optional[str] - phone: Optional[str] - email: Optional[str] - status: Optional[str] - del_flag: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - - class Config: - orm_mode = True - - -class RoleModel(BaseModel): - """ - 角色表对应pydantic模型 - """ - role_id: Optional[int] - role_name: Optional[str] - role_key: Optional[str] - role_sort: Optional[int] - data_scope: Optional[str] - menu_check_strictly: Optional[int] - dept_check_strictly: Optional[int] - status: Optional[str] - del_flag: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) + user_id: Optional[int] = Field(default=None, description='用户ID') + post_id: Optional[int] = Field(default=None, description='岗位ID') -class PostModel(BaseModel): - """ - 岗位信息表对应pydantic模型 - """ - post_id: Optional[int] - post_code: Optional[str] - post_name: Optional[str] - post_sort: Optional[int] - status: Optional[str] - create_by: Optional[str] - create_time: Optional[str] - update_by: Optional[str] - update_time: Optional[str] - remark: Optional[str] - class Config: - orm_mode = True +class UserInfoModel(UserModel): + post_ids: Optional[Union[str, None]] = Field(default=None, description='岗位ID信息') + role_ids: Optional[Union[str, None]] = Field(default=None, description='角色ID信息') + dept: Optional[Union[DeptModel, None]] = Field(default=None, description='部门信息') + role: Optional[List[Union[RoleModel, None]]] = Field(default=[], description='角色信息') -class CurrentUserInfo(BaseModel): - """ - 数据库返回当前用户信息 - """ - user_basic_info: Union[UserModel, None] - user_dept_info: Union[DeptModel, None] - user_role_info: List[Union[RoleModel, None]] - user_post_info: List[Union[PostModel, None]] - user_menu_info: Union[List, None] +class CurrentUserModel(BaseModel): + permissions: List = Field(description='权限信息') + roles: List = Field(description='角色信息') + user: Union[UserInfoModel, None] = Field(description='用户信息') class UserDetailModel(BaseModel): """ 获取用户详情信息响应模型 """ - user: Union[UserModel, None] - dept: Union[DeptModel, None] - role: List[Union[RoleModel, None]] - post: List[Union[PostModel, None]] + + data: Optional[Union[UserInfoModel, None]] = Field(default=None, description='用户信息') + post_ids: Optional[List] = Field(default=None, description='岗位ID信息') + posts: List[Union[PostModel, None]] = Field(description='岗位信息') + role_ids: Optional[List] = Field(default=None, description='角色ID信息') + roles: List[Union[RoleModel, None]] = Field(description='角色信息') -class CurrentUserInfoServiceResponse(UserDetailModel): +class UserProfileModel(BaseModel): """ - 获取当前用户信息响应模型 + 获取个人信息响应模型 """ - menu: Union[List, None] + + data: Union[UserInfoModel, None] = Field(description='用户信息') + post_group: Union[str, None] = Field(description='岗位信息') + role_group: Union[str, None] = Field(description='角色信息') class UserQueryModel(UserModel): """ 用户管理不分页查询模型 """ - create_time_start: Optional[str] - create_time_end: Optional[str] + + begin_time: Optional[str] = Field(default=None, description='开始时间') + end_time: Optional[str] = Field(default=None, description='结束时间') -class UserPageObject(UserQueryModel): +class UserPageQueryModel(UserQueryModel): """ 用户管理分页查询模型 """ - page_num: int - page_size: int + + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') -class UserInfoJoinDept(UserModel): +class AddUserModel(UserModel): """ - 数据库查询用户列表返回模型 + 新增用户模型 """ - dept_name: Optional[str] + + role_ids: Optional[List] = Field(default=[], description='角色ID信息') + post_ids: Optional[List] = Field(default=[], description='岗位ID信息') + type: Optional[str] = Field(default=None, description='操作类型') -class UserPageObjectResponse(BaseModel): +class EditUserModel(AddUserModel): """ - 用户管理列表分页查询返回模型 + 编辑用户模型 """ - rows: List[Union[UserInfoJoinDept, None]] = [] - page_num: int - page_size: int - total: int - has_next: bool + role: Optional[List] = Field(default=[], description='角色信息') -class AddUserModel(UserModel): + +class ResetPasswordModel(BaseModel): """ - 新增用户模型 + 重置密码模型 """ - role_id: Optional[str] - post_id: Optional[str] - type: Optional[str] + + old_password: Optional[str] = Field(default=None, description='旧密码') + new_password: Optional[str] = Field(default=None, description='新密码') + + @model_validator(mode='after') + def check_new_password(self) -> 'ResetPasswordModel': + pattern = r"""^[^<>"'|\\]+$""" + if self.new_password is None or re.match(pattern, self.new_password): + return self + else: + raise ModelValidatorException(message='密码不能包含非法字符:< > " \' \\ |') class ResetUserModel(UserModel): """ 重置用户密码模型 """ - old_password: Optional[str] - sms_code: Optional[str] - session_id: Optional[str] + + old_password: Optional[str] = Field(default=None, description='旧密码') + sms_code: Optional[str] = Field(default=None, description='验证码') + session_id: Optional[str] = Field(default=None, description='会话id') class DeleteUserModel(BaseModel): """ 删除用户模型 """ - user_ids: str - update_by: Optional[str] - update_time: Optional[str] + user_ids: str = Field(description='需要删除的用户ID') + update_by: Optional[str] = Field(default=None, description='更新者') + update_time: Optional[datetime] = Field(default=None, description='更新时间') -class UserRoleQueryModel(UserRoleModel): + +class UserRoleQueryModel(UserModel): """ 用户角色关联管理不分页查询模型 """ - user_name: Optional[str] - phonenumber: Optional[str] - role_name: Optional[str] - role_key: Optional[str] + + role_id: Optional[int] = Field(default=None, description='角色ID') -class UserRolePageObject(UserRoleQueryModel): +class UserRolePageQueryModel(UserRoleQueryModel): """ 用户角色关联管理分页查询模型 """ - page_num: int - page_size: int - -class UserRolePageObjectResponse(BaseModel): - """ - 用户角色关联管理列表分页查询返回模型 - """ - rows: List = [] - page_num: int - page_size: int - total: int - has_next: bool + page_num: int = Field(default=1, description='当前页码') + page_size: int = Field(default=10, description='每页记录数') -class CrudUserRoleModel(BaseModel): +class SelectedRoleModel(RoleModel): """ - 新增、删除用户关联角色及角色关联用户模型 + 是否选择角色模型 """ - user_ids: Optional[str] - role_ids: Optional[str] - -class ImportUserModel(BaseModel): - """ - 批量导入用户模型 - """ - url: str - is_update: bool + flag: Optional[bool] = Field(default=False, description='选择标识') -class CrudUserResponse(BaseModel): +class UserRoleResponseModel(BaseModel): """ - 操作用户响应模型 + 用户角色关联管理列表返回模型 """ - is_success: bool - message: str - -class DeptInfo(BaseModel): - """ - 查询部门树 - """ - dept_id: int - dept_name: str - ancestors: str + roles: List[Union[SelectedRoleModel, None]] = Field(default=[], description='角色信息') + user: UserInfoModel = Field(description='用户信息') -class RoleInfo(BaseModel): +class CrudUserRoleModel(BaseModel): """ - 用户角色信息 + 新增、删除用户关联角色及角色关联用户模型 """ - role_info: Union[List, None] - -class MenuList(BaseModel): - """ - 用户菜单信息 - """ - menu_info: Union[List, None] + user_id: Optional[int] = Field(default=None, description='用户ID') + user_ids: Optional[str] = Field(default=None, description='用户ID信息') + role_id: Optional[int] = Field(default=None, description='角色ID') + role_ids: Optional[str] = Field(default=None, description='角色ID信息') diff --git a/dash-fastapi-backend/module_admin/service/cache_service.py b/dash-fastapi-backend/module_admin/service/cache_service.py index 1b0f92bfed50e7a57c8377ff67684e03d7af543d..ccecf6c9b7358afa14d5da4df395f79713eda41b 100644 --- a/dash-fastapi-backend/module_admin/service/cache_service.py +++ b/dash-fastapi-backend/module_admin/service/cache_service.py @@ -1,7 +1,8 @@ from fastapi import Request -from module_admin.entity.vo.cache_vo import * -from config.env import RedisInitKeyConfig +from config.enums import RedisInitKeyConfig from config.get_redis import RedisUtil +from module_admin.entity.vo.cache_vo import CacheInfoModel, CacheMonitorModel +from module_admin.entity.vo.common_vo import CrudResponseModel class CacheService: @@ -13,35 +14,37 @@ class CacheService: async def get_cache_monitor_statistical_info_services(cls, request: Request): """ 获取缓存监控信息service + :param request: Request对象 :return: 缓存监控信息 """ info = await request.app.state.redis.info() db_size = await request.app.state.redis.dbsize() command_stats_dict = await request.app.state.redis.info('commandstats') - command_stats = [dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in - command_stats_dict.items()] - result = dict(command_stats=command_stats, db_size=db_size, info=info) + command_stats = [ + dict(name=key.split('_')[1], value=str(value.get('calls'))) for key, value in command_stats_dict.items() + ] + result = CacheMonitorModel(command_stats=command_stats, db_size=db_size, info=info) - return CacheMonitorModel(**result) + return result @classmethod - def get_cache_monitor_cache_name_services(cls): + async def get_cache_monitor_cache_name_services(cls): """ 获取缓存名称列表信息service + :return: 缓存名称列表信息 """ name_list = [] - for attr_name in dir(RedisInitKeyConfig): - if not attr_name.startswith('__') and isinstance(getattr(RedisInitKeyConfig, attr_name), dict): - name_list.append( - CacheInfoModel( - cache_key="", - cache_name=getattr(RedisInitKeyConfig, attr_name).get('key'), - cache_value="", - remark=getattr(RedisInitKeyConfig, attr_name).get('remark') - ) + for key_config in RedisInitKeyConfig: + name_list.append( + CacheInfoModel( + cache_key='', + cache_name=key_config.key, + cache_value='', + remark=key_config.remark, ) + ) return name_list @@ -49,12 +52,13 @@ class CacheService: async def get_cache_monitor_cache_key_services(cls, request: Request, cache_name: str): """ 获取缓存键名列表信息service + :param request: Request对象 :param cache_name: 缓存名称 :return: 缓存键名列表信息 """ - cache_keys = await request.app.state.redis.keys(f"{cache_name}*") - cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f"{cache_name}:")] + cache_keys = await request.app.state.redis.keys(f'{cache_name}*') + cache_key_list = [key.split(':', 1)[1] for key in cache_keys if key.startswith(f'{cache_name}:')] return cache_key_list @@ -62,48 +66,51 @@ class CacheService: async def get_cache_monitor_cache_value_services(cls, request: Request, cache_name: str, cache_key: str): """ 获取缓存内容信息service + :param request: Request对象 :param cache_name: 缓存名称 :param cache_key: 缓存键名 :return: 缓存内容信息 """ - cache_value = await request.app.state.redis.get(f"{cache_name}:{cache_key}") + cache_value = await request.app.state.redis.get(f'{cache_name}:{cache_key}') - return CacheInfoModel(cache_key=cache_key, cache_name=cache_name, cache_value=cache_value, remark="") + return CacheInfoModel(cache_key=cache_key, cache_name=cache_name, cache_value=cache_value, remark='') @classmethod async def clear_cache_monitor_cache_name_services(cls, request: Request, cache_name: str): """ 清除缓存名称对应所有键值service + :param request: Request对象 :param cache_name: 缓存名称 :return: 操作缓存响应信息 """ - cache_keys = await request.app.state.redis.keys(f"{cache_name}*") + cache_keys = await request.app.state.redis.keys(f'{cache_name}*') if cache_keys: await request.app.state.redis.delete(*cache_keys) - result = dict(is_success=True, message=f"{cache_name}对应键值清除成功") - return CrudCacheResponse(**result) + return CrudResponseModel(is_success=True, message=f'{cache_name}对应键值清除成功') @classmethod - async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_name: str, cache_key: str): + async def clear_cache_monitor_cache_key_services(cls, request: Request, cache_key: str): """ 清除缓存名称对应所有键值service + :param request: Request对象 - :param cache_name: 缓存名称 :param cache_key: 缓存键名 :return: 操作缓存响应信息 """ - await request.app.state.redis.delete(f"{cache_name}:{cache_key}") - result = dict(is_success=True, message=f"{cache_name}:{cache_key}清除成功") + cache_keys = await request.app.state.redis.keys(f'*{cache_key}') + if cache_keys: + await request.app.state.redis.delete(*cache_keys) - return CrudCacheResponse(**result) + return CrudResponseModel(is_success=True, message=f'{cache_key}清除成功') @classmethod async def clear_cache_monitor_all_services(cls, request: Request): """ 清除所有缓存service + :param request: Request对象 :return: 操作缓存响应信息 """ @@ -111,8 +118,7 @@ class CacheService: if cache_keys: await request.app.state.redis.delete(*cache_keys) - result = dict(is_success=True, message="所有缓存清除成功") await RedisUtil.init_sys_dict(request.app.state.redis) await RedisUtil.init_sys_config(request.app.state.redis) - return CrudCacheResponse(**result) + return CrudResponseModel(is_success=True, message='所有缓存清除成功') diff --git a/dash-fastapi-backend/module_admin/service/captcha_service.py b/dash-fastapi-backend/module_admin/service/captcha_service.py index bcad42c92886d5c44dac19a1a1a190d32c2680ff..1be8ffb4ea6d491a4735f252bbc8a098b41aa001 100644 --- a/dash-fastapi-backend/module_admin/service/captcha_service.py +++ b/dash-fastapi-backend/module_admin/service/captcha_service.py @@ -1,8 +1,8 @@ -from PIL import Image, ImageDraw, ImageFont +import base64 import io import os import random -import base64 +from PIL import Image, ImageDraw, ImageFont class CaptchaService: @@ -11,15 +11,15 @@ class CaptchaService: """ @classmethod - def create_captcha_image_service(cls): + async def create_captcha_image_service(cls): # 创建空白图像 - image = Image.new('RGB', (400, 300), color='#EAEAEA') + image = Image.new('RGB', (160, 60), color='#EAEAEA') # 创建绘图对象 draw = ImageDraw.Draw(image) # 设置字体 - font = ImageFont.truetype(os.path.join(os.path.abspath(os.getcwd()), 'assets', 'font', 'Arial.ttf'), size=100) + font = ImageFont.truetype(os.path.join(os.path.abspath(os.getcwd()), 'assets', 'font', 'Arial.ttf'), size=30) # 生成两个0-9之间的随机整数 num1 = random.randint(0, 9) @@ -35,14 +35,14 @@ class CaptchaService: else: result = num1 * num2 # 绘制文本 - text = f"{num1} {operational_character} {num2} = ?" - draw.text((10, 120), text, fill='blue', font=font) + text = f'{num1} {operational_character} {num2} = ?' + draw.text((25, 15), text, fill='blue', font=font) # 将图像数据保存到内存中 buffer = io.BytesIO() image.save(buffer, format='PNG') # 将图像数据转换为base64字符串 - base64_string = f'data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode()}' + base64_string = base64.b64encode(buffer.getvalue()).decode() return [base64_string, result] diff --git a/dash-fastapi-backend/module_admin/service/common_service.py b/dash-fastapi-backend/module_admin/service/common_service.py index 447f28add007cc13551c7f6dbe5b8d71e099c736..e0c325a38292e3fe684d82adfe80635c637905e0 100644 --- a/dash-fastapi-backend/module_admin/service/common_service.py +++ b/dash-fastapi-backend/module_admin/service/common_service.py @@ -1,5 +1,10 @@ import os -from fastapi import UploadFile +from datetime import datetime +from fastapi import BackgroundTasks, Request, UploadFile +from config.env import UploadConfig +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel, UploadResponseModel +from utils.upload_util import UploadUtil class CommonService: @@ -8,11 +13,79 @@ class CommonService: """ @classmethod - def upload_service(cls, path: str, task_path: str, upload_id: str, file: UploadFile): + async def upload_service(cls, request: Request, file: UploadFile): + """ + 通用上传service - filepath = os.path.join(path, task_path, upload_id, f'{file.filename}') - with open(filepath, 'wb') as f: - # 流式写出大型文件,这里的10代表10MB - for chunk in iter(lambda: file.file.read(1024 * 1024 * 10), b''): - f.write(chunk) + :param request: Request对象 + :param file: 上传文件对象 + :return: 上传结果 + """ + if not UploadUtil.check_file_extension(file): + raise ServiceException(message='文件类型不合法') + else: + relative_path = f'upload/{datetime.now().strftime("%Y")}/{datetime.now().strftime("%m")}/{datetime.now().strftime("%d")}' + dir_path = os.path.join(UploadConfig.UPLOAD_PATH, relative_path) + try: + os.makedirs(dir_path) + except FileExistsError: + pass + filename = f'{file.filename.rsplit(".", 1)[0]}_{datetime.now().strftime("%Y%m%d%H%M%S")}{UploadConfig.UPLOAD_MACHINE}{UploadUtil.generate_random_number()}.{file.filename.rsplit(".")[-1]}' + filepath = os.path.join(dir_path, filename) + with open(filepath, 'wb') as f: + # 流式写出大型文件,这里的10代表10MB + for chunk in iter(lambda: file.file.read(1024 * 1024 * 10), b''): + f.write(chunk) + return CrudResponseModel( + is_success=True, + result=UploadResponseModel( + file_name=f'{UploadConfig.UPLOAD_PREFIX}/{relative_path}/{filename}', + new_file_name=filename, + original_filename=file.filename, + url=f'{request.base_url}{UploadConfig.UPLOAD_PREFIX[1:]}/{relative_path}/{filename}', + ), + message='上传成功', + ) + + @classmethod + async def download_services(cls, background_tasks: BackgroundTasks, file_name, delete: bool): + """ + 下载下载目录文件service + + :param background_tasks: 后台任务对象 + :param file_name: 下载的文件名称 + :param delete: 是否在下载完成后删除文件 + :return: 上传结果 + """ + filepath = os.path.join(UploadConfig.DOWNLOAD_PATH, file_name) + if '..' in file_name: + raise ServiceException(message='文件名称不合法') + elif not UploadUtil.check_file_exists(filepath): + raise ServiceException(message='文件不存在') + else: + if delete: + background_tasks.add_task(UploadUtil.delete_file, filepath) + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') + + @classmethod + async def download_resource_services(cls, resource: str): + """ + 下载上传目录文件service + + :param resource: 下载的文件名称 + :return: 上传结果 + """ + filepath = os.path.join(resource.replace(UploadConfig.UPLOAD_PREFIX, UploadConfig.UPLOAD_PATH)) + filename = resource.rsplit('/', 1)[-1] + if ( + '..' in filename + or not UploadUtil.check_file_timestamp(filename) + or not UploadUtil.check_file_machine(filename) + or not UploadUtil.check_file_random_code(filename) + ): + raise ServiceException(message='文件名称不合法') + elif not UploadUtil.check_file_exists(filepath): + raise ServiceException(message='文件不存在') + else: + return CrudResponseModel(is_success=True, result=UploadUtil.generate_file(filepath), message='下载成功') diff --git a/dash-fastapi-backend/module_admin/service/config_service.py b/dash-fastapi-backend/module_admin/service/config_service.py index 3e9674a48db8fefe25080e8bebcfd4f5656b4e07..16d0c35e4b7c79eabdfa00917b5a0c36ea531423 100644 --- a/dash-fastapi-backend/module_admin/service/config_service.py +++ b/dash-fastapi-backend/module_admin/service/config_service.py @@ -1,8 +1,13 @@ from fastapi import Request -from config.env import RedisInitKeyConfig -from module_admin.entity.vo.config_vo import * -from module_admin.dao.config_dao import * -from utils.common_util import export_list2excel +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from config.enums import RedisInitKeyConfig +from exceptions.exception import ServiceException +from module_admin.dao.config_dao import ConfigDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.config_vo import ConfigModel, ConfigPageQueryModel, DeleteConfigModel +from utils.common_util import export_list2excel, SqlalchemyUtil class ConfigService: @@ -11,180 +16,220 @@ class ConfigService: """ @classmethod - def get_config_list_services(cls, result_db: Session, query_object: ConfigQueryModel): + async def get_config_list_services( + cls, query_db: AsyncSession, query_object: ConfigPageQueryModel, is_page: bool = False + ): """ 获取参数配置列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 参数配置列表信息对象 """ - config_list_result = ConfigDao.get_config_list(result_db, query_object) + config_list_result = await ConfigDao.get_config_list(query_db, query_object, is_page) return config_list_result @classmethod - async def init_cache_sys_config_services(cls, result_db: Session, redis): + async def init_cache_sys_config_services(cls, query_db: AsyncSession, redis): """ 应用初始化:获取所有参数配置对应的键值对信息并缓存service - :param result_db: orm对象 + + :param query_db: orm对象 :param redis: redis对象 :return: """ # 获取以sys_config:开头的键列表 - keys = await redis.keys(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:*") + keys = await redis.keys(f'{RedisInitKeyConfig.SYS_CONFIG.key}:*') # 删除匹配的键 if keys: await redis.delete(*keys) - config_all = ConfigDao.get_config_list(result_db, ConfigQueryModel(**dict())) + config_all = await ConfigDao.get_config_list(query_db, ConfigPageQueryModel(**dict()), is_page=False) for config_obj in config_all: - if config_obj.config_type == 'Y': - await redis.set(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:{config_obj.config_key}", config_obj.config_value) + await redis.set( + f"{RedisInitKeyConfig.SYS_CONFIG.key}:{config_obj.get('config_key')}", + config_obj.get('config_value'), + ) @classmethod async def query_config_list_from_cache_services(cls, redis, config_key: str): """ 从缓存获取参数键名对应值service + :param redis: redis对象 :param config_key: 参数键名 :return: 参数键名对应值 """ - result = await redis.get(f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:{config_key}") + result = await redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_key}') return result @classmethod - async def add_config_services(cls, request: Request, result_db: Session, page_object: ConfigModel): + async def check_config_key_unique_services(cls, query_db: AsyncSession, page_object: ConfigModel): + """ + 校验参数键名是否唯一service + + :param query_db: orm对象 + :param page_object: 参数配置对象 + :return: 校验结果 + """ + config_id = -1 if page_object.config_id is None else page_object.config_id + config = await ConfigDao.get_config_detail_by_info(query_db, ConfigModel(config_key=page_object.config_key)) + if config and config.config_id != config_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): """ 新增参数配置信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 新增参数配置对象 :return: 新增参数配置校验结果 """ - config = ConfigDao.get_config_detail_by_info(result_db, ConfigModel(**dict(config_key=page_object.config_key))) - if config: - result = dict(is_success=False, message='参数键名已存在') + if not await cls.check_config_key_unique_services(query_db, page_object): + raise ServiceException(message=f'新增参数{page_object.config_name}失败,参数键名已存在') else: try: - ConfigDao.add_config_dao(result_db, page_object) - result_db.commit() - await cls.init_cache_sys_config_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='新增成功') + await ConfigDao.add_config_dao(query_db, page_object) + await query_db.commit() + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + ) + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudConfigResponse(**result) + await query_db.rollback() + raise e @classmethod - async def edit_config_services(cls, request: Request, result_db: Session, page_object: ConfigModel): + async def edit_config_services(cls, request: Request, query_db: AsyncSession, page_object: ConfigModel): """ 编辑参数配置信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 编辑参数配置对象 :return: 编辑参数配置校验结果 """ - edit_config = page_object.dict(exclude_unset=True) - config_info = cls.detail_config_services(result_db, edit_config.get('config_id')) - if config_info: - if config_info.config_key != page_object.config_key or config_info.config_value != page_object.config_value: - config = ConfigDao.get_config_detail_by_info(result_db, page_object) - if config: - result = dict(is_success=False, message='参数配置已存在') - return CrudConfigResponse(**result) - try: - ConfigDao.edit_config_dao(result_db, edit_config) - result_db.commit() - await cls.init_cache_sys_config_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + edit_config = page_object.model_dump(exclude_unset=True) + config_info = await cls.config_detail_services(query_db, page_object.config_id) + if config_info.config_id: + if not await cls.check_config_key_unique_services(query_db, page_object): + raise ServiceException(message=f'修改参数{page_object.config_name}失败,参数键名已存在') + else: + try: + await ConfigDao.edit_config_dao(query_db, edit_config) + await query_db.commit() + if config_info.config_key != page_object.config_key: + await request.app.state.redis.delete( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}' + ) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_CONFIG.key}:{page_object.config_key}', page_object.config_value + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='参数配置不存在') - - return CrudConfigResponse(**result) + raise ServiceException(message='参数配置不存在') @classmethod - async def delete_config_services(cls, request: Request, result_db: Session, page_object: DeleteConfigModel): + async def delete_config_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteConfigModel): """ 删除参数配置信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 删除参数配置对象 :return: 删除参数配置校验结果 """ - if page_object.config_ids.split(','): + if page_object.config_ids: config_id_list = page_object.config_ids.split(',') try: + delete_config_key_list = [] for config_id in config_id_list: - config_id_dict = dict(config_id=config_id) - ConfigDao.delete_config_dao(result_db, ConfigModel(**config_id_dict)) - result_db.commit() - await cls.init_cache_sys_config_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='删除成功') + config_info = await cls.config_detail_services(query_db, int(config_id)) + if config_info.config_type == CommonConstant.YES: + raise ServiceException(message=f'内置参数{config_info.config_key}不能删除') + else: + await ConfigDao.delete_config_dao(query_db, ConfigModel(config_id=int(config_id))) + delete_config_key_list.append(f'{RedisInitKeyConfig.SYS_CONFIG.key}:{config_info.config_key}') + await query_db.commit() + if delete_config_key_list: + await request.app.state.redis.delete(*delete_config_key_list) + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入字典数据id为空') - return CrudConfigResponse(**result) + raise ServiceException(message='传入参数配置id为空') @classmethod - def detail_config_services(cls, result_db: Session, config_id: int): + async def config_detail_services(cls, query_db: AsyncSession, config_id: int): """ 获取参数配置详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param config_id: 参数配置id :return: 参数配置id对应的信息 """ - config = ConfigDao.get_config_detail_by_id(result_db, config_id=config_id) + config = await ConfigDao.get_config_detail_by_id(query_db, config_id=config_id) + if config: + result = ConfigModel(**SqlalchemyUtil.serialize_result(config)) + else: + result = ConfigModel(**dict()) - return config + return result @staticmethod - def export_config_list_services(config_list: List): + async def export_config_list_services(config_list: List): """ 导出参数配置信息service + :param config_list: 参数配置信息列表 :return: 参数配置信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "config_id": "参数主键", - "config_name": "参数名称", - "config_key": "参数键名", - "config_value": "参数键值", - "config_type": "系统内置", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'config_id': '参数主键', + 'config_name': '参数名称', + 'config_key': '参数键名', + 'config_value': '参数键值', + 'config_type': '系统内置', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } - data = [ConfigModel(**vars(row)).dict() for row in config_list] + data = config_list for item in data: if item.get('config_type') == 'Y': item['config_type'] = '是' else: item['config_type'] = '否' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @classmethod - async def refresh_sys_config_services(cls, request: Request, result_db: Session): + async def refresh_sys_config_services(cls, request: Request, query_db: AsyncSession): """ 刷新字典缓存信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :return: 刷新字典缓存校验结果 """ - await cls.init_cache_sys_config_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='刷新成功') + await cls.init_cache_sys_config_services(query_db, request.app.state.redis) - return CrudConfigResponse(**result) + return CrudResponseModel(is_success=True, message='刷新成功') diff --git a/dash-fastapi-backend/module_admin/service/dept_service.py b/dash-fastapi-backend/module_admin/service/dept_service.py index 0be3d4a4a123a45fe1bed07d31273ad64a5e43a7..31440802f0ef941bc11e1ffd87d86fa5982d6782 100644 --- a/dash-fastapi-backend/module_admin/service/dept_service.py +++ b/dash-fastapi-backend/module_admin/service/dept_service.py @@ -1,5 +1,10 @@ -from module_admin.entity.vo.dept_vo import * -from module_admin.dao.dept_dao import * +from sqlalchemy.ext.asyncio import AsyncSession +from config.constant import CommonConstant +from exceptions.exception import ServiceException, ServiceWarning +from module_admin.dao.dept_dao import DeptDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.dept_vo import DeleteDeptModel, DeptModel +from utils.common_util import SqlalchemyUtil class DeptService: @@ -8,164 +13,203 @@ class DeptService: """ @classmethod - def get_dept_tree_services(cls, result_db: Session, page_object: DeptModel, data_scope_sql: str): + async def get_dept_tree_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): """ 获取部门树信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门树信息对象 """ - dept_list_result = DeptDao.get_dept_list_for_tree(result_db, page_object, data_scope_sql) + dept_list_result = await DeptDao.get_dept_list_for_tree(query_db, page_object, data_scope_sql) dept_tree_result = cls.list_to_tree(dept_list_result) return dept_tree_result @classmethod - def get_dept_tree_for_edit_option_services(cls, result_db: Session, page_object: DeptModel, data_scope_sql: str): + async def get_dept_for_edit_option_services( + cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str + ): """ 获取部门编辑部门树信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门树信息对象 """ - dept_list_result = DeptDao.get_dept_info_for_edit_option(result_db, page_object, data_scope_sql) - dept_tree_result = cls.list_to_tree(dept_list_result) + dept_list_result = await DeptDao.get_dept_info_for_edit_option(query_db, page_object, data_scope_sql) - return dept_tree_result + return SqlalchemyUtil.serialize_result(dept_list_result) @classmethod - def get_dept_list_services(cls, result_db: Session, page_object: DeptModel, data_scope_sql: str): + async def get_dept_list_services(cls, query_db: AsyncSession, page_object: DeptModel, data_scope_sql: str): """ 获取部门列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 分页查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 :return: 部门列表信息对象 """ - dept_list_result = DeptDao.get_dept_list(result_db, page_object, data_scope_sql) + dept_list_result = await DeptDao.get_dept_list(query_db, page_object, data_scope_sql) + + return SqlalchemyUtil.serialize_result(dept_list_result) - return dept_list_result + @classmethod + async def check_dept_data_scope_services(cls, query_db: AsyncSession, dept_id: int, data_scope_sql: str): + """ + 校验部门是否有数据权限service + + :param query_db: orm对象 + :param dept_id: 部门id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + depts = await DeptDao.get_dept_list(query_db, DeptModel(dept_id=dept_id), data_scope_sql) + if depts: + return CrudResponseModel(is_success=True, message='校验通过') + else: + raise ServiceException(message='没有权限访问部门数据') @classmethod - def add_dept_services(cls, result_db: Session, page_object: DeptModel): + async def check_dept_name_unique_services(cls, query_db: AsyncSession, page_object: DeptModel): + """ + 校验部门名称是否唯一service + + :param query_db: orm对象 + :param page_object: 部门对象 + :return: 校验结果 + """ + dept_id = -1 if page_object.dept_id is None else page_object.dept_id + dept = await DeptDao.get_dept_detail_by_info( + query_db, DeptModel(dept_name=page_object.dept_name, parent_id=page_object.parent_id) + ) + if dept and dept.dept_id != dept_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): """ 新增部门信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增部门对象 :return: 新增部门校验结果 """ - parent_info = DeptDao.get_dept_by_id(result_db, page_object.parent_id) - if parent_info: - page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' - else: - page_object.ancestors = '0' - dept = DeptDao.get_dept_detail_by_info(result_db, DeptModel(**dict(parent_id=page_object.parent_id, dept_name=page_object.dept_name))) - if dept: - result = dict(is_success=False, message='同一部门下不允许存在同名的部门') - else: - try: - DeptDao.add_dept_dao(result_db, page_object) - result_db.commit() - result = dict(is_success=True, message='新增成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudDeptResponse(**result) + if not await cls.check_dept_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增部门{page_object.dept_name}失败,部门名称已存在') + parent_info = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) + if parent_info.status != CommonConstant.DEPT_NORMAL: + raise ServiceException(message=f'部门{parent_info.dept_name}停用,不允许新增') + page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' + try: + await DeptDao.add_dept_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - def edit_dept_services(cls, result_db: Session, page_object: DeptModel): + async def edit_dept_services(cls, query_db: AsyncSession, page_object: DeptModel): """ 编辑部门信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑部门对象 :return: 编辑部门校验结果 """ - parent_info = DeptDao.get_dept_by_id(result_db, page_object.parent_id) - if parent_info: - page_object.ancestors = f'{parent_info.ancestors},{page_object.parent_id}' - else: - page_object.ancestors = '0' - edit_dept = page_object.dict(exclude_unset=True) - dept_info = cls.detail_dept_services(result_db, edit_dept.get('dept_id')) - if dept_info: - if dept_info.parent_id != page_object.parent_id or dept_info.dept_name != page_object.dept_name: - dept = DeptDao.get_dept_detail_by_info(result_db, DeptModel( - **dict(parent_id=page_object.parent_id, dept_name=page_object.dept_name))) - if dept: - result = dict(is_success=False, message='同一部门下不允许存在同名的部门') - return CrudDeptResponse(**result) - try: - DeptDao.edit_dept_dao(result_db, edit_dept) - cls.update_children_info(result_db, DeptModel(dept_id=page_object.dept_id, - ancestors=page_object.ancestors, - update_by=page_object.update_by, - update_time=page_object.update_time - ) - ) - result_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - else: - result = dict(is_success=False, message='部门不存在') - - return CrudDeptResponse(**result) + if not await cls.check_dept_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,部门名称已存在') + elif page_object.dept_id == page_object.parent_id: + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,上级部门不能是自己') + elif ( + page_object.status == CommonConstant.DEPT_DISABLE + and (await DeptDao.count_normal_children_dept_dao(query_db, page_object.dept_id)) > 0 + ): + raise ServiceException(message=f'修改部门{page_object.dept_name}失败,该部门包含未停用的子部门') + new_parent_dept = await DeptDao.get_dept_by_id(query_db, page_object.parent_id) + old_dept = await DeptDao.get_dept_by_id(query_db, page_object.dept_id) + try: + if new_parent_dept and old_dept: + new_ancestors = f'{new_parent_dept.ancestors},{new_parent_dept.dept_id}' + old_ancestors = old_dept.ancestors + page_object.ancestors = new_ancestors + await cls.update_dept_children(query_db, page_object.dept_id, new_ancestors, old_ancestors) + edit_dept = page_object.model_dump(exclude_unset=True) + await DeptDao.edit_dept_dao(query_db, edit_dept) + if ( + page_object.status == CommonConstant.DEPT_NORMAL + and page_object.ancestors + and page_object.ancestors != 0 + ): + await cls.update_parent_dept_status_normal(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod - def delete_dept_services(cls, result_db: Session, page_object: DeleteDeptModel): + async def delete_dept_services(cls, query_db: AsyncSession, page_object: DeleteDeptModel): """ 删除部门信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除部门对象 :return: 删除部门校验结果 """ - if page_object.dept_ids.split(','): + if page_object.dept_ids: dept_id_list = page_object.dept_ids.split(',') - ancestors = DeptDao.get_dept_all_ancestors(result_db) try: for dept_id in dept_id_list: - for ancestor in ancestors: - if dept_id in ancestor[0]: - result = dict(is_success=False, message='该部门下有子部门,不允许删除') + if (await DeptDao.count_children_dept_dao(query_db, int(dept_id))) > 0: + raise ServiceWarning(message='存在下级部门,不允许删除') + elif (await DeptDao.count_dept_user_dao(query_db, int(dept_id))) > 0: + raise ServiceWarning(message='部门存在用户,不允许删除') - return CrudDeptResponse(**result) - - dept_id_dict = dict(dept_id=dept_id) - DeptDao.delete_dept_dao(result_db, DeptModel(**dept_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await DeptDao.delete_dept_dao(query_db, DeptModel(dept_id=dept_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入部门id为空') - return CrudDeptResponse(**result) + raise ServiceException(message='传入部门id为空') @classmethod - def detail_dept_services(cls, result_db: Session, dept_id: int): + async def dept_detail_services(cls, query_db: AsyncSession, dept_id: int): """ 获取部门详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param dept_id: 部门id :return: 部门id对应的信息 """ - dept = DeptDao.get_dept_detail_by_id(result_db, dept_id=dept_id) + dept = await DeptDao.get_dept_detail_by_id(query_db, dept_id=dept_id) + if dept: + result = DeptModel(**SqlalchemyUtil.serialize_result(dept)) + else: + result = DeptModel(**dict()) - return dept + return result @classmethod def list_to_tree(cls, permission_list: list) -> list: """ 工具方法:根据部门列表信息生成树形嵌套数据 + :param permission_list: 部门列表信息 :return: 部门树形嵌套数据 """ - permission_list = [dict(title=item.dept_name, key=str(item.dept_id), value=str(item.dept_id), parent_id=str(item.parent_id)) for item in permission_list] - # 转成dept_id为key的字典 + permission_list = [ + dict(key=str(item.dept_id), title=item.dept_name, value=str(item.dept_id), parent_id=str(item.parent_id)) + for item in permission_list + ] + # 转成id为key的字典 mapping: dict = dict(zip([i['key'] for i in permission_list], permission_list)) # 树容器 @@ -186,52 +230,47 @@ class DeptService: return container @classmethod - def get_dept_tree(cls, pid: int, permission_list: DeptTree): + async def replace_first(cls, original_str: str, old_str: str, new_str: str): """ - 工具方法:根据部门信息生成树形嵌套数据 - :param pid: 部门id - :param permission_list: 部门列表信息 - :return: 部门树形嵌套数据 + 工具方法:替换字符串 + + :param original_str: 需要替换的原始字符串 + :param old_str: 用于匹配的字符串 + :param new_str: 替换的字符串 + :return: 替换后的字符串 """ - dept_list = [] - for permission in permission_list.dept_tree: - if permission.parent_id == pid: - children = cls.get_dept_tree(permission.dept_id, permission_list) - dept_list_data = {} - if children: - dept_list_data['title'] = permission.dept_name - dept_list_data['key'] = str(permission.dept_id) - dept_list_data['value'] = str(permission.dept_id) - dept_list_data['children'] = children - else: - dept_list_data['title'] = permission.dept_name - dept_list_data['key'] = str(permission.dept_id) - dept_list_data['value'] = str(permission.dept_id) - dept_list.append(dept_list_data) - - return dept_list + if original_str.startswith(old_str): + return original_str.replace(old_str, new_str, 1) + else: + return original_str @classmethod - def update_children_info(cls, result_db, page_object): + async def update_parent_dept_status_normal(cls, query_db: AsyncSession, dept: DeptModel): """ - 工具方法:递归更新子部门信息 - :param result_db: orm对象 - :param page_object: 编辑部门对象 + 更新父部门状态为正常 + + :param query_db: orm对象 + :param dept: 部门对象 + :return: + """ + dept_id_list = dept.ancestors.split(',') + await DeptDao.update_dept_status_normal_dao(query_db, dept_id_list) + + @classmethod + async def update_dept_children(cls, query_db: AsyncSession, dept_id: int, new_ancestors: str, old_ancestors: str): + """ + 更新子部门信息 + + :param query_db: orm对象 + :param dept_id: 部门id + :param new_ancestors: 新的祖先 + :param old_ancestors: 旧的祖先 :return: """ - children_info = DeptDao.get_children_dept(result_db, page_object.dept_id) - if children_info: - for child in children_info: - child.ancestors = f'{page_object.ancestors},{page_object.dept_id}' - DeptDao.edit_dept_dao(result_db, - dict(dept_id=child.dept_id, - ancestors=child.ancestors, - update_by=page_object.update_by, - update_time=page_object.update_time - ) - ) - cls.update_children_info(result_db, DeptModel(dept_id=child.dept_id, - ancestors=child.ancestors, - update_by=page_object.update_by, - update_time=page_object.update_time - )) + children = await DeptDao.get_children_dept_dao(query_db, dept_id) + update_children = [] + for child in children: + child_ancestors = await cls.replace_first(child.ancestors, old_ancestors, new_ancestors) + update_children.append({'dept_id': child.dept_id, 'ancestors': child_ancestors}) + if children: + await DeptDao.update_dept_children_dao(query_db, update_children) diff --git a/dash-fastapi-backend/module_admin/service/dict_service.py b/dash-fastapi-backend/module_admin/service/dict_service.py index e557b2f9bb68d37af0967f79a6ef0553cf3d7558..2175b6bbaae2a24909ee9ed0b8473bb6b1135259 100644 --- a/dash-fastapi-backend/module_admin/service/dict_service.py +++ b/dash-fastapi-backend/module_admin/service/dict_service.py @@ -1,9 +1,21 @@ -from fastapi import Request import json -from config.env import RedisInitKeyConfig -from module_admin.entity.vo.dict_vo import * -from module_admin.dao.dict_dao import * -from utils.common_util import export_list2excel +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from config.enums import RedisInitKeyConfig +from exceptions.exception import ServiceException +from module_admin.dao.dict_dao import DictDataDao, DictTypeDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.dict_vo import ( + DeleteDictDataModel, + DeleteDictTypeModel, + DictDataModel, + DictDataPageQueryModel, + DictTypeModel, + DictTypePageQueryModel, +) +from utils.common_util import export_list2excel, SqlalchemyUtil class DictTypeService: @@ -12,170 +24,201 @@ class DictTypeService: """ @classmethod - def get_dict_type_list_services(cls, result_db: Session, query_object: DictTypeQueryModel): + async def get_dict_type_list_services( + cls, query_db: AsyncSession, query_object: DictTypePageQueryModel, is_page: bool = False + ): """ 获取字典类型列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 字典类型列表信息对象 """ - dict_type_list_result = DictTypeDao.get_dict_type_list(result_db, query_object) + dict_type_list_result = await DictTypeDao.get_dict_type_list(query_db, query_object, is_page) return dict_type_list_result @classmethod - def get_all_dict_type_services(cls, result_db: Session): + async def check_dict_type_unique_services(cls, query_db: AsyncSession, page_object: DictTypeModel): """ - 获取字所有典类型列表信息service - :param result_db: orm对象 - :return: 字典类型列表信息对象 - """ - dict_type_list_result = DictTypeDao.get_all_dict_type(result_db) + 校验字典类型称是否唯一service - return dict_type_list_result + :param query_db: orm对象 + :param page_object: 字典类型对象 + :return: 校验结果 + """ + dict_id = -1 if page_object.dict_id is None else page_object.dict_id + dict_type = await DictTypeDao.get_dict_type_detail_by_info( + query_db, DictTypeModel(dict_type=page_object.dict_type) + ) + if dict_type and dict_type.dict_id != dict_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE @classmethod - async def add_dict_type_services(cls, request: Request, result_db: Session, page_object: DictTypeModel): + async def add_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): """ 新增字典类型信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 新增岗位对象 :return: 新增字典类型校验结果 """ - dict_type = DictTypeDao.get_dict_type_detail_by_info(result_db, DictTypeModel(**dict(dict_type=page_object.dict_type))) - if dict_type: - result = dict(is_success=False, message='字典类型已存在') + if not await cls.check_dict_type_unique_services(query_db, page_object): + raise ServiceException(message=f'新增字典{page_object.dict_name}失败,字典类型已存在') else: try: - DictTypeDao.add_dict_type_dao(result_db, page_object) - result_db.commit() - await DictDataService.init_cache_sys_dict_services(result_db, request.app.state.redis) + await DictTypeDao.add_dict_type_dao(query_db, page_object) + await query_db.commit() + await request.app.state.redis.set(f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', '') result = dict(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e - return CrudDictResponse(**result) + return CrudResponseModel(**result) @classmethod - async def edit_dict_type_services(cls, request: Request, result_db: Session, page_object: DictTypeModel): + async def edit_dict_type_services(cls, request: Request, query_db: AsyncSession, page_object: DictTypeModel): """ 编辑字典类型信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 编辑字典类型对象 :return: 编辑字典类型校验结果 """ - edit_dict_type = page_object.dict(exclude_unset=True) - dict_type_info = cls.detail_dict_type_services(result_db, edit_dict_type.get('dict_id')) - if dict_type_info: - if dict_type_info.dict_type != page_object.dict_type or dict_type_info.dict_name != page_object.dict_name: - dict_type = DictTypeDao.get_dict_type_detail_by_info(result_db, DictTypeModel( - **dict(dict_type=page_object.dict_type))) - if dict_type: - result = dict(is_success=False, message='字典类型已存在') - return CrudDictResponse(**result) - try: - if dict_type_info.dict_type != page_object.dict_type: - query_dict_data = DictDataModel(**(dict(dict_type=dict_type_info.dict_type))) - dict_data_list = DictDataDao.get_dict_data_list(result_db, query_dict_data) - for dict_data in dict_data_list: - edit_dict_data = DictDataModel(**(dict(dict_code=dict_data.dict_code, dict_type=page_object.dict_type, update_by=page_object.update_by))).dict(exclude_unset=True) - DictDataDao.edit_dict_data_dao(result_db, edit_dict_data) - DictTypeDao.edit_dict_type_dao(result_db, edit_dict_type) - result_db.commit() - await DictDataService.init_cache_sys_dict_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + edit_dict_type = page_object.model_dump(exclude_unset=True) + dict_type_info = await cls.dict_type_detail_services(query_db, page_object.dict_id) + if dict_type_info.dict_id: + if not await cls.check_dict_type_unique_services(query_db, page_object): + raise ServiceException(message=f'修改字典{page_object.dict_name}失败,字典类型已存在') + else: + try: + query_dict_data = DictDataPageQueryModel(dict_type=dict_type_info.dict_type) + dict_data_list = await DictDataDao.get_dict_data_list(query_db, query_dict_data, is_page=False) + if dict_type_info.dict_type != page_object.dict_type: + for dict_data in dict_data_list: + edit_dict_data = DictDataModel( + dict_code=dict_data.dict_code, + dict_type=page_object.dict_type, + update_by=page_object.update_by, + ).model_dump(exclude_unset=True) + await DictDataDao.edit_dict_data_dao(query_db, edit_dict_data) + await DictTypeDao.edit_dict_type_dao(query_db, edit_dict_type) + await query_db.commit() + if dict_type_info.dict_type != page_object.dict_type: + dict_data = [SqlalchemyUtil.serialize_result(row) for row in dict_data_list if row] + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps(dict_data, ensure_ascii=False, default=str), + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='字典类型不存在') - - return CrudDictResponse(**result) + raise ServiceException(message='字典类型不存在') @classmethod - async def delete_dict_type_services(cls, request: Request, result_db: Session, page_object: DeleteDictTypeModel): + async def delete_dict_type_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteDictTypeModel + ): """ 删除字典类型信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 删除字典类型对象 :return: 删除字典类型校验结果 """ - if page_object.dict_ids.split(','): + if page_object.dict_ids: dict_id_list = page_object.dict_ids.split(',') try: + delete_dict_type_list = [] for dict_id in dict_id_list: - dict_id_dict = dict(dict_id=dict_id) - DictTypeDao.delete_dict_type_dao(result_db, DictTypeModel(**dict_id_dict)) - result_db.commit() - await DictDataService.init_cache_sys_dict_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='删除成功') + dict_type_into = await cls.dict_type_detail_services(query_db, int(dict_id)) + if (await DictDataDao.count_dict_data_dao(query_db, dict_type_into.dict_type)) > 0: + raise ServiceException(message=f'{dict_type_into.dict_name}已分配,不能删除') + await DictTypeDao.delete_dict_type_dao(query_db, DictTypeModel(dict_id=int(dict_id))) + delete_dict_type_list.append(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type_into.dict_type}') + await query_db.commit() + if delete_dict_type_list: + await request.app.state.redis.delete(*delete_dict_type_list) + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入字典类型id为空') - return CrudDictResponse(**result) + raise ServiceException(message='传入字典类型id为空') @classmethod - def detail_dict_type_services(cls, result_db: Session, dict_id: int): + async def dict_type_detail_services(cls, query_db: AsyncSession, dict_id: int): """ 获取字典类型详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param dict_id: 字典类型id :return: 字典类型id对应的信息 """ - dict_type = DictTypeDao.get_dict_type_detail_by_id(result_db, dict_id=dict_id) + dict_type = await DictTypeDao.get_dict_type_detail_by_id(query_db, dict_id=dict_id) + if dict_type: + result = DictTypeModel(**SqlalchemyUtil.serialize_result(dict_type)) + else: + result = DictTypeModel(**dict()) - return dict_type + return result @staticmethod - def export_dict_type_list_services(dict_type_list: List): + async def export_dict_type_list_services(dict_type_list: List): """ 导出字典类型信息service + :param dict_type_list: 字典信息列表 :return: 字典信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "dict_id": "字典编号", - "dict_name": "字典名称", - "dict_type": "字典类型", - "status": "状态", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'dict_id': '字典编号', + 'dict_name': '字典名称', + 'dict_type': '字典类型', + 'status': '状态', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } - data = [DictTypeModel(**vars(row)).dict() for row in dict_type_list] + data = dict_type_list for item in data: if item.get('status') == '0': item['status'] = '正常' else: item['status'] = '停用' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @classmethod - async def refresh_sys_dict_services(cls, request: Request, result_db: Session): + async def refresh_sys_dict_services(cls, request: Request, query_db: AsyncSession): """ 刷新字典缓存信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :return: 刷新字典缓存校验结果 """ - await DictDataService.init_cache_sys_dict_services(result_db, request.app.state.redis) + await DictDataService.init_cache_sys_dict_services(query_db, request.app.state.redis) result = dict(is_success=True, message='刷新成功') - return CrudDictResponse(**result) + return CrudResponseModel(**result) class DictDataService: @@ -184,181 +227,234 @@ class DictDataService: """ @classmethod - def get_dict_data_list_services(cls, result_db: Session, query_object: DictDataModel): + async def get_dict_data_list_services( + cls, query_db: AsyncSession, query_object: DictDataPageQueryModel, is_page: bool = False + ): """ 获取字典数据列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 字典数据列表信息对象 """ - dict_data_list_result = DictDataDao.get_dict_data_list(result_db, query_object) + dict_data_list_result = await DictDataDao.get_dict_data_list(query_db, query_object, is_page) return dict_data_list_result @classmethod - def query_dict_data_list_services(cls, result_db: Session, dict_type: str): + async def query_dict_data_list_services(cls, query_db: AsyncSession, dict_type: str): """ 获取字典数据列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param dict_type: 字典类型 :return: 字典数据列表信息对象 """ - dict_data_list_result = DictDataDao.query_dict_data_list(result_db, dict_type) + dict_data_list_result = await DictDataDao.query_dict_data_list(query_db, dict_type) return dict_data_list_result @classmethod - async def init_cache_sys_dict_services(cls, result_db: Session, redis): + async def init_cache_sys_dict_services(cls, query_db: AsyncSession, redis): """ 应用初始化:获取所有字典类型对应的字典数据信息并缓存service - :param result_db: orm对象 + + :param query_db: orm对象 :param redis: redis对象 :return: """ # 获取以sys_dict:开头的键列表 - keys = await redis.keys(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:*") + keys = await redis.keys(f'{RedisInitKeyConfig.SYS_DICT.key}:*') # 删除匹配的键 if keys: await redis.delete(*keys) - dict_type_all = DictTypeDao.get_all_dict_type(result_db) + dict_type_all = await DictTypeDao.get_all_dict_type(query_db) for dict_type_obj in [item for item in dict_type_all if item.status == '0']: dict_type = dict_type_obj.dict_type - dict_data_list = DictDataDao.query_dict_data_list(result_db, dict_type) - dict_data = [DictDataModel(**vars(row)).dict() for row in dict_data_list if row] - await redis.set(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:{dict_type}", json.dumps(dict_data, ensure_ascii=False)) + dict_data_list = await DictDataDao.query_dict_data_list(query_db, dict_type) + dict_data = [SqlalchemyUtil.serialize_result(row) for row in dict_data_list if row] + await redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}', + json.dumps(dict_data, ensure_ascii=False, default=str), + ) @classmethod async def query_dict_data_list_from_cache_services(cls, redis, dict_type: str): """ 从缓存获取字典数据列表信息service + :param redis: redis对象 :param dict_type: 字典类型 :return: 字典数据列表信息对象 """ result = [] - dict_data_list_result = await redis.get(f"{RedisInitKeyConfig.SYS_DICT.get('key')}:{dict_type}") + dict_data_list_result = await redis.get(f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}') if dict_data_list_result: result = json.loads(dict_data_list_result) - return result + return SqlalchemyUtil.serialize_result(result) + + @classmethod + async def check_dict_data_unique_services(cls, query_db: AsyncSession, page_object: DictDataModel): + """ + 校验字典数据是否唯一service + + :param query_db: orm对象 + :param page_object: 字典数据对象 + :return: 校验结果 + """ + dict_code = -1 if page_object.dict_code is None else page_object.dict_code + dict_data = await DictDataDao.get_dict_data_detail_by_info(query_db, page_object) + if dict_data and dict_data.dict_code != dict_code: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE @classmethod - async def add_dict_data_services(cls, request: Request, result_db: Session, page_object: DictDataModel): + async def add_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): """ 新增字典数据信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 新增岗位对象 :return: 新增字典数据校验结果 """ - dict_data = DictDataDao.get_dict_data_detail_by_info(result_db, page_object) - if dict_data: - result = dict(is_success=False, message='字典数据已存在') + if not await cls.check_dict_data_unique_services(query_db, page_object): + raise ServiceException( + message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据' + ) else: try: - DictDataDao.add_dict_data_dao(result_db, page_object) - result_db.commit() - await cls.init_cache_sys_dict_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='新增成功') + await DictDataDao.add_dict_data_dao(query_db, page_object) + await query_db.commit() + dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps( + SqlalchemyUtil.serialize_result(dict_data_list), ensure_ascii=False, default=str + ), + ) + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudDictResponse(**result) + await query_db.rollback() + raise e @classmethod - async def edit_dict_data_services(cls, request: Request, result_db: Session, page_object: DictDataModel): + async def edit_dict_data_services(cls, request: Request, query_db: AsyncSession, page_object: DictDataModel): """ 编辑字典数据信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 编辑字典数据对象 :return: 编辑字典数据校验结果 """ - edit_data_type = page_object.dict(exclude_unset=True) - dict_data_info = cls.detail_dict_data_services(result_db, edit_data_type.get('dict_code')) - if dict_data_info: - if dict_data_info.dict_type != page_object.dict_type or dict_data_info.dict_label != page_object.dict_label or dict_data_info.dict_value != page_object.dict_value: - dict_data = DictDataDao.get_dict_data_detail_by_info(result_db, page_object) - if dict_data: - result = dict(is_success=False, message='字典数据已存在') - return CrudDictResponse(**result) - try: - DictDataDao.edit_dict_data_dao(result_db, edit_data_type) - result_db.commit() - await cls.init_cache_sys_dict_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + edit_data_type = page_object.model_dump(exclude_unset=True) + dict_data_info = await cls.dict_data_detail_services(query_db, page_object.dict_code) + if dict_data_info.dict_code: + if not await cls.check_dict_data_unique_services(query_db, page_object): + raise ServiceException( + message=f'新增字典数据{page_object.dict_label}失败,{page_object.dict_type}下已存在该字典数据' + ) + else: + try: + await DictDataDao.edit_dict_data_dao(query_db, edit_data_type) + await query_db.commit() + dict_data_list = await cls.query_dict_data_list_services(query_db, page_object.dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{page_object.dict_type}', + json.dumps( + SqlalchemyUtil.serialize_result(dict_data_list), ensure_ascii=False, default=str + ), + ) + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='字典数据不存在') - - return CrudDictResponse(**result) + raise ServiceException(message='字典数据不存在') @classmethod - async def delete_dict_data_services(cls, request: Request, result_db: Session, page_object: DeleteDictDataModel): + async def delete_dict_data_services( + cls, request: Request, query_db: AsyncSession, page_object: DeleteDictDataModel + ): """ 删除字典数据信息service + :param request: Request对象 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 删除字典数据对象 :return: 删除字典数据校验结果 """ - if page_object.dict_codes.split(','): + if page_object.dict_codes: dict_code_list = page_object.dict_codes.split(',') try: + delete_dict_type_list = [] for dict_code in dict_code_list: - dict_code_dict = dict(dict_code=dict_code) - DictDataDao.delete_dict_data_dao(result_db, DictDataModel(**dict_code_dict)) - result_db.commit() - await cls.init_cache_sys_dict_services(result_db, request.app.state.redis) - result = dict(is_success=True, message='删除成功') + dict_data = await cls.dict_data_detail_services(query_db, int(dict_code)) + await DictDataDao.delete_dict_data_dao(query_db, DictDataModel(dict_code=dict_code)) + delete_dict_type_list.append(dict_data.dict_type) + await query_db.commit() + for dict_type in list(set(delete_dict_type_list)): + dict_data_list = await cls.query_dict_data_list_services(query_db, dict_type) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SYS_DICT.key}:{dict_type}', + json.dumps( + SqlalchemyUtil.serialize_result(dict_data_list), ensure_ascii=False, default=str + ), + ) + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入字典数据id为空') - return CrudDictResponse(**result) + raise ServiceException(message='传入字典数据id为空') @classmethod - def detail_dict_data_services(cls, result_db: Session, dict_code: int): + async def dict_data_detail_services(cls, query_db: AsyncSession, dict_code: int): """ 获取字典数据详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param dict_code: 字典数据id :return: 字典数据id对应的信息 """ - dict_data = DictDataDao.get_dict_data_detail_by_id(result_db, dict_code=dict_code) + dict_data = await DictDataDao.get_dict_data_detail_by_id(query_db, dict_code=dict_code) + if dict_data: + result = DictDataModel(**SqlalchemyUtil.serialize_result(dict_data)) + else: + result = DictDataModel(**dict()) - return dict_data + return result @staticmethod - def export_dict_data_list_services(dict_data_list: List): + async def export_dict_data_list_services(dict_data_list: List): """ 导出字典数据信息service + :param dict_data_list: 字典数据信息列表 :return: 字典数据信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "dict_code": "字典编码", - "dict_sort": "字典标签", - "dict_label": "字典键值", - "dict_value": "字典排序", - "dict_type": "字典类型", - "css_class": "样式属性", - "list_class": "表格回显样式", - "is_default": "是否默认", - "status": "状态", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'dict_code': '字典编码', + 'dict_sort': '字典标签', + 'dict_label': '字典键值', + 'dict_value': '字典排序', + 'dict_type': '字典类型', + 'css_class': '样式属性', + 'list_class': '表格回显样式', + 'is_default': '是否默认', + 'status': '状态', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } - data = [DictDataModel(**vars(row)).dict() for row in dict_data_list] + data = dict_data_list for item in data: if item.get('status') == '0': @@ -369,7 +465,9 @@ class DictDataService: item['is_default'] = '是' else: item['is_default'] = '否' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/dash-fastapi-backend/module_admin/service/job_log_service.py b/dash-fastapi-backend/module_admin/service/job_log_service.py index f022559886e168f6c8d62d3bc074ac138d04da3b..8ad7bfbb7d0d0d772457638c3aa3ea18b3aa8e45 100644 --- a/dash-fastapi-backend/module_admin/service/job_log_service.py +++ b/dash-fastapi-backend/module_admin/service/job_log_service.py @@ -1,6 +1,11 @@ -from module_admin.entity.vo.job_vo import * -from module_admin.dao.job_log_dao import * -from module_admin.dao.dict_dao import DictDataDao +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import Session +from typing import List +from module_admin.dao.job_log_dao import JobLogDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.job_vo import DeleteJobLogModel, JobLogModel, JobLogPageQueryModel +from module_admin.service.dict_service import DictDataService from utils.common_util import export_list2excel @@ -10,119 +15,119 @@ class JobLogService: """ @classmethod - def get_job_log_list_services(cls, result_db: Session, query_object: JobLogQueryModel): + async def get_job_log_list_services( + cls, query_db: AsyncSession, query_object: JobLogPageQueryModel, is_page: bool = False + ): """ 获取定时任务日志列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 定时任务日志列表信息对象 """ - job_log_list_result = JobLogDao.get_job_log_list(result_db, query_object) + job_log_list_result = await JobLogDao.get_job_log_list(query_db, query_object, is_page) return job_log_list_result @classmethod - def add_job_log_services(cls, result_db: Session, page_object: JobLogModel): + def add_job_log_services(cls, query_db: Session, page_object: JobLogModel): """ 新增定时任务日志信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增定时任务日志对象 :return: 新增定时任务日志校验结果 """ try: - JobLogDao.add_job_log_dao(result_db, page_object) - result_db.commit() + JobLogDao.add_job_log_dao(query_db, page_object) + query_db.commit() result = dict(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() + query_db.rollback() result = dict(is_success=False, message=str(e)) - return CrudJobResponse(**result) + return CrudResponseModel(**result) @classmethod - def delete_job_log_services(cls, result_db: Session, page_object: DeleteJobLogModel): + async def delete_job_log_services(cls, query_db: AsyncSession, page_object: DeleteJobLogModel): """ 删除定时任务日志信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除定时任务日志对象 :return: 删除定时任务日志校验结果 """ - if page_object.job_log_ids.split(','): + if page_object.job_log_ids: job_log_id_list = page_object.job_log_ids.split(',') try: for job_log_id in job_log_id_list: - job_log_id_dict = dict(job_log_id=job_log_id) - JobLogDao.delete_job_log_dao(result_db, JobLogModel(**job_log_id_dict)) - result_db.commit() + await JobLogDao.delete_job_log_dao(query_db, JobLogModel(job_log_id=job_log_id)) + await query_db.commit() result = dict(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: result = dict(is_success=False, message='传入定时任务日志id为空') - return CrudJobResponse(**result) - - @classmethod - def detail_job_log_services(cls, result_db: Session, job_log_id: int): - """ - 获取定时任务日志详细信息service - :param result_db: orm对象 - :param job_log_id: 定时任务日志id - :return: 定时任务日志id对应的信息 - """ - job_log = JobLogDao.get_job_log_detail_by_id(result_db, job_log_id=job_log_id) - - return job_log + return CrudResponseModel(**result) @classmethod - def clear_job_log_services(cls, result_db: Session, page_object: ClearJobLogModel): + async def clear_job_log_services(cls, query_db: AsyncSession): """ 清除定时任务日志信息service - :param result_db: orm对象 - :param page_object: 清除定时任务日志对象 + + :param query_db: orm对象 :return: 清除定时任务日志校验结果 """ - if page_object.oper_type == 'clear': - try: - JobLogDao.clear_job_log_dao(result_db) - result_db.commit() - result = dict(is_success=True, message='清除成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - else: - result = dict(is_success=False, message='清除标识不合法') + try: + await JobLogDao.clear_job_log_dao(query_db) + await query_db.commit() + result = dict(is_success=True, message='清除成功') + except Exception as e: + await query_db.rollback() + raise e - return CrudJobResponse(**result) + return CrudResponseModel(**result) @staticmethod - def export_job_log_list_services(result_db, job_log_list: List): + async def export_job_log_list_services(request: Request, job_log_list: List): """ 导出定时任务日志信息service - :param result_db: orm对象 + + :param request: Request对象 :param job_log_list: 定时任务日志信息列表 :return: 定时任务日志信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "job_log_id": "任务日志编码", - "job_name": "任务名称", - "job_group": "任务组名", - "job_executor": "任务执行器", - "invoke_target": "调用目标字符串", - "job_args": "位置参数", - "job_kwargs": "关键字参数", - "job_trigger": "任务触发器", - "job_message": "日志信息", - "status": "执行状态", - "exception_info": "异常信息", - "create_time": "创建时间", + 'job_log_id': '任务日志编码', + 'job_name': '任务名称', + 'job_group': '任务组名', + 'job_executor': '任务执行器', + 'invoke_target': '调用目标字符串', + 'job_args': '位置参数', + 'job_kwargs': '关键字参数', + 'job_trigger': '任务触发器', + 'job_message': '日志信息', + 'status': '执行状态', + 'exception_info': '异常信息', + 'create_time': '创建时间', } - data = [JobLogModel(**vars(row)).dict() for row in job_log_list] - job_group_list = DictDataDao.query_dict_data_list(result_db, dict_type='sys_job_group') - job_group_option = [dict(label=item.dict_label, value=item.dict_value) for item in job_group_list] + data = job_log_list + job_group_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_group' + ) + job_group_option = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in job_group_list] job_group_option_dict = {item.get('value'): item for item in job_group_option} + job_executor_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_executor' + ) + job_executor_option = [ + dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in job_executor_list + ] + job_executor_option_dict = {item.get('value'): item for item in job_executor_option} for item in data: if item.get('status') == '0': @@ -131,8 +136,11 @@ class JobLogService: item['status'] = '暂停' if str(item.get('job_group')) in job_group_option_dict.keys(): item['job_group'] = job_group_option_dict.get(str(item.get('job_group'))).get('label') - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in - data] + if str(item.get('job_executor')) in job_executor_option_dict.keys(): + item['job_executor'] = job_executor_option_dict.get(str(item.get('job_executor'))).get('label') + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/dash-fastapi-backend/module_admin/service/job_service.py b/dash-fastapi-backend/module_admin/service/job_service.py index 5f50e8cf61b90363b0e07c38409b7e9a94350073..5a8c3bd163f7c669bb08f52e661a040f6cb8d9ec 100644 --- a/dash-fastapi-backend/module_admin/service/job_service.py +++ b/dash-fastapi-backend/module_admin/service/job_service.py @@ -1,8 +1,16 @@ -from module_admin.entity.vo.job_vo import * -from module_admin.dao.job_dao import * -from module_admin.service.dict_service import Request, DictDataService -from utils.common_util import export_list2excel +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant, JobConstant from config.get_scheduler import SchedulerUtil +from exceptions.exception import ServiceException +from module_admin.dao.job_dao import JobDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.job_vo import DeleteJobModel, EditJobModel, JobModel, JobPageQueryModel +from module_admin.service.dict_service import DictDataService +from utils.common_util import export_list2excel, SqlalchemyUtil +from utils.cron_util import CronUtil +from utils.string_util import StringUtil class JobService: @@ -11,164 +19,233 @@ class JobService: """ @classmethod - def get_job_list_services(cls, result_db: Session, query_object: JobModel): + async def get_job_list_services( + cls, query_db: AsyncSession, query_object: JobPageQueryModel, is_page: bool = False + ): """ 获取定时任务列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 定时任务列表信息对象 """ - job_list_result = JobDao.get_job_list(result_db, query_object) + job_list_result = await JobDao.get_job_list(query_db, query_object, is_page) return job_list_result @classmethod - def add_job_services(cls, result_db: Session, page_object: JobModel): + async def check_job_unique_services(cls, query_db: AsyncSession, page_object: JobModel): + """ + 校验定时任务是否存在service + + :param query_db: orm对象 + :param page_object: 定时任务对象 + :return: 校验结果 + """ + job_id = -1 if page_object.job_id is None else page_object.job_id + job = await JobDao.get_job_detail_by_info(query_db, page_object) + if job and job.job_id != job_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_job_services(cls, query_db: AsyncSession, page_object: JobModel): """ 新增定时任务信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增定时任务对象 :return: 新增定时任务校验结果 """ - job = JobDao.get_job_detail_by_info(result_db, page_object) - if job: - result = dict(is_success=False, message='定时任务已存在') + if not CronUtil.validate_cron_expression(page_object.cron_expression): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,Cron表达式不正确') + elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS] + ): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用') + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS] + ): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用') + elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串存在违规') + elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'新增定时任务{page_object.job_name}失败,定时任务已存在') else: try: - JobDao.add_job_dao(result_db, page_object) - job_info = JobDao.get_job_detail_by_info(result_db, page_object) + add_job = await JobDao.add_job_dao(query_db, page_object) + job_info = await cls.job_detail_services(query_db, add_job.job_id) if job_info.status == '0': SchedulerUtil.add_scheduler_job(job_info=job_info) - result_db.commit() + await query_db.commit() result = dict(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e - return CrudJobResponse(**result) + return CrudResponseModel(**result) @classmethod - def edit_job_services(cls, result_db: Session, page_object: EditJobModel): + async def edit_job_services(cls, query_db: AsyncSession, page_object: EditJobModel): """ 编辑定时任务信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑定时任务对象 :return: 编辑定时任务校验结果 """ - edit_job = page_object.dict(exclude_unset=True) + edit_job = page_object.model_dump(exclude_unset=True) if page_object.type == 'status': del edit_job['type'] - job_info = cls.detail_job_services(result_db, edit_job.get('job_id')) + job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: - if page_object.type != 'status' and (job_info.job_name != page_object.job_name or job_info.job_group != page_object.job_group or job_info.invoke_target != page_object.invoke_target or job_info.cron_expression != page_object.cron_expression): - job = JobDao.get_job_detail_by_info(result_db, page_object) - if job: - result = dict(is_success=False, message='定时任务已存在') - return CrudJobResponse(**result) + if page_object.type != 'status': + if not CronUtil.validate_cron_expression(page_object.cron_expression): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,Cron表达式不正确') + elif StringUtil.contains_ignore_case(page_object.invoke_target, CommonConstant.LOOKUP_RMI): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许rmi调用') + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS] + ): + raise ServiceException( + message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许ldap(s)调用' + ) + elif StringUtil.contains_any_ignore_case( + page_object.invoke_target, [CommonConstant.HTTP, CommonConstant.HTTPS] + ): + raise ServiceException( + message=f'修改定时任务{page_object.job_name}失败,目标字符串不允许http(s)调用' + ) + elif StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_ERROR_LIST): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串存在违规') + elif not StringUtil.startswith_any_case(page_object.invoke_target, JobConstant.JOB_WHITE_LIST): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,目标字符串不在白名单内') + elif not await cls.check_job_unique_services(query_db, page_object): + raise ServiceException(message=f'修改定时任务{page_object.job_name}失败,定时任务已存在') try: - JobDao.edit_job_dao(result_db, edit_job) + await JobDao.edit_job_dao(query_db, edit_job) query_job = SchedulerUtil.get_scheduler_job(job_id=edit_job.get('job_id')) if query_job: SchedulerUtil.remove_scheduler_job(job_id=edit_job.get('job_id')) if edit_job.get('status') == '0': + job_info = await cls.job_detail_services(query_db, edit_job.get('job_id')) SchedulerUtil.add_scheduler_job(job_info=job_info) - result_db.commit() - result = dict(is_success=True, message='更新成功') + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='定时任务不存在') - - return CrudJobResponse(**result) + raise ServiceException(message='定时任务不存在') @classmethod - def execute_job_once_services(cls, result_db: Session, page_object: JobModel): + async def execute_job_once_services(cls, query_db: AsyncSession, page_object: JobModel): """ 执行一次定时任务service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 定时任务对象 :return: 执行一次定时任务结果 """ query_job = SchedulerUtil.get_scheduler_job(job_id=page_object.job_id) if query_job: SchedulerUtil.remove_scheduler_job(job_id=page_object.job_id) - job_info = cls.detail_job_services(result_db, page_object.job_id) + job_info = await cls.job_detail_services(query_db, page_object.job_id) if job_info: SchedulerUtil.execute_scheduler_job_once(job_info=job_info) - result = dict(is_success=True, message='执行成功') + return CrudResponseModel(is_success=True, message='执行成功') else: - result = dict(is_success=False, message='定时任务不存在') - - return CrudJobResponse(**result) + raise ServiceException(message='定时任务不存在') @classmethod - def delete_job_services(cls, result_db: Session, page_object: DeleteJobModel): + async def delete_job_services(cls, query_db: AsyncSession, page_object: DeleteJobModel): """ 删除定时任务信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除定时任务对象 :return: 删除定时任务校验结果 """ - if page_object.job_ids.split(','): + if page_object.job_ids: job_id_list = page_object.job_ids.split(',') try: for job_id in job_id_list: - job_id_dict = dict(job_id=job_id) - JobDao.delete_job_dao(result_db, JobModel(**job_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await JobDao.delete_job_dao(query_db, JobModel(job_id=job_id)) + query_job = SchedulerUtil.get_scheduler_job(job_id=job_id) + if query_job: + SchedulerUtil.remove_scheduler_job(job_id=job_id) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入定时任务id为空') - return CrudJobResponse(**result) + raise ServiceException(message='传入定时任务id为空') @classmethod - def detail_job_services(cls, result_db: Session, job_id: int): + async def job_detail_services(cls, query_db: AsyncSession, job_id: int): """ 获取定时任务详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param job_id: 定时任务id :return: 定时任务id对应的信息 """ - job = JobDao.get_job_detail_by_id(result_db, job_id=job_id) + job = await JobDao.get_job_detail_by_id(query_db, job_id=job_id) + if job: + result = JobModel(**SqlalchemyUtil.serialize_result(job)) + else: + result = JobModel(**dict()) - return job + return result @staticmethod async def export_job_list_services(request: Request, job_list: List): """ 导出定时任务信息service + :param request: Request对象 :param job_list: 定时任务信息列表 :return: 定时任务信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "job_id": "任务编码", - "job_name": "任务名称", - "job_group": "任务组名", - "job_executor": "任务执行器", - "invoke_target": "调用目标字符串", - "job_args": "位置参数", - "job_kwargs": "关键字参数", - "cron_expression": "cron执行表达式", - "misfire_policy": "计划执行错误策略", - "concurrent": "是否并发执行", - "status": "状态", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'job_id': '任务编码', + 'job_name': '任务名称', + 'job_group': '任务组名', + 'job_executor': '任务执行器', + 'invoke_target': '调用目标字符串', + 'job_args': '位置参数', + 'job_kwargs': '关键字参数', + 'cron_expression': 'cron执行表达式', + 'misfire_policy': '计划执行错误策略', + 'concurrent': '是否并发执行', + 'status': '状态', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } - data = [JobModel(**vars(row)).dict() for row in job_list] - job_group_list = await DictDataService.query_dict_data_list_from_cache_services(request.app.state.redis, dict_type='sys_job_group') + data = job_list + job_group_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_group' + ) job_group_option = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in job_group_list] job_group_option_dict = {item.get('value'): item for item in job_group_option} + job_executor_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_job_executor' + ) + job_executor_option = [ + dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in job_executor_list + ] + job_executor_option_dict = {item.get('value'): item for item in job_executor_option} for item in data: if item.get('status') == '0': @@ -177,6 +254,8 @@ class JobService: item['status'] = '暂停' if str(item.get('job_group')) in job_group_option_dict.keys(): item['job_group'] = job_group_option_dict.get(str(item.get('job_group'))).get('label') + if str(item.get('job_executor')) in job_executor_option_dict.keys(): + item['job_executor'] = job_executor_option_dict.get(str(item.get('job_executor'))).get('label') if item.get('misfire_policy') == '1': item['misfire_policy'] = '立即执行' elif item.get('misfire_policy') == '2': @@ -187,7 +266,9 @@ class JobService: item['concurrent'] = '允许' else: item['concurrent'] = '禁止' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/dash-fastapi-backend/module_admin/service/log_service.py b/dash-fastapi-backend/module_admin/service/log_service.py index 6783c54a941eabfe0fc850f204ca048d61b688e1..1401ac1fa8765fe96f9502bb7421f6898b95dca7 100644 --- a/dash-fastapi-backend/module_admin/service/log_service.py +++ b/dash-fastapi-backend/module_admin/service/log_service.py @@ -1,6 +1,19 @@ -from module_admin.entity.vo.log_vo import * -from module_admin.dao.log_dao import * -from module_admin.service.dict_service import Request, DictDataService +from fastapi import Request +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from exceptions.exception import ServiceException +from module_admin.dao.log_dao import LoginLogDao, OperationLogDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.log_vo import ( + DeleteLoginLogModel, + DeleteOperLogModel, + LogininforModel, + LoginLogPageQueryModel, + OperLogModel, + OperLogPageQueryModel, + UnlockUser, +) +from module_admin.service.dict_service import DictDataService from utils.common_util import export_list2excel @@ -10,122 +23,112 @@ class OperationLogService: """ @classmethod - def get_operation_log_list_services(cls, result_db: Session, query_object: OperLogQueryModel): + async def get_operation_log_list_services( + cls, query_db: AsyncSession, query_object: OperLogPageQueryModel, is_page: bool = False + ): """ 获取操作日志列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 操作日志列表信息对象 """ - operation_log_list_result = OperationLogDao.get_operation_log_list(result_db, query_object) + operation_log_list_result = await OperationLogDao.get_operation_log_list(query_db, query_object, is_page) return operation_log_list_result @classmethod - def add_operation_log_services(cls, result_db: Session, page_object: OperLogModel): + async def add_operation_log_services(cls, query_db: AsyncSession, page_object: OperLogModel): """ 新增操作日志service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增操作日志对象 :return: 新增操作日志校验结果 """ try: - OperationLogDao.add_operation_log_dao(result_db, page_object) - result_db.commit() - result = dict(is_success=True, message='新增成功') + await OperationLogDao.add_operation_log_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudLogResponse(**result) + await query_db.rollback() + raise e @classmethod - def delete_operation_log_services(cls, result_db: Session, page_object: DeleteOperLogModel): + async def delete_operation_log_services(cls, query_db: AsyncSession, page_object: DeleteOperLogModel): """ 删除操作日志信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除操作日志对象 :return: 删除操作日志校验结果 """ - if page_object.oper_ids.split(','): + if page_object.oper_ids: oper_id_list = page_object.oper_ids.split(',') try: for oper_id in oper_id_list: - oper_id_dict = dict(oper_id=oper_id) - OperationLogDao.delete_operation_log_dao(result_db, OperLogModel(**oper_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await OperationLogDao.delete_operation_log_dao(query_db, OperLogModel(oper_id=oper_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入操作日志id为空') - return CrudLogResponse(**result) + raise ServiceException(message='传入操作日志id为空') @classmethod - def clear_operation_log_services(cls, result_db: Session, page_object: ClearOperLogModel): + async def clear_operation_log_services(cls, query_db: AsyncSession): """ 清除操作日志信息service - :param result_db: orm对象 - :param page_object: 清除操作日志对象 - :return: 清除操作日志校验结果 - """ - if page_object.oper_type == 'clear': - try: - OperationLogDao.clear_operation_log_dao(result_db) - result_db.commit() - result = dict(is_success=True, message='清除成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - else: - result = dict(is_success=False, message='清除标识不合法') - - return CrudLogResponse(**result) - @classmethod - def detail_operation_log_services(cls, result_db: Session, oper_id: int): - """ - 获取操作日志详细信息service - :param result_db: orm对象 - :param oper_id: 操作日志id - :return: 操作日志id对应的信息 + :param query_db: orm对象 + :return: 清除操作日志校验结果 """ - operation_log = OperationLogDao.get_operation_log_detail_by_id(result_db, oper_id=oper_id) - - return operation_log + try: + await OperationLogDao.clear_operation_log_dao(query_db) + await query_db.commit() + return CrudResponseModel(is_success=True, message='清除成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod async def export_operation_log_list_services(cls, request: Request, operation_log_list: List): """ 导出操作日志信息service + :param request: Request对象 :param operation_log_list: 操作日志信息列表 :return: 操作日志信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "oper_id": "日志编号", - "title": "系统模块", - "business_type": "操作类型", - "method": "方法名称", - "request_method": "请求方式", - "oper_name": "操作人员", - "dept_name": "部门名称", - "oper_url": "请求URL", - "oper_ip": "操作地址", - "oper_location": "操作地点", - "oper_param": "请求参数", - "json_result": "返回参数", - "status": "操作状态", - "error_msg": "错误消息", - "oper_time": "操作日期", - "cost_time": "消耗时间(毫秒)" + 'oper_id': '日志编号', + 'title': '系统模块', + 'business_type': '操作类型', + 'method': '方法名称', + 'request_method': '请求方式', + 'oper_name': '操作人员', + 'dept_name': '部门名称', + 'oper_url': '请求URL', + 'oper_ip': '操作地址', + 'oper_location': '操作地点', + 'oper_param': '请求参数', + 'json_result': '返回参数', + 'status': '操作状态', + 'error_msg': '错误消息', + 'oper_time': '操作日期', + 'cost_time': '消耗时间(毫秒)', } - data = [OperLogModel(**vars(row)).dict() for row in operation_log_list] - operation_type_list = await DictDataService.query_dict_data_list_from_cache_services(request.app.state.redis, dict_type='sys_oper_type') - operation_type_option = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in operation_type_list] + data = operation_log_list + operation_type_list = await DictDataService.query_dict_data_list_from_cache_services( + request.app.state.redis, dict_type='sys_oper_type' + ) + operation_type_option = [ + dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in operation_type_list + ] operation_type_option_dict = {item.get('value'): item for item in operation_type_option} for item in data: @@ -136,7 +139,9 @@ class OperationLogService: if str(item.get('business_type')) in operation_type_option_dict.keys(): item['business_type'] = operation_type_option_dict.get(str(item.get('business_type'))).get('label') - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @@ -148,117 +153,116 @@ class LoginLogService: """ @classmethod - def get_login_log_list_services(cls, result_db: Session, query_object: LoginLogQueryModel): + async def get_login_log_list_services( + cls, query_db: AsyncSession, query_object: LoginLogPageQueryModel, is_page: bool = False + ): """ 获取登录日志列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 登录日志列表信息对象 """ - operation_log_list_result = LoginLogDao.get_login_log_list(result_db, query_object) + operation_log_list_result = await LoginLogDao.get_login_log_list(query_db, query_object, is_page) return operation_log_list_result @classmethod - def add_login_log_services(cls, result_db: Session, page_object: LogininforModel): + async def add_login_log_services(cls, query_db: AsyncSession, page_object: LogininforModel): """ 新增登录日志service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增登录日志对象 :return: 新增登录日志校验结果 """ try: - LoginLogDao.add_login_log_dao(result_db, page_object) - result_db.commit() - result = dict(is_success=True, message='新增成功') + await LoginLogDao.add_login_log_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudLogResponse(**result) + await query_db.rollback() + raise e @classmethod - def delete_login_log_services(cls, result_db: Session, page_object: DeleteLoginLogModel): + async def delete_login_log_services(cls, query_db: AsyncSession, page_object: DeleteLoginLogModel): """ 删除操作日志信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除操作日志对象 :return: 删除操作日志校验结果 """ - if page_object.info_ids.split(','): + if page_object.info_ids: info_id_list = page_object.info_ids.split(',') try: for info_id in info_id_list: - info_id_dict = dict(info_id=info_id) - LoginLogDao.delete_login_log_dao(result_db, LogininforModel(**info_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await LoginLogDao.delete_login_log_dao(query_db, LogininforModel(info_id=info_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入登录日志id为空') - return CrudLogResponse(**result) + raise ServiceException(message='传入登录日志id为空') @classmethod - def clear_login_log_services(cls, result_db: Session, page_object: ClearLoginLogModel): + async def clear_login_log_services(cls, query_db: AsyncSession): """ 清除操作日志信息service - :param result_db: orm对象 - :param page_object: 清除操作日志对象 + + :param query_db: orm对象 :return: 清除操作日志校验结果 """ - if page_object.oper_type == 'clear': - try: - LoginLogDao.clear_login_log_dao(result_db) - result_db.commit() - result = dict(is_success=True, message='清除成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - else: - result = dict(is_success=False, message='清除标识不合法') - - return CrudLogResponse(**result) + try: + await LoginLogDao.clear_login_log_dao(query_db) + await query_db.commit() + return CrudResponseModel(is_success=True, message='清除成功') + except Exception as e: + await query_db.rollback() + raise e @classmethod async def unlock_user_services(cls, request: Request, unlock_user: UnlockUser): - locked_user = await request.app.state.redis.get(f"account_lock:{unlock_user.user_name}") + locked_user = await request.app.state.redis.get(f'account_lock:{unlock_user.user_name}') if locked_user: - await request.app.state.redis.delete(f"account_lock:{unlock_user.user_name}") - result = dict(is_success=True, message='解锁成功') + await request.app.state.redis.delete(f'account_lock:{unlock_user.user_name}') + return CrudResponseModel(is_success=True, message='解锁成功') else: - result = dict(is_success=False, message='该用户未锁定') - return CrudLogResponse(**result) + raise ServiceException(message='该用户未锁定') @staticmethod - def export_login_log_list_services(login_log_list: List): + async def export_login_log_list_services(login_log_list: List): """ 导出登录日志信息service + :param login_log_list: 登录日志信息列表 :return: 登录日志信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "info_id": "访问编号", - "user_name": "用户名称", - "ipaddr": "登录地址", - "login_location": "登录地点", - "browser": "浏览器", - "os": "操作系统", - "status": "登录状态", - "msg": "操作信息", - "login_time": "登录日期" + 'info_id': '访问编号', + 'user_name': '用户名称', + 'ipaddr': '登录地址', + 'login_location': '登录地点', + 'browser': '浏览器', + 'os': '操作系统', + 'status': '登录状态', + 'msg': '操作信息', + 'login_time': '登录日期', } - data = [LogininforModel(**vars(row)).dict() for row in login_log_list] + data = login_log_list for item in data: if item.get('status') == '0': item['status'] = '成功' else: item['status'] = '失败' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/dash-fastapi-backend/module_admin/service/login_service.py b/dash-fastapi-backend/module_admin/service/login_service.py index 9197bc7438bc0ec4e4774d5b0de7b6a57274e7d5..9511681ac0b438c2aeb13e6438ef4608bc6cc34d 100644 --- a/dash-fastapi-backend/module_admin/service/login_service.py +++ b/dash-fastapi-backend/module_admin/service/login_service.py @@ -1,330 +1,602 @@ -from fastapi import Request, Form -from fastapi import Depends -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jose import JWTError, jwt +import jwt import random import uuid -from datetime import timedelta -from typing import Dict -from module_admin.entity.vo.user_vo import * -from module_admin.entity.vo.login_vo import * -from module_admin.dao.login_dao import * -from module_admin.service.user_service import UserService -from module_admin.dao.user_dao import * -from config.env import AppConfig, JwtConfig, RedisInitKeyConfig -from utils.pwd_util import * -from utils.response_util import * -from utils.message_util import * +from datetime import datetime, timedelta, timezone +from fastapi import Depends, Form, Request +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from jwt.exceptions import InvalidTokenError +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Dict, List, Optional, Union +from config.constant import CommonConstant, MenuConstant +from config.enums import RedisInitKeyConfig +from config.env import AppConfig, JwtConfig from config.get_db import get_db +from exceptions.exception import LoginException, AuthException, ServiceException +from module_admin.dao.login_dao import login_by_account +from module_admin.dao.user_dao import UserDao +from module_admin.entity.do.menu_do import SysMenu +from module_admin.entity.vo.login_vo import MenuTreeModel, MetaModel, RouterModel, SmsCode, UserLogin, UserRegister +from module_admin.entity.vo.user_vo import AddUserModel, CurrentUserModel, ResetUserModel, TokenData, UserInfoModel +from module_admin.service.user_service import UserService +from utils.common_util import SqlalchemyUtil +from utils.log_util import logger +from utils.message_util import message_service +from utils.pwd_util import PwdUtil -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login/loginByAccount") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl='login') class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): """ 自定义OAuth2PasswordRequestForm类,增加验证码及会话编号参数 """ + def __init__( - self, - grant_type: str = Form(default=None, regex="password"), - username: str = Form(), - password: str = Form(), - scope: str = Form(default=""), - client_id: Optional[str] = Form(default=None), - client_secret: Optional[str] = Form(default=None), - captcha: Optional[str] = Form(default=""), - session_id: Optional[str] = Form(default=""), - login_info: Optional[Dict[str, str]] = Form(default=None) + self, + grant_type: str = Form(default=None, regex='password'), + username: str = Form(), + password: str = Form(), + scope: str = Form(default=''), + client_id: Optional[str] = Form(default=None), + client_secret: Optional[str] = Form(default=None), + code: Optional[str] = Form(default=''), + uuid: Optional[str] = Form(default=''), + login_info: Optional[Dict[str, str]] = Form(default=None), ): - super().__init__(grant_type=grant_type, username=username, password=password, - scope=scope, client_id=client_id, client_secret=client_secret) - self.captcha = captcha - self.session_id = session_id + super().__init__( + grant_type=grant_type, + username=username, + password=password, + scope=scope, + client_id=client_id, + client_secret=client_secret, + ) + self.code = code + self.uuid = uuid self.login_info = login_info -async def get_current_user(request: Request = Request, token: str = Depends(oauth2_scheme), - result_db: Session = Depends(get_db)): +class LoginService: """ - 根据token获取当前用户信息 - :param request: Request对象 - :param token: 用户token - :param result_db: orm对象 - :return: 当前用户信息对象 - :raise: 令牌异常AuthException + 登录模块服务层 """ - # if token[:6] != 'Bearer': - # logger.warning("用户token不合法") - # raise AuthException(data="", message="用户token不合法") - try: - if token.startswith('Bearer'): - token = token.split(' ')[1] - payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) - user_id: str = payload.get("user_id") - session_id: str = payload.get("session_id") - if user_id is None: - logger.warning("用户token不合法") - raise AuthException(data="", message="用户token不合法") - token_data = TokenData(user_id=int(user_id)) - except JWTError: - logger.warning("用户token已失效,请重新登录") - raise AuthException(data="", message="用户token已失效,请重新登录") - user = UserDao.get_user_by_id(result_db, user_id=token_data.user_id) - if user is None: - logger.warning("用户token不合法") - raise AuthException(data="", message="用户token不合法") - if AppConfig.app_same_time_login: - redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}") - else: - # 此方法可实现同一账号同一时间只能登录一次 - redis_token = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{user.user_basic_info.user_id}") - if token == redis_token: + + @classmethod + async def authenticate_user(cls, request: Request, query_db: AsyncSession, login_user: UserLogin): + """ + 根据用户名密码校验用户登录 + + :param request: Request对象 + :param query_db: orm对象 + :param login_user: 登录用户对象 + :return: 校验结果 + """ + await cls.__check_login_ip(request) + account_lock = await request.app.state.redis.get( + f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}' + ) + if login_user.user_name == account_lock: + logger.warning('账号已锁定,请稍后再试') + raise LoginException(data='', message='账号已锁定,请稍后再试') + # 判断请求是否来自于api文档,如果是返回指定格式的结果,用于修复api文档认证成功后token显示undefined的bug + request_from_swagger = ( + request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False + ) + request_from_redoc = ( + request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False + ) + # 判断是否开启验证码,开启则验证,否则不验证(dev模式下来自API文档的登录请求不检验) + if not login_user.captcha_enabled or ( + (request_from_swagger or request_from_redoc) and AppConfig.app_env == 'dev' + ): + pass + else: + await cls.__check_login_captcha(request, login_user) + user = await login_by_account(query_db, login_user.user_name) + if not user: + logger.warning('用户不存在') + raise LoginException(data='', message='用户不存在') + if not PwdUtil.verify_password(login_user.password, user[0].password): + cache_password_error_count = await request.app.state.redis.get( + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' + ) + password_error_counted = 0 + if cache_password_error_count: + password_error_counted = cache_password_error_count + password_error_count = int(password_error_counted) + 1 + await request.app.state.redis.set( + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}', + password_error_count, + ex=timedelta(minutes=10), + ) + if password_error_count > 5: + await request.app.state.redis.delete( + f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}' + ) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCOUNT_LOCK.key}:{login_user.user_name}', + login_user.user_name, + ex=timedelta(minutes=10), + ) + logger.warning('10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试') + raise LoginException(data='', message='10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试') + logger.warning('密码错误') + raise LoginException(data='', message='密码错误') + if user[0].status == '1': + logger.warning('用户已停用') + raise LoginException(data='', message='用户已停用') + await request.app.state.redis.delete(f'{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.key}:{login_user.user_name}') + return user + + @classmethod + async def __check_login_ip(cls, request: Request): + """ + 校验用户登录ip是否在黑名单内 + + :param request: Request对象 + :return: 校验结果 + """ + black_ip_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.login.blackIPList') + black_ip_list = black_ip_value.split(',') if black_ip_value else [] + if request.headers.get('X-Forwarded-For') in black_ip_list: + logger.warning('当前IP禁止登录') + raise LoginException(data='', message='当前IP禁止登录') + return True + + @classmethod + async def __check_login_captcha(cls, request: Request, login_user: UserLogin): + """ + 校验用户登录验证码 + + :param request: Request对象 + :param login_user: 登录用户对象 + :return: 校验结果 + """ + captcha_value = await request.app.state.redis.get(f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{login_user.uuid}') + if not captcha_value: + logger.warning('验证码已失效') + raise LoginException(data='', message='验证码已失效') + if login_user.code != str(captcha_value): + logger.warning('验证码错误') + raise LoginException(data='', message='验证码错误') + return True + + @classmethod + async def create_access_token(cls, data: dict, expires_delta: Union[timedelta, None] = None): + """ + 根据登录信息创建当前用户token + + :param data: 登录信息 + :param expires_delta: token有效期 + :return: token + """ + to_encode = data.copy() + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + timedelta(minutes=30) + to_encode.update({'exp': expire}) + encoded_jwt = jwt.encode(to_encode, JwtConfig.jwt_secret_key, algorithm=JwtConfig.jwt_algorithm) + return encoded_jwt + + @classmethod + async def get_current_user( + cls, request: Request = Request, token: str = Depends(oauth2_scheme), query_db: AsyncSession = Depends(get_db) + ): + """ + 根据token获取当前用户信息 + + :param request: Request对象 + :param token: 用户token + :param query_db: orm对象 + :return: 当前用户信息对象 + :raise: 令牌异常AuthException + """ + # if token[:6] != 'Bearer': + # logger.warning("用户token不合法") + # raise AuthException(data="", message="用户token不合法") + try: + if token.startswith('Bearer'): + token = token.split(' ')[1] + payload = jwt.decode(token, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) + user_id: str = payload.get('user_id') + session_id: str = payload.get('session_id') + if not user_id: + logger.warning('用户token不合法') + raise AuthException(data='', message='用户token不合法') + token_data = TokenData(user_id=int(user_id)) + except InvalidTokenError: + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') + query_user = await UserDao.get_user_by_id(query_db, user_id=token_data.user_id) + if query_user.get('user_basic_info') is None: + logger.warning('用户token不合法') + raise AuthException(data='', message='用户token不合法') if AppConfig.app_same_time_login: - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}", redis_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) + redis_token = await request.app.state.redis.get(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') else: - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{user.user_basic_info.user_id}", redis_token, - ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes)) - - return CurrentUserInfoServiceResponse( - user=user.user_basic_info, - dept=user.user_dept_info, - role=user.user_role_info, - post=user.user_post_info, - menu=user.user_menu_info + # 此方法可实现同一账号同一时间只能登录一次 + redis_token = await request.app.state.redis.get( + f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}" + ) + if token == redis_token: + if AppConfig.app_same_time_login: + await request.app.state.redis.set( + f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}', + redis_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) + else: + await request.app.state.redis.set( + f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:{query_user.get('user_basic_info').user_id}", + redis_token, + ex=timedelta(minutes=JwtConfig.jwt_redis_expire_minutes), + ) + + role_id_list = [item.role_id for item in query_user.get('user_role_info')] + if 1 in role_id_list: + permissions = ['*:*:*'] + else: + permissions = [row.perms for row in query_user.get('user_menu_info')] + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + roles = [row.role_key for row in query_user.get('user_role_info')] + + current_user = CurrentUserModel( + permissions=permissions, + roles=roles, + user=UserInfoModel( + **SqlalchemyUtil.serialize_result(query_user.get('user_basic_info')), + post_ids=post_ids, + role_ids=role_ids, + dept=SqlalchemyUtil.serialize_result(query_user.get('user_dept_info')), + role=SqlalchemyUtil.serialize_result(query_user.get('user_role_info')), + ), + ) + return current_user + else: + logger.warning('用户token已失效,请重新登录') + raise AuthException(data='', message='用户token已失效,请重新登录') + + @classmethod + async def get_current_user_routers(cls, user_id: int, query_db: AsyncSession): + """ + 根据用户id获取当前用户路由信息 + + :param user_id: 用户id + :param query_db: orm对象 + :return: 当前用户路由信息对象 + """ + query_user = await UserDao.get_user_by_id(query_db, user_id=user_id) + user_router_menu = sorted( + [ + row + for row in query_user.get('user_menu_info') + if row.menu_type in [MenuConstant.TYPE_DIR, MenuConstant.TYPE_MENU] + ], + key=lambda x: x.order_num, ) - else: - logger.warning("用户token已失效,请重新登录") - raise AuthException(data="", message="用户token已失效,请重新登录") - - -async def get_sms_code_services(request: Request, result_db: Session, user: ResetUserModel): - """ - 获取短信验证码service - :param request: Request对象 - :param result_db: orm对象 - :param user: 用户对象 - :return: 短信验证码对象 - """ - redis_sms_result = await request.app.state.redis.get(f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{user.session_id}") - if redis_sms_result: - return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='短信验证码仍在有效期内')) - is_user = UserDao.get_user_by_name(result_db, user.user_name) - if is_user: - sms_code = str(random.randint(100000, 999999)) - session_id = str(uuid.uuid4()) - await request.app.state.redis.set(f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{session_id}", sms_code, ex=timedelta(minutes=2)) - # 此处模拟调用短信服务 - message_service(sms_code) - - return SmsCode(**dict(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功')) - - return SmsCode(**dict(is_success=False, sms_code='', session_id='', message='用户不存在')) - - -async def forget_user_services(request: Request, result_db: Session, forget_user: ResetUserModel): - """ - 用户忘记密码services - :param request: Request对象 - :param result_db: orm对象 - :param forget_user: 重置用户对象 - :return: 重置结果 - """ - redis_sms_result = await request.app.state.redis.get(f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{forget_user.session_id}") - if forget_user.sms_code == redis_sms_result: - forget_user.password = PwdUtil.get_password_hash(forget_user.password) - forget_user.user_id = UserDao.get_user_by_name(result_db, forget_user.user_name).user_id - edit_result = UserService.reset_user_services(result_db, forget_user) - result = edit_result.dict() - elif not redis_sms_result: - result = dict(is_success=False, message='短信验证码已过期') - else: - await request.app.state.redis.delete(f"{RedisInitKeyConfig.SMS_CODE.get('key')}:{forget_user.session_id}") - result = dict(is_success=False, message='短信验证码不正确') - - return CrudUserResponse(**result) - - -async def logout_services(request: Request, session_id: str): - """ - 退出登录services - :param request: Request对象 - :param session_id: 会话编号 - :return: 退出登录结果 - """ - await request.app.state.redis.delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}") - # await request.app.state.redis.delete(f'{current_user.user.user_id}_access_token') - # await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id') - - return True - + menus = cls.__generate_menus(0, user_router_menu) + user_router = cls.__generate_user_router_menu(menus) + return [router.model_dump(exclude_unset=True) for router in user_router] + + @classmethod + def __generate_menus(cls, pid: int, permission_list: List[SysMenu]): + """ + 工具方法:根据菜单信息生成菜单信息树形嵌套数据 + + :param pid: 菜单id + :param permission_list: 菜单列表信息 + :return: 菜单信息树形嵌套数据 + """ + menu_list: List[MenuTreeModel] = [] + for permission in permission_list: + if permission.parent_id == pid: + children = cls.__generate_menus(permission.menu_id, permission_list) + menu_list_data = MenuTreeModel(**SqlalchemyUtil.serialize_result(permission)) + if children: + menu_list_data.children = children + menu_list.append(menu_list_data) + + return menu_list + + @classmethod + def __generate_user_router_menu(cls, permission_list: List[MenuTreeModel]): + """ + 工具方法:根据菜单树信息生成路由信息树形嵌套数据 + + :param permission_list: 菜单树列表信息 + :return: 路由信息树形嵌套数据 + """ + router_list: List[RouterModel] = [] + for permission in permission_list: + router = RouterModel( + hidden=True if permission.visible == '1' else False, + name=RouterUtil.get_router_name(permission), + path=RouterUtil.get_router_path(permission), + component=RouterUtil.get_component(permission), + query=permission.query, + meta=MetaModel( + title=permission.menu_name, + icon=permission.icon, + no_cache=True if permission.is_cache == 1 else False, + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), + ) + c_menus = permission.children + if c_menus and permission.menu_type == MenuConstant.TYPE_DIR: + router.always_show = True + router.redirect = 'noRedirect' + router.children = cls.__generate_user_router_menu(c_menus) + elif RouterUtil.is_menu_frame(permission): + router.meta = None + children_list: List[RouterModel] = [] + children = RouterModel( + path=permission.path, + component=permission.component, + name=RouterUtil.get_route_name(permission.route_name, permission.path), + meta=MetaModel( + title=permission.menu_name, + icon=permission.icon, + no_cache=True if permission.is_cache == 1 else False, + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), + query=permission.query, + ) + children_list.append(children) + router.children = children_list + elif permission.parent_id == 0 and RouterUtil.is_inner_link(permission): + router.meta = MetaModel(title=permission.menu_name, icon=permission.icon) + router.path = '/' + children_list: List[RouterModel] = [] + router_path = RouterUtil.inner_link_replace_each(permission.path) + children = RouterModel( + path=router_path, + component=MenuConstant.INNER_LINK, + name=RouterUtil.get_route_name(permission.route_name, permission.path), + meta=MetaModel( + title=permission.menu_name, + icon=permission.icon, + link=permission.path if RouterUtil.is_http(permission.path) else None, + ), + ) + children_list.append(children) + router.children = children_list + + router_list.append(router) + + return router_list + + @classmethod + async def register_user_services(cls, request: Request, query_db: AsyncSession, user_register: UserRegister): + """ + 用户注册services + + :param request: Request对象 + :param query_db: orm对象 + :param user_register: 注册用户对象 + :return: 注册结果 + """ + register_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.registerUser') + == 'true' + else False + ) + captcha_enabled = ( + True + if await request.app.state.redis.get(f'{RedisInitKeyConfig.SYS_CONFIG.key}:sys.account.captchaEnabled') + == 'true' + else False + ) + if user_register.password == user_register.confirm_password: + if register_enabled: + if captcha_enabled: + captcha_value = await request.app.state.redis.get( + f'{RedisInitKeyConfig.CAPTCHA_CODES.key}:{user_register.uuid}' + ) + if not captcha_value: + raise ServiceException(message='验证码已失效') + elif user_register.code != str(captcha_value): + raise ServiceException(message='验证码错误') + add_user = AddUserModel( + user_name=user_register.username, + nick_name=user_register.username, + password=PwdUtil.get_password_hash(user_register.password), + ) + result = await UserService.add_user_services(query_db, add_user) + return result + else: + raise ServiceException(message='注册程序已关闭,禁止注册') + else: + raise ServiceException(message='两次输入的密码不一致') + + @classmethod + async def get_sms_code_services(cls, request: Request, query_db: AsyncSession, user: ResetUserModel): + """ + 获取短信验证码service + + :param request: Request对象 + :param query_db: orm对象 + :param user: 用户对象 + :return: 短信验证码对象 + """ + redis_sms_result = await request.app.state.redis.get(f'{RedisInitKeyConfig.SMS_CODE.key}:{user.session_id}') + if redis_sms_result: + raise ServiceException(message='短信验证码仍在有效期内') + is_user = await UserDao.get_user_by_name(query_db, user.user_name) + if is_user: + sms_code = str(random.randint(100000, 999999)) + session_id = str(uuid.uuid4()) + await request.app.state.redis.set( + f'{RedisInitKeyConfig.SMS_CODE.key}:{session_id}', sms_code, ex=timedelta(minutes=2) + ) + # 此处模拟调用短信服务 + message_service(sms_code) + + return SmsCode(is_success=True, sms_code=sms_code, session_id=session_id, message='获取成功') + + raise ServiceException(message='用户不存在') + + @classmethod + async def forget_user_services(cls, request: Request, query_db: AsyncSession, forget_user: ResetUserModel): + """ + 用户忘记密码services + + :param request: Request对象 + :param query_db: orm对象 + :param forget_user: 重置用户对象 + :return: 重置结果 + """ + redis_sms_result = await request.app.state.redis.get( + f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}' + ) + if forget_user.sms_code == redis_sms_result: + forget_user.user_id = (await UserDao.get_user_by_name(query_db, forget_user.user_name)).user_id + edit_result = await UserService.reset_user_services(query_db, forget_user) + return edit_result + elif not redis_sms_result: + raise ServiceException(message='短信验证码已过期') + else: + await request.app.state.redis.delete(f'{RedisInitKeyConfig.SMS_CODE.key}:{forget_user.session_id}') + raise ServiceException(message='短信验证码不正确') -async def check_login_ip(request: Request, login_user: UserLogin): - """ - 校验用户登录ip是否在黑名单内 - :param request: Request对象 - :param login_user: 登录用户对象 - :return: 校验结果 - """ - black_ip_value = await request.app.state.redis.get( - f"{RedisInitKeyConfig.SYS_CONFIG.get('key')}:sys.login.blackIPList") - black_ip_list = black_ip_value.split(',') if black_ip_value else [] - if login_user.login_info.get('ipaddr') in black_ip_list: - logger.warning("当前IP禁止登录") - raise LoginException(data="", message="当前IP禁止登录") - return True + @classmethod + async def logout_services(cls, request: Request, session_id: str): + """ + 退出登录services + :param request: Request对象 + :param session_id: 会话编号 + :return: 退出登录结果 + """ + await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{session_id}') + # await request.app.state.redis.delete(f'{current_user.user.user_id}_access_token') + # await request.app.state.redis.delete(f'{current_user.user.user_id}_session_id') -async def check_login_captcha(request: Request, login_user: UserLogin): - """ - 校验用户登录验证码 - :param request: Request对象 - :param login_user: 登录用户对象 - :return: 校验结果 - """ - captcha_value = await request.app.state.redis.get(f"{RedisInitKeyConfig.CAPTCHA_CODES.get('key')}:{login_user.session_id}") - if not captcha_value: - logger.warning("验证码已失效") - raise LoginException(data="", message="验证码已失效") - if login_user.captcha != str(captcha_value): - logger.warning("验证码错误") - raise LoginException(data="", message="验证码错误") - return True + return True -async def authenticate_user(request: Request, query_db: Session, login_user: UserLogin): - """ - 根据用户名密码校验用户登录 - :param request: Request对象 - :param query_db: orm对象 - :param login_user: 登录用户对象 - :return: 校验结果 +class RouterUtil: """ - await check_login_ip(request, login_user) - account_lock = await request.app.state.redis.get(f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}") - if login_user.user_name == account_lock: - logger.warning("账号已锁定,请稍后再试") - raise LoginException(data="", message="账号已锁定,请稍后再试") - # 判断请求是否来自于api文档 - request_from_swagger = request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False - request_from_redoc = request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False - # 判断是否开启验证码,开启则验证,否则不验证(dev模式下来自API文档的登录请求不检验) - if not login_user.captcha_enabled or ((request_from_swagger or request_from_redoc) and AppConfig.app_env == 'dev'): - pass - else: - await check_login_captcha(request, login_user) - user = login_by_account(query_db, login_user.user_name) - if not user: - logger.warning("用户不存在") - raise LoginException(data="", message="用户不存在") - if not PwdUtil.verify_password(login_user.password, user[0].password): - cache_password_error_count = await request.app.state.redis.get(f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") - password_error_counted = 0 - if cache_password_error_count: - password_error_counted = cache_password_error_count - password_error_count = int(password_error_counted) + 1 - await request.app.state.redis.set(f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}", password_error_count, - ex=timedelta(minutes=10)) - if password_error_count > 5: - await request.app.state.redis.delete(f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") - await request.app.state.redis.set(f"{RedisInitKeyConfig.ACCOUNT_LOCK.get('key')}:{login_user.user_name}", login_user.user_name, - ex=timedelta(minutes=10)) - logger.warning("10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试") - raise LoginException(data="", message="10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试") - logger.warning("密码错误") - raise LoginException(data="", message="密码错误") - if user[0].status == '1': - logger.warning("用户已停用") - raise LoginException(data="", message="用户已停用") - await request.app.state.redis.delete(f"{RedisInitKeyConfig.PASSWORD_ERROR_COUNT.get('key')}:{login_user.user_name}") - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): + 路由处理工具类 """ - 根据登录信息创建当前用户token - :param data: 登录信息 - :param expires_delta: token有效期 - :return: token - """ - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, JwtConfig.jwt_secret_key, algorithm=JwtConfig.jwt_algorithm) - return encoded_jwt - - -def deal_user_dept_info(db: Session, dept_info: DeptInfo): - tmp_dept_name = dept_info.dept_name - dept_ancestors = dept_info.ancestors.split(',') - tmp_dept_list = [] - for item in dept_ancestors: - dept_obj = UserDao.get_user_dept_info(db, int(item)) - if dept_obj: - tmp_dept_list.append(dept_obj.dept_name) - tmp_dept_list.append(tmp_dept_name) - user_dept_info = '/'.join(tmp_dept_list) - - return user_dept_info - - -def deal_user_role_info(role_info: RoleInfo): - tmp_user_role_info = [] - for item in role_info.role_info: - tmp_user_role_info.append(item.role_name) - user_role_info = '/'.join(tmp_user_role_info) - - return user_role_info + @classmethod + def get_router_name(cls, menu: MenuTreeModel): + """ + 获取路由名称 + + :param menu: 菜单数对象 + :return: 路由名称 + """ + # 非外链并且是一级目录(类型为目录) + if cls.is_menu_frame(menu): + return '' + + return cls.get_route_name(menu.route_name, menu.path) + + @classmethod + def get_route_name(cls, name: str, path: str): + """ + 获取路由名称,如没有配置路由名称则取路由地址 + + :param name: 路由名称 + :param path: 路由地址 + :return: 路由名称(驼峰格式) + """ + router_name = name if name else path + return router_name.capitalize() + + @classmethod + def get_router_path(cls, menu: MenuTreeModel): + """ + 获取路由地址 + + :param menu: 菜单数对象 + :return: 路由地址 + """ + # 内链打开外网方式 + router_path = menu.path + if menu.parent_id != 0 and cls.is_inner_link(menu): + router_path = cls.inner_link_replace_each(router_path) + # 非外链并且是一级目录(类型为目录) + if menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_DIR and menu.is_frame == MenuConstant.NO_FRAME: + router_path = f'/{menu.path}' + # 非外链并且是一级目录(类型为菜单) + elif cls.is_menu_frame(menu): + router_path = '/' + return router_path + + @classmethod + def get_component(cls, menu: MenuTreeModel): + """ + 获取组件信息 + + :param menu: 菜单数对象 + :return: 组件信息 + """ + component = MenuConstant.LAYOUT + if menu.component and not cls.is_menu_frame(menu): + component = menu.component + elif (menu.component is None or menu.component == '') and menu.parent_id != 0 and cls.is_inner_link(menu): + component = MenuConstant.INNER_LINK + elif (menu.component is None or menu.component == '') and cls.is_parent_view(menu): + component = MenuConstant.PARENT_VIEW + return component + + @classmethod + def is_menu_frame(cls, menu: MenuTreeModel): + """ + 判断是否为菜单内部跳转 + + :param menu: 菜单数对象 + :return: 是否为菜单内部跳转 + """ + return ( + menu.parent_id == 0 and menu.menu_type == MenuConstant.TYPE_MENU and menu.is_frame == MenuConstant.NO_FRAME + ) -def deal_user_menu_info(pid: int, permission_list: MenuList): - """ - 工具方法:根据菜单信息生成树形嵌套数据 - :param pid: 菜单id - :param permission_list: 菜单列表信息 - :return: 菜单树形嵌套数据 - """ - menu_list = [] - for permission in permission_list.menu_info: - if permission.parent_id == pid: - children = deal_user_menu_info(permission.menu_id, permission_list) - antd_menu_list_data = {} - if children and permission.menu_type == 'M': - antd_menu_list_data['component'] = 'SubMenu' - antd_menu_list_data['props'] = { - 'key': str(permission.menu_id), - 'title': permission.menu_name, - 'icon': permission.icon - } - antd_menu_list_data['children'] = children - elif children and permission.menu_type == 'C': - antd_menu_list_data['component'] = 'Item' - antd_menu_list_data['props'] = { - 'key': str(permission.menu_id), - 'title': permission.menu_name, - 'icon': permission.icon, - 'href': permission.path, - 'modules': permission.component - } - antd_menu_list_data['button'] = children - elif permission.menu_type == 'F': - antd_menu_list_data['component'] = 'Button' - antd_menu_list_data['props'] = { - 'key': str(permission.menu_id), - 'title': permission.menu_name, - 'icon': permission.icon - } - else: - antd_menu_list_data['component'] = 'Item' - antd_menu_list_data['props'] = { - 'key': str(permission.menu_id), - 'title': permission.menu_name, - 'icon': permission.icon, - 'href': permission.path, - } - menu_list.append(antd_menu_list_data) - - return menu_list + @classmethod + def is_inner_link(cls, menu: MenuTreeModel): + """ + 判断是否为内链组件 + + :param menu: 菜单数对象 + :return: 是否为内链组件 + """ + return menu.is_frame == MenuConstant.NO_FRAME and cls.is_http(menu.path) + + @classmethod + def is_parent_view(cls, menu: MenuTreeModel): + """ + 判断是否为parent_view组件 + + :param menu: 菜单数对象 + :return: 是否为parent_view组件 + """ + return menu.parent_id != 0 and menu.menu_type == MenuConstant.TYPE_DIR + + @classmethod + def is_http(cls, link: str): + """ + 判断是否为http(s)://开头 + + :param link: 链接 + :return: 是否为http(s)://开头 + """ + return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + + @classmethod + def inner_link_replace_each(cls, path: str): + """ + 内链域名特殊字符替换 + + :param path: 内链域名 + :return: 替换后的内链域名 + """ + old_values = [CommonConstant.HTTP, CommonConstant.HTTPS, CommonConstant.WWW, '.', ':'] + new_values = ['', '', '', '/', '/'] + for old, new in zip(old_values, new_values): + path = path.replace(old, new) + return path diff --git a/dash-fastapi-backend/module_admin/service/menu_service.py b/dash-fastapi-backend/module_admin/service/menu_service.py index 203495d32d9203470046f5381f782333330894d3..2cf7639b38ebb71be15d8a60828654007c6b4feb 100644 --- a/dash-fastapi-backend/module_admin/service/menu_service.py +++ b/dash-fastapi-backend/module_admin/service/menu_service.py @@ -1,6 +1,15 @@ -from module_admin.entity.vo.menu_vo import * -from module_admin.dao.menu_dao import * -from module_admin.entity.vo.user_vo import CurrentUserInfoServiceResponse +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional +from config.constant import CommonConstant, MenuConstant +from exceptions.exception import ServiceException, ServiceWarning +from module_admin.dao.menu_dao import MenuDao +from module_admin.dao.role_dao import RoleDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.menu_vo import DeleteMenuModel, MenuQueryModel, MenuModel +from module_admin.entity.vo.role_vo import RoleMenuQueryModel +from module_admin.entity.vo.user_vo import CurrentUserModel +from utils.common_util import SqlalchemyUtil +from utils.string_util import StringUtil class MenuService: @@ -9,162 +18,199 @@ class MenuService: """ @classmethod - def get_menu_tree_services(cls, result_db: Session, page_object: MenuTreeModel, current_user: Optional[CurrentUserInfoServiceResponse] = None): + async def get_menu_tree_services(cls, query_db: AsyncSession, current_user: Optional[CurrentUserModel] = None): """ 获取菜单树信息service - :param result_db: orm对象 - :param page_object: 查询参数对象 + + :param query_db: orm对象 :param current_user: 当前用户对象 :return: 菜单树信息对象 """ - menu_tree_option = [] - menu_list_result = MenuDao.get_menu_list_for_tree(result_db, MenuModel(**page_object.dict()), current_user.user.user_id, current_user.role) - menu_tree_result = cls.get_menu_tree(0, MenuTree(menu_tree=menu_list_result)) - if page_object.type != 'role': - menu_tree_option.append(dict(title='主类目', value='0', key='0', children=menu_tree_result)) - else: - menu_tree_option = [menu_tree_result, menu_list_result] + menu_list_result = await MenuDao.get_menu_list_for_tree( + query_db, current_user.user.user_id, current_user.user.role + ) + menu_tree_result = cls.list_to_tree(menu_list_result) - return menu_tree_option + return menu_tree_result @classmethod - def get_menu_tree_for_edit_option_services(cls, result_db: Session, page_object: MenuModel, current_user: Optional[CurrentUserInfoServiceResponse] = None): + async def get_role_menu_tree_services( + cls, query_db: AsyncSession, role_id: int, current_user: Optional[CurrentUserModel] = None + ): """ - 获取菜单编辑菜单树信息service - :param result_db: orm对象 - :param page_object: 查询参数对象 - :param current_user: 当前用户 - :return: 菜单树信息对象 + 根据角色id获取菜单树信息service + + :param query_db: orm对象 + :param role_id: 角色id + :param current_user: 当前用户对象 + :return: 当前角色id的菜单树信息对象 """ - menu_tree_option = [] - menu_list_result = MenuDao.get_menu_info_for_edit_option(result_db, page_object, current_user.user.user_id, current_user.role) - menu_tree_result = cls.get_menu_tree(0, MenuTree(menu_tree=menu_list_result)) - menu_tree_option.append(dict(title='主类目', value='0', key='0', children=menu_tree_result)) + menu_list_result = await MenuDao.get_menu_list_for_tree( + query_db, current_user.user.user_id, current_user.user.role + ) + menu_tree_result = cls.list_to_tree(menu_list_result) + role = await RoleDao.get_role_detail_by_id(query_db, role_id) + role_menu_list = await RoleDao.get_role_menu_dao(query_db, role) + checked_keys = [row.menu_id for row in role_menu_list] + result = RoleMenuQueryModel(menus=menu_tree_result, checked_keys=checked_keys) - return menu_tree_option + return result @classmethod - def get_menu_list_services(cls, result_db: Session, page_object: MenuModel, current_user: Optional[CurrentUserInfoServiceResponse] = None): + async def get_menu_list_services( + cls, query_db: AsyncSession, page_object: MenuQueryModel, current_user: Optional[CurrentUserModel] = None + ): """ 获取菜单列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 分页查询参数对象 :param current_user: 当前用户对象 :return: 菜单列表信息对象 """ - menu_list_result = MenuDao.get_menu_list(result_db, page_object, current_user.user.user_id, current_user.role) + menu_list_result = await MenuDao.get_menu_list( + query_db, page_object, current_user.user.user_id, current_user.user.role + ) - return menu_list_result + return SqlalchemyUtil.serialize_result(menu_list_result) @classmethod - def add_menu_services(cls, result_db: Session, page_object: MenuModel): + async def check_menu_name_unique_services(cls, query_db: AsyncSession, page_object: MenuModel): + """ + 校验菜单名称是否唯一service + + :param query_db: orm对象 + :param page_object: 菜单对象 + :return: 校验结果 + """ + menu_id = -1 if page_object.menu_id is None else page_object.menu_id + menu = await MenuDao.get_menu_detail_by_info(query_db, MenuModel(menu_name=page_object.menu_name)) + if menu and menu.menu_id != menu_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): """ 新增菜单信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增菜单对象 :return: 新增菜单校验结果 """ - menu = MenuDao.get_menu_detail_by_info(result_db, MenuModel( - **dict(parent_id=page_object.parent_id, menu_name=page_object.menu_name, menu_type=page_object.menu_type))) - if menu: - result = dict(is_success=False, message='同一目录下不允许存在同名同类型的菜单') + if not await cls.check_menu_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增菜单{page_object.post_name}失败,菜单名称已存在') + elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + raise ServiceException(message=f'新增菜单{page_object.post_name}失败,地址必须以http(s)://开头') else: try: - MenuDao.add_menu_dao(result_db, page_object) - result_db.commit() - result = dict(is_success=True, message='新增成功') + await MenuDao.add_menu_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudMenuResponse(**result) + await query_db.rollback() + raise e @classmethod - def edit_menu_services(cls, result_db: Session, page_object: MenuModel): + async def edit_menu_services(cls, query_db: AsyncSession, page_object: MenuModel): """ 编辑菜单信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑部门对象 :return: 编辑菜单校验结果 """ - edit_menu = page_object.dict(exclude_unset=True) - menu_info = cls.detail_menu_services(result_db, edit_menu.get('menu_id')) - if menu_info: - if menu_info.parent_id != page_object.parent_id or menu_info.menu_name != page_object.menu_name or menu_info.menu_type != page_object.menu_type: - menu = MenuDao.get_menu_detail_by_info(result_db, MenuModel( - **dict(parent_id=page_object.parent_id, menu_name=page_object.menu_name, menu_type=page_object.menu_type))) - if menu: - result = dict(is_success=False, message='同一目录下不允许存在同名同类型的菜单') - return CrudMenuResponse(**result) - try: - MenuDao.edit_menu_dao(result_db, edit_menu) - result_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + edit_menu = page_object.model_dump(exclude_unset=True) + menu_info = await cls.menu_detail_services(query_db, page_object.menu_id) + if menu_info.menu_id: + if not await cls.check_menu_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改菜单{page_object.post_name}失败,菜单名称已存在') + elif page_object.is_frame == MenuConstant.YES_FRAME and not StringUtil.is_http(page_object.path): + raise ServiceException(message=f'修改菜单{page_object.post_name}失败,地址必须以http(s)://开头') + elif page_object.menu_id == page_object.parent_id: + raise ServiceException(message=f'修改菜单{page_object.post_name}失败,上级菜单不能选择自己') + else: + try: + await MenuDao.edit_menu_dao(query_db, edit_menu) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='菜单不存在') - - return CrudMenuResponse(**result) + raise ServiceException(message='菜单不存在') @classmethod - def delete_menu_services(cls, result_db: Session, page_object: DeleteMenuModel): + async def delete_menu_services(cls, query_db: AsyncSession, page_object: DeleteMenuModel): """ 删除菜单信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除菜单对象 :return: 删除菜单校验结果 """ - if page_object.menu_ids.split(','): + if page_object.menu_ids: menu_id_list = page_object.menu_ids.split(',') try: for menu_id in menu_id_list: - menu_id_dict = dict(menu_id=menu_id) - MenuDao.delete_menu_dao(result_db, MenuModel(**menu_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + if (await MenuDao.has_child_by_menu_id_dao(query_db, int(menu_id))) > 0: + raise ServiceWarning(message='存在子菜单,不允许删除') + elif (await MenuDao.check_menu_exist_role_dao(query_db, int(menu_id))) > 0: + raise ServiceWarning(message='菜单已分配,不允许删除') + await MenuDao.delete_menu_dao(query_db, MenuModel(menu_id=menu_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入菜单id为空') - return CrudMenuResponse(**result) + raise ServiceException(message='传入菜单id为空') @classmethod - def detail_menu_services(cls, result_db: Session, menu_id: int): + async def menu_detail_services(cls, query_db: AsyncSession, menu_id: int): """ 获取菜单详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param menu_id: 菜单id :return: 菜单id对应的信息 """ - menu = MenuDao.get_menu_detail_by_id(result_db, menu_id=menu_id) + menu = await MenuDao.get_menu_detail_by_id(query_db, menu_id=menu_id) + if menu: + result = MenuModel(**SqlalchemyUtil.serialize_result(menu)) + else: + result = MenuModel(**dict()) - return menu + return result @classmethod - def get_menu_tree(cls, pid: int, permission_list: MenuTree): + def list_to_tree(cls, permission_list: list) -> list: """ - 工具方法:根据菜单信息生成树形嵌套数据 - :param pid: 菜单id + 工具方法:根据菜单列表信息生成树形嵌套数据 + :param permission_list: 菜单列表信息 :return: 菜单树形嵌套数据 """ - menu_list = [] - for permission in permission_list.menu_tree: - if permission.parent_id == pid: - children = cls.get_menu_tree(permission.menu_id, permission_list) - menu_list_data = {} - if children: - menu_list_data['title'] = permission.menu_name - menu_list_data['key'] = str(permission.menu_id) - menu_list_data['value'] = str(permission.menu_id) - menu_list_data['children'] = children - else: - menu_list_data['title'] = permission.menu_name - menu_list_data['key'] = str(permission.menu_id) - menu_list_data['value'] = str(permission.menu_id) - menu_list.append(menu_list_data) - - return menu_list + permission_list = [ + dict(key=str(item.menu_id), title=item.menu_name, value=str(item.menu_id), parent_id=str(item.parent_id)) + for item in permission_list + ] + # 转成id为key的字典 + mapping: dict = dict(zip([i['key'] for i in permission_list], permission_list)) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d['parent_id']) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container diff --git a/dash-fastapi-backend/module_admin/service/notice_service.py b/dash-fastapi-backend/module_admin/service/notice_service.py index a96c9a984e5dd7654d844dbbe798f4a1b75003a6..1b076d93cfef432d5e4a32e6129f92e0a628b4e4 100644 --- a/dash-fastapi-backend/module_admin/service/notice_service.py +++ b/dash-fastapi-backend/module_admin/service/notice_service.py @@ -1,5 +1,10 @@ -from module_admin.entity.vo.notice_vo import * -from module_admin.dao.notice_dao import * +from sqlalchemy.ext.asyncio import AsyncSession +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.notice_dao import NoticeDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.notice_vo import DeleteNoticeModel, NoticeModel, NoticePageQueryModel +from utils.common_util import SqlalchemyUtil class NoticeService: @@ -8,98 +13,116 @@ class NoticeService: """ @classmethod - def get_notice_list_services(cls, result_db: Session, query_object: NoticeQueryModel): + async def get_notice_list_services( + cls, query_db: AsyncSession, query_object: NoticePageQueryModel, is_page: bool = True + ): """ 获取通知公告列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param is_page: 是否开启分页 :return: 通知公告列表信息对象 """ - notice_list_result = NoticeDao.get_notice_list(result_db, query_object) + notice_list_result = await NoticeDao.get_notice_list(query_db, query_object, is_page) return notice_list_result @classmethod - def add_notice_services(cls, result_db: Session, page_object: NoticeModel): + async def check_notice_unique_services(cls, query_db: AsyncSession, page_object: NoticeModel): + """ + 校验通知公告是否存在service + + :param query_db: orm对象 + :param page_object: 通知公告对象 + :return: 校验结果 + """ + notice_id = -1 if page_object.notice_id is None else page_object.notice_id + notice = await NoticeDao.get_notice_detail_by_info(query_db, page_object) + if notice and notice.notice_id != notice_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): """ 新增通知公告信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增通知公告对象 :return: 新增通知公告校验结果 """ - notice = NoticeDao.get_notice_detail_by_info(result_db, page_object) - if notice: - result = dict(is_success=False, message='通知公告已存在') + if not await cls.check_notice_unique_services(query_db, page_object): + raise ServiceException(message=f'新增通知公告{page_object.notice_title}失败,通知公告已存在') else: try: - NoticeDao.add_notice_dao(result_db, page_object) - result_db.commit() - result = dict(is_success=True, message='新增成功') + await NoticeDao.add_notice_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudNoticeResponse(**result) + await query_db.rollback() + raise e @classmethod - def edit_notice_services(cls, result_db: Session, page_object: NoticeModel): + async def edit_notice_services(cls, query_db: AsyncSession, page_object: NoticeModel): """ 编辑通知公告信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑通知公告对象 :return: 编辑通知公告校验结果 """ - edit_notice = page_object.dict(exclude_unset=True) - notice_info = cls.detail_notice_services(result_db, edit_notice.get('notice_id')) - if notice_info: - if notice_info.notice_title != page_object.notice_title or notice_info.notice_type != page_object.notice_type or notice_info.notice_content != page_object.notice_content: - notice = NoticeDao.get_notice_detail_by_info(result_db, page_object) - if notice: - result = dict(is_success=False, message='通知公告已存在') - return CrudNoticeResponse(**result) - try: - NoticeDao.edit_notice_dao(result_db, edit_notice) - result_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + edit_notice = page_object.model_dump(exclude_unset=True) + notice_info = await cls.notice_detail_services(query_db, page_object.notice_id) + if notice_info.notice_id: + if not await cls.check_notice_unique_services(query_db, page_object): + raise ServiceException(message=f'修改通知公告{page_object.notice_title}失败,通知公告已存在') + else: + try: + await NoticeDao.edit_notice_dao(query_db, edit_notice) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='通知公告不存在') - - return CrudNoticeResponse(**result) + raise ServiceException(message='通知公告不存在') @classmethod - def delete_notice_services(cls, result_db: Session, page_object: DeleteNoticeModel): + async def delete_notice_services(cls, query_db: AsyncSession, page_object: DeleteNoticeModel): """ 删除通知公告信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除通知公告对象 :return: 删除通知公告校验结果 """ - if page_object.notice_ids.split(','): + if page_object.notice_ids: notice_id_list = page_object.notice_ids.split(',') try: for notice_id in notice_id_list: - notice_id_dict = dict(notice_id=notice_id) - NoticeDao.delete_notice_dao(result_db, NoticeModel(**notice_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await NoticeDao.delete_notice_dao(query_db, NoticeModel(notice_id=notice_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入通知公告id为空') - return CrudNoticeResponse(**result) + raise ServiceException(message='传入通知公告id为空') @classmethod - def detail_notice_services(cls, result_db: Session, notice_id: int): + async def notice_detail_services(cls, query_db: AsyncSession, notice_id: int): """ 获取通知公告详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param notice_id: 通知公告id :return: 通知公告id对应的信息 """ - notice = NoticeDao.get_notice_detail_by_id(result_db, notice_id=notice_id) + notice = await NoticeDao.get_notice_detail_by_id(query_db, notice_id=notice_id) + if notice: + result = NoticeModel(**SqlalchemyUtil.serialize_result(notice)) + else: + result = NoticeModel(**dict()) - return notice + return result diff --git a/dash-fastapi-backend/module_admin/service/online_service.py b/dash-fastapi-backend/module_admin/service/online_service.py index 6ef9080faf25bee31f17ed252d7e130eae4a27d2..fbcc68ea3a462230d55b686bb0d8ea06ff28183b 100644 --- a/dash-fastapi-backend/module_admin/service/online_service.py +++ b/dash-fastapi-backend/module_admin/service/online_service.py @@ -1,7 +1,10 @@ +import jwt from fastapi import Request -from jose import jwt -from config.env import JwtConfig, RedisInitKeyConfig -from module_admin.entity.vo.online_vo import * +from config.enums import RedisInitKeyConfig +from config.env import JwtConfig +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.online_vo import DeleteOnlineModel, OnlineQueryModel class OnlineService: @@ -10,14 +13,15 @@ class OnlineService: """ @classmethod - async def get_online_list_services(cls, request: Request, query_object: OnlinePageObject): + async def get_online_list_services(cls, request: Request, query_object: OnlineQueryModel): """ 获取在线用户表信息service + :param request: Request对象 :param query_object: 查询参数对象 :return: 在线用户列表信息 """ - access_token_keys = await request.app.state.redis.keys(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}*") + access_token_keys = await request.app.state.redis.keys(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}*') if not access_token_keys: access_token_keys = [] access_token_values_list = [await request.app.state.redis.get(key) for key in access_token_keys] @@ -25,17 +29,17 @@ class OnlineService: for item in access_token_values_list: payload = jwt.decode(item, JwtConfig.jwt_secret_key, algorithms=[JwtConfig.jwt_algorithm]) online_dict = dict( - session_id=payload.get('session_id'), + token_id=payload.get('session_id'), user_name=payload.get('user_name'), dept_name=payload.get('dept_name'), ipaddr=payload.get('login_info').get('ipaddr'), login_location=payload.get('login_info').get('login_location'), browser=payload.get('login_info').get('browser'), os=payload.get('login_info').get('os'), - login_time=payload.get('login_info').get('login_time') + login_time=payload.get('login_info').get('login_time'), ) if query_object.user_name and not query_object.ipaddr: - if query_object.user_name == payload.get('user_name'): + if query_object.user_name == payload.get('login_info').get('ipaddr'): online_info_list = [online_dict] break elif not query_object.user_name and query_object.ipaddr: @@ -43,7 +47,9 @@ class OnlineService: online_info_list = [online_dict] break elif query_object.user_name and query_object.ipaddr: - if query_object.user_name == payload.get('user_name') and query_object.ipaddr == payload.get('ipaddr'): + if query_object.user_name == payload.get('user_name') and query_object.ipaddr == payload.get( + 'login_info' + ).get('ipaddr'): online_info_list = [online_dict] break else: @@ -55,15 +61,15 @@ class OnlineService: async def delete_online_services(cls, request: Request, page_object: DeleteOnlineModel): """ 强退在线用户信息service + :param request: Request对象 :param page_object: 强退在线用户对象 :return: 强退在线用户校验结果 """ - if page_object.session_ids.split(','): - session_id_list = page_object.session_ids.split(',') - for session_id in session_id_list: - await request.app.state.redis.delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.get('key')}:{session_id}") - result = dict(is_success=True, message='强退成功') + if page_object.token_ids: + token_id_list = page_object.token_ids.split(',') + for token_id in token_id_list: + await request.app.state.redis.delete(f'{RedisInitKeyConfig.ACCESS_TOKEN.key}:{token_id}') + return CrudResponseModel(is_success=True, message='强退成功') else: - result = dict(is_success=False, message='传入session_id为空') - return CrudOnlineResponse(**result) + raise ServiceException(message='传入session_id为空') diff --git a/dash-fastapi-backend/module_admin/service/post_service.py b/dash-fastapi-backend/module_admin/service/post_service.py index 16e954fb24f908688d0672f1232e8331fe79ec9c..3c1f364bb437ca243b3b42004591c6f53f4cb0e7 100644 --- a/dash-fastapi-backend/module_admin/service/post_service.py +++ b/dash-fastapi-backend/module_admin/service/post_service.py @@ -1,6 +1,11 @@ -from module_admin.entity.vo.post_vo import * -from module_admin.dao.post_dao import * -from utils.common_util import export_list2excel +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.post_dao import PostDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.post_vo import DeletePostModel, PostModel, PostPageQueryModel +from utils.common_util import export_list2excel, SqlalchemyUtil class PostService: @@ -9,142 +14,174 @@ class PostService: """ @classmethod - def get_post_select_option_services(cls, result_db: Session): + async def get_post_list_services( + cls, query_db: AsyncSession, query_object: PostPageQueryModel, is_page: bool = False + ): """ - 获取岗位列表不分页信息service - :param result_db: orm对象 - :return: 岗位列表不分页信息对象 + 获取岗位列表信息service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 岗位列表信息对象 """ - post_list_result = PostDao.get_post_select_option_dao(result_db) + post_list_result = await PostDao.get_post_list(query_db, query_object, is_page) return post_list_result @classmethod - def get_post_list_services(cls, result_db: Session, query_object: PostModel): + async def check_post_name_unique_services(cls, query_db: AsyncSession, page_object: PostModel): """ - 获取岗位列表信息service - :param result_db: orm对象 - :param query_object: 查询参数对象 - :return: 岗位列表信息对象 + 检查岗位名称是否唯一service + + :param query_db: orm对象 + :param page_object: 岗位对象 + :return: 校验结果 """ - post_list_result = PostDao.get_post_list(result_db, query_object) + post_id = -1 if page_object.post_id is None else page_object.post_id + post = await PostDao.get_post_detail_by_info(query_db, PostModel(post_name=page_object.post_name)) + if post and post.post_id != post_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE - return post_list_result + @classmethod + async def check_post_code_unique_services(cls, query_db: AsyncSession, page_object: PostModel): + """ + 检查岗位编码是否唯一service + + :param query_db: orm对象 + :param page_object: 岗位对象 + :return: 校验结果 + """ + post_id = -1 if page_object.post_id is None else page_object.post_id + post = await PostDao.get_post_detail_by_info(query_db, PostModel(post_code=page_object.post_code)) + if post and post.post_id != post_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE @classmethod - def add_post_services(cls, result_db: Session, page_object: PostModel): + async def add_post_services(cls, query_db: AsyncSession, page_object: PostModel): """ 新增岗位信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增岗位对象 :return: 新增岗位校验结果 """ - post = PostDao.get_post_detail_by_info(result_db, PostModel(**dict(post_name=page_object.post_name))) - if post: - result = dict(is_success=False, message='岗位名称已存在') + if not await cls.check_post_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位名称已存在') + elif not await cls.check_post_code_unique_services(query_db, page_object): + raise ServiceException(message=f'新增岗位{page_object.post_name}失败,岗位编码已存在') else: try: - PostDao.add_post_dao(result_db, page_object) - result_db.commit() - result = dict(is_success=True, message='新增成功') + await PostDao.add_post_dao(query_db, page_object) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudPostResponse(**result) + await query_db.rollback() + raise e @classmethod - def edit_post_services(cls, result_db: Session, page_object: PostModel): + async def edit_post_services(cls, query_db: AsyncSession, page_object: PostModel): """ 编辑岗位信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑岗位对象 :return: 编辑岗位校验结果 """ - edit_post = page_object.dict(exclude_unset=True) - post_info = cls.detail_post_services(result_db, edit_post.get('post_id')) - if post_info: - if post_info.post_name != page_object.post_name: - post = PostDao.get_post_detail_by_info(result_db, PostModel(**dict(post_name=page_object.post_name))) - if post: - result = dict(is_success=False, message='岗位名称已存在') - return CrudPostResponse(**result) - try: - PostDao.edit_post_dao(result_db, edit_post) - result_db.commit() - result = dict(is_success=True, message='更新成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + edit_post = page_object.model_dump(exclude_unset=True) + post_info = await cls.post_detail_services(query_db, page_object.post_id) + if post_info.post_id: + if not await cls.check_post_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位名称已存在') + elif not await cls.check_post_code_unique_services(query_db, page_object): + raise ServiceException(message=f'修改岗位{page_object.post_name}失败,岗位编码已存在') + else: + try: + await PostDao.edit_post_dao(query_db, edit_post) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='岗位不存在') - - return CrudPostResponse(**result) + raise ServiceException(message='岗位不存在') @classmethod - def delete_post_services(cls, result_db: Session, page_object: DeletePostModel): + async def delete_post_services(cls, query_db: AsyncSession, page_object: DeletePostModel): """ 删除岗位信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除岗位对象 :return: 删除岗位校验结果 """ - if page_object.post_ids.split(','): + if page_object.post_ids: post_id_list = page_object.post_ids.split(',') try: for post_id in post_id_list: - post_id_dict = dict(post_id=post_id) - PostDao.delete_post_dao(result_db, PostModel(**post_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + post = await cls.post_detail_services(query_db, int(post_id)) + if (await PostDao.count_user_post_dao(query_db, int(post_id))) > 0: + raise ServiceException(message=f'{post.post_name}已分配,不能删除') + await PostDao.delete_post_dao(query_db, PostModel(post_id=post_id)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入岗位id为空') - return CrudPostResponse(**result) + raise ServiceException(message='传入岗位id为空') @classmethod - def detail_post_services(cls, result_db: Session, post_id: int): + async def post_detail_services(cls, query_db: AsyncSession, post_id: int): """ 获取岗位详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param post_id: 岗位id :return: 岗位id对应的信息 """ - post = PostDao.get_post_detail_by_id(result_db, post_id=post_id) + post = await PostDao.get_post_detail_by_id(query_db, post_id=post_id) + if post: + result = PostModel(**SqlalchemyUtil.serialize_result(post)) + else: + result = PostModel(**dict()) - return post + return result @staticmethod - def export_post_list_services(post_list: List): + async def export_post_list_services(post_list: List): """ 导出岗位信息service + :param post_list: 岗位信息列表 :return: 岗位信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "post_id": "岗位编号", - "post_code": "岗位编码", - "post_name": "岗位名称", - "post_sort": "显示顺序", - "status": "状态", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'post_id': '岗位编号', + 'post_code': '岗位编码', + 'post_name': '岗位名称', + 'post_sort': '显示顺序', + 'status': '状态', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } - data = [PostModel(**vars(row)).dict() for row in post_list] + data = post_list for item in data: if item.get('status') == '0': item['status'] = '正常' else: item['status'] = '停用' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data diff --git a/dash-fastapi-backend/module_admin/service/role_service.py b/dash-fastapi-backend/module_admin/service/role_service.py index 76902559df8048f29dbb049bb8f0c2d419bf7705..995b53dddac6486f7e0b99fe79e0628790d86f0f 100644 --- a/dash-fastapi-backend/module_admin/service/role_service.py +++ b/dash-fastapi-backend/module_admin/service/role_service.py @@ -1,6 +1,22 @@ -from module_admin.entity.vo.role_vo import * -from module_admin.dao.role_dao import * -from utils.common_util import export_list2excel +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.role_vo import ( + AddRoleModel, + DeleteRoleModel, + RoleDeptModel, + RoleDeptQueryModel, + RoleMenuModel, + RoleModel, + RolePageQueryModel, +) +from module_admin.entity.vo.user_vo import UserInfoModel, UserRolePageQueryModel +from module_admin.dao.role_dao import RoleDao +from module_admin.dao.user_dao import UserDao +from utils.common_util import export_list2excel, SqlalchemyUtil +from utils.page_util import PageResponseModel class RoleService: @@ -9,198 +25,336 @@ class RoleService: """ @classmethod - def get_role_select_option_services(cls, result_db: Session): + async def get_role_select_option_services(cls, query_db: AsyncSession): """ 获取角色列表不分页信息service - :param result_db: orm对象 + + :param query_db: orm对象 :return: 角色列表不分页信息对象 """ - role_list_result = RoleDao.get_role_select_option_dao(result_db) + role_list_result = await RoleDao.get_role_select_option_dao(query_db) - return role_list_result + return SqlalchemyUtil.serialize_result(role_list_result) + + @classmethod + async def get_role_dept_tree_services(cls, query_db: AsyncSession, role_id: int): + """ + 根据角色id获取部门树信息service + + :param query_db: orm对象 + :param role_id: 角色id + :return: 当前角色id的部门树信息对象 + """ + role = await cls.role_detail_services(query_db, role_id) + role_dept_list = await RoleDao.get_role_dept_dao(query_db, role) + checked_keys = [row.dept_id for row in role_dept_list] + result = RoleDeptQueryModel(checked_keys=checked_keys) + + return result @classmethod - def get_role_list_services(cls, result_db: Session, query_object: RoleQueryModel): + async def get_role_list_services( + cls, query_db: AsyncSession, query_object: RolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 获取角色列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 :return: 角色列表信息对象 """ - role_list_result = RoleDao.get_role_list(result_db, query_object) + role_list_result = await RoleDao.get_role_list(query_db, query_object, data_scope_sql, is_page) return role_list_result @classmethod - def add_role_services(cls, result_db: Session, page_object: AddRoleModel): + async def check_role_allowed_services(cls, check_role: RoleModel): + """ + 校验角色是否允许操作service + + :param check_role: 角色信息 + :return: 校验结果 + """ + if check_role.admin: + raise ServiceException(message='不允许操作超级管理员角色') + else: + return CrudResponseModel(is_success=True, message='校验通过') + + @classmethod + async def check_role_data_scope_services(cls, query_db: AsyncSession, role_ids: str, data_scope_sql: str): + """ + 校验角色是否有数据权限service + + :param query_db: orm对象 + :param role_ids: 角色id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + role_id_list = role_ids.split(',') if role_ids else [] + if role_id_list: + for role_id in role_id_list: + roles = await RoleDao.get_role_list( + query_db, RolePageQueryModel(role_id=int(role_id)), data_scope_sql, is_page=False + ) + if roles: + continue + else: + raise ServiceException(message='没有权限访问角色数据') + + @classmethod + async def check_role_name_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 校验角色名称是否唯一service + + :param query_db: orm对象 + :param page_object: 角色对象 + :return: 校验结果 + """ + role_id = -1 if page_object.role_id is None else page_object.role_id + role = await RoleDao.get_role_by_info(query_db, RoleModel(role_name=page_object.role_name)) + if role and role.role_id != role_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_role_key_unique_services(cls, query_db: AsyncSession, page_object: RoleModel): + """ + 校验角色权限字符是否唯一service + + :param query_db: orm对象 + :param page_object: 角色对象 + :return: 校验结果 + """ + role_id = -1 if page_object.role_id is None else page_object.role_id + role = await RoleDao.get_role_by_info(query_db, RoleModel(role_key=page_object.role_key)) + if role and role.role_id != role_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): """ 新增角色信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增角色对象 :return: 新增角色校验结果 """ - add_role = RoleModel(**page_object.dict()) - role = RoleDao.get_role_by_info(result_db, RoleModel(**dict(role_name=page_object.role_name))) - if role: - result = dict(is_success=False, message='角色名称已存在') + add_role = RoleModel(**page_object.model_dump()) + if not await cls.check_role_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色名称已存在') + elif not await cls.check_role_key_unique_services(query_db, page_object): + raise ServiceException(message=f'新增角色{page_object.role_name}失败,角色权限已存在') else: try: - add_result = RoleDao.add_role_dao(result_db, add_role) + add_result = await RoleDao.add_role_dao(query_db, add_role) role_id = add_result.role_id - if page_object.menu_id: - menu_id_list = page_object.menu_id.split(',') - for menu in menu_id_list: - menu_dict = dict(role_id=role_id, menu_id=menu) - RoleDao.add_role_menu_dao(result_db, RoleMenuModel(**menu_dict)) - result_db.commit() - result = dict(is_success=True, message='新增成功') + if page_object.menu_ids: + for menu in page_object.menu_ids: + await RoleDao.add_role_menu_dao(query_db, RoleMenuModel(role_id=role_id, menu_id=menu)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudRoleResponse(**result) + await query_db.rollback() + raise e @classmethod - def edit_role_services(cls, result_db: Session, page_object: AddRoleModel): + async def edit_role_services(cls, query_db: AsyncSession, page_object: AddRoleModel): """ 编辑角色信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑角色对象 :return: 编辑角色校验结果 """ - edit_role = page_object.dict(exclude_unset=True) + edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin'}) if page_object.type != 'status': - del edit_role['menu_id'] + del edit_role['menu_ids'] if page_object.type == 'status': del edit_role['type'] - role_info = cls.detail_role_services(result_db, edit_role.get('role_id')) + role_info = await cls.role_detail_services(query_db, edit_role.get('role_id')) if role_info: - if page_object.type != 'status' and role_info.role.role_name != page_object.role_name: - role = RoleDao.get_role_by_info(result_db, RoleModel(**dict(role_name=page_object.role_name))) - if role: - result = dict(is_success=False, message='角色名称已存在') - return CrudRoleResponse(**result) + if page_object.type != 'status': + if not await cls.check_role_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色名称已存在') + elif not await cls.check_role_key_unique_services(query_db, page_object): + raise ServiceException(message=f'修改角色{page_object.role_name}失败,角色权限已存在') try: - RoleDao.edit_role_dao(result_db, edit_role) + await RoleDao.edit_role_dao(query_db, edit_role) if page_object.type != 'status': - role_id_dict = dict(role_id=page_object.role_id) - RoleDao.delete_role_menu_dao(result_db, RoleMenuModel(**role_id_dict)) - if page_object.menu_id: - menu_id_list = page_object.menu_id.split(',') - for menu in menu_id_list: - menu_dict = dict(role_id=page_object.role_id, menu_id=menu) - RoleDao.add_role_menu_dao(result_db, RoleMenuModel(**menu_dict)) - result_db.commit() - result = dict(is_success=True, message='更新成功') + await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(role_id=page_object.role_id)) + if page_object.menu_ids: + for menu in page_object.menu_ids: + await RoleDao.add_role_menu_dao( + query_db, RoleMenuModel(role_id=page_object.role_id, menu_id=menu) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='角色不存在') - - return CrudRoleResponse(**result) + raise ServiceException(message='角色不存在') @classmethod - def role_datascope_services(cls, result_db: Session, page_object: RoleDataScopeModel): + async def role_datascope_services(cls, query_db: AsyncSession, page_object: AddRoleModel): """ 分配角色数据权限service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 角色数据权限对象 :return: 分配角色数据权限结果 """ - edit_role = page_object.dict(exclude_unset=True) - del edit_role['dept_id'] - role_info = cls.detail_role_services(result_db, edit_role.get('role_id')) - if role_info: - if role_info.role.role_name != page_object.role_name: - role = RoleDao.get_role_by_info(result_db, RoleModel(**dict(role_name=page_object.role_name))) - if role: - result = dict(is_success=False, message='角色名称已存在') - return CrudRoleResponse(**result) + edit_role = page_object.model_dump(exclude_unset=True, exclude={'admin', 'dept_ids'}) + role_info = await cls.role_detail_services(query_db, page_object.role_id) + if role_info.role_id: try: - RoleDao.edit_role_dao(result_db, edit_role) - role_id_dict = dict(role_id=page_object.role_id) - RoleDao.delete_role_dept_dao(result_db, RoleDeptModel(**role_id_dict)) - if page_object.dept_id and page_object.data_scope == '2': - dept_id_list = page_object.dept_id.split(',') - for dept in dept_id_list: - dept_dict = dict(role_id=page_object.role_id, dept_id=dept) - RoleDao.add_role_dept_dao(result_db, RoleDeptModel(**dept_dict)) - result_db.commit() - result = dict(is_success=True, message='分配成功') + await RoleDao.edit_role_dao(query_db, edit_role) + await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(role_id=page_object.role_id)) + if page_object.dept_ids and page_object.data_scope == '2': + for dept in page_object.dept_ids: + await RoleDao.add_role_dept_dao( + query_db, RoleDeptModel(role_id=page_object.role_id, dept_id=dept) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='分配成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='角色不存在') - - return CrudRoleResponse(**result) + raise ServiceException(message='角色不存在') @classmethod - def delete_role_services(cls, result_db: Session, page_object: DeleteRoleModel): + async def delete_role_services(cls, query_db: AsyncSession, page_object: DeleteRoleModel): """ 删除角色信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除角色对象 :return: 删除角色校验结果 """ - if page_object.role_ids.split(','): + if page_object.role_ids: role_id_list = page_object.role_ids.split(',') try: for role_id in role_id_list: - role_id_dict = dict(role_id=role_id, update_by=page_object.update_by, update_time=page_object.update_time) - RoleDao.delete_role_menu_dao(result_db, RoleMenuModel(**role_id_dict)) - RoleDao.delete_role_dao(result_db, RoleModel(**role_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + role = await cls.role_detail_services(query_db, int(role_id)) + if (await RoleDao.count_user_role_dao(query_db, int(role_id))) > 0: + raise ServiceException(message=f'角色{role.role_name}已分配,不能删除') + role_id_dict = dict( + role_id=role_id, update_by=page_object.update_by, update_time=page_object.update_time + ) + await RoleDao.delete_role_menu_dao(query_db, RoleMenuModel(**role_id_dict)) + await RoleDao.delete_role_dept_dao(query_db, RoleDeptModel(**role_id_dict)) + await RoleDao.delete_role_dao(query_db, RoleModel(**role_id_dict)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入角色id为空') - return CrudRoleResponse(**result) + raise ServiceException(message='传入角色id为空') @classmethod - def detail_role_services(cls, result_db: Session, role_id: int): + async def role_detail_services(cls, query_db: AsyncSession, role_id: int): """ 获取角色详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param role_id: 角色id :return: 角色id对应的信息 """ - role = RoleDao.get_role_detail_by_id(result_db, role_id=role_id) + role = await RoleDao.get_role_detail_by_id(query_db, role_id=role_id) + if role: + result = RoleModel(**SqlalchemyUtil.serialize_result(role)) + else: + result = RoleModel(**dict()) - return role + return result @staticmethod - def export_role_list_services(role_list: List): + async def export_role_list_services(role_list: List): """ 导出角色列表信息service + :param role_list: 角色信息列表 :return: 角色列表信息对象 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "role_id": "角色编号", - "role_name": "角色名称", - "role_key": "权限字符", - "role_sort": "显示顺序", - "status": "状态", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'role_id': '角色编号', + 'role_name': '角色名称', + 'role_key': '权限字符', + 'role_sort': '显示顺序', + 'status': '状态', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } - data = [RoleModel(**vars(row)).dict() for row in role_list] + data = role_list for item in data: if item.get('status') == '0': item['status'] = '正常' else: item['status'] = '停用' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data + + @classmethod + async def get_role_user_allocated_list_services( + cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据角色id获取已分配用户列表 + + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 已分配用户列表 + """ + query_user_list = await UserDao.get_user_role_allocated_list_by_role_id( + query_db, page_object, data_scope_sql, is_page + ) + allocated_list = PageResponseModel( + **{ + **query_user_list.model_dump(), + 'rows': [UserInfoModel(**row) for row in query_user_list.rows], + } + ) + + return allocated_list + + @classmethod + async def get_role_user_unallocated_list_services( + cls, query_db: AsyncSession, page_object: UserRolePageQueryModel, data_scope_sql: str, is_page: bool = False + ): + """ + 根据角色id获取未分配用户列表 + + :param query_db: orm对象 + :param page_object: 用户关联角色对象 + :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 + :return: 未分配用户列表 + """ + query_user_list = await UserDao.get_user_role_unallocated_list_by_role_id( + query_db, page_object, data_scope_sql, is_page + ) + unallocated_list = PageResponseModel( + **{ + **query_user_list.model_dump(), + 'rows': [UserInfoModel(**row) for row in query_user_list.rows], + } + ) + + return unallocated_list diff --git a/dash-fastapi-backend/module_admin/service/server_service.py b/dash-fastapi-backend/module_admin/service/server_service.py index 0c41c8eb864f152c31f2478d43cd865fd32f328c..6e9e71a713f1f86629c305e53ddebef3d6685d05 100644 --- a/dash-fastapi-backend/module_admin/service/server_service.py +++ b/dash-fastapi-backend/module_admin/service/server_service.py @@ -1,10 +1,10 @@ -import psutil -from utils.common_util import bytes2human +import os import platform +import psutil import socket -import os import time -from module_admin.entity.vo.server_vo import * +from module_admin.entity.vo.server_vo import CpuInfo, MemoryInfo, PyInfo, ServerMonitorModel, SysFiles, SysInfo +from utils.common_util import bytes2human class ServerService: @@ -13,23 +13,23 @@ class ServerService: """ @staticmethod - def get_server_monitor_info(): + async def get_server_monitor_info(): # CPU信息 # 获取CPU总核心数 cpu_num = psutil.cpu_count(logical=True) cpu_usage_percent = psutil.cpu_times_percent() - cpu_used = f'{cpu_usage_percent.user}%' - cpu_sys = f'{cpu_usage_percent.system}%' - cpu_free = f'{cpu_usage_percent.idle}%' - cpu = CpuInfo(**dict(cpu_num=cpu_num, used=cpu_used, sys=cpu_sys, free=cpu_free)) + cpu_used = cpu_usage_percent.user + cpu_sys = cpu_usage_percent.system + cpu_free = cpu_usage_percent.idle + cpu = CpuInfo(cpu_num=cpu_num, used=cpu_used, sys=cpu_sys, free=cpu_free) # 内存信息 memory_info = psutil.virtual_memory() memory_total = bytes2human(memory_info.total) memory_used = bytes2human(memory_info.used) memory_free = bytes2human(memory_info.free) - memory_usage = f'{memory_info.percent}%' - mem = MemoryInfo(**dict(total=memory_total, used=memory_used, free=memory_free, usage=memory_usage)) + memory_usage = memory_info.percent + mem = MemoryInfo(total=memory_total, used=memory_used, free=memory_free, usage=memory_usage) # 主机信息 # 获取主机名 @@ -39,7 +39,10 @@ class ServerService: os_name = platform.platform() computer_name = platform.node() os_arch = platform.machine() - sys = SysInfo(**dict(computer_ip=computer_ip, computer_name=computer_name, os_arch=os_arch, os_name=os_name)) + user_dir = os.path.abspath(os.getcwd()) + sys = SysInfo( + computer_ip=computer_ip, computer_name=computer_name, os_arch=os_arch, os_name=os_name, user_dir=user_dir + ) # python解释器信息 current_pid = os.getpid() @@ -48,24 +51,28 @@ class ServerService: python_version = platform.python_version() python_home = current_process.exe() start_time_stamp = current_process.create_time() - start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time_stamp)) + start_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time_stamp)) current_time_stamp = time.time() difference = current_time_stamp - start_time_stamp # 将时间差转换为天、小时和分钟数 days = int(difference // (24 * 60 * 60)) # 每天的秒数 hours = int((difference % (24 * 60 * 60)) // (60 * 60)) # 每小时的秒数 minutes = int((difference % (60 * 60)) // 60) # 每分钟的秒数 - run_time = f"{days}天{hours}小时{minutes}分钟" - project_dir = os.path.abspath(os.getcwd()) + run_time = f'{days}天{hours}小时{minutes}分钟' + # 获取当前Python程序的pid + pid = os.getpid() + # 获取该进程的内存信息 + current_process_memory_info = psutil.Process(pid).memory_info() py = PyInfo( - **dict( - name=python_name, - version=python_version, - start_time=start_time, - run_time=run_time, - home=python_home, - project_dir=project_dir - ) + name=python_name, + version=python_version, + start_time=start_time, + run_time=run_time, + home=python_home, + total=bytes2human(memory_info.available), + used=bytes2human(current_process_memory_info.rss), + free=bytes2human(memory_info.available - current_process_memory_info.rss), + usage=round((current_process_memory_info.rss / memory_info.available) * 100, 2), ) # 磁盘信息 @@ -73,17 +80,17 @@ class ServerService: sys_files = [] for i in io: o = psutil.disk_usage(i.device) - disk_data = { - "dir_name": i.device, - "sys_type_name": i.fstype, - "disk_name": "本地固定磁盘(" + i.mountpoint.replace('\\', '') + ")", - "total": bytes2human(o.total), - "used": bytes2human(o.used), - "free": bytes2human(o.free), - "usage": f'{psutil.disk_usage(i.device).percent}%' - } - sys_files.append(SysFiles(**disk_data)) + disk_data = SysFiles( + dir_name=i.device, + sys_type_name=i.fstype, + type_name='本地固定磁盘(' + i.mountpoint.replace('\\', '') + ')', + total=bytes2human(o.total), + used=bytes2human(o.used), + free=bytes2human(o.free), + usage=f'{psutil.disk_usage(i.device).percent}%', + ) + sys_files.append(disk_data) - result = dict(cpu=cpu, mem=mem, sys=sys, py=py, sys_files=sys_files) + result = ServerMonitorModel(cpu=cpu, mem=mem, sys=sys, py=py, sys_files=sys_files) - return ServerMonitorModel(**result) + return result diff --git a/dash-fastapi-backend/module_admin/service/user_service.py b/dash-fastapi-backend/module_admin/service/user_service.py index c0baa8db27e32f284c3cd4e6b343b1d53e2b2205..87dd0cab7dee1a4b275a02798495e6f88973a266 100644 --- a/dash-fastapi-backend/module_admin/service/user_service.py +++ b/dash-fastapi-backend/module_admin/service/user_service.py @@ -1,7 +1,39 @@ -from module_admin.entity.vo.user_vo import * -from module_admin.dao.user_dao import * -from utils.pwd_util import * -from utils.common_util import * +import io +import pandas as pd +from datetime import datetime +from fastapi import Request, UploadFile +from sqlalchemy.ext.asyncio import AsyncSession +from typing import List, Union +from config.constant import CommonConstant +from exceptions.exception import ServiceException +from module_admin.dao.user_dao import UserDao +from module_admin.entity.vo.common_vo import CrudResponseModel +from module_admin.entity.vo.post_vo import PostPageQueryModel +from module_admin.entity.vo.user_vo import ( + AddUserModel, + CrudUserRoleModel, + CurrentUserModel, + DeleteUserModel, + EditUserModel, + ResetUserModel, + SelectedRoleModel, + UserDetailModel, + UserInfoModel, + UserModel, + UserPageQueryModel, + UserPostModel, + UserProfileModel, + UserRoleModel, + UserRoleQueryModel, + UserRoleResponseModel, +) +from module_admin.service.config_service import ConfigService +from module_admin.service.dept_service import DeptService +from module_admin.service.post_service import PostService +from module_admin.service.role_service import RoleService +from utils.common_util import export_list2excel, get_excel_template, SqlalchemyUtil +from utils.page_util import PageResponseModel +from utils.pwd_util import PwdUtil class UserService: @@ -10,190 +42,342 @@ class UserService: """ @classmethod - def get_user_list_services(cls, result_db: Session, query_object: UserQueryModel, data_scope_sql: str): + async def get_user_list_services( + cls, query_db: AsyncSession, query_object: UserPageQueryModel, data_scope_sql: str, is_page: bool = False + ): """ 获取用户列表信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param query_object: 查询参数对象 :param data_scope_sql: 数据权限对应的查询sql语句 + :param is_page: 是否开启分页 :return: 用户列表信息对象 """ - user_list_result = UserDao.get_user_list(result_db, query_object, data_scope_sql) + query_result = await UserDao.get_user_list(query_db, query_object, data_scope_sql, is_page) + if is_page: + user_list_result = PageResponseModel( + **{ + **query_result.model_dump(), + 'rows': [{**row[0], 'dept': row[1]} for row in query_result.rows], + } + ) + else: + user_list_result = [] + if query_result: + user_list_result = [{**row[0], 'dept': row[1]} for row in query_result] return user_list_result @classmethod - def add_user_services(cls, result_db: Session, page_object: AddUserModel): + async def check_user_allowed_services(cls, check_user: UserModel): + """ + 校验用户是否允许操作service + + :param check_user: 用户信息 + :return: 校验结果 + """ + if check_user.admin: + raise ServiceException(message='不允许操作超级管理员用户') + else: + return CrudResponseModel(is_success=True, message='校验通过') + + @classmethod + async def check_user_data_scope_services(cls, query_db: AsyncSession, user_id: int, data_scope_sql: str): + """ + 校验用户数据权限service + + :param query_db: orm对象 + :param user_id: 用户id + :param data_scope_sql: 数据权限对应的查询sql语句 + :return: 校验结果 + """ + users = await UserDao.get_user_list( + query_db, UserPageQueryModel(user_id=user_id), data_scope_sql, is_page=False + ) + if users: + return CrudResponseModel(is_success=True, message='校验通过') + else: + raise ServiceException(message='没有权限访问用户数据') + + @classmethod + async def check_user_name_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户名是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(user_name=page_object.user_name)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_phonenumber_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户手机号是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(phonenumber=page_object.phonenumber)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def check_email_unique_services(cls, query_db: AsyncSession, page_object: UserModel): + """ + 校验用户邮箱是否唯一service + + :param query_db: orm对象 + :param page_object: 用户对象 + :return: 校验结果 + """ + user_id = -1 if page_object.user_id is None else page_object.user_id + user = await UserDao.get_user_by_info(query_db, UserModel(email=page_object.email)) + if user and user.user_id != user_id: + return CommonConstant.NOT_UNIQUE + return CommonConstant.UNIQUE + + @classmethod + async def add_user_services(cls, query_db: AsyncSession, page_object: AddUserModel): """ 新增用户信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增用户对象 :return: 新增用户校验结果 """ - add_user = UserModel(**page_object.dict()) - user = UserDao.get_user_by_info(result_db, UserModel(**dict(user_name=page_object.user_name))) - if user: - result = dict(is_success=False, message='用户名已存在') + add_user = UserModel(**page_object.model_dump()) + if not await cls.check_user_name_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,登录账号已存在') + elif page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,手机号码已存在') + elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + raise ServiceException(message=f'新增用户{page_object.user_name}失败,邮箱账号已存在') else: try: - add_result = UserDao.add_user_dao(result_db, add_user) + add_result = await UserDao.add_user_dao(query_db, add_user) user_id = add_result.user_id - if page_object.role_id: - role_id_list = page_object.role_id.split(',') - for role in role_id_list: - role_dict = dict(user_id=user_id, role_id=role) - UserDao.add_user_role_dao(result_db, UserRoleModel(**role_dict)) - if page_object.post_id: - post_id_list = page_object.post_id.split(',') - for post in post_id_list: - post_dict = dict(user_id=user_id, post_id=post) - UserDao.add_user_post_dao(result_db, UserPostModel(**post_dict)) - result_db.commit() - result = dict(is_success=True, message='新增成功') + if page_object.role_ids: + for role in page_object.role_ids: + await UserDao.add_user_role_dao(query_db, UserRoleModel(user_id=user_id, role_id=role)) + if page_object.post_ids: + for post in page_object.post_ids: + await UserDao.add_user_post_dao(query_db, UserPostModel(user_id=user_id, post_id=post)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudUserResponse(**result) + await query_db.rollback() + raise e @classmethod - def edit_user_services(cls, result_db: Session, page_object: AddUserModel): + async def edit_user_services(cls, query_db: AsyncSession, page_object: EditUserModel): """ 编辑用户信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 编辑用户对象 :return: 编辑用户校验结果 """ - edit_user = page_object.dict(exclude_unset=True) - if page_object.type != 'status' and page_object.type != 'avatar': - del edit_user['role_id'] - del edit_user['post_id'] - if page_object.type == 'status' or page_object.type == 'avatar': + edit_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + del edit_user['role_ids'] + del edit_user['post_ids'] + del edit_user['role'] + if page_object.type == 'status' or page_object.type == 'avatar' or page_object.type == 'pwd': del edit_user['type'] - user_info = cls.detail_user_services(result_db, edit_user.get('user_id')) - if user_info: - if page_object.type != 'status' and page_object.type != 'avatar' and user_info.user.user_name != page_object.user_name: - user = UserDao.get_user_by_info(result_db, UserModel(**dict(user_name=page_object.user_name))) - if user: - result = dict(is_success=False, message='用户名已存在') - return CrudUserResponse(**result) + user_info = await cls.user_detail_services(query_db, edit_user.get('user_id')) + if user_info.data and user_info.data.user_id: + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + if not await cls.check_user_name_unique_services(query_db, page_object): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,登录账号已存在') + elif page_object.phonenumber and not await cls.check_phonenumber_unique_services(query_db, page_object): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,手机号码已存在') + elif page_object.email and not await cls.check_email_unique_services(query_db, page_object): + raise ServiceException(message=f'修改用户{page_object.user_name}失败,邮箱账号已存在') try: - UserDao.edit_user_dao(result_db, edit_user) - if page_object.type != 'status' and page_object.type != 'avatar': - user_id_dict = dict(user_id=page_object.user_id) - UserDao.delete_user_role_dao(result_db, UserRoleModel(**user_id_dict)) - UserDao.delete_user_post_dao(result_db, UserPostModel(**user_id_dict)) - if page_object.role_id: - role_id_list = page_object.role_id.split(',') - for role in role_id_list: - role_dict = dict(user_id=page_object.user_id, role_id=role) - UserDao.add_user_role_dao(result_db, UserRoleModel(**role_dict)) - if page_object.post_id: - post_id_list = page_object.post_id.split(',') - for post in post_id_list: - post_dict = dict(user_id=page_object.user_id, post_id=post) - UserDao.add_user_post_dao(result_db, UserPostModel(**post_dict)) - result_db.commit() - result = dict(is_success=True, message='更新成功') + await UserDao.edit_user_dao(query_db, edit_user) + if page_object.type != 'status' and page_object.type != 'avatar' and page_object.type != 'pwd': + await UserDao.delete_user_role_dao(query_db, UserRoleModel(user_id=page_object.user_id)) + await UserDao.delete_user_post_dao(query_db, UserPostModel(user_id=page_object.user_id)) + if page_object.role_ids: + for role in page_object.role_ids: + await UserDao.add_user_role_dao( + query_db, UserRoleModel(user_id=page_object.user_id, role_id=role) + ) + if page_object.post_ids: + for post in page_object.post_ids: + await UserDao.add_user_post_dao( + query_db, UserPostModel(user_id=page_object.user_id, post_id=post) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='更新成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='用户不存在') - - return CrudUserResponse(**result) + raise ServiceException(message='用户不存在') @classmethod - def delete_user_services(cls, result_db: Session, page_object: DeleteUserModel): + async def delete_user_services(cls, query_db: AsyncSession, page_object: DeleteUserModel): """ 删除用户信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除用户对象 :return: 删除用户校验结果 """ - if page_object.user_ids.split(','): + if page_object.user_ids: user_id_list = page_object.user_ids.split(',') try: for user_id in user_id_list: - user_id_dict = dict(user_id=user_id, update_by=page_object.update_by, update_time=page_object.update_time) - UserDao.delete_user_role_dao(result_db, UserRoleModel(**user_id_dict)) - UserDao.delete_user_post_dao(result_db, UserPostModel(**user_id_dict)) - UserDao.delete_user_dao(result_db, UserModel(**user_id_dict)) - result_db.commit() - result = dict(is_success=True, message='删除成功') + user_id_dict = dict( + user_id=user_id, update_by=page_object.update_by, update_time=page_object.update_time + ) + await UserDao.delete_user_role_dao(query_db, UserRoleModel(**user_id_dict)) + await UserDao.delete_user_post_dao(query_db, UserPostModel(**user_id_dict)) + await UserDao.delete_user_dao(query_db, UserModel(**user_id_dict)) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入用户id为空') - return CrudUserResponse(**result) + raise ServiceException(message='传入用户id为空') @classmethod - def detail_user_services(cls, result_db: Session, user_id: int): + async def user_detail_services(cls, query_db: AsyncSession, user_id: Union[int, str]): """ 获取用户详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param user_id: 用户id :return: 用户id对应的信息 """ - user = UserDao.get_user_detail_by_id(result_db, user_id=user_id) + posts = await PostService.get_post_list_services(query_db, PostPageQueryModel(**{}), is_page=False) + roles = await RoleService.get_role_select_option_services(query_db) + if user_id != '': + query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + post_ids_list = [row.post_id for row in query_user.get('user_post_info')] + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + role_ids_list = [row.role_id for row in query_user.get('user_role_info')] + + return UserDetailModel( + data=UserInfoModel( + **SqlalchemyUtil.serialize_result(query_user.get('user_basic_info')), + post_ids=post_ids, + role_ids=role_ids, + dept=SqlalchemyUtil.serialize_result(query_user.get('user_dept_info')), + role=SqlalchemyUtil.serialize_result(query_user.get('user_role_info')), + ), + post_ids=post_ids_list, + posts=posts, + role_ids=role_ids_list, + roles=roles, + ) - return UserDetailModel( - user=user.user_basic_info, - dept=user.user_dept_info, - role=user.user_role_info, - post=user.user_post_info + return UserDetailModel(posts=posts, roles=roles) + + @classmethod + async def user_profile_services(cls, query_db: AsyncSession, user_id: int): + """ + 获取用户个人详细信息service + + :param query_db: orm对象 + :param user_id: 用户id + :return: 用户id对应的信息 + """ + query_user = await UserDao.get_user_detail_by_id(query_db, user_id=user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + post_group = ','.join([row.post_name for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + role_group = ','.join([row.role_name for row in query_user.get('user_role_info')]) + + return UserProfileModel( + data=UserInfoModel( + **SqlalchemyUtil.serialize_result(query_user.get('user_basic_info')), + post_ids=post_ids, + role_ids=role_ids, + dept=SqlalchemyUtil.serialize_result(query_user.get('user_dept_info')), + role=SqlalchemyUtil.serialize_result(query_user.get('user_role_info')), + ), + post_group=post_group, + role_group=role_group, ) @classmethod - def reset_user_services(cls, result_db: Session, page_object: ResetUserModel): + async def reset_user_services(cls, query_db: AsyncSession, page_object: ResetUserModel): """ 重置用户密码service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 重置用户对象 :return: 重置用户校验结果 """ - reset_user = page_object.dict(exclude_unset=True) + reset_user = page_object.model_dump(exclude_unset=True, exclude={'admin'}) if page_object.old_password: - user = UserDao.get_user_detail_by_id(result_db, user_id=page_object.user_id).user_basic_info + user = (await UserDao.get_user_detail_by_id(query_db, user_id=page_object.user_id)).get('user_basic_info') if not PwdUtil.verify_password(page_object.old_password, user.password): - result = dict(is_success=False, message='旧密码不正确') - return CrudUserResponse(**result) + raise ServiceException(message='修改密码失败,旧密码错误') + elif PwdUtil.verify_password(page_object.password, user.password): + raise ServiceException(message='新密码不能与旧密码相同') else: del reset_user['old_password'] if page_object.sms_code and page_object.session_id: del reset_user['sms_code'] del reset_user['session_id'] try: - UserDao.edit_user_dao(result_db, reset_user) - result_db.commit() - result = dict(is_success=True, message='重置成功') + reset_user['password'] = PwdUtil.get_password_hash(page_object.password) + await UserDao.edit_user_dao(query_db, reset_user) + await query_db.commit() + return CrudResponseModel(is_success=True, message='重置成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudUserResponse(**result) + await query_db.rollback() + raise e @classmethod - def batch_import_user_services(cls, result_db: Session, user_import: ImportUserModel, current_user: CurrentUserInfoServiceResponse): + async def batch_import_user_services( + cls, + request: Request, + query_db: AsyncSession, + file: UploadFile, + update_support: bool, + current_user: CurrentUserModel, + user_data_scope_sql: str, + dept_data_scope_sql: str, + ): """ 批量导入用户service - :param user_import: 用户导入参数对象 - :param result_db: orm对象 + + :param request: Request对象 + :param query_db: orm对象 + :param file: 用户导入文件对象 + :param update_support: 用户存在时是否更新 :param current_user: 当前用户对象 + :param user_data_scope_sql: 用户数据权限sql + :param dept_data_scope_sql: 部门数据权限sql :return: 批量导入用户结果 """ header_dict = { - "部门编号": "dept_id", - "登录名称": "user_name", - "用户名称": "nick_name", - "用户邮箱": "email", - "手机号码": "phonenumber", - "用户性别": "sex", - "帐号状态": "status" + '部门编号': 'dept_id', + '登录名称': 'user_name', + '用户名称': 'nick_name', + '用户邮箱': 'email', + '手机号码': 'phonenumber', + '用户性别': 'sex', + '帐号状态': 'status', } - filepath = get_filepath_from_url(user_import.url) - df = pd.read_excel(filepath) + contents = await file.read() + df = pd.read_excel(io.BytesIO(contents)) + await file.close() df.rename(columns=header_dict, inplace=True) add_error_result = [] count = 0 @@ -211,86 +395,103 @@ class UserService: if row['status'] == '停用': row['status'] = '1' add_user = UserModel( - **dict( - dept_id=row['dept_id'], - user_name=row['user_name'], - password=PwdUtil.get_password_hash('123456'), - nick_name=row['nick_name'], - email=row['email'], - phonenumber=row['phonenumber'], - sex=row['sex'], - status=row['status'], - create_by=current_user.user.user_name, - create_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - update_by=current_user.user.user_name, - update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - ) + dept_id=row['dept_id'], + user_name=row['user_name'], + password=PwdUtil.get_password_hash( + await ConfigService.query_config_list_from_cache_services( + request.app.state.redis, 'sys.user.initPassword' + ) + ), + nick_name=row['nick_name'], + email=row['email'], + phonenumber=str(row['phonenumber']), + sex=row['sex'], + status=row['status'], + create_by=current_user.user.user_name, + create_time=datetime.now(), + update_by=current_user.user.user_name, + update_time=datetime.now(), ) - user_info = UserDao.get_user_by_info(result_db, UserModel(**dict(user_name=row['user_name']))) + user_info = await UserDao.get_user_by_info(query_db, UserModel(user_name=row['user_name'])) if user_info: - if user_import.is_update: - edit_user = UserModel( - **dict( - user_id=user_info.user_id, - dept_id=row['dept_id'], - user_name=row['user_name'], - nick_name=row['nick_name'], - email=row['email'], - phonenumber=row['phonenumber'], - sex=row['sex'], - status=row['status'], - update_by=current_user.user.user_name, - update_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if update_support: + edit_user_model = UserModel( + user_id=user_info.user_id, + dept_id=row['dept_id'], + user_name=row['user_name'], + nick_name=row['nick_name'], + email=row['email'], + phonenumber=str(row['phonenumber']), + sex=row['sex'], + status=row['status'], + update_by=current_user.user.user_name, + update_time=datetime.now(), + ) + edit_user_model.validate_fields() + await cls.check_user_allowed_services(edit_user_model) + if not current_user.user.admin: + await cls.check_user_data_scope_services( + query_db, edit_user_model.user_id, user_data_scope_sql + ) + await DeptService.check_dept_data_scope_services( + query_db, edit_user_model.dept_id, dept_data_scope_sql ) - ).dict(exclude_unset=True) - UserDao.edit_user_dao(result_db, edit_user) + edit_user = edit_user_model.model_dump(exclude_unset=True) + await UserDao.edit_user_dao(query_db, edit_user) else: add_error_result.append(f"{count}.用户账号{row['user_name']}已存在") else: - UserDao.add_user_dao(result_db, add_user) - result_db.commit() - result = dict(is_success=True, message='\n'.join(add_error_result)) + add_user.validate_fields() + if not current_user.user.admin: + await DeptService.check_dept_data_scope_services( + query_db, add_user.dept_id, dept_data_scope_sql + ) + await UserDao.add_user_dao(query_db, add_user) + await query_db.commit() + return CrudResponseModel(is_success=True, message='\n'.join(add_error_result)) except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - - return CrudUserResponse(**result) + await query_db.rollback() + raise e @staticmethod - def get_user_import_template_services(): + async def get_user_import_template_services(): """ 获取用户导入模板service + :return: 用户导入模板excel的二进制数据 """ - header_list = ["部门编号", "登录名称", "用户名称", "用户邮箱", "手机号码", "用户性别", "帐号状态"] - selector_header_list = ["用户性别", "帐号状态"] - option_list = [{"用户性别": ["男", "女", "未知"]}, {"帐号状态": ["正常", "停用"]}] - binary_data = get_excel_template(header_list=header_list, selector_header_list=selector_header_list, option_list=option_list) + header_list = ['部门编号', '登录名称', '用户名称', '用户邮箱', '手机号码', '用户性别', '帐号状态'] + selector_header_list = ['用户性别', '帐号状态'] + option_list = [{'用户性别': ['男', '女', '未知']}, {'帐号状态': ['正常', '停用']}] + binary_data = get_excel_template( + header_list=header_list, selector_header_list=selector_header_list, option_list=option_list + ) return binary_data @staticmethod - def export_user_list_services(user_list: List): + async def export_user_list_services(user_list: List): """ 导出用户信息service + :param user_list: 用户信息列表 :return: 用户信息对应excel的二进制数据 """ # 创建一个映射字典,将英文键映射到中文键 mapping_dict = { - "user_id": "用户编号", - "user_name": "用户名称", - "nick_name": "用户昵称", - "dept_name": "部门", - "email": "邮箱地址", - "phonenumber": "手机号码", - "sex": "性别", - "status": "状态", - "create_by": "创建者", - "create_time": "创建时间", - "update_by": "更新者", - "update_time": "更新时间", - "remark": "备注", + 'user_id': '用户编号', + 'user_name': '用户名称', + 'nick_name': '用户昵称', + 'dept_name': '部门', + 'email': '邮箱地址', + 'phonenumber': '手机号码', + 'sex': '性别', + 'status': '状态', + 'create_by': '创建者', + 'create_time': '创建时间', + 'update_by': '更新者', + 'update_time': '更新时间', + 'remark': '备注', } data = user_list @@ -306,133 +507,144 @@ class UserService: item['sex'] = '女' else: item['sex'] = '未知' - new_data = [{mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data] + new_data = [ + {mapping_dict.get(key): value for key, value in item.items() if mapping_dict.get(key)} for item in data + ] binary_data = export_list2excel(new_data) return binary_data @classmethod - def get_user_role_allocated_list_services(cls, result_db: Session, page_object: UserRoleQueryModel): + async def get_user_role_allocated_list_services(cls, query_db: AsyncSession, page_object: UserRoleQueryModel): """ - 根据用户id获取已分配角色列表或根据角色id获取已分配用户列表 - :param result_db: orm对象 - :param page_object: 用户关联角色对象 - :return: 已分配角色列表或已分配用户列表 - """ - allocated_list = [] - if page_object.user_id: - allocated_list = UserDao.get_user_role_allocated_list_by_user_id(result_db, page_object) - if page_object.role_id: - allocated_list = UserDao.get_user_role_allocated_list_by_role_id(result_db, page_object) - - return allocated_list + 根据用户id获取已分配角色列表 - @classmethod - def get_user_role_unallocated_list_services(cls, result_db: Session, page_object: UserRoleQueryModel): - """ - 根据用户id获取未分配角色列表或根据角色id获取未分配用户列表 - :param result_db: orm对象 + :param query_db: orm对象 :param page_object: 用户关联角色对象 - :return: 未分配角色列表或未分配用户列表 + :return: 已分配角色列表 """ - unallocated_list = [] - if page_object.user_id: - unallocated_list = UserDao.get_user_role_unallocated_list_by_user_id(result_db, page_object) - if page_object.role_id: - unallocated_list = UserDao.get_user_role_unallocated_list_by_role_id(result_db, page_object) + query_user = await UserDao.get_user_detail_by_id(query_db, page_object.user_id) + post_ids = ','.join([str(row.post_id) for row in query_user.get('user_post_info')]) + role_ids = ','.join([str(row.role_id) for row in query_user.get('user_role_info')]) + user = UserInfoModel( + **SqlalchemyUtil.serialize_result(query_user.get('user_basic_info')), + post_ids=post_ids, + role_ids=role_ids, + dept=SqlalchemyUtil.serialize_result(query_user.get('user_dept_info')), + role=SqlalchemyUtil.serialize_result(query_user.get('user_role_info')), + ) + query_role_list = [ + SelectedRoleModel(**row) for row in await RoleService.get_role_select_option_services(query_db) + ] + for model_a in query_role_list: + for model_b in user.role: + if model_a.role_id == model_b.role_id: + model_a.flag = True + result = UserRoleResponseModel(roles=query_role_list, user=user) - return unallocated_list + return result @classmethod - def add_user_role_services(cls, result_db: Session, page_object: CrudUserRoleModel): + async def add_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): """ 新增用户关联角色信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 新增用户关联角色对象 :return: 新增用户关联角色校验结果 """ - if page_object.user_ids and page_object.role_ids: - user_id_list = page_object.user_ids.split(',') + if page_object.user_id and page_object.role_ids: role_id_list = page_object.role_ids.split(',') - if len(user_id_list) == 1 and len(role_id_list) >= 1: - try: - for role_id in role_id_list: - user_role_dict = dict(user_id=page_object.user_ids, role_id=role_id) - user_role = cls.detail_user_role_services(result_db, UserRoleModel(**user_role_dict)) - if user_role: - continue - else: - UserDao.add_user_role_dao(result_db, UserRoleModel(**user_role_dict)) - result_db.commit() - result = dict(is_success=True, message='新增成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - elif len(user_id_list) >= 1 and len(role_id_list) == 1: - try: - for user_id in user_id_list: - user_role_dict = dict(user_id=user_id, role_id=page_object.role_ids) - user_role = cls.detail_user_role_services(result_db, UserRoleModel(**user_role_dict)) - if user_role: - continue - else: - UserDao.add_user_role_dao(result_db, UserRoleModel(**user_role_dict)) - result_db.commit() - result = dict(is_success=True, message='新增成功') - except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - else: - result = dict(is_success=False, message='不满足新增条件') + try: + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(user_id=page_object.user_id) + ) + for role_id in role_id_list: + await UserDao.add_user_role_dao( + query_db, UserRoleModel(user_id=page_object.user_id, role_id=role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='分配成功') + except Exception as e: + await query_db.rollback() + raise e + elif page_object.user_id and not page_object.role_ids: + try: + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(user_id=page_object.user_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='分配成功') + except Exception as e: + await query_db.rollback() + raise e + elif page_object.user_ids and page_object.role_id: + user_id_list = page_object.user_ids.split(',') + try: + for user_id in user_id_list: + user_role = await cls.detail_user_role_services( + query_db, UserRoleModel(user_id=user_id, role_id=page_object.role_id) + ) + if user_role: + continue + else: + await UserDao.add_user_role_dao( + query_db, UserRoleModel(user_id=user_id, role_id=page_object.role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='新增成功') + except Exception as e: + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='传入用户角色关联信息为空') - - return CrudUserResponse(**result) + raise ServiceException(message='不满足新增条件') @classmethod - def delete_user_role_services(cls, result_db: Session, page_object: CrudUserRoleModel): + async def delete_user_role_services(cls, query_db: AsyncSession, page_object: CrudUserRoleModel): """ 删除用户关联角色信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 删除用户关联角色对象 :return: 删除用户关联角色校验结果 """ - if page_object.user_ids and page_object.role_ids: - user_id_list = page_object.user_ids.split(',') - role_id_list = page_object.role_ids.split(',') - if len(user_id_list) == 1 and len(role_id_list) >= 1: + if (page_object.user_id and page_object.role_id) or (page_object.user_ids and page_object.role_id): + if page_object.user_id and page_object.role_id: try: - for role_id in role_id_list: - UserDao.delete_user_role_by_user_and_role_dao(result_db, UserRoleModel(**dict(user_id=page_object.user_ids, role_id=role_id))) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(user_id=page_object.user_id, role_id=page_object.role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) - elif len(user_id_list) >= 1 and len(role_id_list) == 1: + await query_db.rollback() + raise e + elif page_object.user_ids and page_object.role_id: + user_id_list = page_object.user_ids.split(',') try: for user_id in user_id_list: - UserDao.delete_user_role_by_user_and_role_dao(result_db, UserRoleModel(**dict(user_id=user_id, role_id=page_object.role_ids))) - result_db.commit() - result = dict(is_success=True, message='删除成功') + await UserDao.delete_user_role_by_user_and_role_dao( + query_db, UserRoleModel(user_id=user_id, role_id=page_object.role_id) + ) + await query_db.commit() + return CrudResponseModel(is_success=True, message='删除成功') except Exception as e: - result_db.rollback() - result = dict(is_success=False, message=str(e)) + await query_db.rollback() + raise e else: - result = dict(is_success=False, message='不满足删除条件') + raise ServiceException(message='不满足删除条件') else: - result = dict(is_success=False, message='传入用户角色关联信息为空') - - return CrudUserResponse(**result) + raise ServiceException(message='传入用户角色关联信息为空') @classmethod - def detail_user_role_services(cls, result_db: Session, page_object: UserRoleModel): + async def detail_user_role_services(cls, query_db: AsyncSession, page_object: UserRoleModel): """ 获取用户关联角色详细信息service - :param result_db: orm对象 + + :param query_db: orm对象 :param page_object: 用户关联角色对象 :return: 用户关联角色详细信息 """ - user_role = UserDao.get_user_role_detail(result_db, page_object) + user_role = await UserDao.get_user_role_detail(query_db, page_object) return user_role diff --git a/dash-fastapi-backend/module_task/__init__.py b/dash-fastapi-backend/module_task/__init__.py index 36fefe9bdc99a4966a3627472fb980bff6c96a39..1f4b41292aae3b1b4b030a58a1ccf4fbaf99a696 100644 --- a/dash-fastapi-backend/module_task/__init__.py +++ b/dash-fastapi-backend/module_task/__init__.py @@ -1 +1 @@ -from . import scheduler_test +from . import scheduler_test # noqa: F401 diff --git a/dash-fastapi-backend/module_task/scheduler_test.py b/dash-fastapi-backend/module_task/scheduler_test.py index b22dc6fc37a56bd4c4ae2d95b47d767a7bf62a8b..1c8441f9d6ccad2742d4b7c67d81a4220a5bdf29 100644 --- a/dash-fastapi-backend/module_task/scheduler_test.py +++ b/dash-fastapi-backend/module_task/scheduler_test.py @@ -4,4 +4,4 @@ from datetime import datetime def job(*args, **kwargs): print(args) print(kwargs) - print(f"{datetime.now()}执行了") + print(f'{datetime.now()}执行了') diff --git a/dash-fastapi-backend/ruff.toml b/dash-fastapi-backend/ruff.toml new file mode 100644 index 0000000000000000000000000000000000000000..ea953686cf07cd98f8382ffc6e5dd54f2b183c4c --- /dev/null +++ b/dash-fastapi-backend/ruff.toml @@ -0,0 +1,4 @@ +line-length = 120 + +[format] +quote-style = "single" \ No newline at end of file diff --git a/dash-fastapi-backend/server.py b/dash-fastapi-backend/server.py new file mode 100644 index 0000000000000000000000000000000000000000..00b4661dfc132653e08a1cfe648abf124f6af057 --- /dev/null +++ b/dash-fastapi-backend/server.py @@ -0,0 +1,83 @@ +from contextlib import asynccontextmanager +from fastapi import FastAPI +from config.env import AppConfig +from config.get_db import init_create_table +from config.get_redis import RedisUtil +from config.get_scheduler import SchedulerUtil +from exceptions.handle import handle_exception +from middlewares.handle import handle_middleware +from module_admin.controller.cache_controller import cacheController +from module_admin.controller.captcha_controller import captchaController +from module_admin.controller.common_controller import commonController +from module_admin.controller.config_controller import configController +from module_admin.controller.dept_controller import deptController +from module_admin.controller.dict_controller import dictController +from module_admin.controller.log_controller import logController +from module_admin.controller.login_controller import loginController +from module_admin.controller.job_controller import jobController +from module_admin.controller.menu_controller import menuController +from module_admin.controller.notice_controller import noticeController +from module_admin.controller.online_controller import onlineController +from module_admin.controller.post_controler import postController +from module_admin.controller.role_controller import roleController +from module_admin.controller.server_controller import serverController +from module_admin.controller.user_controller import userController +from sub_applications.handle import handle_sub_applications +from utils.common_util import worship +from utils.log_util import logger + + +# 生命周期事件 +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info(f'{AppConfig.app_name}开始启动') + worship() + await init_create_table() + app.state.redis = await RedisUtil.create_redis_pool() + await RedisUtil.init_sys_dict(app.state.redis) + await RedisUtil.init_sys_config(app.state.redis) + await SchedulerUtil.init_system_scheduler() + logger.info(f'{AppConfig.app_name}启动成功') + yield + await RedisUtil.close_redis_pool(app) + await SchedulerUtil.close_system_scheduler() + + +# 初始化FastAPI对象 +app = FastAPI( + title=AppConfig.app_name, + description=f'{AppConfig.app_name}接口文档', + version=AppConfig.app_version, + lifespan=lifespan, +) + +# 挂载子应用 +handle_sub_applications(app) +# 加载中间件处理方法 +handle_middleware(app) +# 加载全局异常处理方法 +handle_exception(app) + + +# 加载路由列表 +controller_list = [ + {'router': loginController, 'tags': ['登录模块']}, + {'router': captchaController, 'tags': ['验证码模块']}, + {'router': userController, 'tags': ['系统管理-用户管理']}, + {'router': roleController, 'tags': ['系统管理-角色管理']}, + {'router': menuController, 'tags': ['系统管理-菜单管理']}, + {'router': deptController, 'tags': ['系统管理-部门管理']}, + {'router': postController, 'tags': ['系统管理-岗位管理']}, + {'router': dictController, 'tags': ['系统管理-字典管理']}, + {'router': configController, 'tags': ['系统管理-参数管理']}, + {'router': noticeController, 'tags': ['系统管理-通知公告管理']}, + {'router': logController, 'tags': ['系统管理-日志管理']}, + {'router': onlineController, 'tags': ['系统监控-在线用户']}, + {'router': jobController, 'tags': ['系统监控-定时任务']}, + {'router': serverController, 'tags': ['系统监控-菜单管理']}, + {'router': cacheController, 'tags': ['系统监控-缓存监控']}, + {'router': commonController, 'tags': ['通用模块']}, +] + +for controller in controller_list: + app.include_router(router=controller.get('router'), tags=controller.get('tags')) diff --git a/dash-fastapi-backend/sql/dash-fastapi.sql b/dash-fastapi-backend/sql/dash-fastapi.sql index 8514d9d9e9a46a1430161247524025d6d49cce21..29ec09713147eb0e4d3134bcc9c7b9c30eb61540 100644 --- a/dash-fastapi-backend/sql/dash-fastapi.sql +++ b/dash-fastapi-backend/sql/dash-fastapi.sql @@ -1,721 +1,713 @@ -/* - Navicat Premium Data Transfer - - Source Server : 本机mysql - Source Server Type : MySQL - Source Server Version : 80013 - Source Host : localhost:3306 - Source Schema : dash-fastapi - - Target Server Type : MySQL - Target Server Version : 80013 - File Encoding : 65001 - - Date: 09/03/2023 14:30:35 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for gen_table --- ---------------------------- -DROP TABLE IF EXISTS `gen_table`; -CREATE TABLE `gen_table` ( - `table_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', - `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表名称', - `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表描述', - `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联子表的表名', - `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', - `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '实体类名称', - `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', - `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成包路径', - `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模块名', - `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成业务名', - `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能名', - `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能作者', - `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', - `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', - `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '其它生成选项', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`table_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of gen_table --- ---------------------------- - --- ---------------------------- --- Table structure for gen_table_column --- ---------------------------- -DROP TABLE IF EXISTS `gen_table_column`; -CREATE TABLE `gen_table_column` ( - `column_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', - `table_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '归属表编号', - `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列名称', - `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列描述', - `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列类型', - `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA类型', - `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', - `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', - `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', - `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', - `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', - `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', - `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', - `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', - `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', - `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', - `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', - `sort` int(11) NULL DEFAULT NULL COMMENT '排序', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`column_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of gen_table_column --- ---------------------------- - --- ---------------------------- --- Table structure for sys_config --- ---------------------------- -DROP TABLE IF EXISTS `sys_config`; -CREATE TABLE `sys_config` ( - `config_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '参数主键', - `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数名称', - `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键名', - `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键值', - `config_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`config_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_config --- ---------------------------- -INSERT INTO `sys_config` VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', '#1890ff', 'Y', 'admin', '2023-05-23 16:13:34', 'admin', '2023-05-23 16:13:34', '蓝色 #1890ff'); -INSERT INTO `sys_config` VALUES (2, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', '2023-05-23 16:13:34', 'admin', '2023-05-23 16:13:34', '是否开启验证码功能(true开启,false关闭)'); -INSERT INTO `sys_config` VALUES (3, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', '2023-05-23 16:13:34', '', NULL, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); -INSERT INTO `sys_config` VALUES (4, '账号自助-是否开启忘记密码功能', 'sys.account.forgetUser', 'true', 'Y', 'admin', '2023-05-23 16:13:34', 'admin', '2023-05-23 16:13:34', '是否开启忘记密码功能(true开启,false关闭)'); - --- ---------------------------- --- Table structure for sys_dept --- ---------------------------- -DROP TABLE IF EXISTS `sys_dept`; -CREATE TABLE `sys_dept` ( - `dept_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '部门id', - `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父部门id', - `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', - `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', - `order_num` int(11) NULL DEFAULT 0 COMMENT '显示顺序', - `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', - `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', - `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`dept_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 201 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_dept --- ---------------------------- -INSERT INTO `sys_dept` VALUES (100, 0, '0', '集团总公司', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '上海分公司', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '广东分公司', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '研发部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); -INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-08 15:57:58'); - --- ---------------------------- --- Table structure for sys_dict_data --- ---------------------------- -DROP TABLE IF EXISTS `sys_dict_data`; -CREATE TABLE `sys_dict_data` ( - `dict_code` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典编码', - `dict_sort` int(11) NULL DEFAULT 0 COMMENT '字典排序', - `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签', - `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值', - `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', - `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', - `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', - `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`dict_code`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_dict_data --- ---------------------------- -INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '性别男'); -INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '性别女'); -INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '性别未知'); -INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '显示菜单'); -INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '隐藏菜单'); -INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '0', 'sys_normal_disable', '{\"color\": \"blue\"}', 'primary', 'Y', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '正常状态'); -INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '1', 'sys_normal_disable', '{\"color\": \"volcano\"}', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', 'admin', '2023-08-18 11:24:23', '停用状态'); -INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '正常状态'); -INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '停用状态'); -INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'default', 'sys_job_group', '{\"color\": \"blue\"}', '', 'Y', '0', 'admin', '2023-02-11 22:23:38', 'admin', '2023-08-20 16:33:32', '默认分组'); -INSERT INTO `sys_dict_data` VALUES (11, 2, '数据库', 'sqlalchemy', 'sys_job_group', '{\"color\": \"green\"}', '', 'N', '0', 'admin', '2023-02-11 22:23:38', 'admin', '2023-08-27 22:54:40', '数据库分组'); -INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '系统默认是'); -INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '系统默认否'); -INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '{\"color\": \"gold\"}', 'warning', 'Y', '0', 'admin', '2023-02-11 22:23:38', 'admin', '2023-08-20 16:12:53', '通知'); -INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '{\"color\": \"green\"}', 'success', 'N', '0', 'admin', '2023-02-11 22:23:38', 'admin', '2023-08-20 16:13:03', '公告'); -INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '正常状态'); -INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '关闭状态'); -INSERT INTO `sys_dict_data` VALUES (18, 99, '其他', '0', 'sys_oper_type', '{\"color\": \"purple\"}', 'info', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '其他操作'); -INSERT INTO `sys_dict_data` VALUES (19, 1, '新增', '1', 'sys_oper_type', '{\"color\": \"green\"}', 'info', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '新增操作'); -INSERT INTO `sys_dict_data` VALUES (20, 2, '修改', '2', 'sys_oper_type', '{\"color\": \"orange\"}', 'info', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '修改操作'); -INSERT INTO `sys_dict_data` VALUES (21, 3, '删除', '3', 'sys_oper_type', '{\"color\": \"red\"}', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '删除操作'); -INSERT INTO `sys_dict_data` VALUES (22, 4, '授权', '4', 'sys_oper_type', '{\"color\": \"lime\"}', 'primary', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '授权操作'); -INSERT INTO `sys_dict_data` VALUES (23, 5, '导出', '5', 'sys_oper_type', '{\"color\": \"geekblue\"}', 'warning', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '导出操作'); -INSERT INTO `sys_dict_data` VALUES (24, 6, '导入', '6', 'sys_oper_type', '{\"color\": \"blue\"}', 'warning', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '导入操作'); -INSERT INTO `sys_dict_data` VALUES (25, 7, '强退', '7', 'sys_oper_type', '{\"color\": \"magenta\"}', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '强退操作'); -INSERT INTO `sys_dict_data` VALUES (26, 8, '生成代码', '8', 'sys_oper_type', '{\"color\": \"cyan\"}', 'warning', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '生成操作'); -INSERT INTO `sys_dict_data` VALUES (27, 9, '清空数据', '9', 'sys_oper_type', '{\"color\": \"volcano\"}', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '清空操作'); -INSERT INTO `sys_dict_data` VALUES (28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '正常状态'); -INSERT INTO `sys_dict_data` VALUES (29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '停用状态'); -INSERT INTO `sys_dict_data` VALUES (100, 3, 'Redis', 'redis', 'sys_job_group', '{\"color\": \"gold\"}', 'default', 'N', '0', 'admin', '2023-08-27 22:52:05', 'admin', '2023-08-27 22:52:05', 'redis分组'); - --- ---------------------------- --- Table structure for sys_dict_type --- ---------------------------- -DROP TABLE IF EXISTS `sys_dict_type`; -CREATE TABLE `sys_dict_type` ( - `dict_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典主键', - `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典名称', - `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`dict_id`) USING BTREE, - UNIQUE INDEX `dict_type`(`dict_type`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_dict_type --- ---------------------------- -INSERT INTO `sys_dict_type` VALUES (1, '用户性别', 'sys_user_sex', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '用户性别列表'); -INSERT INTO `sys_dict_type` VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '菜单状态列表'); -INSERT INTO `sys_dict_type` VALUES (3, '系统开关', 'sys_normal_disable', '0', 'admin', '2023-02-11 22:23:38', 'admin', '2023-08-18 11:23:39', '系统开关列表'); -INSERT INTO `sys_dict_type` VALUES (4, '任务状态', 'sys_job_status', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '任务状态列表'); -INSERT INTO `sys_dict_type` VALUES (5, '任务分组', 'sys_job_group', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '任务分组列表'); -INSERT INTO `sys_dict_type` VALUES (6, '系统是否', 'sys_yes_no', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '系统是否列表'); -INSERT INTO `sys_dict_type` VALUES (7, '通知类型', 'sys_notice_type', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '通知类型列表'); -INSERT INTO `sys_dict_type` VALUES (8, '通知状态', 'sys_notice_status', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '通知状态列表'); -INSERT INTO `sys_dict_type` VALUES (9, '操作类型', 'sys_oper_type', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '操作类型列表'); -INSERT INTO `sys_dict_type` VALUES (10, '系统状态', 'sys_common_status', '0', 'admin', '2023-02-11 22:23:38', '', NULL, '登录状态列表'); - --- ---------------------------- --- Table structure for sys_job --- ---------------------------- -DROP TABLE IF EXISTS `sys_job`; -CREATE TABLE `sys_job` ( - `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID', - `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', - `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT '任务组名', - `job_executor` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT '任务执行器', - `invoke_target` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', - `job_args` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '位置参数', - `job_kwargs` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关键字参数', - `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', - `misfire_policy` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', - `concurrent` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注信息', - PRIMARY KEY (`job_id`, `job_name`, `job_group`, `job_executor`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_job --- ---------------------------- -INSERT INTO `sys_job` VALUES (1, '系统默认(无参)', 'default', 'default', 'module_task.scheduler_test.job', 'test', NULL, '0/10 * * * * ?', '2', '0', '1', 'admin', '2023-05-23 16:13:34', 'admin', '2023-05-23 16:13:34', ''); -INSERT INTO `sys_job` VALUES (2, '系统默认(有参)', 'sqlalchemy', 'default', 'module_task.scheduler_test.job', 'new', '{\"test\": 111}', '0/15 * * * * ?', '1', '1', '1', 'admin', '2023-05-23 16:13:34', 'admin', '2023-05-23 16:13:34', ''); -INSERT INTO `sys_job` VALUES (3, '系统默认(多参)', 'redis', 'default', 'module_task.scheduler_test.job', NULL, NULL, '0/20 * * * * ?', '3', '1', '1', 'admin', '2023-05-23 16:13:34', '', NULL, ''); - --- ---------------------------- --- Table structure for sys_job_log --- ---------------------------- -DROP TABLE IF EXISTS `sys_job_log`; -CREATE TABLE `sys_job_log` ( - `job_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', - `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', - `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', - `job_executor` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务执行器', - `invoke_target` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', - `job_args` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '位置参数', - `job_kwargs` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关键字参数', - `job_trigger` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务触发器', - `job_message` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志信息', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '执行状态(0正常 1失败)', - `exception_info` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '异常信息', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - PRIMARY KEY (`job_log_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_job_log --- ---------------------------- - --- ---------------------------- --- Table structure for sys_logininfor --- ---------------------------- -DROP TABLE IF EXISTS `sys_logininfor`; -CREATE TABLE `sys_logininfor` ( - `info_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '访问ID', - `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', - `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', - `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', - `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', - `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', - `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', - `login_time` datetime(0) NULL DEFAULT NULL COMMENT '访问时间', - PRIMARY KEY (`info_id`) USING BTREE, - INDEX `idx_sys_logininfor_s`(`status`) USING BTREE, - INDEX `idx_sys_logininfor_lt`(`login_time`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_logininfor --- ---------------------------- - --- ---------------------------- --- Table structure for sys_menu --- ---------------------------- -DROP TABLE IF EXISTS `sys_menu`; -CREATE TABLE `sys_menu` ( - `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', - `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', - `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', - `order_num` int(11) NULL DEFAULT 0 COMMENT '显示顺序', - `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址', - `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径', - `query` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由参数', - `is_frame` int(11) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', - `is_cache` int(11) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', - `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', - `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', - `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', - `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', - PRIMARY KEY (`menu_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2000 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_menu --- ---------------------------- -INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, '/system', NULL, '', 1, 0, 'M', '0', '0', '', 'antd-setting', 'admin', '2023-05-23 16:13:33', '', NULL, '系统管理目录'); -INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 2, '/monitor', NULL, '', 1, 0, 'M', '0', '0', '', 'antd-fund-projection-screen', 'admin', '2023-05-23 16:13:33', '', NULL, '系统监控目录'); -INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, '/tool', NULL, '', 1, 0, 'M', '0', '0', '', 'antd-repair', 'admin', '2023-05-23 16:13:33', '', NULL, '系统工具目录'); -INSERT INTO `sys_menu` VALUES (4, '若依官网', 0, 4, 'http://ruoyi.vip', NULL, NULL, 0, 0, 'M', '0', '0', '', 'antd-send', 'admin', '2023-05-23 16:13:33', '', NULL, '若依官网地址'); -INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, '/system/user', 'system.user', '', 1, 0, 'C', '0', '0', 'system:user:list', 'antd-user', 'admin', '2023-05-23 16:13:33', '', NULL, '用户管理菜单'); -INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, '/system/role', 'system.role', '', 1, 0, 'C', '0', '0', 'system:role:list', 'antd-team', 'admin', '2023-05-23 16:13:33', '', NULL, '角色管理菜单'); -INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, '/system/menu', 'system.menu', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'antd-app-store-add', 'admin', '2023-05-23 16:13:33', '', NULL, '菜单管理菜单'); -INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, '/system/dept', 'system.dept', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'antd-cluster', 'admin', '2023-05-23 16:13:33', '', NULL, '部门管理菜单'); -INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, '/system/post', 'system.post', '', 1, 0, 'C', '0', '0', 'system:post:list', 'antd-idcard', 'admin', '2023-05-23 16:13:33', 'ry', '2023-07-06 09:47:26', '岗位管理菜单'); -INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, '/system/dict', 'system.dict', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'antd-read', 'admin', '2023-05-23 16:13:33', 'ry', '2023-07-06 16:25:44', '字典管理菜单'); -INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, '/system/config', 'system.config', '', 1, 0, 'C', '0', '0', 'system:config:list', 'antd-calculator', 'admin', '2023-05-23 16:13:33', '', NULL, '参数设置菜单'); -INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, '/system/notice', 'system.notice', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'antd-notification', 'admin', '2023-05-23 16:13:33', '', NULL, '通知公告菜单'); -INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, '/log', '', '', 1, 0, 'M', '0', '0', '', 'antd-bug', 'admin', '2023-05-23 16:13:33', '', NULL, '日志管理菜单'); -INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, '/monitor/online', 'monitor.online', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'antd-desktop', 'admin', '2023-05-23 16:13:33', '', NULL, '在线用户菜单'); -INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, '/monitor/job', 'monitor.job', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'antd-deployment-unit', 'admin', '2023-05-23 16:13:33', '', NULL, '定时任务菜单'); -INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, '/monitor/druid', 'monitor.druid', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'antd-database', 'admin', '2023-05-23 16:13:33', '', NULL, '数据监控菜单'); -INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, '/monitor/server', 'monitor.server', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'antd-cloud-server', 'admin', '2023-05-23 16:13:33', '', NULL, '服务监控菜单'); -INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, '/monitor/cache/control', 'monitor.cache.control', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'antd-cloud-sync', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-11 10:52:56', '缓存监控菜单'); -INSERT INTO `sys_menu` VALUES (114, '缓存列表', 2, 6, '/monitor/cache/list', 'monitor.cache.list', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'antd-table', 'admin', '2023-05-23 16:13:33', 'admin', '2023-08-11 10:53:11', '缓存列表菜单'); -INSERT INTO `sys_menu` VALUES (115, '表单构建', 3, 1, '/tool/build', 'tool.build', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'antd-build', 'admin', '2023-05-23 16:13:33', '', NULL, '表单构建菜单'); -INSERT INTO `sys_menu` VALUES (116, '代码生成', 3, 2, '/tool/gen', 'tool.gen', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'antd-console-sql', 'admin', '2023-05-23 16:13:33', '', NULL, '代码生成菜单'); -INSERT INTO `sys_menu` VALUES (117, '系统接口', 3, 3, '/tool/swagger', 'tool.swagger', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'antd-api', 'admin', '2023-05-23 16:13:33', '', NULL, '系统接口菜单'); -INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, '/monitor/operlog', 'monitor.operlog', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'antd-clear', 'admin', '2023-05-23 16:13:33', '', NULL, '操作日志菜单'); -INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, '/monitor/logininfor', 'monitor.logininfor', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'antd-control', 'admin', '2023-05-23 16:13:33', '', NULL, '登录日志菜单'); -INSERT INTO `sys_menu` VALUES (1000, '用户查询', 100, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1001, '用户新增', 100, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1002, '用户修改', 100, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1003, '用户删除', 100, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1004, '用户导出', 100, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1005, '用户导入', 100, 6, '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1006, '重置密码', 100, 7, '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1007, '角色查询', 101, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1008, '角色新增', 101, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1009, '角色修改', 101, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1010, '角色删除', 101, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1011, '角色导出', 101, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1012, '菜单查询', 102, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1013, '菜单新增', 102, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1014, '菜单修改', 102, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1015, '菜单删除', 102, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1016, '部门查询', 103, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1017, '部门新增', 103, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1018, '部门修改', 103, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1019, '部门删除', 103, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1020, '岗位查询', 104, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1021, '岗位新增', 104, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1022, '岗位修改', 104, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1023, '岗位删除', 104, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1024, '岗位导出', 104, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1025, '字典查询', 105, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1026, '字典新增', 105, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1027, '字典修改', 105, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1028, '字典删除', 105, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1029, '字典导出', 105, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1030, '参数查询', 106, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1031, '参数新增', 106, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1032, '参数修改', 106, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1033, '参数删除', 106, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1034, '参数导出', 106, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1035, '公告查询', 107, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1036, '公告新增', 107, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1037, '公告修改', 107, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1038, '公告删除', 107, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1039, '操作查询', 500, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1040, '操作删除', 500, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1041, '日志导出', 500, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1042, '登录查询', 501, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1043, '登录删除', 501, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1044, '日志导出', 501, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1045, '账户解锁', 501, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 6, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1055, '生成查询', 116, 1, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1056, '生成修改', 116, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1057, '生成删除', 116, 3, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1058, '导入代码', 116, 4, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1059, '预览代码', 116, 5, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1060, '生成代码', 116, 6, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2023-05-23 16:13:33', '', NULL, ''); - --- ---------------------------- --- Table structure for sys_notice --- ---------------------------- -DROP TABLE IF EXISTS `sys_notice`; -CREATE TABLE `sys_notice` ( - `notice_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '公告ID', - `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题', - `notice_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', - `notice_content` longblob NULL COMMENT '公告内容', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`notice_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_notice --- ---------------------------- -INSERT INTO `sys_notice` VALUES (1, '温馨提醒:2018-07-01 若依新版本发布啦', '2', 0xE696B0E78988E69CACE58685E5AEB9, '0', 'admin', '2023-05-23 16:13:34', '', NULL, '管理员'); -INSERT INTO `sys_notice` VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '1', 0xE7BBB4E68AA4E58685E5AEB9, '0', 'admin', '2023-05-23 16:13:34', '', NULL, '管理员'); - --- ---------------------------- --- Table structure for sys_oper_log --- ---------------------------- -DROP TABLE IF EXISTS `sys_oper_log`; -CREATE TABLE `sys_oper_log` ( - `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', - `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', - `business_type` int(11) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', - `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', - `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', - `operator_type` int(11) NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', - `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', - `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', - `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', - `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', - `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', - `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', - `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '返回参数', - `status` int(11) NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', - `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', - `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间', - `cost_time` bigint(20) NULL DEFAULT 0 COMMENT '消耗时间', - PRIMARY KEY (`oper_id`) USING BTREE, - INDEX `idx_sys_oper_log_bt`(`business_type`) USING BTREE, - INDEX `idx_sys_oper_log_s`(`status`) USING BTREE, - INDEX `idx_sys_oper_log_ot`(`oper_time`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_oper_log --- ---------------------------- - --- ---------------------------- --- Table structure for sys_post --- ---------------------------- -DROP TABLE IF EXISTS `sys_post`; -CREATE TABLE `sys_post` ( - `post_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '岗位ID', - `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', - `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', - `post_sort` int(11) NOT NULL COMMENT '显示顺序', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态(0正常 1停用)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`post_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_post --- ---------------------------- -INSERT INTO `sys_post` VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_post` VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2023-05-23 16:13:33', '', NULL, ''); -INSERT INTO `sys_post` VALUES (3, 'hr', '人力资源', 3, '0', 'admin', '2023-05-23 16:13:33', 'ry', '2023-06-05 15:49:31', ''); -INSERT INTO `sys_post` VALUES (4, 'user', '普通员工', 4, '0', 'admin', '2023-05-23 16:13:33', '', NULL, ''); - --- ---------------------------- --- Table structure for sys_role --- ---------------------------- -DROP TABLE IF EXISTS `sys_role`; -CREATE TABLE `sys_role` ( - `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', - `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', - `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串', - `role_sort` int(11) NOT NULL COMMENT '显示顺序', - `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', - `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', - `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`role_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_role --- ---------------------------- -INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2023-05-23 16:13:33', '', NULL, '超级管理员'); -INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2023-05-23 16:13:33', 'admin', '2023-09-01 15:53:26', '普通角色'); - --- ---------------------------- --- Table structure for sys_role_dept --- ---------------------------- -DROP TABLE IF EXISTS `sys_role_dept`; -CREATE TABLE `sys_role_dept` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `dept_id` bigint(20) NOT NULL COMMENT '部门ID', - PRIMARY KEY (`role_id`, `dept_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_role_dept --- ---------------------------- -INSERT INTO `sys_role_dept` VALUES (2, 100); -INSERT INTO `sys_role_dept` VALUES (2, 101); -INSERT INTO `sys_role_dept` VALUES (2, 105); - --- ---------------------------- --- Table structure for sys_role_menu --- ---------------------------- -DROP TABLE IF EXISTS `sys_role_menu`; -CREATE TABLE `sys_role_menu` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', - PRIMARY KEY (`role_id`, `menu_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_role_menu --- ---------------------------- -INSERT INTO `sys_role_menu` VALUES (2, 1); -INSERT INTO `sys_role_menu` VALUES (2, 2); -INSERT INTO `sys_role_menu` VALUES (2, 3); -INSERT INTO `sys_role_menu` VALUES (2, 4); -INSERT INTO `sys_role_menu` VALUES (2, 100); -INSERT INTO `sys_role_menu` VALUES (2, 101); -INSERT INTO `sys_role_menu` VALUES (2, 102); -INSERT INTO `sys_role_menu` VALUES (2, 103); -INSERT INTO `sys_role_menu` VALUES (2, 104); -INSERT INTO `sys_role_menu` VALUES (2, 105); -INSERT INTO `sys_role_menu` VALUES (2, 106); -INSERT INTO `sys_role_menu` VALUES (2, 107); -INSERT INTO `sys_role_menu` VALUES (2, 108); -INSERT INTO `sys_role_menu` VALUES (2, 109); -INSERT INTO `sys_role_menu` VALUES (2, 110); -INSERT INTO `sys_role_menu` VALUES (2, 111); -INSERT INTO `sys_role_menu` VALUES (2, 112); -INSERT INTO `sys_role_menu` VALUES (2, 113); -INSERT INTO `sys_role_menu` VALUES (2, 114); -INSERT INTO `sys_role_menu` VALUES (2, 115); -INSERT INTO `sys_role_menu` VALUES (2, 116); -INSERT INTO `sys_role_menu` VALUES (2, 117); -INSERT INTO `sys_role_menu` VALUES (2, 500); -INSERT INTO `sys_role_menu` VALUES (2, 501); -INSERT INTO `sys_role_menu` VALUES (2, 1000); -INSERT INTO `sys_role_menu` VALUES (2, 1001); -INSERT INTO `sys_role_menu` VALUES (2, 1002); -INSERT INTO `sys_role_menu` VALUES (2, 1003); -INSERT INTO `sys_role_menu` VALUES (2, 1004); -INSERT INTO `sys_role_menu` VALUES (2, 1005); -INSERT INTO `sys_role_menu` VALUES (2, 1006); -INSERT INTO `sys_role_menu` VALUES (2, 1007); -INSERT INTO `sys_role_menu` VALUES (2, 1008); -INSERT INTO `sys_role_menu` VALUES (2, 1009); -INSERT INTO `sys_role_menu` VALUES (2, 1010); -INSERT INTO `sys_role_menu` VALUES (2, 1011); -INSERT INTO `sys_role_menu` VALUES (2, 1012); -INSERT INTO `sys_role_menu` VALUES (2, 1013); -INSERT INTO `sys_role_menu` VALUES (2, 1014); -INSERT INTO `sys_role_menu` VALUES (2, 1015); -INSERT INTO `sys_role_menu` VALUES (2, 1016); -INSERT INTO `sys_role_menu` VALUES (2, 1017); -INSERT INTO `sys_role_menu` VALUES (2, 1018); -INSERT INTO `sys_role_menu` VALUES (2, 1019); -INSERT INTO `sys_role_menu` VALUES (2, 1020); -INSERT INTO `sys_role_menu` VALUES (2, 1021); -INSERT INTO `sys_role_menu` VALUES (2, 1022); -INSERT INTO `sys_role_menu` VALUES (2, 1023); -INSERT INTO `sys_role_menu` VALUES (2, 1024); -INSERT INTO `sys_role_menu` VALUES (2, 1025); -INSERT INTO `sys_role_menu` VALUES (2, 1026); -INSERT INTO `sys_role_menu` VALUES (2, 1027); -INSERT INTO `sys_role_menu` VALUES (2, 1028); -INSERT INTO `sys_role_menu` VALUES (2, 1029); -INSERT INTO `sys_role_menu` VALUES (2, 1030); -INSERT INTO `sys_role_menu` VALUES (2, 1031); -INSERT INTO `sys_role_menu` VALUES (2, 1032); -INSERT INTO `sys_role_menu` VALUES (2, 1033); -INSERT INTO `sys_role_menu` VALUES (2, 1034); -INSERT INTO `sys_role_menu` VALUES (2, 1035); -INSERT INTO `sys_role_menu` VALUES (2, 1036); -INSERT INTO `sys_role_menu` VALUES (2, 1037); -INSERT INTO `sys_role_menu` VALUES (2, 1038); -INSERT INTO `sys_role_menu` VALUES (2, 1039); -INSERT INTO `sys_role_menu` VALUES (2, 1040); -INSERT INTO `sys_role_menu` VALUES (2, 1041); -INSERT INTO `sys_role_menu` VALUES (2, 1042); -INSERT INTO `sys_role_menu` VALUES (2, 1043); -INSERT INTO `sys_role_menu` VALUES (2, 1044); -INSERT INTO `sys_role_menu` VALUES (2, 1045); -INSERT INTO `sys_role_menu` VALUES (2, 1046); -INSERT INTO `sys_role_menu` VALUES (2, 1047); -INSERT INTO `sys_role_menu` VALUES (2, 1048); -INSERT INTO `sys_role_menu` VALUES (2, 1049); -INSERT INTO `sys_role_menu` VALUES (2, 1050); -INSERT INTO `sys_role_menu` VALUES (2, 1051); -INSERT INTO `sys_role_menu` VALUES (2, 1052); -INSERT INTO `sys_role_menu` VALUES (2, 1053); -INSERT INTO `sys_role_menu` VALUES (2, 1054); -INSERT INTO `sys_role_menu` VALUES (2, 1055); -INSERT INTO `sys_role_menu` VALUES (2, 1056); -INSERT INTO `sys_role_menu` VALUES (2, 1057); -INSERT INTO `sys_role_menu` VALUES (2, 1058); -INSERT INTO `sys_role_menu` VALUES (2, 1059); -INSERT INTO `sys_role_menu` VALUES (2, 1060); - --- ---------------------------- --- Table structure for sys_user --- ---------------------------- -DROP TABLE IF EXISTS `sys_user`; -CREATE TABLE `sys_user` ( - `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', - `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', - `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', - `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', - `user_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', - `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱', - `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', - `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', - `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像地址', - `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', - `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', - `login_date` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', - `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`user_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_user --- ---------------------------- -INSERT INTO `sys_user` VALUES (1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '18888888888', '1', '/common/caches?taskPath=avatar&taskId=admin&filename=admin_avatar.jpeg', '$2b$12$dW/S5ummJs3Z5fMXqsesWuJgTimgtpK85L0zFhUAuFmHDznII/F66', '0', '0', '127.0.0.1', '2023-05-23 16:13:33', 'admin', '2023-05-23 16:13:33', 'admin', '2023-05-23 16:13:33', '管理员'); -INSERT INTO `sys_user` VALUES (2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '16688888888', '0', '/common/caches?taskPath=avatar&taskId=ry&filename=ry_avatar.jpeg', '$2b$12$UkNwXNmyQ2RbS1BROXmCRelWLkEgKWQmVsY1S9O1/nZpUSPud1Oz2', '0', '0', '127.0.0.1', '2023-05-23 16:13:33', 'admin', '2023-05-23 16:13:33', 'admin', '2023-05-23 16:13:33', '测试员'); - --- ---------------------------- --- Table structure for sys_user_post --- ---------------------------- -DROP TABLE IF EXISTS `sys_user_post`; -CREATE TABLE `sys_user_post` ( - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `post_id` bigint(20) NOT NULL COMMENT '岗位ID', - PRIMARY KEY (`user_id`, `post_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_user_post --- ---------------------------- -INSERT INTO `sys_user_post` VALUES (1, 1); -INSERT INTO `sys_user_post` VALUES (2, 1); - --- ---------------------------- --- Table structure for sys_user_role --- ---------------------------- -DROP TABLE IF EXISTS `sys_user_role`; -CREATE TABLE `sys_user_role` ( - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - PRIMARY KEY (`user_id`, `role_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_user_role --- ---------------------------- -INSERT INTO `sys_user_role` VALUES (1, 1); -INSERT INTO `sys_user_role` VALUES (2, 2); - -SET FOREIGN_KEY_CHECKS = 1; +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigint(20) not null auto_increment comment '部门id', + parent_id bigint(20) default 0 comment '父部门id', + ancestors varchar(50) default '' comment '祖级列表', + dept_name varchar(30) default '' comment '部门名称', + order_num int(4) default 0 comment '显示顺序', + leader varchar(20) default null comment '负责人', + phone varchar(11) default null comment '联系电话', + email varchar(50) default null comment '邮箱', + status char(1) default '0' comment '部门状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (dept_id) +) engine=innodb auto_increment=200 comment = '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '集团总公司', 0, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(101, 100, '0,100', '深圳分公司', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '年糕', '15888888888', 'niangao@qq.com', '0', '0', 'admin', sysdate(), '', null); + + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigint(20) not null auto_increment comment '用户ID', + dept_id bigint(20) default null comment '部门ID', + user_name varchar(30) not null comment '用户账号', + nick_name varchar(30) not null comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户)', + email varchar(50) default '' comment '用户邮箱', + phonenumber varchar(11) default '' comment '手机号码', + sex char(1) default '0' comment '用户性别(0男 1女 2未知)', + avatar varchar(100) default '' comment '头像地址', + password varchar(100) default '' comment '密码', + status char(1) default '0' comment '帐号状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + login_ip varchar(128) default '' comment '最后登录IP', + login_date datetime comment '最后登录时间', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id) +) engine=innodb auto_increment=100 comment = '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '超级管理员', '00', 'niangao@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'niangao', '年糕', '00', 'niangao@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), 'admin', sysdate(), '', null, '测试员'); + + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post +( + post_id bigint(20) not null auto_increment comment '岗位ID', + post_code varchar(64) not null comment '岗位编码', + post_name varchar(50) not null comment '岗位名称', + post_sort int(4) not null comment '显示顺序', + status char(1) not null comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (post_id) +) engine=innodb comment = '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigint(20) not null auto_increment comment '角色ID', + role_name varchar(30) not null comment '角色名称', + role_key varchar(100) not null comment '角色权限字符串', + role_sort int(4) not null comment '显示顺序', + data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示', + dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示', + status char(1) not null comment '角色状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (role_id) +) engine=innodb auto_increment=100 comment = '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigint(20) not null auto_increment comment '菜单ID', + menu_name varchar(50) not null comment '菜单名称', + parent_id bigint(20) default 0 comment '父菜单ID', + order_num int(4) default 0 comment '显示顺序', + path varchar(200) default '' comment '路由地址', + component varchar(255) default null comment '组件路径', + query varchar(255) default null comment '路由参数', + route_name varchar(50) default '' comment '路由名称', + is_frame int(1) default 1 comment '是否为外链(0是 1否)', + is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)', + menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', + visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', + status char(1) default 0 comment '菜单状态(0正常 1停用)', + perms varchar(100) default null comment '权限标识', + icon varchar(100) default '#' comment '菜单图标', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注', + primary key (menu_id) +) engine=innodb auto_increment=2000 comment = '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'antd-setting', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'antd-fund-projection-screen', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'antd-repair', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'antd-send', 'admin', sysdate(), '', null, '若依官网地址'); +-- 二级菜单 +insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system.user', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'antd-user', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system.role', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'antd-team', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system.menu', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'antd-app-store-add', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system.dept', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'antd-cluster', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system.post', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'antd-idcard', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system.dict', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'antd-read', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system.config', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'antd-calculator', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system.notice', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'antd-notification', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'antd-bug', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor.online', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'antd-desktop', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor.job', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'antd-deployment-unit', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor.druid', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'antd-database', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor.server', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'antd-cloud-server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor.cache.control', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'antd-cloud-sync', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor.cache.list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'antd-table', 'admin', sysdate(), '', null, '缓存列表菜单'); +insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool.build', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'antd-build', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool.gen', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'antd-console-sql', 'admin', sysdate(), '', null, '代码生成菜单'); +insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool.swagger', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'antd-api', 'admin', sysdate(), '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor.operlog', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'antd-clear', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor.logininfor', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'antd-control', 'admin', sysdate(), '', null, '登录日志菜单'); +-- 用户管理按钮 +insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); +-- 角色管理按钮 +insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); +-- 菜单管理按钮 +insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); +-- 部门管理按钮 +insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); +-- 岗位管理按钮 +insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); +-- 字典管理按钮 +insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); +-- 参数设置按钮 +insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); +-- 通知公告按钮 +insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); +-- 操作日志按钮 +insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); +-- 登录日志按钮 +insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); +-- 在线用户按钮 +insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); +-- 定时任务按钮 +insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); +-- 代码生成按钮 +insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint(20) not null comment '用户ID', + role_id bigint(20) not null comment '角色ID', + primary key(user_id, role_id) +) engine=innodb comment = '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values ('1', '1'); +insert into sys_user_role values ('2', '2'); + + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint(20) not null comment '角色ID', + menu_id bigint(20) not null comment '菜单ID', + primary key(role_id, menu_id) +) engine=innodb comment = '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values ('2', '1'); +insert into sys_role_menu values ('2', '2'); +insert into sys_role_menu values ('2', '3'); +insert into sys_role_menu values ('2', '4'); +insert into sys_role_menu values ('2', '100'); +insert into sys_role_menu values ('2', '101'); +insert into sys_role_menu values ('2', '102'); +insert into sys_role_menu values ('2', '103'); +insert into sys_role_menu values ('2', '104'); +insert into sys_role_menu values ('2', '105'); +insert into sys_role_menu values ('2', '106'); +insert into sys_role_menu values ('2', '107'); +insert into sys_role_menu values ('2', '108'); +insert into sys_role_menu values ('2', '109'); +insert into sys_role_menu values ('2', '110'); +insert into sys_role_menu values ('2', '111'); +insert into sys_role_menu values ('2', '112'); +insert into sys_role_menu values ('2', '113'); +insert into sys_role_menu values ('2', '114'); +insert into sys_role_menu values ('2', '115'); +insert into sys_role_menu values ('2', '116'); +insert into sys_role_menu values ('2', '117'); +insert into sys_role_menu values ('2', '500'); +insert into sys_role_menu values ('2', '501'); +insert into sys_role_menu values ('2', '1000'); +insert into sys_role_menu values ('2', '1001'); +insert into sys_role_menu values ('2', '1002'); +insert into sys_role_menu values ('2', '1003'); +insert into sys_role_menu values ('2', '1004'); +insert into sys_role_menu values ('2', '1005'); +insert into sys_role_menu values ('2', '1006'); +insert into sys_role_menu values ('2', '1007'); +insert into sys_role_menu values ('2', '1008'); +insert into sys_role_menu values ('2', '1009'); +insert into sys_role_menu values ('2', '1010'); +insert into sys_role_menu values ('2', '1011'); +insert into sys_role_menu values ('2', '1012'); +insert into sys_role_menu values ('2', '1013'); +insert into sys_role_menu values ('2', '1014'); +insert into sys_role_menu values ('2', '1015'); +insert into sys_role_menu values ('2', '1016'); +insert into sys_role_menu values ('2', '1017'); +insert into sys_role_menu values ('2', '1018'); +insert into sys_role_menu values ('2', '1019'); +insert into sys_role_menu values ('2', '1020'); +insert into sys_role_menu values ('2', '1021'); +insert into sys_role_menu values ('2', '1022'); +insert into sys_role_menu values ('2', '1023'); +insert into sys_role_menu values ('2', '1024'); +insert into sys_role_menu values ('2', '1025'); +insert into sys_role_menu values ('2', '1026'); +insert into sys_role_menu values ('2', '1027'); +insert into sys_role_menu values ('2', '1028'); +insert into sys_role_menu values ('2', '1029'); +insert into sys_role_menu values ('2', '1030'); +insert into sys_role_menu values ('2', '1031'); +insert into sys_role_menu values ('2', '1032'); +insert into sys_role_menu values ('2', '1033'); +insert into sys_role_menu values ('2', '1034'); +insert into sys_role_menu values ('2', '1035'); +insert into sys_role_menu values ('2', '1036'); +insert into sys_role_menu values ('2', '1037'); +insert into sys_role_menu values ('2', '1038'); +insert into sys_role_menu values ('2', '1039'); +insert into sys_role_menu values ('2', '1040'); +insert into sys_role_menu values ('2', '1041'); +insert into sys_role_menu values ('2', '1042'); +insert into sys_role_menu values ('2', '1043'); +insert into sys_role_menu values ('2', '1044'); +insert into sys_role_menu values ('2', '1045'); +insert into sys_role_menu values ('2', '1046'); +insert into sys_role_menu values ('2', '1047'); +insert into sys_role_menu values ('2', '1048'); +insert into sys_role_menu values ('2', '1049'); +insert into sys_role_menu values ('2', '1050'); +insert into sys_role_menu values ('2', '1051'); +insert into sys_role_menu values ('2', '1052'); +insert into sys_role_menu values ('2', '1053'); +insert into sys_role_menu values ('2', '1054'); +insert into sys_role_menu values ('2', '1055'); +insert into sys_role_menu values ('2', '1056'); +insert into sys_role_menu values ('2', '1057'); +insert into sys_role_menu values ('2', '1058'); +insert into sys_role_menu values ('2', '1059'); +insert into sys_role_menu values ('2', '1060'); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint(20) not null comment '角色ID', + dept_id bigint(20) not null comment '部门ID', + primary key(role_id, dept_id) +) engine=innodb comment = '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values ('2', '100'); +insert into sys_role_dept values ('2', '101'); +insert into sys_role_dept values ('2', '105'); + + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post +( + user_id bigint(20) not null comment '用户ID', + post_id bigint(20) not null comment '岗位ID', + primary key (user_id, post_id) +) engine=innodb comment = '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values ('1', '1'); +insert into sys_user_post values ('2', '2'); + + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigint(20) not null auto_increment comment '日志主键', + title varchar(50) default '' comment '模块标题', + business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', + method varchar(100) default '' comment '方法名称', + request_method varchar(10) default '' comment '请求方式', + operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', + oper_name varchar(50) default '' comment '操作人员', + dept_name varchar(50) default '' comment '部门名称', + oper_url varchar(255) default '' comment '请求URL', + oper_ip varchar(128) default '' comment '主机地址', + oper_location varchar(255) default '' comment '操作地点', + oper_param varchar(2000) default '' comment '请求参数', + json_result varchar(2000) default '' comment '返回参数', + status int(1) default 0 comment '操作状态(0正常 1异常)', + error_msg varchar(2000) default '' comment '错误消息', + oper_time datetime comment '操作时间', + cost_time bigint(20) default 0 comment '消耗时间', + primary key (oper_id), + key idx_sys_oper_log_bt (business_type), + key idx_sys_oper_log_s (status), + key idx_sys_oper_log_ot (oper_time) +) engine=innodb auto_increment=100 comment = '操作日志记录'; + + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type +( + dict_id bigint(20) not null auto_increment comment '字典主键', + dict_name varchar(100) default '' comment '字典名称', + dict_type varchar(100) default '' comment '字典类型', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_id), + unique (dict_type) +) engine=innodb auto_increment=100 comment = '字典类型表'; + +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); +insert into sys_dict_type values(6, '任务执行器', 'sys_job_executor', '0', 'admin', sysdate(), '', null, '任务执行器列表'); +insert into sys_dict_type values(7, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); +insert into sys_dict_type values(8, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); +insert into sys_dict_type values(9, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); +insert into sys_dict_type values(10, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); +insert into sys_dict_type values(11, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); + + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data +( + dict_code bigint(20) not null auto_increment comment '字典编码', + dict_sort int(4) default 0 comment '字典排序', + dict_label varchar(100) default '' comment '字典标签', + dict_value varchar(100) default '' comment '字典键值', + dict_type varchar(100) default '' comment '字典类型', + css_class varchar(100) default null comment '样式属性(其他样式扩展)', + list_class varchar(100) default null comment '表格回显样式', + is_default char(1) default 'N' comment '是否默认(Y是 N否)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_code) +) engine=innodb auto_increment=100 comment = '字典数据表'; + +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'default', 'sys_job_group', '', 'default', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '数据库', 'sqlalchemy', 'sys_job_group', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '数据库分组'); +insert into sys_dict_data values(12, 3, 'redis', 'redis', 'sys_job_group', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, 'reids分组'); +insert into sys_dict_data values(13, 1, '默认', 'default', 'sys_job_executor', '', 'default', 'N', '0', 'admin', sysdate(), '', null, '线程池'); +insert into sys_dict_data values(14, 2, '进程池', 'processpool', 'sys_job_executor', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '进程池'); +insert into sys_dict_data values(15, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); +insert into sys_dict_data values(16, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); +insert into sys_dict_data values(17, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); +insert into sys_dict_data values(18, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); +insert into sys_dict_data values(19, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(20, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); +insert into sys_dict_data values(21, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); +insert into sys_dict_data values(22, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); +insert into sys_dict_data values(23, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); +insert into sys_dict_data values(24, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); +insert into sys_dict_data values(25, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); +insert into sys_dict_data values(26, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); +insert into sys_dict_data values(27, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); +insert into sys_dict_data values(28, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); +insert into sys_dict_data values(29, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); +insert into sys_dict_data values(30, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); +insert into sys_dict_data values(31, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(32, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); + + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id int(5) not null auto_increment comment '参数主键', + config_name varchar(100) default '' comment '参数名称', + config_key varchar(100) default '' comment '参数键名', + config_value varchar(500) default '' comment '参数键值', + config_type char(1) default 'N' comment '系统内置(Y是 N否)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (config_id) +) engine=innodb auto_increment=100 comment = '参数配置表'; + +insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' ); +insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' ); +insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' ); +insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(6, '账号自助-是否开启忘记密码功能', 'sys.account.forgetUser', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启忘记密码功能(true开启,false关闭)'); +insert into sys_config values(7, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); + + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigint(20) not null auto_increment comment '访问ID', + user_name varchar(50) default '' comment '用户账号', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status char(1) default '0' comment '登录状态(0成功 1失败)', + msg varchar(255) default '' comment '提示消息', + login_time datetime comment '访问时间', + primary key (info_id), + key idx_sys_logininfor_s (status), + key idx_sys_logininfor_lt (login_time) +) engine=innodb auto_increment=100 comment = '系统访问记录'; + + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigint(20) not null auto_increment comment '任务ID', + job_name varchar(64) default '' comment '任务名称', + job_group varchar(64) default 'default' comment '任务组名', + job_executor varchar(64) default 'default' comment '任务执行器', + invoke_target varchar(500) not null comment '调用目标字符串', + job_args varchar(255) default '' comment '位置参数', + job_kwargs varchar(255) default '' comment '关键字参数', + cron_expression varchar(255) default '' comment 'cron执行表达式', + misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', + status char(1) default '0' comment '状态(0正常 1暂停)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注信息', + primary key (job_id, job_name, job_group) +) engine=innodb auto_increment=100 comment = '定时任务调度表'; + +insert into sys_job values(1, '系统默认(无参)', 'default', 'default', 'module_task.scheduler_test.job', NULL, NULL, '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'default', 'default', 'module_task.scheduler_test.job', 'test', NULL, '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'default', 'default', 'module_task.scheduler_test.job', 'new', '{\"test\": 111}', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigint(20) not null auto_increment comment '任务日志ID', + job_name varchar(64) not null comment '任务名称', + job_group varchar(64) not null comment '任务组名', + job_executor varchar(64) not null comment '任务执行器', + invoke_target varchar(500) not null comment '调用目标字符串', + job_args varchar(255) default '' comment '位置参数', + job_kwargs varchar(255) default '' comment '关键字参数', + job_trigger varchar(255) default '' comment '任务触发器', + job_message varchar(500) comment '日志信息', + status char(1) default '0' comment '执行状态(0正常 1失败)', + exception_info varchar(2000) default '' comment '异常信息', + create_time datetime comment '创建时间', + primary key (job_log_id) +) engine=innodb comment = '定时任务调度日志表'; + + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id int(4) not null auto_increment comment '公告ID', + notice_title varchar(50) not null comment '公告标题', + notice_type char(1) not null comment '公告类型(1通知 2公告)', + notice_content longblob default null comment '公告内容', + status char(1) default '0' comment '公告状态(0正常 1关闭)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(255) default null comment '备注', + primary key (notice_id) +) engine=innodb auto_increment=10 comment = '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values('1', '温馨提醒:2018-07-01 vfadmin新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('2', '维护通知:2018-07-01 vfadmin系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); + + +-- ---------------------------- +-- 18、代码生成业务表 +-- ---------------------------- +drop table if exists gen_table; +create table gen_table ( + table_id bigint(20) not null auto_increment comment '编号', + table_name varchar(200) default '' comment '表名称', + table_comment varchar(500) default '' comment '表描述', + sub_table_name varchar(64) default null comment '关联子表的表名', + sub_table_fk_name varchar(64) default null comment '子表关联的外键名', + class_name varchar(100) default '' comment '实体类名称', + tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作)', + tpl_web_type varchar(30) default '' comment '前端模板类型(element-ui模版 element-plus模版)', + package_name varchar(100) comment '生成包路径', + module_name varchar(30) comment '生成模块名', + business_name varchar(30) comment '生成业务名', + function_name varchar(50) comment '生成功能名', + function_author varchar(50) comment '生成功能作者', + gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)', + gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)', + options varchar(1000) comment '其它生成选项', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (table_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表'; + + +-- ---------------------------- +-- 19、代码生成业务表字段 +-- ---------------------------- +drop table if exists gen_table_column; +create table gen_table_column ( + column_id bigint(20) not null auto_increment comment '编号', + table_id bigint(20) comment '归属表编号', + column_name varchar(200) comment '列名称', + column_comment varchar(500) comment '列描述', + column_type varchar(100) comment '列类型', + java_type varchar(500) comment 'JAVA类型', + java_field varchar(200) comment 'JAVA字段名', + is_pk char(1) comment '是否主键(1是)', + is_increment char(1) comment '是否自增(1是)', + is_required char(1) comment '是否必填(1是)', + is_insert char(1) comment '是否为插入字段(1是)', + is_edit char(1) comment '是否编辑字段(1是)', + is_list char(1) comment '是否列表字段(1是)', + is_query char(1) comment '是否查询字段(1是)', + query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)', + html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + dict_type varchar(200) default '' comment '字典类型', + sort int comment '排序', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (column_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表字段'; \ No newline at end of file diff --git a/dash-fastapi-backend/sub_applications/handle.py b/dash-fastapi-backend/sub_applications/handle.py new file mode 100644 index 0000000000000000000000000000000000000000..df2a5f481a56afa59be84e9aa9490b794600dd60 --- /dev/null +++ b/dash-fastapi-backend/sub_applications/handle.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI +from sub_applications.staticfiles import mount_staticfiles + + +def handle_sub_applications(app: FastAPI): + """ + 全局处理子应用挂载 + """ + # 挂载静态文件 + mount_staticfiles(app) diff --git a/dash-fastapi-backend/sub_applications/staticfiles.py b/dash-fastapi-backend/sub_applications/staticfiles.py new file mode 100644 index 0000000000000000000000000000000000000000..c481d729f7e6beb81ebbe8357303b113b530ded0 --- /dev/null +++ b/dash-fastapi-backend/sub_applications/staticfiles.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from config.env import UploadConfig + + +def mount_staticfiles(app: FastAPI): + """ + 挂载静态文件 + """ + app.mount(f'{UploadConfig.UPLOAD_PREFIX}', StaticFiles(directory=f'{UploadConfig.UPLOAD_PATH}'), name='profile') diff --git a/dash-fastapi-backend/utils/common_util.py b/dash-fastapi-backend/utils/common_util.py index b1e8233daafb0260396721017a672a0c05869935..bcbe751ac61a0b0487472120e544690fb521dc2e 100644 --- a/dash-fastapi-backend/utils/common_util.py +++ b/dash-fastapi-backend/utils/common_util.py @@ -1,11 +1,14 @@ -import pandas as pd import io import os +import pandas as pd +import re from openpyxl import Workbook from openpyxl.styles import Alignment, PatternFill from openpyxl.utils import get_column_letter from openpyxl.worksheet.datavalidation import DataValidation +from sqlalchemy.engine.row import Row from typing import List +from config.database import Base from config.env import CachePathConfig @@ -31,12 +34,163 @@ def worship(): // ========`-.____`-.___\_____/___.-`____.-'======== // // `=---=' // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // -// 佛祖保佑 永不宕机 永无BUG // +// 佛祖保佑 永不宕机 永无BUG // //////////////////////////////////////////////////////////////////// """) -def bytes2human(n, format_str="%(value).1f%(symbol)s"): +class SqlalchemyUtil: + """ + sqlalchemy工具类 + """ + + @classmethod + def base_to_dict(cls, obj: Base): + """ + 将sqlalchemy模型对象转换为字典 + + :param obj: sqlalchemy模型对象 + :return: 字典结果 + """ + base_dict = obj.__dict__.copy() + base_dict.pop('_sa_instance_state', None) + + return base_dict + + @classmethod + def serialize_result(cls, result): + """ + 将sqlalchemy查询结果序列化 + + :param result: sqlalchemy查询结果 + :return: 序列化结果 + """ + if isinstance(result, Base): + return cls.base_to_dict(result) + elif isinstance(result, list): + return [cls.serialize_result(row) for row in result] + elif isinstance(result, Row): + if all([isinstance(row, Base) for row in result]): + return [cls.base_to_dict(row) for row in result] + elif any([isinstance(row, Base) for row in result]): + return [cls.serialize_result(row) for row in result] + else: + return result._asdict() + return result + + +class CamelCaseUtil: + """ + 下划线形式(snake_case)转小驼峰形式(camelCase)工具方法 + """ + + @classmethod + def snake_to_camel(cls, snake_str): + """ + 下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase) + + :param snake_str: 下划线形式字符串 + :return: 小驼峰形式字符串 + """ + # 分割字符串 + words = snake_str.split('_') + # 小驼峰命名,第一个词首字母小写,其余词首字母大写 + return words[0] + ''.join(word.capitalize() for word in words[1:]) + + @classmethod + def transform_result(cls, result): + """ + 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + + :param result: 输入数据 + :return: 小驼峰形式结果 + """ + if result is None: + return result + # 如果是字典,直接转换键 + elif isinstance(result, dict): + return {cls.snake_to_camel(k): v for k, v in result.items()} + # 如果是一组字典或其他类型的列表,遍历列表进行转换 + elif isinstance(result, list): + return [ + cls.transform_result(row) + if isinstance(row, (dict, Row)) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] + # 如果是sqlalchemy的Row实例,遍历Row进行转换 + elif isinstance(result, Row): + return [ + cls.transform_result(row) + if isinstance(row, dict) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] + # 如果是其他类型,如模型实例,先转换为字典 + else: + return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns}) + + +class SnakeCaseUtil: + """ + 小驼峰形式(camelCase)转下划线形式(snake_case)工具方法 + """ + + @classmethod + def camel_to_snake(cls, camel_str): + """ + 小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case) + + :param camel_str: 小驼峰形式字符串 + :return: 下划线形式字符串 + """ + # 在大写字母前添加一个下划线,然后将整个字符串转为小写 + words = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower() + + @classmethod + def transform_result(cls, result): + """ + 针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法 + + :param result: 输入数据 + :return: 小驼峰形式结果 + """ + if result is None: + return result + # 如果是字典,直接转换键 + elif isinstance(result, dict): + return {cls.camel_to_snake(k): v for k, v in result.items()} + # 如果是一组字典或其他类型的列表,遍历列表进行转换 + elif isinstance(result, list): + return [ + cls.transform_result(row) + if isinstance(row, (dict, Row)) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] + # 如果是sqlalchemy的Row实例,遍历Row进行转换 + elif isinstance(result, Row): + return [ + cls.transform_result(row) + if isinstance(row, dict) + else ( + cls.transform_result({c.name: getattr(row, c.name) for c in row.__table__.columns}) if row else row + ) + for row in result + ] + # 如果是其他类型,如模型实例,先转换为字典 + else: + return cls.transform_result({c.name: getattr(result, c.name) for c in result.__table__.columns}) + + +def bytes2human(n, format_str='%(value).1f%(symbol)s'): """Used by various scripts. See: http://goo.gl/zeJZl @@ -63,6 +217,7 @@ def bytes2file_response(bytes_info): def export_list2excel(list_data: List): """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + :param list_data: 数据列表 :return: 字典信息对应excel的二进制数据 """ @@ -77,6 +232,7 @@ def export_list2excel(list_data: List): def get_excel_template(header_list: List, selector_header_list: List, option_list: List[dict]): """ 工具方法:将需要导出的list数据转化为对应excel的二进制数据 + :param header_list: 表头数据列表 :param selector_header_list: 需要设置为选择器格式的表头数据列表 :param option_list: 选择器格式的表头预设的选项列表 @@ -91,7 +247,7 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis headers = header_list # 设置表头背景样式为灰色,前景色为白色 - header_fill = PatternFill(start_color="ababab", end_color="ababab", fill_type="solid") + header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid') # 将表头写入第一行 for col_num, header in enumerate(headers, 1): @@ -115,10 +271,11 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis for option in options: if option.get(selector_header): header_option = option.get(selector_header) - dv = DataValidation(type="list", formula1=f'"{",".join(header_option)}"') + dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"') # 设置数据有效性规则的起始单元格和结束单元格 dv.add( - f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576') + f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576' + ) # 添加数据有效性规则到工作表 ws.add_data_validation(dv) @@ -136,13 +293,14 @@ def get_excel_template(header_list: List, selector_header_list: List, option_lis def get_filepath_from_url(url: str): """ 工具方法:根据请求参数获取文件路径 + :param url: 请求参数中的url参数 :return: 文件路径 """ - file_info = url.split("?")[1].split("&") - task_id = file_info[0].split("=")[1] - file_name = file_info[1].split("=")[1] - task_path = file_info[2].split("=")[1] + file_info = url.split('?')[1].split('&') + task_id = file_info[0].split('=')[1] + file_name = file_info[1].split('=')[1] + task_path = file_info[2].split('=')[1] filepath = os.path.join(CachePathConfig.PATH, task_path, task_id, file_name) return filepath diff --git a/dash-fastapi-backend/utils/cron_util.py b/dash-fastapi-backend/utils/cron_util.py new file mode 100644 index 0000000000000000000000000000000000000000..62329627d27fd547e215276c8885f074ac12749a --- /dev/null +++ b/dash-fastapi-backend/utils/cron_util.py @@ -0,0 +1,172 @@ +import re +from datetime import datetime + + +class CronUtil: + """ + Cron表达式工具类 + """ + + @classmethod + def __valid_range(cls, search_str: str, start_range: int, end_range: int): + match = re.match(r'^(\d+)-(\d+)$', search_str) + if match: + start, end = int(match.group(1)), int(match.group(2)) + return start_range <= start < end <= end_range + return False + + @classmethod + def __valid_sum( + cls, search_str: str, start_range_a: int, start_range_b: int, end_range_a: int, end_range_b: int, sum_range: int + ): + match = re.match(r'^(\d+)/(\d+)$', search_str) + if match: + start, end = int(match.group(1)), int(match.group(2)) + return ( + start_range_a <= start <= start_range_b + and end_range_a <= end <= end_range_b + and start + end <= sum_range + ) + return False + + @classmethod + def validate_second_or_minute(cls, second_or_minute: str): + """ + 校验秒或分钟值是否正确 + + :param second_or_minute: 秒或分钟值 + :return: 校验结果 + """ + if ( + second_or_minute == '*' + or ('-' in second_or_minute and cls.__valid_range(second_or_minute, 0, 59)) + or ('/' in second_or_minute and cls.__valid_sum(second_or_minute, 0, 58, 1, 59, 59)) + or re.match(r'^(?:[0-5]?\d|59)(?:,[0-5]?\d|59)*$', second_or_minute) + ): + return True + return False + + @classmethod + def validate_hour(cls, hour: str): + """ + 校验小时值是否正确 + + :param hour: 小时值 + :return: 校验结果 + """ + if ( + hour == '*' + or ('-' in hour and cls.__valid_range(hour, 0, 23)) + or ('/' in hour and cls.__valid_sum(hour, 0, 22, 1, 23, 23)) + or re.match(r'^(?:0|[1-9]|1\d|2[0-3])(?:,(?:0|[1-9]|1\d|2[0-3]))*$', hour) + ): + return True + return False + + @classmethod + def validate_day(cls, day: str): + """ + 校验日值是否正确 + + :param day: 日值 + :return: 校验结果 + """ + if ( + day in ['*', '?', 'L'] + or ('-' in day and cls.__valid_range(day, 1, 31)) + or ('/' in day and cls.__valid_sum(day, 1, 30, 1, 30, 31)) + or ('W' in day and re.match(r'^(?:[1-9]|1\d|2\d|3[01])W$', day)) + or re.match(r'^(?:0|[1-9]|1\d|2[0-9]|3[0-1])(?:,(?:0|[1-9]|1\d|2[0-9]|3[0-1]))*$', day) + ): + return True + return False + + @classmethod + def validate_month(cls, month: str): + """ + 校验月值是否正确 + + :param month: 月值 + :return: 校验结果 + """ + if ( + month == '*' + or ('-' in month and cls.__valid_range(month, 1, 12)) + or ('/' in month and cls.__valid_sum(month, 1, 11, 1, 11, 12)) + or re.match(r'^(?:0|[1-9]|1[0-2])(?:,(?:0|[1-9]|1[0-2]))*$', month) + ): + return True + return False + + @classmethod + def validate_week(cls, week: str): + """ + 校验周值是否正确 + + :param week: 周值 + :return: 校验结果 + """ + if ( + week in ['*', '?'] + or ('-' in week and cls.__valid_range(week, 1, 7)) + or ('#' in week and re.match(r'^[1-7]#[1-4]$', week)) + or ('L' in week and re.match(r'^[1-7]L$', week)) + or re.match(r'^[1-7](?:(,[1-7]))*$', week) + ): + return True + return False + + @classmethod + def validate_year(cls, year: str): + """ + 校验年值是否正确 + + :param year: 年值 + :return: 校验结果 + """ + current_year = int(datetime.now().year) + future_years = [current_year + i for i in range(9)] + if ( + year == '*' + or ('-' in year and cls.__valid_range(year, current_year, 2099)) + or ('/' in year and cls.__valid_sum(year, current_year, 2098, 1, 2099 - current_year, 2099)) + or ('#' in year and re.match(r'^[1-7]#[1-4]$', year)) + or ('L' in year and re.match(r'^[1-7]L$', year)) + or ( + (len(year) == 4 or ',' in year) + and all(int(item) in future_years and current_year <= int(item) <= 2099 for item in year.split(',')) + ) + ): + return True + return False + + @classmethod + def validate_cron_expression(cls, cron_expression: str): + """ + 校验Cron表达式是否正确 + + :param cron_expression: Cron表达式 + :return: 校验结果 + """ + values = cron_expression.split() + if len(values) != 6 and len(values) != 7: + return False + second_validation = cls.validate_second_or_minute(values[0]) + minute_validation = cls.validate_second_or_minute(values[1]) + hour_validation = cls.validate_hour(values[2]) + day_validation = cls.validate_day(values[3]) + month_validation = cls.validate_month(values[4]) + week_validation = cls.validate_week(values[5]) + validation = ( + second_validation + and minute_validation + and hour_validation + and day_validation + and month_validation + and week_validation + ) + if len(values) == 6: + return validation + if len(values) == 7: + year_validation = cls.validate_year(values[6]) + return validation and year_validation diff --git a/dash-fastapi-backend/utils/log_util.py b/dash-fastapi-backend/utils/log_util.py index e9046538f001eb13338a754d7356a60edecb3280..e42f3938cc8435e49f02c17aac4bfa215ed866a7 100644 --- a/dash-fastapi-backend/utils/log_util.py +++ b/dash-fastapi-backend/utils/log_util.py @@ -8,4 +8,4 @@ if not os.path.exists(log_path): log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log') -logger.add(log_path_error, rotation="50MB", encoding="utf-8", enqueue=True, compression="zip") +logger.add(log_path_error, rotation='50MB', encoding='utf-8', enqueue=True, compression='zip') diff --git a/dash-fastapi-backend/utils/message_util.py b/dash-fastapi-backend/utils/message_util.py index 6a21f88d0e714cd89425c011150122301f9d4e19..3d3eb51aebd51cb1f97b5a756e3a4bf493c1ead4 100644 --- a/dash-fastapi-backend/utils/message_util.py +++ b/dash-fastapi-backend/utils/message_util.py @@ -2,4 +2,4 @@ from utils.log_util import logger def message_service(sms_code: str): - logger.info(f"短信验证码为{sms_code}") + logger.info(f'短信验证码为{sms_code}') diff --git a/dash-fastapi-backend/utils/page_util.py b/dash-fastapi-backend/utils/page_util.py index dd780214ee1b070e103a64c06e4461e6f0b07a6a..2f5edda72ed3a484712a5ecf83aa85f2588cb882 100644 --- a/dash-fastapi-backend/utils/page_util.py +++ b/dash-fastapi-backend/utils/page_util.py @@ -1,63 +1,98 @@ import math -from typing import List +from pydantic import BaseModel, ConfigDict +from sqlalchemy import func, select, Select +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Optional, List +from utils.common_util import SqlalchemyUtil -from pydantic import BaseModel - -class PageModel(BaseModel): +class PageResponseModel(BaseModel): """ - 分页模型 + 列表分页查询返回模型 """ - offset: int - page_num: int - page_size: int - total: int - has_next: bool - -class PageObjectResponse(BaseModel): - """ - 用户管理列表分页查询返回模型 - """ rows: List = [] - page_num: int - page_size: int + page_num: Optional[int] = None + page_size: Optional[int] = None total: int - has_next: bool + has_next: Optional[bool] = None -def get_page_info(offset: int, page_num: int, page_size: int, count: int): +class PageUtil: """ - 根据分页参数获取分页信息 - :param offset: 起始数据位置 - :param page_num: 当前页码 - :param page_size: 当前页面数据量 - :param count: 数据总数 - :return: 分页信息对象 + 分页工具类 """ - has_next = False - if offset >= count: - res_offset_1 = (page_num - 2) * page_size - if res_offset_1 < 0: - res_offset = 0 - res_page_num = 1 - else: - res_offset = res_offset_1 - res_page_num = page_num - 1 - else: - res_offset = offset - if (res_offset + page_size) < count: - has_next = True - res_page_num = page_num - result = dict(offset=res_offset, page_num=res_page_num, page_size=page_size, total=count, has_next=has_next) + @classmethod + def get_page_obj(cls, data_list: List, page_num: int, page_size: int): + """ + 输入数据列表data_list和分页信息,返回分页数据列表结果 + + :param data_list: 原始数据列表 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :return: 分页数据对象 + """ + # 计算起始索引和结束索引 + start = (page_num - 1) * page_size + end = page_num * page_size + + # 根据计算得到的起始索引和结束索引对数据列表进行切片 + paginated_data = data_list[start:end] + has_next = True if math.ceil(len(data_list) / page_size) > page_num else False + + result = PageResponseModel( + rows=paginated_data, page_num=page_num, page_size=page_size, total=len(data_list), has_next=has_next + ) + + return result + + @classmethod + async def paginate(cls, db: AsyncSession, query: Select, page_num: int, page_size: int, is_page: bool = False): + """ + 输入查询语句和分页信息,返回分页数据列表结果 + + :param db: orm对象 + :param query: sqlalchemy查询语句 + :param page_num: 当前页码 + :param page_size: 当前页面数据量 + :param is_page: 是否开启分页 + :return: 分页数据对象 + """ + if is_page: + total = (await db.execute(select(func.count('*')).select_from(query.subquery()))).scalar() + query_result = await db.execute(query.offset((page_num - 1) * page_size).limit(page_size)) + paginated_data = [] + for row in query_result: + if row and len(row) == 1: + paginated_data.append(row[0]) + else: + paginated_data.append(row) + has_next = math.ceil(total / page_size) > page_num + result = PageResponseModel( + rows=SqlalchemyUtil.serialize_result(paginated_data), + page_num=page_num, + page_size=page_size, + total=total, + has_next=has_next, + ) + else: + query_result = await db.execute(query) + no_paginated_data = [] + for row in query_result: + if row and len(row) == 1: + no_paginated_data.append(row[0]) + else: + no_paginated_data.append(row) + result = SqlalchemyUtil.serialize_result(no_paginated_data) - return PageModel(**result) + return result def get_page_obj(data_list: List, page_num: int, page_size: int): """ 输入数据列表data_list和分页信息,返回分页数据列表结果 + :param data_list: 原始数据列表 :param page_num: 当前页码 :param page_size: 当前页面数据量 @@ -71,14 +106,8 @@ def get_page_obj(data_list: List, page_num: int, page_size: int): paginated_data = data_list[start:end] has_next = True if math.ceil(len(data_list) / page_size) > page_num else False - result = dict( - rows=paginated_data, - page_num=page_num, - page_size=page_size, - total=len(data_list), - has_next=has_next + result = PageResponseModel( + rows=paginated_data, page_num=page_num, page_size=page_size, total=len(data_list), has_next=has_next ) - return PageObjectResponse(**result) - - + return result diff --git a/dash-fastapi-backend/utils/pwd_util.py b/dash-fastapi-backend/utils/pwd_util.py index 2e9a2f9a72f69f20e819c206ea8eb61258d5de54..86e9c271e6a0afb0e217e04264f5f92e28546443 100644 --- a/dash-fastapi-backend/utils/pwd_util.py +++ b/dash-fastapi-backend/utils/pwd_util.py @@ -1,6 +1,6 @@ from passlib.context import CryptContext -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') class PwdUtil: @@ -12,6 +12,7 @@ class PwdUtil: def verify_password(cls, plain_password, hashed_password): """ 工具方法:校验当前输入的密码与数据库存储的密码是否一致 + :param plain_password: 当前输入的密码 :param hashed_password: 数据库存储的密码 :return: 校验结果 @@ -22,6 +23,7 @@ class PwdUtil: def get_password_hash(cls, input_password): """ 工具方法:对当前输入的密码进行加密 + :param input_password: 输入的密码 :return: 加密成功的密码 """ diff --git a/dash-fastapi-backend/utils/response_util.py b/dash-fastapi-backend/utils/response_util.py index 4ac66e30fac59e9d9a2f60eac87fac5d59bdd33e..3881773e580b0ed248b27d0bdee99846fcb2f13e 100644 --- a/dash-fastapi-backend/utils/response_util.py +++ b/dash-fastapi-backend/utils/response_util.py @@ -1,114 +1,193 @@ +from datetime import datetime from fastapi import status -from fastapi.responses import JSONResponse, Response, StreamingResponse from fastapi.encoders import jsonable_encoder -from typing import Any -from datetime import datetime - - -def response_200(*, data: Any = None, message="获取成功") -> Response: - return JSONResponse( - status_code=status.HTTP_200_OK, - content=jsonable_encoder( - { - 'code': 200, - 'message': message, - 'data': data, - 'success': 'true', - 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - ) - ) - - -def response_400(*, data: Any = None, message: str = "获取失败") -> Response: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content=jsonable_encoder( - { - 'code': 400, - 'message': message, - 'data': data, - 'success': 'false', - 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - ) - ) - - -def response_401(*, data: Any = None, message: str = "获取失败") -> Response: - return JSONResponse( - status_code=status.HTTP_401_UNAUTHORIZED, - content=jsonable_encoder( - { - 'code': 401, - 'message': message, - 'data': data, - 'success': 'false', - 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - ) - ) - - -def response_403(*, data: Any = None, message: str = "获取失败") -> Response: - return JSONResponse( - status_code=status.HTTP_403_FORBIDDEN, - content=jsonable_encoder( - { - 'code': 403, - 'message': message, - 'data': data, - 'success': 'false', - 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - ) - ) - - -def response_500(*, data: Any = None, message: str = "接口异常") -> Response: - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content=jsonable_encoder( - { - 'code': 500, - 'message': message, - 'data': data, - 'success': 'false', - 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") - } - ) - ) - - -def streaming_response_200(*, data: Any = None): - return StreamingResponse( - status_code=status.HTTP_200_OK, - content=data, - ) - - -class AuthException(Exception): - """ - 自定义令牌异常AuthException - """ - def __init__(self, data: str = None, message: str = None): - self.data = data - self.message = message +from fastapi.responses import JSONResponse, Response, StreamingResponse +from pydantic import BaseModel +from typing import Any, Dict, Optional +from config.constant import HttpStatusConstant -class PermissionException(Exception): +class ResponseUtil: """ - 自定义权限异常PermissionException + 响应工具类 """ - def __init__(self, data: str = None, message: str = None): - self.data = data - self.message = message - -class LoginException(Exception): - """ - 自定义登录异常LoginException - """ - def __init__(self, data: str = None, message: str = None): - self.data = data - self.message = message + @classmethod + def success( + cls, + msg: str = '操作成功', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: + """ + 成功响应方法 + + :param msg: 可选,自定义成功响应信息 + :param data: 可选,成功响应结果中属性为data的值 + :param rows: 可选,成功响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,成功响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,成功响应结果中自定义属性的值 + :return: 成功响应结果 + """ + result = {'code': HttpStatusConstant.SUCCESS, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump()) + + result.update({'success': True, 'time': datetime.now()}) + + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + + @classmethod + def failure( + cls, + msg: str = '操作失败', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: + """ + 失败响应方法 + + :param msg: 可选,自定义失败响应信息 + :param data: 可选,失败响应结果中属性为data的值 + :param rows: 可选,失败响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,失败响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,失败响应结果中自定义属性的值 + :return: 失败响应结果 + """ + result = {'code': HttpStatusConstant.WARN, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump()) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + + @classmethod + def unauthorized( + cls, + msg: str = '登录信息已过期,访问系统资源失败', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: + """ + 未认证响应方法 + + :param msg: 可选,自定义未认证响应信息 + :param data: 可选,未认证响应结果中属性为data的值 + :param rows: 可选,未认证响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未认证响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未认证响应结果中自定义属性的值 + :return: 未认证响应结果 + """ + result = {'code': HttpStatusConstant.UNAUTHORIZED, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump()) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + + @classmethod + def forbidden( + cls, + msg: str = '该用户无此接口权限', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: + """ + 未授权响应方法 + + :param msg: 可选,自定义未授权响应信息 + :param data: 可选,未授权响应结果中属性为data的值 + :param rows: 可选,未授权响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,未授权响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,未授权响应结果中自定义属性的值 + :return: 未授权响应结果 + """ + result = {'code': HttpStatusConstant.FORBIDDEN, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump()) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + + @classmethod + def error( + cls, + msg: str = '接口异常', + data: Optional[Any] = None, + rows: Optional[Any] = None, + dict_content: Optional[Dict] = None, + model_content: Optional[BaseModel] = None, + ) -> Response: + """ + 错误响应方法 + + :param msg: 可选,自定义错误响应信息 + :param data: 可选,错误响应结果中属性为data的值 + :param rows: 可选,错误响应结果中属性为rows的值 + :param dict_content: 可选,dict类型,错误响应结果中自定义属性的值 + :param model_content: 可选,BaseModel类型,错误响应结果中自定义属性的值 + :return: 错误响应结果 + """ + result = {'code': HttpStatusConstant.ERROR, 'msg': msg} + + if data is not None: + result['data'] = data + if rows is not None: + result['rows'] = rows + if dict_content is not None: + result.update(dict_content) + if model_content is not None: + result.update(model_content.model_dump()) + + result.update({'success': False, 'time': datetime.now()}) + + return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder(result)) + + @classmethod + def streaming(cls, *, data: Any = None): + """ + 流式响应方法 + + :param data: 流式传输的内容 + :return: 流式响应结果 + """ + return StreamingResponse(status_code=status.HTTP_200_OK, content=data) diff --git a/dash-fastapi-backend/utils/string_util.py b/dash-fastapi-backend/utils/string_util.py new file mode 100644 index 0000000000000000000000000000000000000000..0be9e653bffdd63dace928ed6d95059dabf74ce8 --- /dev/null +++ b/dash-fastapi-backend/utils/string_util.py @@ -0,0 +1,101 @@ +from typing import List +from config.constant import CommonConstant + + +class StringUtil: + """ + 字符串工具类 + """ + + @classmethod + def is_blank(cls, string: str) -> bool: + """ + 校验字符串是否为''或全空格 + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + if string is None: + return False + str_len = len(string) + if str_len == 0: + return True + else: + for i in range(str_len): + if string[i] != ' ': + return False + return True + + @classmethod + def is_empty(cls, string) -> bool: + """ + 校验字符串是否为''或None + + :param string: 需要校验的字符串 + :return: 校验结果 + """ + return string is None or len(string) == 0 + + @classmethod + def is_http(cls, link: str): + """ + 判断是否为http(s)://开头 + + :param link: 链接 + :return: 是否为http(s)://开头 + """ + return link.startswith(CommonConstant.HTTP) or link.startswith(CommonConstant.HTTPS) + + @classmethod + def contains_ignore_case(cls, search_str: str, compare_str: str): + """ + 查找指定字符串是否包含指定字符串同时串忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 查找结果 + """ + if compare_str and search_str: + return compare_str.lower() in search_str.lower() + return False + + @classmethod + def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + """ + 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 查找结果 + """ + if search_str and compare_str_list: + for compare_str in compare_str_list: + return cls.contains_ignore_case(search_str, compare_str) + return False + + @classmethod + def startswith_case(cls, search_str: str, compare_str: str): + """ + 查找指定字符串是否以指定字符串开头 + + :param search_str: 查找的字符串 + :param compare_str: 比对的字符串 + :return: 查找结果 + """ + if compare_str and search_str: + return search_str.startswith(compare_str) + return False + + @classmethod + def startswith_any_case(cls, search_str: str, compare_str_list: List[str]): + """ + 查找指定字符串是否以指定字符串列表中的任意一个字符串开头 + + :param search_str: 查找的字符串 + :param compare_str_list: 比对的字符串列表 + :return: 查找结果 + """ + if search_str and compare_str_list: + for compare_str in compare_str_list: + return cls.startswith_case(search_str, compare_str) + return False diff --git a/dash-fastapi-backend/utils/time_format_util.py b/dash-fastapi-backend/utils/time_format_util.py index ba19489f64f3f825b41077509d66fc2b980978de..380537ebaa28be2ed2e0d291d87847cd5c651ac4 100644 --- a/dash-fastapi-backend/utils/time_format_util.py +++ b/dash-fastapi-backend/utils/time_format_util.py @@ -29,6 +29,7 @@ def list_format_datetime(lst): def format_datetime_dict_list(dicts): """ 递归遍历嵌套字典,并将 datetime 值转换为字符串格式 + :param dicts: 输入一个嵌套字典的列表 :return: 对目标列表中所有字典的datetime类型的属性格式化 """ diff --git a/dash-fastapi-backend/utils/upload_util.py b/dash-fastapi-backend/utils/upload_util.py new file mode 100644 index 0000000000000000000000000000000000000000..726789edaa965fcfe99412f3c5763011143ba1cd --- /dev/null +++ b/dash-fastapi-backend/utils/upload_util.py @@ -0,0 +1,105 @@ +import os +import random +from datetime import datetime +from fastapi import UploadFile +from config.env import UploadConfig + + +class UploadUtil: + """ + 上传工具类 + """ + + @classmethod + def generate_random_number(cls): + """ + 生成3位数字构成的字符串 + + :return: 3位数字构成的字符串 + """ + random_number = random.randint(1, 999) + + return f'{random_number:03}' + + @classmethod + def check_file_exists(cls, filepath: str): + """ + 检查文件是否存在 + + :param filepath: 文件路径 + :return: 校验结果 + """ + return os.path.exists(filepath) + + @classmethod + def check_file_extension(cls, file: UploadFile): + """ + 检查文件后缀是否合法 + + :param file: 文件对象 + :return: 校验结果 + """ + file_extension = file.filename.rsplit('.', 1)[-1] + if file_extension in UploadConfig.DEFAULT_ALLOWED_EXTENSION: + return True + return False + + @classmethod + def check_file_timestamp(cls, filename: str): + """ + 校验文件时间戳是否合法 + + :param filename: 文件名称 + :return: 校验结果 + """ + timestamp = filename.rsplit('.', 1)[0].split('_')[-1].split(UploadConfig.UPLOAD_MACHINE)[0] + try: + datetime.strptime(timestamp, '%Y%m%d%H%M%S') + return True + except ValueError: + return False + + @classmethod + def check_file_machine(cls, filename: str): + """ + 校验文件机器码是否合法 + + :param filename: 文件名称 + :return: 校验结果 + """ + if filename.rsplit('.', 1)[0][-4] == UploadConfig.UPLOAD_MACHINE: + return True + return False + + @classmethod + def check_file_random_code(cls, filename: str): + """ + 校验文件随机码是否合法 + + :param filename: 文件名称 + :return: 校验结果 + """ + valid_code_list = [f'{i:03}' for i in range(1, 999)] + if filename.rsplit('.', 1)[0][-3:] in valid_code_list: + return True + return False + + @classmethod + def generate_file(cls, filepath: str): + """ + 根据文件生成二进制数据 + + :param filepath: 文件路径 + :yield: 二进制数据 + """ + with open(filepath, 'rb') as response_file: + yield from response_file + + @classmethod + def delete_file(cls, filepath: str): + """ + 根据文件路径删除对应文件 + + :param filepath: 文件路径 + """ + os.remove(filepath) diff --git a/dash-fastapi-frontend/.env.dev b/dash-fastapi-frontend/.env.dev index 6f368488e29aa0104b045df4a94c2e977366e2bb..ee7e0e7544a43d1cf1336f054def2168891e58ef 100644 --- a/dash-fastapi-frontend/.env.dev +++ b/dash-fastapi-frontend/.env.dev @@ -2,7 +2,7 @@ # 应用运行环境 APP_ENV = 'dev' # 应用名称 -APP_NAME = '通用后台管理系统' +APP_NAME = 'DF Admin' # 应用请求后端url APP_BASE_URL = 'http://127.0.0.1:9099' # 后端是否使用代理模式 @@ -19,4 +19,14 @@ APP_PORT = 8088 APP_DEBUG = true # flask-compress压缩配置 APP_COMPRESS_ALGORITHM = 'br' -APP_COMPRESS_BR_LEVEL = 11 \ No newline at end of file +APP_COMPRESS_BR_LEVEL = 11 + +# -------- 缓存配置 -------- +# LRU缓存的限制数量,0表示无限制 +LRU_CACHE_MAXSIZE = 10000 +# LRU缓存不重新分配的容量 +LRU_CACHE_CAPACITY = 10000 +# TTL缓存的限制数量,0表示无限制 +TTL_CACHE_MAXSIZE = 0 +# TTL缓存过期时间(秒) +TTL_CACHE_EXPIRE = 600 \ No newline at end of file diff --git a/dash-fastapi-frontend/.env.prod b/dash-fastapi-frontend/.env.prod index 72685a3acee114f01281576a117ca8d265cd31ee..cf86c049daa1e2cebe6b44629c4506d06efff3f4 100644 --- a/dash-fastapi-frontend/.env.prod +++ b/dash-fastapi-frontend/.env.prod @@ -2,7 +2,7 @@ # 应用运行环境 APP_ENV = 'prod' # 应用名称 -APP_NAME = '通用后台管理系统' +APP_NAME = 'DF Admin' # 应用请求后端url APP_BASE_URL = 'http://127.0.0.1:9099' # 后端是否使用代理模式 @@ -19,4 +19,14 @@ APP_PORT = 8088 APP_DEBUG = false # flask-compress压缩配置 APP_COMPRESS_ALGORITHM = 'br' -APP_COMPRESS_BR_LEVEL = 11 \ No newline at end of file +APP_COMPRESS_BR_LEVEL = 11 + +# -------- 缓存配置 -------- +# LRU缓存的限制数量,0表示无限制 +LRU_CACHE_MAXSIZE = 10000 +# LRU缓存不重新分配的容量 +LRU_CACHE_CAPACITY = 10000 +# TTL缓存的限制数量,0表示无限制 +TTL_CACHE_MAXSIZE = 0 +# TTL缓存过期时间(秒) +TTL_CACHE_EXPIRE = 600 \ No newline at end of file diff --git a/dash-fastapi-frontend/api/cache.py b/dash-fastapi-frontend/api/cache.py deleted file mode 100644 index a29a58a99ae3388e88722439410b7a6ea29db84f..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/cache.py +++ /dev/null @@ -1,36 +0,0 @@ -from utils.request import api_request - - -def get_cache_statistical_info_api(): - - return api_request(method='post', url='/monitor/cache/statisticalInfo', is_headers=True) - - -def get_cache_name_list_api(): - - return api_request(method='post', url='/monitor/cache/getNames', is_headers=True) - - -def get_cache_key_list_api(cache_name: str): - - return api_request(method='post', url=f'/monitor/cache/getKeys/{cache_name}', is_headers=True) - - -def get_cache_value_api(cache_name: str, cache_key: str): - - return api_request(method='post', url=f'/monitor/cache/getValue/{cache_name}/{cache_key}', is_headers=True) - - -def clear_cache_name_api(cache_name: str): - - return api_request(method='post', url=f'/monitor/cache/clearCacheName/{cache_name}', is_headers=True) - - -def clear_cache_key_api(cache_name: str, cache_key: str): - - return api_request(method='post', url=f'/monitor/cache/clearCacheKey/{cache_name}/{cache_key}', is_headers=True) - - -def clear_all_cache_api(): - - return api_request(method='post', url='/monitor/cache/clearCacheAll', is_headers=True) diff --git a/dash-fastapi-frontend/api/config.py b/dash-fastapi-frontend/api/config.py deleted file mode 100644 index f092b8d1fe427b6da1df3633722e9f49183cbabd..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/config.py +++ /dev/null @@ -1,41 +0,0 @@ -from utils.request import api_request - - -def get_config_list_api(page_obj: dict): - - return api_request(method='post', url='/system/config/get', is_headers=True, json=page_obj) - - -def query_config_list_api(config_key: str): - - return api_request(method='get', url=f'/common/config/query/{config_key}', is_headers=False) - - -def add_config_api(page_obj: dict): - - return api_request(method='post', url='/system/config/add', is_headers=True, json=page_obj) - - -def edit_config_api(page_obj: dict): - - return api_request(method='patch', url='/system/config/edit', is_headers=True, json=page_obj) - - -def delete_config_api(page_obj: dict): - - return api_request(method='post', url='/system/config/delete', is_headers=True, json=page_obj) - - -def get_config_detail_api(config_id: int): - - return api_request(method='get', url=f'/system/config/{config_id}', is_headers=True) - - -def export_config_list_api(page_obj: dict): - - return api_request(method='post', url='/system/config/export', is_headers=True, json=page_obj, stream=True) - - -def refresh_config_api(page_obj: dict): - - return api_request(method='post', url='/system/config/refresh', is_headers=True, json=page_obj) diff --git a/dash-fastapi-frontend/api/dept.py b/dash-fastapi-frontend/api/dept.py deleted file mode 100644 index 00a3bcd6f1685f3dd6538c3bec65483084d06f08..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/dept.py +++ /dev/null @@ -1,36 +0,0 @@ -from utils.request import api_request - - -def get_dept_tree_api(page_obj: dict): - - return api_request(method='post', url='/system/dept/tree', is_headers=True, json=page_obj) - - -def get_dept_tree_for_edit_option_api(page_obj: dict): - - return api_request(method='post', url='/system/dept/forEditOption', is_headers=True, json=page_obj) - - -def get_dept_list_api(page_obj: dict): - - return api_request(method='post', url='/system/dept/get', is_headers=True, json=page_obj) - - -def add_dept_api(page_obj: dict): - - return api_request(method='post', url='/system/dept/add', is_headers=True, json=page_obj) - - -def edit_dept_api(page_obj: dict): - - return api_request(method='patch', url='/system/dept/edit', is_headers=True, json=page_obj) - - -def delete_dept_api(page_obj: dict): - - return api_request(method='post', url='/system/dept/delete', is_headers=True, json=page_obj) - - -def get_dept_detail_api(dept_id: int): - - return api_request(method='get', url=f'/system/dept/{dept_id}', is_headers=True) diff --git a/dash-fastapi-frontend/api/dict.py b/dash-fastapi-frontend/api/dict.py deleted file mode 100644 index 92eca5f1801041847d90776fe17ee22556dbaa37..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/dict.py +++ /dev/null @@ -1,76 +0,0 @@ -from utils.request import api_request - - -def get_dict_type_list_api(page_obj: dict): - - return api_request(method='post', url='/system/dictType/get', is_headers=True, json=page_obj) - - -def get_all_dict_type_api(page_obj: dict): - - return api_request(method='post', url='/system/dictType/all', is_headers=True, json=page_obj) - - -def add_dict_type_api(page_obj: dict): - - return api_request(method='post', url='/system/dictType/add', is_headers=True, json=page_obj) - - -def edit_dict_type_api(page_obj: dict): - - return api_request(method='patch', url='/system/dictType/edit', is_headers=True, json=page_obj) - - -def delete_dict_type_api(page_obj: dict): - - return api_request(method='post', url='/system/dictType/delete', is_headers=True, json=page_obj) - - -def get_dict_type_detail_api(dict_id: int): - - return api_request(method='get', url=f'/system/dictType/{dict_id}', is_headers=True) - - -def export_dict_type_list_api(page_obj: dict): - - return api_request(method='post', url='/system/dictType/export', is_headers=True, json=page_obj, stream=True) - - -def refresh_dict_api(page_obj: dict): - - return api_request(method='post', url='/system/dictType/refresh', is_headers=True, json=page_obj) - - -def get_dict_data_list_api(page_obj: dict): - - return api_request(method='post', url='/system/dictData/get', is_headers=True, json=page_obj) - - -def query_dict_data_list_api(dict_type: str): - - return api_request(method='get', url=f'/system/dictData/query/{dict_type}', is_headers=True) - - -def add_dict_data_api(page_obj: dict): - - return api_request(method='post', url='/system/dictData/add', is_headers=True, json=page_obj) - - -def edit_dict_data_api(page_obj: dict): - - return api_request(method='patch', url='/system/dictData/edit', is_headers=True, json=page_obj) - - -def delete_dict_data_api(page_obj: dict): - - return api_request(method='post', url='/system/dictData/delete', is_headers=True, json=page_obj) - - -def get_dict_data_detail_api(dict_code: int): - - return api_request(method='get', url=f'/system/dictData/{dict_code}', is_headers=True) - - -def export_dict_data_list_api(page_obj: dict): - - return api_request(method='post', url='/system/dictData/export', is_headers=True, json=page_obj, stream=True) diff --git a/dash-fastapi-frontend/api/forget.py b/dash-fastapi-frontend/api/forget.py new file mode 100644 index 0000000000000000000000000000000000000000..dc37adcec136371f15e685337f1a1e0c67c10411 --- /dev/null +++ b/dash-fastapi-frontend/api/forget.py @@ -0,0 +1,38 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class ForgetApi: + """ + 忘记密码模块相关接口 + """ + + @classmethod + def forget_password(cls, json: dict): + """ + 忘记密码接口 + + :param data: 忘记密码参数 + :return: + """ + return api_request( + url='/forgetPwd', + method=ApiMethod.POST, + headers={'is_token': False}, + json=json, + ) + + @classmethod + def send_message(cls, json: dict): + """ + 发送短信验证码接口 + + :param data: 发送短信验证码参数 + :return: + """ + return api_request( + url='/getSmsCode', + method=ApiMethod.POST, + headers={'is_token': False}, + json=json, + ) diff --git a/dash-fastapi-frontend/api/job.py b/dash-fastapi-frontend/api/job.py deleted file mode 100644 index 5c125c95e641f75e511079f755a4bd789b67c08a..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/job.py +++ /dev/null @@ -1,61 +0,0 @@ -from utils.request import api_request - - -def get_job_list_api(page_obj: dict): - - return api_request(method='post', url='/monitor/job/get', is_headers=True, json=page_obj) - - -def add_job_api(page_obj: dict): - - return api_request(method='post', url='/monitor/job/add', is_headers=True, json=page_obj) - - -def edit_job_api(page_obj: dict): - - return api_request(method='patch', url='/monitor/job/edit', is_headers=True, json=page_obj) - - -def execute_job_api(page_obj: dict): - - return api_request(method='post', url='/monitor/job/changeStatus', is_headers=True, json=page_obj) - - -def delete_job_api(page_obj: dict): - - return api_request(method='post', url='/monitor/job/delete', is_headers=True, json=page_obj) - - -def get_job_detail_api(job_id: int): - - return api_request(method='get', url=f'/monitor/job/{job_id}', is_headers=True) - - -def export_job_list_api(page_obj: dict): - - return api_request(method='post', url='/monitor/job/export', is_headers=True, json=page_obj, stream=True) - - -def get_job_log_list_api(page_obj: dict): - - return api_request(method='post', url='/monitor/jobLog/get', is_headers=True, json=page_obj) - - -def delete_job_log_api(page_obj: dict): - - return api_request(method='post', url='/monitor/jobLog/delete', is_headers=True, json=page_obj) - - -def clear_job_log_api(page_obj: dict): - - return api_request(method='post', url='/monitor/jobLog/clear', is_headers=True, json=page_obj) - - -def get_job_log_detail_api(job_log_id: int): - - return api_request(method='get', url=f'/monitor/jobLog/{job_log_id}', is_headers=True) - - -def export_job_log_list_api(page_obj: dict): - - return api_request(method='post', url='/monitor/jobLog/export', is_headers=True, json=page_obj, stream=True) diff --git a/dash-fastapi-frontend/api/log.py b/dash-fastapi-frontend/api/log.py deleted file mode 100644 index a58704e8c668244687a2f0d80075b2adc7940b2f..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/log.py +++ /dev/null @@ -1,51 +0,0 @@ -from utils.request import api_request - - -def get_operation_log_list_api(page_obj: dict): - - return api_request(method='post', url='/system/log/operation/get', is_headers=True, json=page_obj) - - -def delete_operation_log_api(page_obj: dict): - - return api_request(method='post', url='/system/log/operation/delete', is_headers=True, json=page_obj) - - -def clear_operation_log_api(page_obj: dict): - - return api_request(method='post', url='/system/log/operation/clear', is_headers=True, json=page_obj) - - -def export_operation_log_list_api(page_obj: dict): - - return api_request(method='post', url='/system/log/operation/export', is_headers=True, json=page_obj, stream=True) - - -def get_operation_log_detail_api(oper_id: int): - - return api_request(method='get', url=f'/system/log/operation/{oper_id}', is_headers=True) - - -def get_login_log_list_api(page_obj: dict): - - return api_request(method='post', url='/system/log/login/get', is_headers=True, json=page_obj) - - -def delete_login_log_api(page_obj: dict): - - return api_request(method='post', url='/system/log/login/delete', is_headers=True, json=page_obj) - - -def clear_login_log_api(page_obj: dict): - - return api_request(method='post', url='/system/log/login/clear', is_headers=True, json=page_obj) - - -def unlock_user_api(page_obj: dict): - - return api_request(method='post', url='/system/log/login/unlock', is_headers=True, json=page_obj) - - -def export_login_log_list_api(page_obj: dict): - - return api_request(method='post', url='/system/log/login/export', is_headers=True, json=page_obj, stream=True) diff --git a/dash-fastapi-frontend/api/login.py b/dash-fastapi-frontend/api/login.py index fd642f28ded29062ca5977ef58b10b1fa72b1c2d..2b0bf6ad167b9f3f9ffb1eb05358d3019db6b3e5 100644 --- a/dash-fastapi-frontend/api/login.py +++ b/dash-fastapi-frontend/api/login.py @@ -1,20 +1,60 @@ +from config.enums import ApiMethod from utils.request import api_request -def login_api(page_obj: dict): - - return api_request(method='post', url='/login/loginByAccount', is_headers=False, data=page_obj) - - -def get_captcha_image_api(): - - return api_request(method='post', url='/captcha/captchaImage', is_headers=False) - - -def get_current_user_info_api(): - - return api_request(method='post', url='/login/getLoginUserInfo', is_headers=True) - - -def logout_api(): - return api_request(method='post', url='/login/logout', is_headers=True) +class LoginApi: + """ + 登录模块相关接口 + """ + + @classmethod + def login(cls, data: dict): + """ + 登录接口 + + :param data: 登录参数 + :return: + """ + return api_request( + url='/login', + method=ApiMethod.POST, + headers={'is_token': False}, + data=data, + ) + + @classmethod + def get_info(cls): + """ + 获取登录用户信息接口 + + :return: + """ + return api_request( + url='/getInfo', + method=ApiMethod.GET, + ) + + @classmethod + def logout(cls): + """ + 退出登录接口 + + :return: + """ + return api_request( + url='/logout', + method=ApiMethod.POST, + ) + + @classmethod + def get_code_img(cls): + """ + 获取图片验证码接口 + + :return: + """ + return api_request( + url='/captchaImage', + method=ApiMethod.GET, + headers={'is_token': False}, + ) diff --git a/dash-fastapi-frontend/api/menu.py b/dash-fastapi-frontend/api/menu.py deleted file mode 100644 index 3581e57d34743fb45bd58986ea62db94080da794..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/menu.py +++ /dev/null @@ -1,36 +0,0 @@ -from utils.request import api_request - - -def get_menu_tree_api(page_obj: dict): - - return api_request(method='post', url='/system/menu/tree', is_headers=True, json=page_obj) - - -def get_menu_tree_for_edit_option_api(page_obj: dict): - - return api_request(method='post', url='/system/menu/forEditOption', is_headers=True, json=page_obj) - - -def get_menu_list_api(page_obj: dict): - - return api_request(method='post', url='/system/menu/get', is_headers=True, json=page_obj) - - -def add_menu_api(page_obj: dict): - - return api_request(method='post', url='/system/menu/add', is_headers=True, json=page_obj) - - -def edit_menu_api(page_obj: dict): - - return api_request(method='patch', url='/system/menu/edit', is_headers=True, json=page_obj) - - -def delete_menu_api(page_obj: dict): - - return api_request(method='post', url='/system/menu/delete', is_headers=True, json=page_obj) - - -def get_menu_detail_api(menu_id: int): - - return api_request(method='get', url=f'/system/menu/{menu_id}', is_headers=True) diff --git a/dash-fastapi-frontend/api/message.py b/dash-fastapi-frontend/api/message.py deleted file mode 100644 index 8257f7635c01f4342baf3c7c51326f598c443a72..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/message.py +++ /dev/null @@ -1,6 +0,0 @@ -from utils.request import api_request - - -def send_message_api(page_obj: dict): - - return api_request(method='post', url='/login/getSmsCode', is_headers=False, json=page_obj) \ No newline at end of file diff --git a/dash-fastapi-frontend/api/monitor/cache.py b/dash-fastapi-frontend/api/monitor/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..d9cc3eadc1adbbe3b431895020d07c86ca67e238 --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/cache.py @@ -0,0 +1,97 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class CacheApi: + """ + 缓存管理模块相关接口 + """ + + @classmethod + def get_cache(cls): + """ + 查询缓存详情接口 + + :return: + """ + return api_request( + url='/monitor/cache', + method=ApiMethod.GET, + ) + + @classmethod + def list_cache_name(cls): + """ + 查询缓存名称列表接口 + + :return: + """ + return api_request( + url='/monitor/cache/getNames', + method=ApiMethod.GET, + ) + + @classmethod + def list_cache_key(cls, cache_name: str): + """ + 查询缓存键名列表接口 + + :param cache_name: 缓存名称 + :return: + """ + return api_request( + url=f'/monitor/cache/getKeys/{cache_name}', + method=ApiMethod.GET, + ) + + @classmethod + def get_cache_value(cls, cache_name: str, cache_key: str): + """ + 查询缓存内容接口 + + :param cache_name: 缓存名称 + :param cache_key: 缓存键名 + :return: + """ + return api_request( + url=f'/monitor/cache/getValue/{cache_name}/{cache_key}', + method=ApiMethod.GET, + ) + + @classmethod + def clear_cache_name(cls, cache_name: str): + """ + 清理指定名称缓存接口 + + :param cache_name: 缓存名称 + :return: + """ + return api_request( + url=f'/monitor/cache/clearCacheName/{cache_name}', + method=ApiMethod.DELETE, + ) + + @classmethod + def clear_cache_key(cls, cache_key: str): + """ + 清理指定键名缓存接口 + + :param cache_key: 缓存键名 + :return: + """ + return api_request( + url=f'/monitor/cache/clearCacheKey/{cache_key}', + method=ApiMethod.DELETE, + ) + + @classmethod + def clear_cache_all(cls): + """ + 清理全部缓存接口 + + :return: + """ + return api_request( + url='/monitor/cache/clearCacheAll', + method=ApiMethod.DELETE, + ) diff --git a/dash-fastapi-frontend/api/monitor/job.py b/dash-fastapi-frontend/api/monitor/job.py new file mode 100644 index 0000000000000000000000000000000000000000..7ca44e3f22989bc93f05cf19da570af7996ee329 --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/job.py @@ -0,0 +1,121 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class JobApi: + """ + 定时任务调度管理模块相关接口 + """ + + @classmethod + def list_job(cls, query: dict): + """ + 查询定时任务调度列表接口 + + :param query: 查询定时任务调度参数 + :return: + """ + return api_request( + url='/monitor/job/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_job(cls, job_id: int): + """ + 查询定时任务调度详情接口 + + :param job_id: 定时任务调度id + :return: + """ + return api_request( + url=f'/monitor/job/{job_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_job(cls, json: dict): + """ + 新增定时任务调度接口 + + :param json: 新增定时任务调度参数 + :return: + """ + return api_request( + url='/monitor/job', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_job(cls, json: dict): + """ + 修改定时任务调度接口 + + :param json: 修改定时任务调度参数 + :return: + """ + return api_request( + url='/monitor/job', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_job(cls, job_id: str): + """ + 删除定时任务调度接口 + + :param job_id: 定时任务调度id + :return: + """ + return api_request( + url=f'/monitor/job/{job_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_job(cls, data: dict): + """ + 导出定时任务调度接口 + + :param data: 导出定时任务调度参数 + :return: + """ + return api_request( + url='/monitor/job/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) + + @classmethod + def change_job_status(cls, job_id: int, status: str): + """ + 定时任务调度状态修改接口 + + :param job_id: 定时任务id + :param status: 定时任务状态 + :return: + """ + return api_request( + url='/monitor/job/changeStatus', + method=ApiMethod.PUT, + json=dict(job_id=job_id, status=status), + ) + + @classmethod + def run_job(cls, job_id: int, job_group: str): + """ + 定时任务立即执行一次接口 + + :param job_id: 定时任务id + :param job_group: 定时任务分组 + :return: + """ + return api_request( + url='/monitor/job/run', + method=ApiMethod.PUT, + json=dict(job_id=job_id, job_group=job_group), + ) diff --git a/dash-fastapi-frontend/api/monitor/job_log.py b/dash-fastapi-frontend/api/monitor/job_log.py new file mode 100644 index 0000000000000000000000000000000000000000..286f417624bb7f1ae642c41096c363720e901f73 --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/job_log.py @@ -0,0 +1,62 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class JobLogApi: + """ + 调度日志管理模块相关接口 + """ + + @classmethod + def list_job_log(cls, query: dict): + """ + 查询调度日志列表接口 + + :param query: 查询调度日志参数 + :return: + """ + return api_request( + url='/monitor/jobLog/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def del_job_log(cls, job_log_id: str): + """ + 删除调度日志接口 + + :param job_log_id: 调度日志id + :return: + """ + return api_request( + url=f'/monitor/jobLog/{job_log_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def clean_job_log(cls): + """ + 清空调度日志接口 + + :return: + """ + return api_request( + url='/monitor/jobLog/clean', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_job_log(cls, data: dict): + """ + 导出调度日志接口 + + :param data: 导出调度日志参数 + :return: + """ + return api_request( + url='/monitor/jobLog/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) diff --git a/dash-fastapi-frontend/api/monitor/logininfor.py b/dash-fastapi-frontend/api/monitor/logininfor.py new file mode 100644 index 0000000000000000000000000000000000000000..a04a893ee7c7f9eaa94c29bbfb9c272a67c20c76 --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/logininfor.py @@ -0,0 +1,75 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class LogininforApi: + """ + 登录日志管理模块相关接口 + """ + + @classmethod + def list_logininfor(cls, query: dict): + """ + 查询登录日志列表接口 + + :param query: 查询登录日志参数 + :return: + """ + return api_request( + url='/monitor/logininfor/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def del_logininfor(cls, info_id: str): + """ + 删除登录日志接口 + + :param info_id: 登录日志id + :return: + """ + return api_request( + url=f'/monitor/logininfor/{info_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def unlock_logininfor(cls, user_name: str): + """ + 解锁用户登录状态接口 + + :param user_name: 用户名称 + :return: + """ + return api_request( + url=f'/monitor/logininfor/unlock/{user_name}', + method=ApiMethod.GET, + ) + + @classmethod + def clean_logininfor(cls): + """ + 清空登录日志接口 + + :return: + """ + return api_request( + url='/monitor/logininfor/clean', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_logininfor(cls, data: dict): + """ + 导出登录日志接口 + + :param data: 导出登录日志参数 + :return: + """ + return api_request( + url='/monitor/logininfor/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) diff --git a/dash-fastapi-frontend/api/monitor/online.py b/dash-fastapi-frontend/api/monitor/online.py new file mode 100644 index 0000000000000000000000000000000000000000..e54b3644395beb6cd5792c0fdb9519838fd85d43 --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/online.py @@ -0,0 +1,35 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class OnlineApi: + """ + 在线用户管理模块相关接口 + """ + + @classmethod + def list_online(cls, query: dict): + """ + 查询在线用户列表接口 + + :param query: 查询在线用户参数 + :return: + """ + return api_request( + url='/monitor/online/list/page', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def force_logout(cls, token_id: str): + """ + 强退用户接口 + + :param token_id: 在线用户token + :return: + """ + return api_request( + url=f'/monitor/online/{token_id}', + method=ApiMethod.DELETE, + ) diff --git a/dash-fastapi-frontend/api/monitor/operlog.py b/dash-fastapi-frontend/api/monitor/operlog.py new file mode 100644 index 0000000000000000000000000000000000000000..b448b4ea06ab6c019c6f88079e1f71927ca0d1bf --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/operlog.py @@ -0,0 +1,62 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class OperlogApi: + """ + 操作日志管理模块相关接口 + """ + + @classmethod + def list_operlog(cls, query: dict): + """ + 查询操作日志列表接口 + + :param query: 查询操作日志参数 + :return: + """ + return api_request( + url='/monitor/operlog/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def del_operlog(cls, oper_id: str): + """ + 删除操作日志接口 + + :param oper_id: 操作日志id + :return: + """ + return api_request( + url=f'/monitor/operlog/{oper_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def clean_operlog(cls): + """ + 清空操作日志接口 + + :return: + """ + return api_request( + url='/monitor/operlog/clean', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_operlog(cls, data: dict): + """ + 导出操作日志接口 + + :param data: 导出操作日志参数 + :return: + """ + return api_request( + url='/monitor/operlog/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) diff --git a/dash-fastapi-frontend/api/monitor/server.py b/dash-fastapi-frontend/api/monitor/server.py new file mode 100644 index 0000000000000000000000000000000000000000..315b04df40c2981c2a5c00b25c1d9deaa94234d1 --- /dev/null +++ b/dash-fastapi-frontend/api/monitor/server.py @@ -0,0 +1,20 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class ServerApi: + """ + 服务监控模块相关接口 + """ + + @classmethod + def get_server(cls): + """ + 获取服务信息接口 + + :return: + """ + return api_request( + url='/monitor/server', + method=ApiMethod.GET, + ) diff --git a/dash-fastapi-frontend/api/notice.py b/dash-fastapi-frontend/api/notice.py deleted file mode 100644 index af972ed12c16c7eaae7755b58b2be7f5e7b5233f..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/notice.py +++ /dev/null @@ -1,26 +0,0 @@ -from utils.request import api_request - - -def get_notice_list_api(page_obj: dict): - - return api_request(method='post', url='/system/notice/get', is_headers=True, json=page_obj) - - -def add_notice_api(page_obj: dict): - - return api_request(method='post', url='/system/notice/add', is_headers=True, json=page_obj) - - -def edit_notice_api(page_obj: dict): - - return api_request(method='patch', url='/system/notice/edit', is_headers=True, json=page_obj) - - -def delete_notice_api(page_obj: dict): - - return api_request(method='post', url='/system/notice/delete', is_headers=True, json=page_obj) - - -def get_notice_detail_api(notice_id: int): - - return api_request(method='get', url=f'/system/notice/{notice_id}', is_headers=True) diff --git a/dash-fastapi-frontend/api/online.py b/dash-fastapi-frontend/api/online.py deleted file mode 100644 index 0504b36774ca4f625f9e082e30eb30f56fc01ad9..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/online.py +++ /dev/null @@ -1,16 +0,0 @@ -from utils.request import api_request - - -def get_online_list_api(page_obj: dict): - - return api_request(method='post', url='/monitor/online/get', is_headers=True, json=page_obj) - - -def force_logout_online_api(page_obj: dict): - - return api_request(method='post', url='/monitor/online/forceLogout', is_headers=True, json=page_obj) - - -def batch_logout_online_api(page_obj: dict): - - return api_request(method='post', url='/monitor/online/batchLogout', is_headers=True, json=page_obj) diff --git a/dash-fastapi-frontend/api/post.py b/dash-fastapi-frontend/api/post.py deleted file mode 100644 index 8c058512cf34ea16597a00c829ffcd2a441ef61f..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/post.py +++ /dev/null @@ -1,36 +0,0 @@ -from utils.request import api_request - - -def get_post_select_option_api(): - - return api_request(method='post', url='/system/post/forSelectOption', is_headers=True) - - -def get_post_list_api(page_obj: dict): - - return api_request(method='post', url='/system/post/get', is_headers=True, json=page_obj) - - -def add_post_api(page_obj: dict): - - return api_request(method='post', url='/system/post/add', is_headers=True, json=page_obj) - - -def edit_post_api(page_obj: dict): - - return api_request(method='patch', url='/system/post/edit', is_headers=True, json=page_obj) - - -def delete_post_api(page_obj: dict): - - return api_request(method='post', url='/system/post/delete', is_headers=True, json=page_obj) - - -def get_post_detail_api(post_id: int): - - return api_request(method='get', url=f'/system/post/{post_id}', is_headers=True) - - -def export_post_list_api(page_obj: dict): - - return api_request(method='post', url='/system/post/export', is_headers=True, json=page_obj, stream=True) diff --git a/dash-fastapi-frontend/api/register.py b/dash-fastapi-frontend/api/register.py new file mode 100644 index 0000000000000000000000000000000000000000..512509aae75e6a85814889977e80d5970d2f8d2a --- /dev/null +++ b/dash-fastapi-frontend/api/register.py @@ -0,0 +1,23 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class RegisterApi: + """ + 注册模块相关接口 + """ + + @classmethod + def register(cls, json: dict): + """ + 注册接口 + + :param data: 注册参数 + :return: + """ + return api_request( + url='/register', + method=ApiMethod.POST, + headers={'is_token': False}, + json=json, + ) diff --git a/dash-fastapi-frontend/api/role.py b/dash-fastapi-frontend/api/role.py deleted file mode 100644 index 2ddbeba13bd70fa621ada3822d89254cad835ad7..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/role.py +++ /dev/null @@ -1,61 +0,0 @@ -from utils.request import api_request - - -def get_role_select_option_api(): - - return api_request(method='post', url='/system/role/forSelectOption', is_headers=True) - - -def get_role_list_api(page_obj: dict): - - return api_request(method='post', url='/system/role/get', is_headers=True, json=page_obj) - - -def add_role_api(page_obj: dict): - - return api_request(method='post', url='/system/role/add', is_headers=True, json=page_obj) - - -def edit_role_api(page_obj: dict): - - return api_request(method='patch', url='/system/role/edit', is_headers=True, json=page_obj) - - -def role_datascope_api(page_obj: dict): - - return api_request(method='patch', url='/system/role/dataScope', is_headers=True, json=page_obj) - - -def delete_role_api(page_obj: dict): - - return api_request(method='post', url='/system/role/delete', is_headers=True, json=page_obj) - - -def get_role_detail_api(role_id: int): - - return api_request(method='get', url=f'/system/role/{role_id}', is_headers=True) - - -def export_role_list_api(page_obj: dict): - - return api_request(method='post', url='/system/role/export', is_headers=True, json=page_obj, stream=True) - - -def get_allocated_user_list_api(page_obj: dict): - - return api_request(method='post', url='/system/role/authUser/allocatedList', is_headers=True, json=page_obj) - - -def get_unallocated_user_list_api(page_obj: dict): - - return api_request(method='post', url='/system/role/authUser/unallocatedList', is_headers=True, json=page_obj) - - -def auth_user_select_all_api(page_obj: dict): - - return api_request(method='post', url='/system/role/authUser/selectAll', is_headers=True, json=page_obj) - - -def auth_user_cancel_api(page_obj: dict): - - return api_request(method='post', url='/system/role/authUser/cancel', is_headers=True, json=page_obj) diff --git a/dash-fastapi-frontend/api/router.py b/dash-fastapi-frontend/api/router.py new file mode 100644 index 0000000000000000000000000000000000000000..92000d8d2b9bbef07533ebfd2346abc3f80b88ea --- /dev/null +++ b/dash-fastapi-frontend/api/router.py @@ -0,0 +1,20 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class RouterApi: + """ + 路由模块相关接口 + """ + + @classmethod + def get_routers(cls): + """ + 获取路由信息接口 + + :return: + """ + return api_request( + url='/getRouters', + method=ApiMethod.GET, + ) diff --git a/dash-fastapi-frontend/api/server.py b/dash-fastapi-frontend/api/server.py deleted file mode 100644 index 9e8e3eb83ad468d37f7d06d0900e0978a240e6d8..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/server.py +++ /dev/null @@ -1,6 +0,0 @@ -from utils.request import api_request - - -def get_server_statistical_info_api(): - - return api_request(method='post', url='/monitor/server/statisticalInfo', is_headers=True) diff --git a/dash-fastapi-frontend/api/system/config.py b/dash-fastapi-frontend/api/system/config.py new file mode 100644 index 0000000000000000000000000000000000000000..916a987fe9dacd10b6a8c09ac9cd4c67cf558cba --- /dev/null +++ b/dash-fastapi-frontend/api/system/config.py @@ -0,0 +1,116 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class ConfigApi: + """ + 参数配置模块相关接口 + """ + + @classmethod + def list_config(cls, query: dict): + """ + 查询参数配置列表接口 + + :param query: 查询参数配置参数 + :return: + """ + return api_request( + url='/system/config/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_config(cls, config_id: int): + """ + 查询参数配置详情接口 + + :param config_id: 参数配置id + :return: + """ + return api_request( + url=f'/system/config/{config_id}', + method=ApiMethod.GET, + ) + + @classmethod + def get_config_key(cls, config_key: str): + """ + 根据参数配置键名查询参数配置值接口 + + :param config_key: 参数键名 + :return: + """ + return api_request( + url=f'/system/config/configKey/{config_key}', + method=ApiMethod.GET, + ) + + @classmethod + def add_config(cls, json: dict): + """ + 新增参数配置接口 + + :param json: 新增参数配置参数 + :return: + """ + return api_request( + url='/system/config', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_config(cls, json: dict): + """ + 修改参数配置接口 + + :param json: 修改参数配置参数 + :return: + """ + return api_request( + url='/system/config', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_config(cls, config_id: str): + """ + 删除参数配置接口 + + :param config_id: 参数配置id + :return: + """ + return api_request( + url=f'/system/config/{config_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def refresh_cache(cls): + """ + 刷新参数配置缓存接口 + + :return: + """ + return api_request( + url='/system/config/refreshCache', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_config(cls, data: dict): + """ + 导出参数配置接口 + + :param data: 导出参数配置参数 + :return: + """ + return api_request( + url='/system/config/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) diff --git a/dash-fastapi-frontend/api/system/dept.py b/dash-fastapi-frontend/api/system/dept.py new file mode 100644 index 0000000000000000000000000000000000000000..1b9ceb9205375737ac95528e86bd23f002266d01 --- /dev/null +++ b/dash-fastapi-frontend/api/system/dept.py @@ -0,0 +1,89 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class DeptApi: + """ + 部门管理模块相关接口 + """ + + @classmethod + def list_dept(cls, query: dict): + """ + 查询部门列表接口 + + :param query: 查询部门参数 + :return: + """ + return api_request( + url='/system/dept/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def list_dept_exclude_child(cls, dept_id: int): + """ + 查询部门列表(排除节点)接口 + + :param query: 部门id + :return: + """ + return api_request( + url=f'/system/dept/list/exclude/{dept_id}', + method=ApiMethod.GET, + ) + + @classmethod + def get_dept(cls, dept_id: int): + """ + 查询部门详情接口 + + :param dept_id: 部门id + :return: + """ + return api_request( + url=f'/system/dept/{dept_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_dept(cls, json: dict): + """ + 新增部门接口 + + :param json: 新增部门参数 + :return: + """ + return api_request( + url='/system/dept', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_dept(cls, json: dict): + """ + 修改部门接口 + + :param json: 修改部门参数 + :return: + """ + return api_request( + url='/system/dept', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_dept(cls, dept_id: str): + """ + 删除部门接口 + + :param dept_id: 部门id + :return: + """ + return api_request( + url=f'/system/dept/{dept_id}', + method=ApiMethod.DELETE, + ) diff --git a/dash-fastapi-frontend/api/system/dict/data.py b/dash-fastapi-frontend/api/system/dict/data.py new file mode 100644 index 0000000000000000000000000000000000000000..bdf08d6f015496c60e28cc9520a981bd77559f21 --- /dev/null +++ b/dash-fastapi-frontend/api/system/dict/data.py @@ -0,0 +1,104 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class DictDataApi: + """ + 字典数据管理模块相关接口 + """ + + @classmethod + def list_data(cls, query: dict): + """ + 查询字典数据列表接口 + + :param query: 查询字典数据参数 + :return: + """ + return api_request( + url='/system/dict/data/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_data(cls, dict_code: int): + """ + 查询字典数据详情接口 + + :param dict_code: 字典数据id + :return: + """ + return api_request( + url=f'/system/dict/data/{dict_code}', + method=ApiMethod.GET, + ) + + @classmethod + def get_dicts(cls, dict_type: str): + """ + 根据字典类型查询字典数据信息接口 + + :param dict_type: 字典类型 + :return: + """ + return api_request( + url=f'/system/dict/data/type/{dict_type}', + method=ApiMethod.GET, + ) + + @classmethod + def add_data(cls, json: dict): + """ + 新增字典数据接口 + + :param json: 新增字典数据参数 + :return: + """ + return api_request( + url='/system/dict/data', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_data(cls, json: dict): + """ + 修改字典数据接口 + + :param json: 修改字典数据参数 + :return: + """ + return api_request( + url='/system/dict/data', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_data(cls, dict_code: str): + """ + 删除字典数据接口 + + :param dict_code: 字典数据id + :return: + """ + return api_request( + url=f'/system/dict/data/{dict_code}', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_data(cls, data: dict): + """ + 导出字典数据接口 + + :param data: 导出字典数据参数 + :return: + """ + return api_request( + url='/system/dict/data/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) diff --git a/dash-fastapi-frontend/api/system/dict/type.py b/dash-fastapi-frontend/api/system/dict/type.py new file mode 100644 index 0000000000000000000000000000000000000000..58a189deb2e76a33a9928365d16322a961e17bd7 --- /dev/null +++ b/dash-fastapi-frontend/api/system/dict/type.py @@ -0,0 +1,115 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class DictTypeApi: + """ + 字典类型管理模块相关接口 + """ + + @classmethod + def list_type(cls, query: dict): + """ + 查询字典类型列表接口 + + :param query: 查询字典类型参数 + :return: + """ + return api_request( + url='/system/dict/type/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_type(cls, dict_id: int): + """ + 查询字典类型详情接口 + + :param dict_id: 字典类型id + :return: + """ + return api_request( + url=f'/system/dict/type/{dict_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_type(cls, json: dict): + """ + 新增字典类型接口 + + :param json: 新增字典类型参数 + :return: + """ + return api_request( + url='/system/dict/type', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_type(cls, json: dict): + """ + 修改字典类型接口 + + :param json: 修改字典类型参数 + :return: + """ + return api_request( + url='/system/dict/type', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_type(cls, dict_id: str): + """ + 删除字典类型接口 + + :param dict_id: 字典类型id + :return: + """ + return api_request( + url=f'/system/dict/type/{dict_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_type(cls, data: dict): + """ + 导出字典类型接口 + + :param data: 导出字典类型参数 + :return: + """ + return api_request( + url='/system/dict/type/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) + + @classmethod + def refresh_cache(cls): + """ + 删除字典类型接口 + + :return: + """ + return api_request( + url='/system/dict/type/refreshCache', + method=ApiMethod.DELETE, + ) + + @classmethod + def optionselect(cls): + """ + 查询字典类型详情接口 + + :return: + """ + return api_request( + url='/system/dict/type/optionselect', + method=ApiMethod.GET, + ) diff --git a/dash-fastapi-frontend/api/system/menu.py b/dash-fastapi-frontend/api/system/menu.py new file mode 100644 index 0000000000000000000000000000000000000000..4b7c54d1a9ea0ec4416fae580c2a17a9d0190e00 --- /dev/null +++ b/dash-fastapi-frontend/api/system/menu.py @@ -0,0 +1,101 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class MenuApi: + """ + 菜单管理模块相关接口 + """ + + @classmethod + def list_menu(cls, query: dict): + """ + 查询菜单列表接口 + + :param query: 查询菜单参数 + :return: + """ + return api_request( + url='/system/menu/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_menu(cls, menu_id: int): + """ + 查询菜单详情接口 + + :param menu_id: 菜单id + :return: + """ + return api_request( + url=f'/system/menu/{menu_id}', + method=ApiMethod.GET, + ) + + @classmethod + def treeselect(cls): + """ + 查询菜单下拉树结构接口 + + :return: + """ + return api_request( + url='/system/menu/treeselect', + method=ApiMethod.GET, + ) + + @classmethod + def role_menu_treeselect(cls, role_id: int): + """ + 根据角色id查询菜单下拉树结构接口 + + :param role_id: 角色id + :return: + """ + return api_request( + url=f'/system/menu/roleMenuTreeselect/{role_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_menu(cls, json: dict): + """ + 新增菜单接口 + + :param json: 新增菜单参数 + :return: + """ + return api_request( + url='/system/menu', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_menu(cls, json: dict): + """ + 修改菜单接口 + + :param json: 修改菜单参数 + :return: + """ + return api_request( + url='/system/menu', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_menu(cls, menu_id: str): + """ + 删除菜单接口 + + :param menu_id: 菜单id + :return: + """ + return api_request( + url=f'/system/menu/{menu_id}', + method=ApiMethod.DELETE, + ) diff --git a/dash-fastapi-frontend/api/system/notice.py b/dash-fastapi-frontend/api/system/notice.py new file mode 100644 index 0000000000000000000000000000000000000000..d6b4f4562c38f406ce578414979730194c12bd8c --- /dev/null +++ b/dash-fastapi-frontend/api/system/notice.py @@ -0,0 +1,76 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class NoticeApi: + """ + 通知公告管理模块相关接口 + """ + + @classmethod + def list_notice(cls, query: dict): + """ + 查询通知公告列表接口 + + :param query: 查询通知公告参数 + :return: + """ + return api_request( + url='/system/notice/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_notice(cls, notice_id: int): + """ + 查询通知公告详情接口 + + :param notice_id: 通知公告id + :return: + """ + return api_request( + url=f'/system/notice/{notice_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_notice(cls, json: dict): + """ + 新增通知公告接口 + + :param json: 新增通知公告参数 + :return: + """ + return api_request( + url='/system/notice', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_notice(cls, json: dict): + """ + 修改通知公告接口 + + :param json: 修改通知公告参数 + :return: + """ + return api_request( + url='/system/notice', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_notice(cls, notice_id: str): + """ + 删除通知公告接口 + + :param notice_id: 通知公告id + :return: + """ + return api_request( + url=f'/system/notice/{notice_id}', + method=ApiMethod.DELETE, + ) diff --git a/dash-fastapi-frontend/api/system/post.py b/dash-fastapi-frontend/api/system/post.py new file mode 100644 index 0000000000000000000000000000000000000000..ebc15e23f1de22368d7dfb6d3c8bae75f5fd6071 --- /dev/null +++ b/dash-fastapi-frontend/api/system/post.py @@ -0,0 +1,91 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class PostApi: + """ + 岗位管理模块相关接口 + """ + + @classmethod + def list_post(cls, query: dict): + """ + 查询岗位列表接口 + + :param query: 查询岗位参数 + :return: + """ + return api_request( + url='/system/post/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_post(cls, post_id: int): + """ + 查询岗位详情接口 + + :param post_id: 岗位id + :return: + """ + return api_request( + url=f'/system/post/{post_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_post(cls, json: dict): + """ + 新增岗位接口 + + :param json: 新增岗位参数 + :return: + """ + return api_request( + url='/system/post', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_post(cls, json: dict): + """ + 修改岗位接口 + + :param json: 修改岗位参数 + :return: + """ + return api_request( + url='/system/post', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_post(cls, post_id: str): + """ + 删除岗位接口 + + :param post_id: 岗位id + :return: + """ + return api_request( + url=f'/system/post/{post_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_post(cls, data: dict): + """ + 导出岗位接口 + + :param data: 导出岗位参数 + :return: + """ + return api_request( + url='/system/post/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) diff --git a/dash-fastapi-frontend/api/system/role.py b/dash-fastapi-frontend/api/system/role.py new file mode 100644 index 0000000000000000000000000000000000000000..a476d104cbd63d8f2ce6b16de7c3df3097624bb8 --- /dev/null +++ b/dash-fastapi-frontend/api/system/role.py @@ -0,0 +1,203 @@ +from config.enums import ApiMethod +from utils.request import api_request + + +class RoleApi: + """ + 角色管理模块相关接口 + """ + + @classmethod + def list_role(cls, query: dict): + """ + 查询角色列表接口 + + :param query: 查询角色参数 + :return: + """ + return api_request( + url='/system/role/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_role(cls, role_id: int): + """ + 查询角色详情接口 + + :param role_id: 角色id + :return: + """ + return api_request( + url=f'/system/role/{role_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_role(cls, json: dict): + """ + 新增角色接口 + + :param json: 新增角色参数 + :return: + """ + return api_request( + url='/system/role', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_role(cls, json: dict): + """ + 修改角色接口 + + :param json: 修改角色参数 + :return: + """ + return api_request( + url='/system/role', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def data_scope(cls, json: dict): + """ + 角色数据权限接口 + + :param json: 角色数据权限参数 + :return: + """ + return api_request( + url='/system/role/dataScope', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def change_role_status(cls, role_id: int, status: str): + """ + 角色状态修改接口 + + :param role_id: 角色id + :param status: 角色状态 + :return: + """ + return api_request( + url='/system/role/changeStatus', + method=ApiMethod.PUT, + json=dict(role_id=role_id, status=status), + ) + + @classmethod + def del_role(cls, role_id: str): + """ + 删除角色接口 + + :param role_id: 角色id + :return: + """ + return api_request( + url=f'/system/role/{role_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def export_role(cls, data: dict): + """ + 导出角色接口 + + :param data: 导出角色参数 + :return: + """ + return api_request( + url='/system/role/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) + + @classmethod + def allocated_user_list(cls, query: dict): + """ + 查询角色已授权用户列表接口 + + :param query: 查询角色已授权用户列表参数 + :return: + """ + return api_request( + url='/system/role/authUser/allocatedList', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def unallocated_user_list(cls, query: dict): + """ + 查询角色未授权用户列表接口 + + :param query: 查询角色未授权用户列表参数 + :return: + """ + return api_request( + url='/system/role/authUser/unallocatedList', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def auth_user_cancel(cls, json: dict): + """ + 取消用户授权角色接口 + + :param json: 用户授权角色参数 + :return: + """ + return api_request( + url='/system/role/authUser/cancel', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def auth_user_cancel_all(cls, params: dict): + """ + 批量取消用户授权角色接口 + + :param params: 用户授权角色参数 + :return: + """ + return api_request( + url='/system/role/authUser/cancelAll', + method=ApiMethod.PUT, + params=params, + ) + + @classmethod + def auth_user_select_all(cls, params: dict): + """ + 授权用户选择接口 + + :param params: 用户选择角色参数 + :return: + """ + return api_request( + url='/system/role/authUser/selectAll', + method=ApiMethod.PUT, + params=params, + ) + + @classmethod + def dept_tree_select(cls, role_id: int): + """ + 根据角色id查询部门树结构接口 + + :param role_id: 角色id + :return: + """ + return api_request( + url=f'/system/role/deptTree/{role_id}', + method=ApiMethod.GET, + ) diff --git a/dash-fastapi-frontend/api/system/user.py b/dash-fastapi-frontend/api/system/user.py new file mode 100644 index 0000000000000000000000000000000000000000..c355645afa0c95264b4ca0290bd77f20c56f4109 --- /dev/null +++ b/dash-fastapi-frontend/api/system/user.py @@ -0,0 +1,242 @@ +from typing import Union +from config.enums import ApiMethod +from utils.request import api_request + + +class UserApi: + """ + 用户管理模块相关接口 + """ + + @classmethod + def list_user(cls, query: dict): + """ + 查询用户列表接口 + + :param query: 查询用户参数 + :return: + """ + return api_request( + url='/system/user/list', + method=ApiMethod.GET, + params=query, + ) + + @classmethod + def get_user(cls, user_id: Union[int, str]): + """ + 查询用户详情接口 + + :param user_id: 用户id + :return: + """ + return api_request( + url=f'/system/user/{user_id}', + method=ApiMethod.GET, + ) + + @classmethod + def add_user(cls, json: dict): + """ + 新增用户接口 + + :param json: 新增用户参数 + :return: + """ + return api_request( + url='/system/user', + method=ApiMethod.POST, + json=json, + ) + + @classmethod + def update_user(cls, json: dict): + """ + 修改用户接口 + + :param json: 修改用户参数 + :return: + """ + return api_request( + url='/system/user', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def del_user(cls, user_id: str): + """ + 删除用户接口 + + :param user_id: 用户id + :return: + """ + return api_request( + url=f'/system/user/{user_id}', + method=ApiMethod.DELETE, + ) + + @classmethod + def download_template(cls): + """ + 下载用户导入模板接口 + + :return: + """ + return api_request( + url='/system/user/importTemplate', + method=ApiMethod.POST, + stream=True, + ) + + @classmethod + def import_user(cls, file: bytes, update_support: bool): + """ + 导入用户接口 + + :param file: 导入模板文件 + :param update_support: 是否更新已存在的用户数据 + :return: + """ + return api_request( + url='/system/user/importData', + method=ApiMethod.POST, + files={'file': file}, + params={'update_support': update_support}, + ) + + @classmethod + def export_user(cls, data: dict): + """ + 导出用户接口 + + :param data: 导出用户参数 + :return: + """ + return api_request( + url='/system/user/export', + method=ApiMethod.POST, + data=data, + stream=True, + ) + + @classmethod + def reset_user_pwd(cls, user_id: int, password: str): + """ + 用户密码重置接口 + + :param user_id: 用户id + :param password: 用户密码 + :return: + """ + return api_request( + url='/system/user/resetPwd', + method=ApiMethod.PUT, + json=dict(user_id=user_id, password=password), + ) + + @classmethod + def change_user_status(cls, user_id: int, status: str): + """ + 用户状态修改接口 + + :param user_id: 用户id + :param password: 用户状态 + :return: + """ + return api_request( + url='/system/user/changeStatus', + method=ApiMethod.PUT, + json=dict(user_id=user_id, status=status), + ) + + @classmethod + def get_user_profile(cls): + """ + 查询用户个人信息接口 + + :return: + """ + return api_request( + url='/system/user/profile', + method=ApiMethod.GET, + ) + + @classmethod + def update_user_profile(cls, json: dict): + """ + 修改用户个人信息接口 + + :param json: 修改用户个人信息参数 + :return: + """ + return api_request( + url='/system/user/profile', + method=ApiMethod.PUT, + json=json, + ) + + @classmethod + def update_user_pwd(cls, old_password: str, new_password: str): + """ + 用户个人密码重置接口 + + :param old_password: 用户旧密码 + :param new_password: 用户新密码 + :return: + """ + return api_request( + url='/system/user/profile/updatePwd', + method=ApiMethod.PUT, + params=dict(old_password=old_password, new_password=new_password), + ) + + @classmethod + def upload_avatar(cls, files: dict): + """ + 用户头像上传接口 + + :param files: 用户头像参数 + :return: + """ + return api_request( + url='/system/user/profile/avatar', + method=ApiMethod.POST, + files=files, + ) + + @classmethod + def get_auth_role(cls, user_id: int): + """ + 查询授权角色接口 + """ + return api_request( + url=f'/system/user/authRole/{user_id}', + method=ApiMethod.GET, + ) + + @classmethod + def update_auth_role(cls, params: dict): + """ + 保存授权角色接口 + + :param params: 授权角色参数 + :return: + """ + return api_request( + url='/system/user/authRole', + method=ApiMethod.PUT, + params=params, + ) + + @classmethod + def dept_tree_select(cls): + """ + 查询部门下拉树结构接口 + + :return: + """ + return api_request( + url='/system/user/deptTree', + method=ApiMethod.GET, + ) diff --git a/dash-fastapi-frontend/api/user.py b/dash-fastapi-frontend/api/user.py deleted file mode 100644 index c426534db3ecd5cae2bea60cabb42a45eef6cf5f..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/api/user.py +++ /dev/null @@ -1,83 +0,0 @@ -from utils.request import api_request - - -def forget_user_pwd_api(page_obj: dict): - - return api_request(method='post', url='/login/forgetPwd', is_headers=False, json=page_obj) - - -def get_user_list_api(page_obj: dict): - - return api_request(method='post', url='/system/user/get', is_headers=True, json=page_obj) - - -def add_user_api(page_obj: dict): - - return api_request(method='post', url='/system/user/add', is_headers=True, json=page_obj) - - -def edit_user_api(page_obj: dict): - - return api_request(method='patch', url='/system/user/edit', is_headers=True, json=page_obj) - - -def delete_user_api(page_obj: dict): - - return api_request(method='post', url='/system/user/delete', is_headers=True, json=page_obj) - - -def get_user_detail_api(user_id: int): - - return api_request(method='get', url=f'/system/user/{user_id}', is_headers=True) - - -def change_user_avatar_api(page_obj: dict): - - return api_request(method='patch', url='/system/user/profile/changeAvatar', is_headers=True, json=page_obj) - - -def change_user_info_api(page_obj: dict): - - return api_request(method='patch', url='/system/user/profile/changeInfo', is_headers=True, json=page_obj) - - -def reset_user_password_api(page_obj: dict): - - return api_request(method='patch', url='/system/user/profile/resetPwd', is_headers=True, json=page_obj) - - -def batch_import_user_api(page_obj: dict): - - return api_request(method='post', url='/system/user/importData', is_headers=True, json=page_obj) - - -def download_user_import_template_api(): - - return api_request(method='post', url='/system/user/importTemplate', is_headers=True, stream=True) - - -def export_user_list_api(page_obj: dict): - - return api_request(method='post', url='/system/user/export', is_headers=True, json=page_obj, stream=True) - - -def get_allocated_role_list_api(page_obj: dict): - - return api_request(method='post', url='/system/user/authRole/allocatedList', is_headers=True, json=page_obj) - - -def get_unallocated_role_list_api(page_obj: dict): - - return api_request(method='post', url='/system/user/authRole/unallocatedList', is_headers=True, json=page_obj) - - -def auth_role_select_all_api(page_obj: dict): - - return api_request(method='post', url='/system/user/authRole/selectAll', is_headers=True, json=page_obj) - - -def auth_role_cancel_api(page_obj: dict): - - return api_request(method='post', url='/system/user/authRole/cancel', is_headers=True, json=page_obj) - - diff --git a/dash-fastapi-frontend/app.py b/dash-fastapi-frontend/app.py index e1d840df98a90a7ab8c2af7b5c8685d1486eaaca..35075f436b600ccd6264034ca434f9b9302ab286 100644 --- a/dash-fastapi-frontend/app.py +++ b/dash-fastapi-frontend/app.py @@ -1,23 +1,11 @@ -import dash -import time -from dash import html, dcc -from dash.dependencies import Input, Output, State import feffery_antd_components as fac import feffery_utils_components as fuc -from flask import session -from operator import itemgetter - -from server import app +from dash import dcc, html +from callbacks import app_c, router_c # noqa: F401 from config.env import AppConfig -from config.global_config import RouterConfig +from server import app from store.store import render_store_container -# 载入子页面 -import views - -from callbacks import app_c -from api.login import get_current_user_info_api -from utils.tree_tool import find_node_values, find_key_by_href, deal_user_menu_info, get_search_panel_data app.layout = html.Div( [ @@ -25,236 +13,52 @@ app.layout = html.Div( fuc.FefferyLocation(id='url-container'), # 用于回调pathname信息 dcc.Location(id='dcc-url', refresh=False), - - # 注入js执行容器 - fuc.FefferyExecuteJs( - id='execute-js-container' - ), - # 注入页面内容挂载点 html.Div(id='app-mount'), - # 注入全局配置容器 fac.AntdConfigProvider(id='app-config-provider'), - # 注入快捷搜索面板 fuc.FefferyShortcutPanel( id='search-panel', data=[], placeholder='输入你想要搜索的菜单...', - panelStyles={ - 'accentColor': '#1890ff', - 'zIndex': 99999 - } + panelStyles={'accentColor': '#1890ff', 'zIndex': 99999}, ), - - # 辅助处理多输入 -> 存储接口返回token校验信息 + # 注入全局store容器 render_store_container(), - # 重定向容器 html.Div(id='redirect-container'), - # 登录消息失效对话框提示 fac.AntdModal( html.Div( [ - fac.AntdIcon(icon='fc-high-priority', style={'font-size': '28px'}), - fac.AntdText('用户信息已过期,请重新登录', style={'margin-left': '5px'}), + fac.AntdIcon( + icon='fc-high-priority', style={'font-size': '28px'} + ), + fac.AntdText( + '登录状态已过期,您可以继续留在该页面,或者重新登录', + style={'margin-left': '5px'}, + ), ] ), id='token-invalid-modal', visible=False, - title='提示', + title='系统提示', + okText='重新登录', renderFooter=True, - centered=True + centered=True, ), - # 注入全局消息提示容器 - html.Div(id='global-message-container'), + fac.Fragment(id='global-message-container'), # 注入全局通知信息容器 - html.Div(id='global-notification-container') + fac.Fragment(id='global-notification-container'), ] ) -@app.callback( - output=dict( - app_mount=Output('app-mount', 'children'), - redirect_container=Output('redirect-container', 'children', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - menu_current_key=Output('current-key-container', 'data'), - menu_info=Output('menu-info-store-container', 'data'), - menu_list=Output('menu-list-store-container', 'data'), - search_panel_data=Output('search-panel', 'data') - ), - inputs=dict(pathname=Input('url-container', 'pathname')), - state=dict( - url_trigger=State('url-container', 'trigger'), - session_token=State('token-container', 'data') - ), - prevent_initial_call=True -) -def router(pathname, url_trigger, session_token): - """ - 全局路由回调 - """ - # 检查当前会话是否已经登录 - token_result = session.get('Authorization') - # 若已登录 - if token_result and session_token and token_result == session_token: - try: - current_user_result = get_current_user_info_api() - if current_user_result['code'] == 200: - current_user = current_user_result['data'] - menu_list = current_user['menu'] - user_menu_list = sorted([item for item in menu_list if item.get('visible') == '0'], key=itemgetter('order_num')) - menu_info = deal_user_menu_info(0, menu_list) - user_menu_info = deal_user_menu_info(0, user_menu_list) - search_panel_data = get_search_panel_data(user_menu_list) - session['user_info'] = current_user['user'] - session['dept_info'] = current_user['dept'] - session['role_info'] = current_user['role'] - session['post_info'] = current_user['post'] - dynamic_valid_pathname_list = find_node_values(menu_info, 'href') - valid_href_list = dynamic_valid_pathname_list + RouterConfig.STATIC_VALID_PATHNAME - if pathname in valid_href_list: - current_key = find_key_by_href(menu_info, pathname) - if pathname == '/': - current_key = '首页' - if pathname == '/user/profile': - current_key = '个人资料' - if url_trigger == 'load': - - # 根据pathname控制渲染行为 - if pathname == '/login' or pathname == '/forget': - # 重定向到主页面 - return dict( - app_mount=dash.no_update, - redirect_container=dcc.Location(pathname='/', id='router-redirect'), - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key={'current_key': current_key}, - menu_info={'menu_info': menu_info}, - menu_list={'menu_list': menu_list}, - search_panel_data=search_panel_data - ) - - # 否则正常渲染主页面 - return dict( - app_mount=views.layout.render_content(user_menu_info), - redirect_container=None, - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key={'current_key': current_key}, - menu_info={'menu_info': menu_info}, - menu_list={'menu_list': menu_list}, - search_panel_data=search_panel_data - ) - - else: - return dict( - app_mount=dash.no_update, - redirect_container=None, - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key={'current_key': current_key}, - menu_info={'menu_info': menu_info}, - menu_list={'menu_list': menu_list}, - search_panel_data=search_panel_data - ) - - else: - # 渲染404状态页 - return dict( - app_mount=views.page_404.render_content(), - redirect_container=None, - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - - else: - return dict( - app_mount=dash.no_update, - redirect_container=dash.no_update, - global_message_container=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - - except Exception as e: - print(e) - - return dict( - app_mount=dash.no_update, - redirect_container=None, - global_message_container=fuc.FefferyFancyNotification('接口异常', type='error', autoClose=2000), - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - else: - # 若未登录 - # 根据pathname控制渲染行为 - # 检验pathname合法性 - if pathname not in RouterConfig.BASIC_VALID_PATHNAME: - # 渲染404状态页 - return dict( - app_mount=views.page_404.render_content(), - redirect_container=None, - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - - if pathname == '/login': - return dict( - app_mount=views.login.render_content(), - redirect_container=None, - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - - if pathname == '/forget': - return dict( - app_mount=views.forget.render_forget_content(), - redirect_container=None, - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - - # 否则重定向到登录页 - return dict( - app_mount=dash.no_update, - redirect_container=dcc.Location(pathname='/login', id='router-redirect'), - global_message_container=None, - api_check_token_trigger={'timestamp': time.time()}, - menu_current_key=dash.no_update, - menu_info=dash.no_update, - menu_list=dash.no_update, - search_panel_data=dash.no_update - ) - - if __name__ == '__main__': - app.run(host=AppConfig.app_host, port=AppConfig.app_port, debug=AppConfig.app_debug) + app.run( + host=AppConfig.app_host, + port=AppConfig.app_port, + debug=AppConfig.app_debug, + ) diff --git a/dash-fastapi-frontend/assets/css/global.css b/dash-fastapi-frontend/assets/css/global.css index 1dc1aa30298bf70e5e702de8e98cc317bb038eeb..2de4d12e4aa507476896c03b59657f12f90399d2 100644 --- a/dash-fastapi-frontend/assets/css/global.css +++ b/dash-fastapi-frontend/assets/css/global.css @@ -6,7 +6,7 @@ top: 50vh; left: 50vw; transform: translate(-50%, -50%); - background-image: url("/assets/imgs/加载动画.webp"); + background-image: url("/assets/imgs/loading.webp"); background-repeat: no-repeat; background-size: 100% 100%; } diff --git a/dash-fastapi-frontend/assets/imgs/background.png b/dash-fastapi-frontend/assets/imgs/background.png new file mode 100644 index 0000000000000000000000000000000000000000..d9fbe2e68acc78f9cdd3bf9e4942df2586e6590c Binary files /dev/null and b/dash-fastapi-frontend/assets/imgs/background.png differ diff --git "a/dash-fastapi-frontend/assets/imgs/\345\212\240\350\275\275\345\212\250\347\224\273.webp" b/dash-fastapi-frontend/assets/imgs/loading.webp similarity index 100% rename from "dash-fastapi-frontend/assets/imgs/\345\212\240\350\275\275\345\212\250\347\224\273.webp" rename to dash-fastapi-frontend/assets/imgs/loading.webp diff --git a/dash-fastapi-frontend/assets/imgs/login-background.jpg b/dash-fastapi-frontend/assets/imgs/login-background.jpg deleted file mode 100644 index 8a89eb8291d5cb7d9f37ec4f275deab911c9e28e..0000000000000000000000000000000000000000 Binary files a/dash-fastapi-frontend/assets/imgs/login-background.jpg and /dev/null differ diff --git a/dash-fastapi-frontend/assets/imgs/profile.jpg b/dash-fastapi-frontend/assets/imgs/profile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..94efdf359a5d11bb316b157b671cf851b578d1e0 Binary files /dev/null and b/dash-fastapi-frontend/assets/imgs/profile.jpg differ diff --git a/dash-fastapi-frontend/assets/js/js.cookie.min.js b/dash-fastapi-frontend/assets/js/js.cookie.min.js new file mode 100644 index 0000000000000000000000000000000000000000..962d48d0e385b2fee7b854575a07046d07d14620 --- /dev/null +++ b/dash-fastapi-frontend/assets/js/js.cookie.min.js @@ -0,0 +1,2 @@ +/*! js-cookie v3.0.5 | MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t(()=>{var t={155:t=>{var e,i,r=t.exports={};function n(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function o(t){if(e===setTimeout)return setTimeout(t,0);if((e===n||!e)&&setTimeout)return e=setTimeout,setTimeout(t,0);try{return e(t,0)}catch(i){try{return e.call(null,t,0)}catch(i){return e.call(this,t,0)}}}!function(){try{e="function"==typeof setTimeout?setTimeout:n}catch(t){e=n}try{i="function"==typeof clearTimeout?clearTimeout:s}catch(t){i=s}}();var h,a=[],u=!1,c=-1;function f(){u&&h&&(u=!1,h.length?a=h.concat(a):c=-1,a.length&&l())}function l(){if(!u){var t=o(f);u=!0;for(var e=a.length;e;){for(h=a,a=[];++c1)for(var i=1;i{for(var r in e)i.o(e,r)&&!i.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var r={};return(()=>{"use strict";i.d(r,{default:()=>ct});var t="0123456789abcdefghijklmnopqrstuvwxyz";function e(e){return t.charAt(e)}function n(t,e){return t&e}function s(t,e){return t|e}function o(t,e){return t^e}function h(t,e){return t&~e}function a(t){if(0==t)return-1;var e=0;return 0==(65535&t)&&(t>>=16,e+=16),0==(255&t)&&(t>>=8,e+=8),0==(15&t)&&(t>>=4,e+=4),0==(3&t)&&(t>>=2,e+=2),0==(1&t)&&++e,e}function u(t){for(var e=0;0!=t;)t&=t-1,++e;return e}var c,f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",l="=";function p(t){var e,i,r="";for(e=0;e+3<=t.length;e+=3)i=parseInt(t.substring(e,e+3),16),r+=f.charAt(i>>6)+f.charAt(63&i);for(e+1==t.length?(i=parseInt(t.substring(e,e+1),16),r+=f.charAt(i<<2)):e+2==t.length&&(i=parseInt(t.substring(e,e+2),16),r+=f.charAt(i>>2)+f.charAt((3&i)<<4));(3&r.length)>0;)r+=l;return r}function g(t){var i,r="",n=0,s=0;for(i=0;i>2),s=3&o,n=1):1==n?(r+=e(s<<2|o>>4),s=15&o,n=2):2==n?(r+=e(s),r+=e(o>>2),s=3&o,n=3):(r+=e(s<<2|o>>4),r+=e(15&o),n=0))}return 1==n&&(r+=e(s<<2)),r}var d,v={decode:function(t){var e;if(void 0===d){var i="= \f\n\r\t \u2028\u2029";for(d=Object.create(null),e=0;e<64;++e)d["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(e)]=e;for(d["-"]=62,d._=63,e=0;e=4?(r[r.length]=n>>16,r[r.length]=n>>8&255,r[r.length]=255&n,n=0,s=0):n<<=6}}switch(s){case 1:throw new Error("Base64 encoding incomplete: at least 2 bits missing");case 2:r[r.length]=n>>10;break;case 3:r[r.length]=n>>16,r[r.length]=n>>8&255}return r},re:/-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/,unarmor:function(t){var e=v.re.exec(t);if(e)if(e[1])t=e[1];else{if(!e[2])throw new Error("RegExp out of sync");t=e[2]}return v.decode(t)}},m=1e13,y=function(){function t(t){this.buf=[+t||0]}return t.prototype.mulAdd=function(t,e){var i,r,n=this.buf,s=n.length;for(i=0;i0&&(n[i]=e)},t.prototype.sub=function(t){var e,i,r=this.buf,n=r.length;for(e=0;e=0;--r)i+=(m+e[r]).toString().substring(1);return i},t.prototype.valueOf=function(){for(var t=this.buf,e=0,i=t.length-1;i>=0;--i)e=e*m+t[i];return e},t.prototype.simplify=function(){var t=this.buf;return 1==t.length?t[0]:this},t}(),b="…",T=/^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/,S=/^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;function E(t,e){return t.length>e&&(t=t.substring(0,e)+b),t}var w,D=function(){function t(e,i){this.hexDigits="0123456789ABCDEF",e instanceof t?(this.enc=e.enc,this.pos=e.pos):(this.enc=e,this.pos=i)}return t.prototype.get=function(t){if(void 0===t&&(t=this.pos++),t>=this.enc.length)throw new Error("Requesting byte offset ".concat(t," on a stream of length ").concat(this.enc.length));return"string"==typeof this.enc?this.enc.charCodeAt(t):this.enc[t]},t.prototype.hexByte=function(t){return this.hexDigits.charAt(t>>4&15)+this.hexDigits.charAt(15&t)},t.prototype.hexDump=function(t,e,i){for(var r="",n=t;n176)return!1}return!0},t.prototype.parseStringISO=function(t,e){for(var i="",r=t;r191&&n<224?String.fromCharCode((31&n)<<6|63&this.get(r++)):String.fromCharCode((15&n)<<12|(63&this.get(r++))<<6|63&this.get(r++))}return i},t.prototype.parseStringBMP=function(t,e){for(var i,r,n="",s=t;s127,s=n?255:0,o="";r==s&&++t4){for(o=r,i<<=3;0==(128&(+o^s));)o=+o<<1,--i;o="("+i+" bit)\n"}n&&(r-=256);for(var h=new y(r),a=t+1;a=a;--u)s+=h>>u&1?"1":"0";if(s.length>i)return n+E(s,i)}return n+s},t.prototype.parseOctetString=function(t,e,i){if(this.isASCII(t,e))return E(this.parseStringISO(t,e),i);var r=e-t,n="("+r+" byte)\n";r>(i/=2)&&(e=t+i);for(var s=t;si&&(n+=b),n},t.prototype.parseOID=function(t,e,i){for(var r="",n=new y,s=0,o=t;oi)return E(r,i);n=new y,s=0}}return s>0&&(r+=".incomplete"),r},t}(),x=function(){function t(t,e,i,r,n){if(!(r instanceof R))throw new Error("Invalid tag value.");this.stream=t,this.header=e,this.length=i,this.tag=r,this.sub=n}return t.prototype.typeName=function(){switch(this.tag.tagClass){case 0:switch(this.tag.tagNumber){case 0:return"EOC";case 1:return"BOOLEAN";case 2:return"INTEGER";case 3:return"BIT_STRING";case 4:return"OCTET_STRING";case 5:return"NULL";case 6:return"OBJECT_IDENTIFIER";case 7:return"ObjectDescriptor";case 8:return"EXTERNAL";case 9:return"REAL";case 10:return"ENUMERATED";case 11:return"EMBEDDED_PDV";case 12:return"UTF8String";case 16:return"SEQUENCE";case 17:return"SET";case 18:return"NumericString";case 19:return"PrintableString";case 20:return"TeletexString";case 21:return"VideotexString";case 22:return"IA5String";case 23:return"UTCTime";case 24:return"GeneralizedTime";case 25:return"GraphicString";case 26:return"VisibleString";case 27:return"GeneralString";case 28:return"UniversalString";case 30:return"BMPString"}return"Universal_"+this.tag.tagNumber.toString();case 1:return"Application_"+this.tag.tagNumber.toString();case 2:return"["+this.tag.tagNumber.toString()+"]";case 3:return"Private_"+this.tag.tagNumber.toString()}},t.prototype.content=function(t){if(void 0===this.tag)return null;void 0===t&&(t=1/0);var e=this.posContent(),i=Math.abs(this.length);if(!this.tag.isUniversal())return null!==this.sub?"("+this.sub.length+" elem)":this.stream.parseOctetString(e,e+i,t);switch(this.tag.tagNumber){case 1:return 0===this.stream.get(e)?"false":"true";case 2:return this.stream.parseInteger(e,e+i);case 3:return this.sub?"("+this.sub.length+" elem)":this.stream.parseBitString(e,e+i,t);case 4:return this.sub?"("+this.sub.length+" elem)":this.stream.parseOctetString(e,e+i,t);case 6:return this.stream.parseOID(e,e+i,t);case 16:case 17:return null!==this.sub?"("+this.sub.length+" elem)":"(no elem)";case 12:return E(this.stream.parseStringUTF(e,e+i),t);case 18:case 19:case 20:case 21:case 22:case 26:return E(this.stream.parseStringISO(e,e+i),t);case 30:return E(this.stream.parseStringBMP(e,e+i),t);case 23:case 24:return this.stream.parseTime(e,e+i,23==this.tag.tagNumber)}return null},t.prototype.toString=function(){return this.typeName()+"@"+this.stream.pos+"[header:"+this.header+",length:"+this.length+",sub:"+(null===this.sub?"null":this.sub.length)+"]"},t.prototype.toPrettyString=function(t){void 0===t&&(t="");var e=t+this.typeName()+" @"+this.stream.pos;if(this.length>=0&&(e+="+"),e+=this.length,this.tag.tagConstructed?e+=" (constructed)":!this.tag.isUniversal()||3!=this.tag.tagNumber&&4!=this.tag.tagNumber||null===this.sub||(e+=" (encapsulates)"),e+="\n",null!==this.sub){t+=" ";for(var i=0,r=this.sub.length;i6)throw new Error("Length over 48 bits not supported at position "+(t.pos-1));if(0===i)return null;e=0;for(var r=0;r>6,this.tagConstructed=0!=(32&e),this.tagNumber=31&e,31==this.tagNumber){var i=new y;do{e=t.get(),i.mulAdd(128,127&e)}while(128&e);this.tagNumber=i.simplify()}}return t.prototype.isUniversal=function(){return 0===this.tagClass},t.prototype.isEOC=function(){return 0===this.tagClass&&0===this.tagNumber},t}(),B=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],O=(1<<26)/B[B.length-1],A=function(){function t(t,e,i){null!=t&&("number"==typeof t?this.fromNumber(t,e,i):null==e&&"string"!=typeof t?this.fromString(t,256):this.fromString(t,e))}return t.prototype.toString=function(t){if(this.s<0)return"-"+this.negate().toString(t);var i;if(16==t)i=4;else if(8==t)i=3;else if(2==t)i=1;else if(32==t)i=5;else{if(4!=t)return this.toRadix(t);i=2}var r,n=(1<0)for(a>a)>0&&(s=!0,o=e(r));h>=0;)a>(a+=this.DB-i)):(r=this[h]>>(a-=i)&n,a<=0&&(a+=this.DB,--h)),r>0&&(s=!0),s&&(o+=e(r));return s?o:"0"},t.prototype.negate=function(){var e=M();return t.ZERO.subTo(this,e),e},t.prototype.abs=function(){return this.s<0?this.negate():this},t.prototype.compareTo=function(t){var e=this.s-t.s;if(0!=e)return e;var i=this.t;if(0!=(e=i-t.t))return this.s<0?-e:e;for(;--i>=0;)if(0!=(e=this[i]-t[i]))return e;return 0},t.prototype.bitLength=function(){return this.t<=0?0:this.DB*(this.t-1)+K(this[this.t-1]^this.s&this.DM)},t.prototype.mod=function(e){var i=M();return this.abs().divRemTo(e,null,i),this.s<0&&i.compareTo(t.ZERO)>0&&e.subTo(i,i),i},t.prototype.modPowInt=function(t,e){var i;return i=t<256||e.isEven()?new I(e):new N(e),this.exp(t,i)},t.prototype.clone=function(){var t=M();return this.copyTo(t),t},t.prototype.intValue=function(){if(this.s<0){if(1==this.t)return this[0]-this.DV;if(0==this.t)return-1}else{if(1==this.t)return this[0];if(0==this.t)return 0}return(this[1]&(1<<32-this.DB)-1)<>24},t.prototype.shortValue=function(){return 0==this.t?this.s:this[0]<<16>>16},t.prototype.signum=function(){return this.s<0?-1:this.t<=0||1==this.t&&this[0]<=0?0:1},t.prototype.toByteArray=function(){var t=this.t,e=[];e[0]=this.s;var i,r=this.DB-t*this.DB%8,n=0;if(t-- >0)for(r>r)!=(this.s&this.DM)>>r&&(e[n++]=i|this.s<=0;)r<8?(i=(this[t]&(1<>(r+=this.DB-8)):(i=this[t]>>(r-=8)&255,r<=0&&(r+=this.DB,--t)),0!=(128&i)&&(i|=-256),0==n&&(128&this.s)!=(128&i)&&++n,(n>0||i!=this.s)&&(e[n++]=i);return e},t.prototype.equals=function(t){return 0==this.compareTo(t)},t.prototype.min=function(t){return this.compareTo(t)<0?this:t},t.prototype.max=function(t){return this.compareTo(t)>0?this:t},t.prototype.and=function(t){var e=M();return this.bitwiseTo(t,n,e),e},t.prototype.or=function(t){var e=M();return this.bitwiseTo(t,s,e),e},t.prototype.xor=function(t){var e=M();return this.bitwiseTo(t,o,e),e},t.prototype.andNot=function(t){var e=M();return this.bitwiseTo(t,h,e),e},t.prototype.not=function(){for(var t=M(),e=0;e=this.t?0!=this.s:0!=(this[e]&1<1){var c=M();for(r.sqrTo(o[1],c);h<=u;)o[h]=M(),r.mulTo(c,o[h-2],o[h]),h+=2}var f,l,p=t.t-1,g=!0,d=M();for(n=K(t[p])-1;p>=0;){for(n>=a?f=t[p]>>n-a&u:(f=(t[p]&(1<0&&(f|=t[p-1]>>this.DB+n-a)),h=i;0==(1&f);)f>>=1,--h;if((n-=h)<0&&(n+=this.DB,--p),g)o[f].copyTo(s),g=!1;else{for(;h>1;)r.sqrTo(s,d),r.sqrTo(d,s),h-=2;h>0?r.sqrTo(s,d):(l=s,s=d,d=l),r.mulTo(d,o[f],s)}for(;p>=0&&0==(t[p]&1<=0?(r.subTo(n,r),i&&s.subTo(h,s),o.subTo(a,o)):(n.subTo(r,n),i&&h.subTo(s,h),a.subTo(o,a))}return 0!=n.compareTo(t.ONE)?t.ZERO:a.compareTo(e)>=0?a.subtract(e):a.signum()<0?(a.addTo(e,a),a.signum()<0?a.add(e):a):a},t.prototype.pow=function(t){return this.exp(t,new V)},t.prototype.gcd=function(t){var e=this.s<0?this.negate():this.clone(),i=t.s<0?t.negate():t.clone();if(e.compareTo(i)<0){var r=e;e=i,i=r}var n=e.getLowestSetBit(),s=i.getLowestSetBit();if(s<0)return e;for(n0&&(e.rShiftTo(s,e),i.rShiftTo(s,i));e.signum()>0;)(n=e.getLowestSetBit())>0&&e.rShiftTo(n,e),(n=i.getLowestSetBit())>0&&i.rShiftTo(n,i),e.compareTo(i)>=0?(e.subTo(i,e),e.rShiftTo(1,e)):(i.subTo(e,i),i.rShiftTo(1,i));return s>0&&i.lShiftTo(s,i),i},t.prototype.isProbablePrime=function(t){var e,i=this.abs();if(1==i.t&&i[0]<=B[B.length-1]){for(e=0;e=0;--e)t[e]=this[e];t.t=this.t,t.s=this.s},t.prototype.fromInt=function(t){this.t=1,this.s=t<0?-1:0,t>0?this[0]=t:t<-1?this[0]=t+this.DV:this.t=0},t.prototype.fromString=function(e,i){var r;if(16==i)r=4;else if(8==i)r=3;else if(256==i)r=8;else if(2==i)r=1;else if(32==i)r=5;else{if(4!=i)return void this.fromRadix(e,i);r=2}this.t=0,this.s=0;for(var n=e.length,s=!1,o=0;--n>=0;){var h=8==r?255&+e[n]:F(e,n);h<0?"-"==e.charAt(n)&&(s=!0):(s=!1,0==o?this[this.t++]=h:o+r>this.DB?(this[this.t-1]|=(h&(1<>this.DB-o):this[this.t-1]|=h<=this.DB&&(o-=this.DB))}8==r&&0!=(128&+e[0])&&(this.s=-1,o>0&&(this[this.t-1]|=(1<0&&this[this.t-1]==t;)--this.t},t.prototype.dlShiftTo=function(t,e){var i;for(i=this.t-1;i>=0;--i)e[i+t]=this[i];for(i=t-1;i>=0;--i)e[i]=0;e.t=this.t+t,e.s=this.s},t.prototype.drShiftTo=function(t,e){for(var i=t;i=0;--h)e[h+s+1]=this[h]>>r|o,o=(this[h]&n)<=0;--h)e[h]=0;e[s]=o,e.t=this.t+s+1,e.s=this.s,e.clamp()},t.prototype.rShiftTo=function(t,e){e.s=this.s;var i=Math.floor(t/this.DB);if(i>=this.t)e.t=0;else{var r=t%this.DB,n=this.DB-r,s=(1<>r;for(var o=i+1;o>r;r>0&&(e[this.t-i-1]|=(this.s&s)<>=this.DB;if(t.t>=this.DB;r+=this.s}else{for(r+=this.s;i>=this.DB;r-=t.s}e.s=r<0?-1:0,r<-1?e[i++]=this.DV+r:r>0&&(e[i++]=r),e.t=i,e.clamp()},t.prototype.multiplyTo=function(e,i){var r=this.abs(),n=e.abs(),s=r.t;for(i.t=s+n.t;--s>=0;)i[s]=0;for(s=0;s=0;)t[i]=0;for(i=0;i=e.DV&&(t[i+e.t]-=e.DV,t[i+e.t+1]=1)}t.t>0&&(t[t.t-1]+=e.am(i,e[i],t,2*i,0,1)),t.s=0,t.clamp()},t.prototype.divRemTo=function(e,i,r){var n=e.abs();if(!(n.t<=0)){var s=this.abs();if(s.t0?(n.lShiftTo(u,o),s.lShiftTo(u,r)):(n.copyTo(o),s.copyTo(r));var c=o.t,f=o[c-1];if(0!=f){var l=f*(1<1?o[c-2]>>this.F2:0),p=this.FV/l,g=(1<=0&&(r[r.t++]=1,r.subTo(y,r)),t.ONE.dlShiftTo(c,y),y.subTo(o,o);o.t=0;){var b=r[--v]==f?this.DM:Math.floor(r[v]*p+(r[v-1]+d)*g);if((r[v]+=o.am(0,b,r,m,0,c))0&&r.rShiftTo(u,r),h<0&&t.ZERO.subTo(r,r)}}},t.prototype.invDigit=function(){if(this.t<1)return 0;var t=this[0];if(0==(1&t))return 0;var e=3&t;return(e=(e=(e=(e=e*(2-(15&t)*e)&15)*(2-(255&t)*e)&255)*(2-((65535&t)*e&65535))&65535)*(2-t*e%this.DV)%this.DV)>0?this.DV-e:-e},t.prototype.isEven=function(){return 0==(this.t>0?1&this[0]:this.s)},t.prototype.exp=function(e,i){if(e>4294967295||e<1)return t.ONE;var r=M(),n=M(),s=i.convert(this),o=K(e)-1;for(s.copyTo(r);--o>=0;)if(i.sqrTo(r,n),(e&1<0)i.mulTo(n,s,r);else{var h=r;r=n,n=h}return i.revert(r)},t.prototype.chunkSize=function(t){return Math.floor(Math.LN2*this.DB/Math.log(t))},t.prototype.toRadix=function(t){if(null==t&&(t=10),0==this.signum()||t<2||t>36)return"0";var e=this.chunkSize(t),i=Math.pow(t,e),r=U(i),n=M(),s=M(),o="";for(this.divRemTo(r,n,s);n.signum()>0;)o=(i+s.intValue()).toString(t).substr(1)+o,n.divRemTo(r,n,s);return s.intValue().toString(t)+o},t.prototype.fromRadix=function(e,i){this.fromInt(0),null==i&&(i=10);for(var r=this.chunkSize(i),n=Math.pow(i,r),s=!1,o=0,h=0,a=0;a=r&&(this.dMultiply(n),this.dAddOffset(h,0),o=0,h=0))}o>0&&(this.dMultiply(Math.pow(i,o)),this.dAddOffset(h,0)),s&&t.ZERO.subTo(this,this)},t.prototype.fromNumber=function(e,i,r){if("number"==typeof i)if(e<2)this.fromInt(1);else for(this.fromNumber(e,r),this.testBit(e-1)||this.bitwiseTo(t.ONE.shiftLeft(e-1),s,this),this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(i);)this.dAddOffset(2,0),this.bitLength()>e&&this.subTo(t.ONE.shiftLeft(e-1),this);else{var n=[],o=7&e;n.length=1+(e>>3),i.nextBytes(n),o>0?n[0]&=(1<>=this.DB;if(t.t>=this.DB;r+=this.s}else{for(r+=this.s;i>=this.DB;r+=t.s}e.s=r<0?-1:0,r>0?e[i++]=r:r<-1&&(e[i++]=this.DV+r),e.t=i,e.clamp()},t.prototype.dMultiply=function(t){this[this.t]=this.am(0,t-1,this,0,0,this.t),++this.t,this.clamp()},t.prototype.dAddOffset=function(t,e){if(0!=t){for(;this.t<=e;)this[this.t++]=0;for(this[e]+=t;this[e]>=this.DV;)this[e]-=this.DV,++e>=this.t&&(this[this.t++]=0),++this[e]}},t.prototype.multiplyLowerTo=function(t,e,i){var r=Math.min(this.t+t.t,e);for(i.s=0,i.t=r;r>0;)i[--r]=0;for(var n=i.t-this.t;r=0;)i[r]=0;for(r=Math.max(e-this.t,0);r0)if(0==e)i=this[0]%t;else for(var r=this.t-1;r>=0;--r)i=(e*i+this[r])%t;return i},t.prototype.millerRabin=function(e){var i=this.subtract(t.ONE),r=i.getLowestSetBit();if(r<=0)return!1;var n=i.shiftRight(r);(e=e+1>>1)>B.length&&(e=B.length);for(var s=M(),o=0;o0&&(i.rShiftTo(o,i),r.rShiftTo(o,r));var h=function(){(s=i.getLowestSetBit())>0&&i.rShiftTo(s,i),(s=r.getLowestSetBit())>0&&r.rShiftTo(s,r),i.compareTo(r)>=0?(i.subTo(r,i),i.rShiftTo(1,i)):(r.subTo(i,r),r.rShiftTo(1,r)),i.signum()>0?setTimeout(h,0):(o>0&&r.lShiftTo(o,r),setTimeout((function(){e(r)}),0))};setTimeout(h,10)}},t.prototype.fromNumberAsync=function(e,i,r,n){if("number"==typeof i)if(e<2)this.fromInt(1);else{this.fromNumber(e,r),this.testBit(e-1)||this.bitwiseTo(t.ONE.shiftLeft(e-1),s,this),this.isEven()&&this.dAddOffset(1,0);var o=this,h=function(){o.dAddOffset(2,0),o.bitLength()>e&&o.subTo(t.ONE.shiftLeft(e-1),o),o.isProbablePrime(i)?setTimeout((function(){n()}),0):setTimeout(h,0)};setTimeout(h,0)}else{var a=[],u=7&e;a.length=1+(e>>3),i.nextBytes(a),u>0?a[0]&=(1<=0?t.mod(this.m):t},t.prototype.revert=function(t){return t},t.prototype.reduce=function(t){t.divRemTo(this.m,null,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}(),N=function(){function t(t){this.m=t,this.mp=t.invDigit(),this.mpl=32767&this.mp,this.mph=this.mp>>15,this.um=(1<0&&this.m.subTo(e,e),e},t.prototype.revert=function(t){var e=M();return t.copyTo(e),this.reduce(e),e},t.prototype.reduce=function(t){for(;t.t<=this.mt2;)t[t.t++]=0;for(var e=0;e>15)*this.mpl&this.um)<<15)&t.DM;for(t[i=e+this.m.t]+=this.m.am(0,r,t,e,0,this.m.t);t[i]>=t.DV;)t[i]-=t.DV,t[++i]++}t.clamp(),t.drShiftTo(this.m.t,t),t.compareTo(this.m)>=0&&t.subTo(this.m,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}(),P=function(){function t(t){this.m=t,this.r2=M(),this.q3=M(),A.ONE.dlShiftTo(2*t.t,this.r2),this.mu=this.r2.divide(t)}return t.prototype.convert=function(t){if(t.s<0||t.t>2*this.m.t)return t.mod(this.m);if(t.compareTo(this.m)<0)return t;var e=M();return t.copyTo(e),this.reduce(e),e},t.prototype.revert=function(t){return t},t.prototype.reduce=function(t){for(t.drShiftTo(this.m.t-1,this.r2),t.t>this.m.t+1&&(t.t=this.m.t+1,t.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);t.compareTo(this.r2)<0;)t.dAddOffset(1,this.m.t+1);for(t.subTo(this.r2,t);t.compareTo(this.m)>=0;)t.subTo(this.m,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}();function M(){return new A(null)}function L(t,e){return new A(t,e)}var j="undefined"!=typeof navigator;j&&"Microsoft Internet Explorer"==navigator.appName?(A.prototype.am=function(t,e,i,r,n,s){for(var o=32767&e,h=e>>15;--s>=0;){var a=32767&this[t],u=this[t++]>>15,c=h*a+u*o;n=((a=o*a+((32767&c)<<15)+i[r]+(1073741823&n))>>>30)+(c>>>15)+h*u+(n>>>30),i[r++]=1073741823&a}return n},w=30):j&&"Netscape"!=navigator.appName?(A.prototype.am=function(t,e,i,r,n,s){for(;--s>=0;){var o=e*this[t++]+i[r]+n;n=Math.floor(o/67108864),i[r++]=67108863&o}return n},w=26):(A.prototype.am=function(t,e,i,r,n,s){for(var o=16383&e,h=e>>14;--s>=0;){var a=16383&this[t],u=this[t++]>>14,c=h*a+u*o;n=((a=o*a+((16383&c)<<14)+i[r]+n)>>28)+(c>>14)+h*u,i[r++]=268435455&a}return n},w=28),A.prototype.DB=w,A.prototype.DM=(1<>>16)&&(t=e,i+=16),0!=(e=t>>8)&&(t=e,i+=8),0!=(e=t>>4)&&(t=e,i+=4),0!=(e=t>>2)&&(t=e,i+=2),0!=(e=t>>1)&&(t=e,i+=1),i}A.ZERO=U(0),A.ONE=U(1);var k,_,z=function(){function t(){this.i=0,this.j=0,this.S=[]}return t.prototype.init=function(t){var e,i,r;for(e=0;e<256;++e)this.S[e]=e;for(i=0,e=0;e<256;++e)i=i+this.S[e]+t[e%t.length]&255,r=this.S[e],this.S[e]=this.S[i],this.S[i]=r;this.i=0,this.j=0},t.prototype.next=function(){var t;return this.i=this.i+1&255,this.j=this.j+this.S[this.i]&255,t=this.S[this.i],this.S[this.i]=this.S[this.j],this.S[this.j]=t,this.S[t+this.S[this.i]&255]},t}(),Z=256,G=null;if(null==G){G=[],_=0;var $=void 0;if("undefined"!=typeof window&&window.crypto&&window.crypto.getRandomValues){var Y=new Uint32Array(256);for(window.crypto.getRandomValues(Y),$=0;$=256||_>=Z)window.removeEventListener?window.removeEventListener("mousemove",X,!1):window.detachEvent&&window.detachEvent("onmousemove",X);else try{var e=t.x+t.y;G[_++]=255&e,J+=1}catch(t){}};"undefined"!=typeof window&&(window.addEventListener?window.addEventListener("mousemove",X,!1):window.attachEvent&&window.attachEvent("onmousemove",X))}function Q(){if(null==k){for(k=new z;_0&&e.length>0?(this.n=L(t,16),this.e=parseInt(e,16)):console.error("Invalid RSA public key")},t.prototype.encrypt=function(t){var e=this.n.bitLength()+7>>3,i=function(t,e){if(e=0&&e>0;){var n=t.charCodeAt(r--);n<128?i[--e]=n:n>127&&n<2048?(i[--e]=63&n|128,i[--e]=n>>6|192):(i[--e]=63&n|128,i[--e]=n>>6&63|128,i[--e]=n>>12|224)}i[--e]=0;for(var s=new W,o=[];e>2;){for(o[0]=0;0==o[0];)s.nextBytes(o);i[--e]=o[0]}return i[--e]=2,i[--e]=0,new A(i)}(t,e);if(null==i)return null;var r=this.doPublic(i);if(null==r)return null;for(var n=r.toString(16),s=n.length,o=0;o<2*e-s;o++)n="0"+n;return n},t.prototype.setPrivate=function(t,e,i){null!=t&&null!=e&&t.length>0&&e.length>0?(this.n=L(t,16),this.e=parseInt(e,16),this.d=L(i,16)):console.error("Invalid RSA private key")},t.prototype.setPrivateEx=function(t,e,i,r,n,s,o,h){null!=t&&null!=e&&t.length>0&&e.length>0?(this.n=L(t,16),this.e=parseInt(e,16),this.d=L(i,16),this.p=L(r,16),this.q=L(n,16),this.dmp1=L(s,16),this.dmq1=L(o,16),this.coeff=L(h,16)):console.error("Invalid RSA private key")},t.prototype.generate=function(t,e){var i=new W,r=t>>1;this.e=parseInt(e,16);for(var n=new A(e,16);;){for(;this.p=new A(t-r,1,i),0!=this.p.subtract(A.ONE).gcd(n).compareTo(A.ONE)||!this.p.isProbablePrime(10););for(;this.q=new A(r,1,i),0!=this.q.subtract(A.ONE).gcd(n).compareTo(A.ONE)||!this.q.isProbablePrime(10););if(this.p.compareTo(this.q)<=0){var s=this.p;this.p=this.q,this.q=s}var o=this.p.subtract(A.ONE),h=this.q.subtract(A.ONE),a=o.multiply(h);if(0==a.gcd(n).compareTo(A.ONE)){this.n=this.p.multiply(this.q),this.d=n.modInverse(a),this.dmp1=this.d.mod(o),this.dmq1=this.d.mod(h),this.coeff=this.q.modInverse(this.p);break}}},t.prototype.decrypt=function(t){var e=L(t,16),i=this.doPrivate(e);return null==i?null:function(t,e){for(var i=t.toByteArray(),r=0;r=i.length)return null;for(var n="";++r191&&s<224?(n+=String.fromCharCode((31&s)<<6|63&i[r+1]),++r):(n+=String.fromCharCode((15&s)<<12|(63&i[r+1])<<6|63&i[r+2]),r+=2)}return n}(i,this.n.bitLength()+7>>3)},t.prototype.generateAsync=function(t,e,i){var r=new W,n=t>>1;this.e=parseInt(e,16);var s=new A(e,16),o=this,h=function(){var e=function(){if(o.p.compareTo(o.q)<=0){var t=o.p;o.p=o.q,o.q=t}var e=o.p.subtract(A.ONE),r=o.q.subtract(A.ONE),n=e.multiply(r);0==n.gcd(s).compareTo(A.ONE)?(o.n=o.p.multiply(o.q),o.d=s.modInverse(n),o.dmp1=o.d.mod(e),o.dmq1=o.d.mod(r),o.coeff=o.q.modInverse(o.p),setTimeout((function(){i()}),0)):setTimeout(h,0)},a=function(){o.q=M(),o.q.fromNumberAsync(n,1,r,(function(){o.q.subtract(A.ONE).gcda(s,(function(t){0==t.compareTo(A.ONE)&&o.q.isProbablePrime(10)?setTimeout(e,0):setTimeout(a,0)}))}))},u=function(){o.p=M(),o.p.fromNumberAsync(t-n,1,r,(function(){o.p.subtract(A.ONE).gcda(s,(function(t){0==t.compareTo(A.ONE)&&o.p.isProbablePrime(10)?setTimeout(a,0):setTimeout(u,0)}))}))};setTimeout(u,0)};setTimeout(h,0)},t.prototype.sign=function(t,e,i){var r=function(t,e){if(e15)throw"ASN.1 length too long to represent by 8x: n = "+t.toString(16);return(128+i).toString(16)+e},this.getEncodedHex=function(){return(null==this.hTLV||this.isModified)&&(this.hV=this.getFreshValueHex(),this.hL=this.getLengthHexFromValue(),this.hTLV=this.hT+this.hL+this.hV,this.isModified=!1),this.hTLV},this.getValueHex=function(){return this.getEncodedHex(),this.hV},this.getFreshValueHex=function(){return""}},rt.asn1.DERAbstractString=function(t){rt.asn1.DERAbstractString.superclass.constructor.call(this),this.getString=function(){return this.s},this.setString=function(t){this.hTLV=null,this.isModified=!0,this.s=t,this.hV=stohex(this.s)},this.setStringHex=function(t){this.hTLV=null,this.isModified=!0,this.s=null,this.hV=t},this.getFreshValueHex=function(){return this.hV},void 0!==t&&("string"==typeof t?this.setString(t):void 0!==t.str?this.setString(t.str):void 0!==t.hex&&this.setStringHex(t.hex))},it.lang.extend(rt.asn1.DERAbstractString,rt.asn1.ASN1Object),rt.asn1.DERAbstractTime=function(t){rt.asn1.DERAbstractTime.superclass.constructor.call(this),this.localDateToUTC=function(t){return utc=t.getTime()+6e4*t.getTimezoneOffset(),new Date(utc)},this.formatDate=function(t,e,i){var r=this.zeroPadding,n=this.localDateToUTC(t),s=String(n.getFullYear());"utc"==e&&(s=s.substr(2,2));var o=s+r(String(n.getMonth()+1),2)+r(String(n.getDate()),2)+r(String(n.getHours()),2)+r(String(n.getMinutes()),2)+r(String(n.getSeconds()),2);if(!0===i){var h=n.getMilliseconds();if(0!=h){var a=r(String(h),3);o=o+"."+(a=a.replace(/[0]+$/,""))}}return o+"Z"},this.zeroPadding=function(t,e){return t.length>=e?t:new Array(e-t.length+1).join("0")+t},this.getString=function(){return this.s},this.setString=function(t){this.hTLV=null,this.isModified=!0,this.s=t,this.hV=stohex(t)},this.setByDateValue=function(t,e,i,r,n,s){var o=new Date(Date.UTC(t,e-1,i,r,n,s,0));this.setByDate(o)},this.getFreshValueHex=function(){return this.hV}},it.lang.extend(rt.asn1.DERAbstractTime,rt.asn1.ASN1Object),rt.asn1.DERAbstractStructured=function(t){rt.asn1.DERAbstractString.superclass.constructor.call(this),this.setByASN1ObjectArray=function(t){this.hTLV=null,this.isModified=!0,this.asn1Array=t},this.appendASN1Object=function(t){this.hTLV=null,this.isModified=!0,this.asn1Array.push(t)},this.asn1Array=new Array,void 0!==t&&void 0!==t.array&&(this.asn1Array=t.array)},it.lang.extend(rt.asn1.DERAbstractStructured,rt.asn1.ASN1Object),rt.asn1.DERBoolean=function(){rt.asn1.DERBoolean.superclass.constructor.call(this),this.hT="01",this.hTLV="0101ff"},it.lang.extend(rt.asn1.DERBoolean,rt.asn1.ASN1Object),rt.asn1.DERInteger=function(t){rt.asn1.DERInteger.superclass.constructor.call(this),this.hT="02",this.setByBigInteger=function(t){this.hTLV=null,this.isModified=!0,this.hV=rt.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t)},this.setByInteger=function(t){var e=new A(String(t),10);this.setByBigInteger(e)},this.setValueHex=function(t){this.hV=t},this.getFreshValueHex=function(){return this.hV},void 0!==t&&(void 0!==t.bigint?this.setByBigInteger(t.bigint):void 0!==t.int?this.setByInteger(t.int):"number"==typeof t?this.setByInteger(t):void 0!==t.hex&&this.setValueHex(t.hex))},it.lang.extend(rt.asn1.DERInteger,rt.asn1.ASN1Object),rt.asn1.DERBitString=function(t){if(void 0!==t&&void 0!==t.obj){var e=rt.asn1.ASN1Util.newObject(t.obj);t.hex="00"+e.getEncodedHex()}rt.asn1.DERBitString.superclass.constructor.call(this),this.hT="03",this.setHexValueIncludingUnusedBits=function(t){this.hTLV=null,this.isModified=!0,this.hV=t},this.setUnusedBitsAndHexValue=function(t,e){if(t<0||7=2?(n[n.length]=s,s=0,o=0):s<<=4}}if(o)throw new Error("Hex encoding incomplete: 4 bits missing");return n}(t):v.unarmor(t),n=x.decode(r);if(3===n.sub.length&&(n=n.sub[2].sub[0]),9===n.sub.length){e=n.sub[1].getHexStringValue(),this.n=L(e,16),i=n.sub[2].getHexStringValue(),this.e=parseInt(i,16);var s=n.sub[3].getHexStringValue();this.d=L(s,16);var o=n.sub[4].getHexStringValue();this.p=L(o,16);var h=n.sub[5].getHexStringValue();this.q=L(h,16);var a=n.sub[6].getHexStringValue();this.dmp1=L(a,16);var u=n.sub[7].getHexStringValue();this.dmq1=L(u,16);var f=n.sub[8].getHexStringValue();this.coeff=L(f,16)}else{if(2!==n.sub.length)return!1;if(n.sub[0].sub){var l=n.sub[1].sub[0];e=l.sub[0].getHexStringValue(),this.n=L(e,16),i=l.sub[1].getHexStringValue(),this.e=parseInt(i,16)}else e=n.sub[0].getHexStringValue(),this.n=L(e,16),i=n.sub[1].getHexStringValue(),this.e=parseInt(i,16)}return!0}catch(t){return!1}},e.prototype.getPrivateBaseKey=function(){var t={array:[new rt.asn1.DERInteger({int:0}),new rt.asn1.DERInteger({bigint:this.n}),new rt.asn1.DERInteger({int:this.e}),new rt.asn1.DERInteger({bigint:this.d}),new rt.asn1.DERInteger({bigint:this.p}),new rt.asn1.DERInteger({bigint:this.q}),new rt.asn1.DERInteger({bigint:this.dmp1}),new rt.asn1.DERInteger({bigint:this.dmq1}),new rt.asn1.DERInteger({bigint:this.coeff})]};return new rt.asn1.DERSequence(t).getEncodedHex()},e.prototype.getPrivateBaseKeyB64=function(){return p(this.getPrivateBaseKey())},e.prototype.getPublicBaseKey=function(){var t=new rt.asn1.DERSequence({array:[new rt.asn1.DERObjectIdentifier({oid:"1.2.840.113549.1.1.1"}),new rt.asn1.DERNull]}),e=new rt.asn1.DERSequence({array:[new rt.asn1.DERInteger({bigint:this.n}),new rt.asn1.DERInteger({int:this.e})]}),i=new rt.asn1.DERBitString({hex:"00"+e.getEncodedHex()});return new rt.asn1.DERSequence({array:[t,i]}).getEncodedHex()},e.prototype.getPublicBaseKeyB64=function(){return p(this.getPublicBaseKey())},e.wordwrap=function(t,e){if(!t)return t;var i="(.{1,"+(e=e||64)+"})( +|$\n?)|(.{1,"+e+"})";return t.match(RegExp(i,"g")).join("\n")},e.prototype.getPrivateKey=function(){var t="-----BEGIN RSA PRIVATE KEY-----\n";return(t+=e.wordwrap(this.getPrivateBaseKeyB64())+"\n")+"-----END RSA PRIVATE KEY-----"},e.prototype.getPublicKey=function(){var t="-----BEGIN PUBLIC KEY-----\n";return(t+=e.wordwrap(this.getPublicBaseKeyB64())+"\n")+"-----END PUBLIC KEY-----"},e.hasPublicKeyProperty=function(t){return(t=t||{}).hasOwnProperty("n")&&t.hasOwnProperty("e")},e.hasPrivateKeyProperty=function(t){return(t=t||{}).hasOwnProperty("n")&&t.hasOwnProperty("e")&&t.hasOwnProperty("d")&&t.hasOwnProperty("p")&&t.hasOwnProperty("q")&&t.hasOwnProperty("dmp1")&&t.hasOwnProperty("dmq1")&&t.hasOwnProperty("coeff")},e.prototype.parsePropertiesFrom=function(t){this.n=t.n,this.e=t.e,t.hasOwnProperty("d")&&(this.d=t.d,this.p=t.p,this.q=t.q,this.dmp1=t.dmp1,this.dmq1=t.dmq1,this.coeff=t.coeff)},e}(tt),at=i(155),ut=void 0!==at?null===(st=at.env)||void 0===st?void 0:"3.3.2":void 0;const ct=function(){function t(t){void 0===t&&(t={}),t=t||{},this.default_key_size=t.default_key_size?parseInt(t.default_key_size,10):1024,this.default_public_exponent=t.default_public_exponent||"010001",this.log=t.log||!1,this.key=null}return t.prototype.setKey=function(t){this.log&&this.key&&console.warn("A key was already set, overriding existing."),this.key=new ht(t)},t.prototype.setPrivateKey=function(t){this.setKey(t)},t.prototype.setPublicKey=function(t){this.setKey(t)},t.prototype.decrypt=function(t){try{return this.getKey().decrypt(g(t))}catch(t){return!1}},t.prototype.encrypt=function(t){try{return p(this.getKey().encrypt(t))}catch(t){return!1}},t.prototype.sign=function(t,e,i){try{return p(this.getKey().sign(t,e,i))}catch(t){return!1}},t.prototype.verify=function(t,e,i){try{return this.getKey().verify(t,g(e),i)}catch(t){return!1}},t.prototype.getKey=function(t){if(!this.key){if(this.key=new ht,t&&"[object Function]"==={}.toString.call(t))return void this.key.generateAsync(this.default_key_size,this.default_public_exponent,t);this.key.generate(this.default_key_size,this.default_public_exponent)}return this.key},t.prototype.getPrivateKey=function(){return this.getKey().getPrivateKey()},t.prototype.getPrivateKeyB64=function(){return this.getKey().getPrivateBaseKeyB64()},t.prototype.getPublicKey=function(){return this.getKey().getPublicKey()},t.prototype.getPublicKeyB64=function(){return this.getKey().getPublicBaseKeyB64()},t.version=ut,t}()})(),r.default})())); \ No newline at end of file diff --git a/dash-fastapi-frontend/assets/js/jsencrypt_func.js b/dash-fastapi-frontend/assets/js/jsencrypt_func.js new file mode 100644 index 0000000000000000000000000000000000000000..c39e47d2ba19cf7049d0c275dde21259dd06738b --- /dev/null +++ b/dash-fastapi-frontend/assets/js/jsencrypt_func.js @@ -0,0 +1,28 @@ +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' + +const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + + 'UP8iWi1Qw0Y=' + +// 加密 +function encrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对数据进行加密 +} + +// 解密 +function decrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) // 设置私钥 + return encryptor.decrypt(txt) // 对数据进行解密 +} + diff --git a/dash-fastapi-frontend/assets/js/utils.js b/dash-fastapi-frontend/assets/js/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..9cbad226762fa1e3067ac3d483277276f29ef8d0 --- /dev/null +++ b/dash-fastapi-frontend/assets/js/utils.js @@ -0,0 +1,47 @@ +function findKeyByPathname(array, href) { + for (let item of array) { + if (item?.props?.href === href) { + return item.props?.key; + } + if (item?.children && Array.isArray(item?.children)) { + const result = findKeyByPathname(item.children, href); + if (result) { + return result; + } + } + } + return null; +} + + +function findByKey(array, key) { + for (let item of array) { + if (item?.props?.key === key) { + return item; + } + if (item?.children && Array.isArray(item?.children)) { + const result = findByKey(item.children, key); + if (result) { + return result; + } + } + } + return null; +} + + +function findKeyPath(array, key, path = []) { + for (let item of array) { + let currentPath = [...path, item.props.key]; + if (item?.props?.key === key) { + return currentPath; + } + if (item?.children && Array.isArray(item?.children)) { + const result = findKeyPath(item.children, key, currentPath); + if (result) { + return result; + } + } + } + return null; +} \ No newline at end of file diff --git a/dash-fastapi-frontend/callbacks/app_c.py b/dash-fastapi-frontend/callbacks/app_c.py index be94265f0957fa09d6374df9e389405cef43509f..9c09674ffc997921fa2a2e8e48687bdd5ac7ff37 100644 --- a/dash-fastapi-frontend/callbacks/app_c.py +++ b/dash-fastapi-frontend/callbacks/app_c.py @@ -1,82 +1,60 @@ -import dash from dash import dcc -import feffery_utils_components as fuc +from dash.exceptions import PreventUpdate from dash.dependencies import Input, Output, State from flask import session from server import app -from api.config import query_config_list_api - - -# api拦截器——根据api返回编码确定是否强制退出 -@app.callback( - [Output('token-invalid-modal', 'visible'), - Output('global-notification-container', 'children', allow_duplicate=True)], - Input('api-check-token', 'data'), - prevent_initial_call=True -) -def check_api_response(data): - - if session.get('code') == 401 and 'token' in session.get('message'): - return [True, fuc.FefferyFancyNotification(session.get('message'), type='error', autoClose=2000)] - - elif session.get('code') == 200: - return [dash.no_update, dash.no_update] - - else: - return [dash.no_update, fuc.FefferyFancyNotification(session.get('message'), type='warning', autoClose=2000)] +from utils.cache_util import CacheManager # api拦截器——退出登录二次确认 @app.callback( - [Output('redirect-container', 'children', allow_duplicate=True), - Output('token-container', 'data', allow_duplicate=True)], + [ + Output('redirect-container', 'children', allow_duplicate=True), + Output('token-container', 'data', allow_duplicate=True), + ], Input('token-invalid-modal', 'okCounts'), - prevent_initial_call=True + prevent_initial_call=True, ) def redirect_page(okCounts): - if okCounts: session.clear() + CacheManager.clear() - return [ - dcc.Location( - pathname='/login', - id='index-redirect' - ), - None - ] + return [dcc.Location(pathname='/login', id='index-redirect'), None] - return [dash.no_update] * 2 + raise PreventUpdate # 应用初始化主题颜色 @app.callback( Output('system-app-primary-color-container', 'data'), Input('app-mount', 'id'), - State('custom-app-primary-color-container', 'data') + State('custom-app-primary-color-container', 'data'), ) def get_primary_color(_, custom_color): - if not custom_color: - primary_color_res = query_config_list_api(config_key='sys.index.skinName') - if primary_color_res.get('code') == 200: - primary_color = primary_color_res.get('data') + # if not custom_color: + # primary_color_res = query_config_list_api(config_key='sys.index.skinName') + # if primary_color_res.get('code') == 200: + # primary_color = primary_color_res.get('data') - return primary_color + # return primary_color - return dash.no_update + raise PreventUpdate app.clientside_callback( - ''' + """ (system_color, custom_color) => { if (custom_color) { return custom_color; } return system_color; } - ''', + """, Output('app-config-provider', 'primaryColor'), - [Input('system-app-primary-color-container', 'data'), - Input('custom-app-primary-color-container', 'data')], - prevent_initial_call=True + [ + Input('system-app-primary-color-container', 'data'), + Input('custom-app-primary-color-container', 'data'), + ], + prevent_initial_call=True, ) diff --git a/dash-fastapi-frontend/callbacks/forget_c.py b/dash-fastapi-frontend/callbacks/forget_c.py index 9c3ac5f9b2577f58982c9527a44dad83ef3534b1..ec400c98a55e2a9c17b0660214d33f866236d1be 100644 --- a/dash-fastapi-frontend/callbacks/forget_c.py +++ b/dash-fastapi-frontend/callbacks/forget_c.py @@ -1,96 +1,81 @@ -import dash -from dash import dcc -import feffery_utils_components as fuc +from dash import dcc, get_asset_url, no_update from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate - +from api.forget import ForgetApi from server import app -from utils.common import validate_data_not_empty -from api.user import forget_user_pwd_api -from api.message import send_message_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager @app.callback( output=dict( - username_form_status=Output('forget-username-form-item', 'validateStatus'), - password_form_status=Output('forget-password-form-item', 'validateStatus'), - password_again_form_status=Output('forget-password-again-form-item', 'validateStatus'), - captcha_form_status=Output('forget-captcha-form-item', 'validateStatus'), + username_form_status=Output( + 'forget-username-form-item', 'validateStatus' + ), + password_form_status=Output( + 'forget-password-form-item', 'validateStatus' + ), + password_again_form_status=Output( + 'forget-password-again-form-item', 'validateStatus' + ), + captcha_form_status=Output( + 'forget-captcha-form-item', 'validateStatus' + ), username_form_help=Output('forget-username-form-item', 'help'), password_form_help=Output('forget-password-form-item', 'help'), - password_again_form_help=Output('forget-password-again-form-item', 'help'), + password_again_form_help=Output( + 'forget-password-again-form-item', 'help' + ), captcha_form_help=Output('forget-captcha-form-item', 'help'), - submit_loading=Output('forget-submit', 'loading'), - redirect_container=Output('redirect-container', 'children', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - nClicks=Input('forget-submit', 'nClicks') + redirect_container=Output( + 'redirect-container', 'children', allow_duplicate=True + ), ), + inputs=dict(nClicks=Input('forget-submit', 'nClicks')), state=dict( username=State('forget-username', 'value'), password=State('forget-password', 'value'), password_again=State('forget-password-again', 'value'), input_captcha=State('forget-input-captcha', 'value'), - session_id=State('sms_code-session_id-container', 'data') + session_id=State('sms_code-session_id-container', 'data'), ), - prevent_initial_call=True + running=[ + [Output('forget-submit', 'loading'), True, False], + [Output('forget-submit', 'children'), '保存中', '保存'], + ], + prevent_initial_call=True, ) -def forget_auth(nClicks, username, password, password_again, input_captcha, session_id): +def forget_auth( + nClicks, username, password, password_again, input_captcha, session_id +): if nClicks: - # 校验全部输入值是否不为空 - if all(validate_data_not_empty(item) for item in [username, password, password_again, input_captcha]): - + # 校验全部输入值是否不为空 + if all( + ValidateUtil.not_empty(item) + for item in [username, password, password_again, input_captcha] + ): if password == password_again: - try: - forget_params = dict(user_name=username, password=password, sms_code=input_captcha, session_id=session_id) - change_result = forget_user_pwd_api(forget_params) - if change_result.get('code') == 200: - - return dict( - username_form_status=None, - password_form_status=None, - password_again_form_status=None, - captcha_form_status=None, - username_form_help=None, - password_form_help=None, - password_again_form_help=None, - captcha_form_help=None, - submit_loading=False, - redirect_container=dcc.Location(pathname='/login', id='forget-redirect'), - global_message_container=fuc.FefferyFancyMessage(change_result.get('message'), type='success') - ) - - else: - - return dict( - username_form_status=None, - password_form_status=None, - password_again_form_status=None, - captcha_form_status=None, - username_form_help=None, - password_form_help=None, - password_again_form_help=None, - captcha_form_help=None, - submit_loading=False, - redirect_container=None, - global_message_container=fuc.FefferyFancyMessage(change_result.get('message'), type='error') - ) - except Exception as e: - - return dict( - username_form_status=None, - password_form_status=None, - password_again_form_status=None, - captcha_form_status=None, - username_form_help=None, - password_form_help=None, - password_again_form_help=None, - captcha_form_help=None, - submit_loading=False, - redirect_container=None, - global_message_container=fuc.FefferyFancyMessage(str(e), type='error') - ) + forget_params = dict( + user_name=username, + password=password, + sms_code=input_captcha, + session_id=session_id, + ) + ForgetApi.forget_password(forget_params) + MessageManager.success(content='重置成功') + return dict( + username_form_status=None, + password_form_status=None, + password_again_form_status=None, + captcha_form_status=None, + username_form_help=None, + password_form_help=None, + password_again_form_help=None, + captcha_form_help=None, + redirect_container=dcc.Location( + pathname='/login', id='forget-redirect' + ), + ) else: return dict( @@ -102,106 +87,118 @@ def forget_auth(nClicks, username, password, password_again, input_captcha, sess password_form_help='两次密码不一致', password_again_form_help='两次密码不一致', captcha_form_help=None, - submit_loading=False, redirect_container=None, - global_message_container=None ) return dict( - username_form_status=None if validate_data_not_empty(username) else 'error', - password_form_status=None if validate_data_not_empty(password) else 'error', - password_again_form_status=None if validate_data_not_empty(password_again) else 'error', - captcha_form_status=None if validate_data_not_empty(input_captcha) else 'error', - username_form_help=None if validate_data_not_empty(username) else '请输入用户名!', - password_form_help=None if validate_data_not_empty(password) else '请输入新密码!', - password_again_form_help=None if validate_data_not_empty(password_again) else '请再次输入新密码!', - captcha_form_help=None if validate_data_not_empty(input_captcha) else '请输入短信验证码!', - submit_loading=False, + username_form_status=None + if ValidateUtil.not_empty(username) + else 'error', + password_form_status=None + if ValidateUtil.not_empty(password) + else 'error', + password_again_form_status=None + if ValidateUtil.not_empty(password_again) + else 'error', + captcha_form_status=None + if ValidateUtil.not_empty(input_captcha) + else 'error', + username_form_help=None + if ValidateUtil.not_empty(username) + else '请输入用户名!', + password_form_help=None + if ValidateUtil.not_empty(password) + else '请输入新密码!', + password_again_form_help=None + if ValidateUtil.not_empty(password_again) + else '请再次输入新密码!', + captcha_form_help=None + if ValidateUtil.not_empty(input_captcha) + else '请输入短信验证码!', redirect_container=None, - global_message_container=None ) raise PreventUpdate @app.callback( - [Output('message-code-count-down', 'delay'), - Output('get-message-code', 'disabled', allow_duplicate=True), - Output('sms_code-session_id-container', 'data'), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('message-code-count-down', 'delay'), + Output('get-message-code', 'disabled', allow_duplicate=True), + Output('sms_code-session_id-container', 'data'), + ], Input('get-message-code', 'nClicks'), - [State('forget-username', 'value'), - State('sms_code-session_id-container', 'data')], - prevent_initial_call=True + [ + State('forget-username', 'value'), + State('sms_code-session_id-container', 'data'), + ], + prevent_initial_call=True, ) def message_countdown(nClicks, username, session_id): if nClicks: + if username: + send_result = ForgetApi.send_message( + dict(user_name=username, session_id=session_id) + ) + MessageManager.success(content='获取成功') + return [ + 120, + True, + send_result.get('data').get('session_id'), + ] - try: - if username: - send_result = send_message_api(dict(user_name=username, session_id=session_id)) - if send_result.get('code') == 200: - - return [ - 120, - True, - send_result.get('data').get('session_id'), - fuc.FefferyFancyMessage(send_result.get('message'), type='success') - ] - else: - - return [ - dash.no_update, - False, - dash.no_update, - fuc.FefferyFancyMessage(send_result.get('message'), type='error') - ] - - else: - return [ - dash.no_update, - False, - dash.no_update, - fuc.FefferyFancyMessage('请输入用户名', type='error') - ] - - except Exception as e: - + else: + MessageManager.error(content='请输入用户名') return [ - dash.no_update, + no_update, False, - dash.no_update, - fuc.FefferyFancyMessage(str(e), type='error') + no_update, ] - return [dash.no_update] * 4 + raise PreventUpdate + + +@app.callback( + Output('forget-page', 'style'), + Input('url-container', 'pathname'), +) +def random_forget_bg(pathname): + return { + 'height': '100vh', + 'overflow': 'auto', + 'WebkitBackgroundSize': '100% 100%', + 'backgroundSize': '100% 100', + 'backgroundImage': 'url({})'.format( + get_asset_url('imgs/background.png') + ), + } app.clientside_callback( - ''' + """ (countdown) => { if (countdown) { return true; } return false; } - ''', + """, Output('get-message-code', 'disabled', allow_duplicate=True), Input('message-code-count-down', 'countdown'), - prevent_initial_call=True + prevent_initial_call=True, ) app.clientside_callback( - ''' + """ (countdown) => { if (countdown) { - return `获取中${countdown}s` + return `${countdown}秒后重新获取` } return '获取验证码' } - ''', + """, Output('get-message-code', 'children'), Input('message-code-count-down', 'countdown'), - prevent_initial_call=True + prevent_initial_call=True, ) diff --git a/dash-fastapi-frontend/callbacks/innnerlink_c.py b/dash-fastapi-frontend/callbacks/innnerlink_c.py new file mode 100644 index 0000000000000000000000000000000000000000..786edd9a3dd658b25385981db8de099c48f56a3d --- /dev/null +++ b/dash-fastapi-frontend/callbacks/innnerlink_c.py @@ -0,0 +1,20 @@ +from dash.dependencies import Input, Output, State +from server import app + + +app.clientside_callback( + """ + (_, current_item) => { + if (current_item?.props?.modules === 'innerlink') { + return [current_item?.props?.link, true]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('innerlink-iframe', 'src'), + Output('init-iframe-interval', 'disabled'), + ], + Input('init-iframe-interval', 'n_intervals'), + State('index-side-menu', 'currentItem'), +) diff --git a/dash-fastapi-frontend/callbacks/layout_c/aside_c.py b/dash-fastapi-frontend/callbacks/layout_c/aside_c.py index 71dfe849fae3270537d15339607f1f3d68762f69..1be292881fcf0b3e03bca7fbcecb067fdb3f02be 100644 --- a/dash-fastapi-frontend/callbacks/layout_c/aside_c.py +++ b/dash-fastapi-frontend/callbacks/layout_c/aside_c.py @@ -1,19 +1,28 @@ -from dash.dependencies import Input, Output - +from dash.dependencies import Input, Output, State from server import app # url-pathname控制currentKey回调 app.clientside_callback( - ''' - (data) => { - if (data) { - return data['current_key']; + """ + (currentPathname, routerList) => { + if (currentPathname) { + let currentKey = findKeyByPathname(routerList, currentPathname); + let currentItem = findByKey(routerList, currentKey); + let currentKeyPath = findKeyPath(routerList, currentKey); + let currentItemPath = currentKeyPath?.map(item => findByKey(routerList, item)); + return [currentKey, currentKeyPath, currentItem, currentItemPath]; } - return window.dash_clientside.no_update; + throw window.dash_clientside.PreventUpdate; } - ''', - Output('index-side-menu', 'currentKey'), - Input('current-key-container', 'data'), - prevent_initial_call=True + """, + [ + Output('index-side-menu', 'currentKey'), + Output('current-key_path-store', 'data'), + Output('current-item-store', 'data'), + Output('current-item_path-store', 'data'), + ], + Input('current-pathname-container', 'data'), + State('router-list-container', 'data'), + prevent_initial_call=True, ) diff --git a/dash-fastapi-frontend/callbacks/layout_c/fold_side_menu.py b/dash-fastapi-frontend/callbacks/layout_c/fold_side_menu.py index 5096ca74a70aeeb16318bc93d035def67215acf1..e35a3b7cb034bde279fc2d1c439cedfdaf78644d 100644 --- a/dash-fastapi-frontend/callbacks/layout_c/fold_side_menu.py +++ b/dash-fastapi-frontend/callbacks/layout_c/fold_side_menu.py @@ -1,28 +1,99 @@ from dash.dependencies import Input, Output, State - from server import app # 侧边栏折叠回调 app.clientside_callback( - ''' - (nClicks, collapsed) => { + """ + (nClicks, collapsed, responsive) => { if (nClicks) { return [ - collapsed ? {'width': 210} : {'width': 60}, + collapsed ? {width: 256} : (!responsive?.sm ? {display: 'none'} : {width: 64}), !collapsed, - collapsed ? {'fontSize': '22px', 'color': 'rgb(255, 255, 255)'} : {'display': 'none'}, + collapsed ? {fontSize: '22px', color: 'rgb(255, 255, 255)'} : {display: 'none'}, collapsed ? 'antd-menu-fold' : 'antd-menu-unfold', ]; } - return window.dash_clientside.no_update; + throw window.dash_clientside.PreventUpdate; } - ''', - [Output('left-side-menu-container', 'style'), - Output('menu-collapse-sider-custom', 'collapsed'), - Output('logo-text', 'style'), - Output('fold-side-menu-icon', 'icon')], + """, + [ + Output('left-side-menu-container', 'style', allow_duplicate=True), + Output('menu-collapse-sider-custom', 'collapsed', allow_duplicate=True), + Output('logo-text', 'style', allow_duplicate=True), + Output('fold-side-menu-icon', 'icon', allow_duplicate=True), + ], Input('fold-side-menu', 'nClicks'), - State('menu-collapse-sider-custom', 'collapsed'), - prevent_initial_call=True + [ + State('menu-collapse-sider-custom', 'collapsed'), + State('responsive-layout-container', 'responsive'), + ], + prevent_initial_call=True, +) + + +# 页面响应式监听自动折叠侧边栏 +app.clientside_callback( + """ + (responsive) => { + if (!responsive?.sm) { + return [ + {display: 'none'}, + true, + {display: 'none'}, + 'antd-menu-unfold', + {display: 'none'}, + {display: 'none'}, + {display: 'none'}, + '6', + 'none', + 'none', + 'none', + ]; + } else if (!responsive?.lg) { + return [ + {width: 64}, + true, + {display: 'none'}, + 'antd-menu-unfold', + {display: 'none'}, + {display: 'none'}, + {display: 'none'}, + '12', + 'none', + 'none', + 'none', + ]; + } else { + return [ + {width: 256}, + false, + {fontSize: '22px', color: 'rgb(255, 255, 255)'}, + 'antd-menu-fold', + {}, + {}, + {}, + '1', + '21', + '6', + '3', + ]; + } + } + """, + [ + Output('left-side-menu-container', 'style', allow_duplicate=True), + Output('menu-collapse-sider-custom', 'collapsed', allow_duplicate=True), + Output('logo-text', 'style', allow_duplicate=True), + Output('fold-side-menu-icon', 'icon', allow_duplicate=True), + Output('header-breadcrumb-col', 'style'), + Output('header-search-col', 'style'), + Output('header-gitee-col', 'style'), + Output('fold-side-menu-col', 'flex'), + Output('header-breadcrumb-col', 'flex'), + Output('header-search-col', 'flex'), + Output('header-gitee-col', 'flex'), + ], + Input('responsive-layout-container', 'responsive'), + prevent_initial_call=True, ) diff --git a/dash-fastapi-frontend/callbacks/layout_c/head_c.py b/dash-fastapi-frontend/callbacks/layout_c/head_c.py index a64910c398245d734ca71088cf97f08c1b6133f3..08cd90dd549df08a3bf9f223417babdd18421e9a 100644 --- a/dash-fastapi-frontend/callbacks/layout_c/head_c.py +++ b/dash-fastapi-frontend/callbacks/layout_c/head_c.py @@ -1,16 +1,16 @@ -import dash -from dash import dcc import feffery_utils_components as fuc -from flask import session +from dash import ctx, dcc, no_update from dash.dependencies import Input, Output, State - +from dash.exceptions import PreventUpdate +from flask import session +from api.login import LoginApi from server import app -from api.login import logout_api +from utils.cache_util import CacheManager # 页首右侧个人中心选项卡回调 app.clientside_callback( - ''' + """ (nClicks, clickedKey) => { if (clickedKey == '退出登录') { return [ @@ -33,56 +33,55 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('dcc-url', 'pathname', allow_duplicate=True), - Output('logout-modal', 'visible'), - Output('layout-setting-drawer', 'visible')], + """, + [ + Output('dcc-url', 'pathname', allow_duplicate=True), + Output('logout-modal', 'visible'), + Output('layout-setting-drawer', 'visible'), + ], Input('index-header-dropdown', 'nClicks'), State('index-header-dropdown', 'clickedKey'), - prevent_initial_call=True + prevent_initial_call=True, ) # 退出登录回调 @app.callback( - [Output('redirect-container', 'children', allow_duplicate=True), - Output('token-container', 'data', allow_duplicate=True)], + [ + Output('redirect-container', 'children', allow_duplicate=True), + Output('token-container', 'data', allow_duplicate=True), + ], Input('logout-modal', 'okCounts'), - prevent_initial_call=True + prevent_initial_call=True, ) def logout_confirm(okCounts): if okCounts: - result = logout_api() + result = LoginApi.logout() if result['code'] == 200: session.clear() + CacheManager.clear() - return [ - dcc.Location( - pathname='/login', - id='index-redirect' - ), - None - ] + return [dcc.Location(pathname='/login', id='index-redirect'), None] - return [dash.no_update] * 2 + raise PreventUpdate # 全局页面重载回调 app.clientside_callback( - ''' + """ (nClicks) => { return true; } - ''', + """, Output('trigger-reload-output', 'reload', allow_duplicate=True), Input('index-reload', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 布局设置回调 app.clientside_callback( - ''' + """ (visible, custom_color) => { if (visible) { if (custom_color) { @@ -91,64 +90,57 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', + """, Output('hex-color-picker', 'color', allow_duplicate=True), Input('layout-setting-drawer', 'visible'), State('custom-app-primary-color-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) @app.callback( - [Output('selected-color-input', 'value'), - Output('selected-color-input', 'style')], + [ + Output('selected-color-input', 'value'), + Output('selected-color-input', 'style'), + ], Input('hex-color-picker', 'color'), State('selected-color-input', 'style'), - prevent_initial_call=True + prevent_initial_call=True, ) def show_selected_color(pick_color, old_style): - - return [ - pick_color, - { - **old_style, - 'background': pick_color - } - ] + return [pick_color, {**old_style, 'background': pick_color}] @app.callback( - [Output('custom-app-primary-color-container', 'data'), - Output('hex-color-picker', 'color', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - [Input('save-setting', 'nClicks'), - Input('reset-setting', 'nClicks')], - [State('selected-color-input', 'value'), - State('system-app-primary-color-container', 'data')], - prevent_initial_call=True + [ + Output('custom-app-primary-color-container', 'data'), + Output('hex-color-picker', 'color', allow_duplicate=True), + Output('global-message-container', 'children', allow_duplicate=True), + ], + [Input('save-setting', 'nClicks'), Input('reset-setting', 'nClicks')], + [ + State('selected-color-input', 'value'), + State('system-app-primary-color-container', 'data'), + ], + prevent_initial_call=True, ) -def save_rest_layout_setting(save_click, reset_click, picked_color, system_color): +def save_rest_layout_setting( + save_click, reset_click, picked_color, system_color +): if save_click or reset_click: - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id == 'save-setting': - return [ picked_color, - dash.no_update, - fuc.FefferyFancyMessage('保存成功', type='success') + no_update, + fuc.FefferyFancyMessage('保存成功', type='success'), ] elif trigger_id == 'reset-setting': - return [ None, system_color, - fuc.FefferyFancyMessage('重置成功', type='success') + fuc.FefferyFancyMessage('重置成功', type='success'), ] - return [ - dash.no_update, - dash.no_update, - dash.no_update - ] - + raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/layout_c/index_c.py b/dash-fastapi-frontend/callbacks/layout_c/index_c.py index d966cf87cba8802f9c86fd1b5db5005074bcdf67..91f75307a771f4168b662c0b8d48d8040c0b1414 100644 --- a/dash-fastapi-frontend/callbacks/layout_c/index_c.py +++ b/dash-fastapi-frontend/callbacks/layout_c/index_c.py @@ -1,29 +1,45 @@ import dash +import feffery_antd_components as fac from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import feffery_antd_components as fac +from importlib import import_module from jsonpath_ng import parse -from flask import json, session -from collections import OrderedDict - +import views # noqa: F401 from server import app -import views -from utils.tree_tool import find_title_by_key, find_modules_by_key, find_href_by_key, find_parents +from utils.router_util import RouterUtil @app.callback( - [Output('tabs-container', 'items', allow_duplicate=True), - Output('tabs-container', 'activeKey', allow_duplicate=True)], - [Input('index-side-menu', 'currentKey'), - Input('tabs-container', 'tabCloseCounts')], - [State('tabs-container', 'latestDeletePane'), - State('tabs-container', 'items'), - State('tabs-container', 'activeKey'), - State('menu-info-store-container', 'data'), - State('menu-list-store-container', 'data')], - prevent_initial_call=True + [ + Output('tabs-container', 'items', allow_duplicate=True), + Output('tabs-container', 'activeKey', allow_duplicate=True), + Output('header-breadcrumb', 'items'), + Output('index-side-menu', 'openKeys'), + ], + [ + Input('index-side-menu', 'currentKey'), + Input('tabs-container', 'tabCloseCounts'), + ], + [ + State('current-key_path-store', 'data'), + State('current-item-store', 'data'), + State('current-item_path-store', 'data'), + State('tabs-container', 'latestDeletePane'), + State('tabs-container', 'items'), + State('tabs-container', 'activeKey'), + ], + prevent_initial_call=True, ) -def handle_tab_switch_and_create(currentKey, tabCloseCounts, latestDeletePane, origin_items, activeKey, menu_info, menu_list): +def handle_tab_switch_and_create( + currentKey, + tabCloseCounts, + currentKeyPath, + currentItem, + currentItemPath, + latestDeletePane, + origin_items, + activeKey, +): """ 这个回调函数用于处理标签页子项的新建、切换及删除 具体策略: @@ -42,76 +58,86 @@ def handle_tab_switch_and_create(currentKey, tabCloseCounts, latestDeletePane, o new_items = dash.Patch() if trigger_id == 'index-side-menu': - + breadcrumb_items = [ + {'title': '首页', 'icon': 'antd-dashboard', 'href': '/'}, + ] + if currentKey == 'Index/': + pass + else: + breadcrumb_items = breadcrumb_items + [ + { + 'title': item.get('props').get('title'), + 'icon': item.get('props').get('icon'), + } + for item in currentItemPath + ] # 判断当前新选中的菜单栏项对应标签页是否已创建 if currentKey in [item['key'] for item in origin_items]: return [ dash.no_update, - currentKey + currentKey, + breadcrumb_items, + currentKeyPath, ] - if currentKey == '个人资料': - menu_title = '个人资料' - button_perms = [] - role_perms = [] - menu_modules = 'system.user.profile' - else: - menu_title = find_title_by_key(menu_info.get('menu_info'), currentKey) - button_perms = [item.get('perms') for item in menu_list.get('menu_list') if str(item.get('parent_id')) == currentKey] - role_perms = [item.get('role_key') for item in session.get('role_info')] - # 判断当前选中的菜单栏项是否存在module,如果有,则动态导入module,否则返回404页面 - menu_modules = find_modules_by_key(menu_info.get('menu_info'), currentKey) + menu_title = currentItem.get('props').get('title') + # 判断当前选中的菜单栏项是否存在module,如果有,则动态导入module,否则返回404页面 + menu_modules = currentItem.get('props').get('modules') for index, item in enumerate(origin_items): - if {'key': '关闭右侧', 'label': '关闭右侧', 'icon': 'antd-arrow-right'} not in item['contextMenu']: - item['contextMenu'].insert(-1, { - 'key': '关闭右侧', - 'label': '关闭右侧', - 'icon': 'antd-arrow-right' - }) + if { + 'key': '关闭右侧', + 'label': '关闭右侧', + 'icon': 'antd-arrow-right', + } not in item['contextMenu']: + item['contextMenu'].insert( + -1, + { + 'key': '关闭右侧', + 'label': '关闭右侧', + 'icon': 'antd-arrow-right', + }, + ) new_items[index]['contextMenu'] = item['contextMenu'] context_menu = [ - { - 'key': '刷新页面', - 'label': '刷新页面', - 'icon': 'antd-reload' - }, - { - 'key': '关闭当前', - 'label': '关闭当前', - 'icon': 'antd-close' - }, + {'key': '刷新页面', 'label': '刷新页面', 'icon': 'antd-reload'}, + {'key': '关闭当前', 'label': '关闭当前', 'icon': 'antd-close'}, { 'key': '关闭其他', 'label': '关闭其他', - 'icon': 'antd-close-circle' + 'icon': 'antd-close-circle', }, { 'key': '全部关闭', 'label': '全部关闭', - 'icon': 'antd-close-circle' - } + 'icon': 'antd-close-circle', + }, ] if len(origin_items) != 1: - context_menu.insert(-1, { - 'key': '关闭左侧', - 'label': '关闭左侧', - 'icon': 'antd-arrow-left' - }) + context_menu.insert( + -1, + { + 'key': '关闭左侧', + 'label': '关闭左侧', + 'icon': 'antd-arrow-left', + }, + ) + if RouterUtil.is_http(currentItem.get('path')): + raise PreventUpdate if menu_modules: - if menu_modules == 'link': - raise PreventUpdate - else: - # 否则追加子项返回 - # 其中若各标签页内元素类似,则推荐配合模式匹配构建交互逻辑 - new_items.append( - { - 'label': menu_title, - 'key': currentKey, - 'children': eval('views.' + menu_modules + '.render(button_perms=button_perms, role_perms=role_perms)'), - 'contextMenu': context_menu - } - ) + # 否则追加子项返回 + # 其中若各标签页内元素类似,则推荐配合模式匹配构建交互逻辑 + new_items.append( + { + 'label': menu_title, + 'key': currentKey, + 'closable': False + if currentItem.get('props').get('affix') + else True, + 'children': import_module('views.' + menu_modules).render(), + 'contextMenu': context_menu, + } + ) else: new_items.append( { @@ -121,22 +147,15 @@ def handle_tab_switch_and_create(currentKey, tabCloseCounts, latestDeletePane, o status='404', title='页面不存在', subTitle='请先配置该路由的页面', - style={ - 'paddingBottom': 0, - 'paddingTop': 0 - } + style={'paddingBottom': 0, 'paddingTop': 0}, ), - 'contextMenu': context_menu + 'contextMenu': context_menu, } ) - return [ - new_items, - currentKey - ] + return [new_items, currentKey, breadcrumb_items, currentKeyPath] elif trigger_id == 'tabs-container': - # 如果删除的是当前标签页则回到最后新增的标签页,否则保持当前标签页不变 for index, item in enumerate(origin_items): if item['key'] == latestDeletePane: @@ -144,75 +163,104 @@ def handle_tab_switch_and_create(currentKey, tabCloseCounts, latestDeletePane, o { 'key': '刷新页面', 'label': '刷新页面', - 'icon': 'antd-reload' + 'icon': 'antd-reload', }, { 'key': '关闭其他', 'label': '关闭其他', - 'icon': 'antd-close-circle' + 'icon': 'antd-close-circle', }, { 'key': '全部关闭', 'label': '全部关闭', - 'icon': 'antd-close-circle' - } + 'icon': 'antd-close-circle', + }, ] if index == 1: if len(origin_items) == 2: new_items[0]['contextMenu'] = context_menu else: - origin_items[2]['contextMenu'].remove({ - 'key': '关闭左侧', - 'label': '关闭左侧', - 'icon': 'antd-arrow-left' - }) - new_items[2]['contextMenu'] = origin_items[2]['contextMenu'] + origin_items[2]['contextMenu'].remove( + { + 'key': '关闭左侧', + 'label': '关闭左侧', + 'icon': 'antd-arrow-left', + } + ) + new_items[2]['contextMenu'] = origin_items[2][ + 'contextMenu' + ] elif index == 2: if len(origin_items) == 3: - origin_items[1]['contextMenu'].remove({ - 'key': '关闭右侧', - 'label': '关闭右侧', - 'icon': 'antd-arrow-right' - }) - new_items[1]['contextMenu'] = origin_items[1]['contextMenu'] + origin_items[1]['contextMenu'].remove( + { + 'key': '关闭右侧', + 'label': '关闭右侧', + 'icon': 'antd-arrow-right', + } + ) + new_items[1]['contextMenu'] = origin_items[1][ + 'contextMenu' + ] else: if index == len(origin_items) - 1: - new_items[index - 1]['contextMenu'] = item['contextMenu'] + new_items[index - 1]['contextMenu'] = item[ + 'contextMenu' + ] del new_items[index] break new_origin_items = [ - item for item in - origin_items if item['key'] != latestDeletePane + item for item in origin_items if item['key'] != latestDeletePane ] return [ new_items, - new_origin_items[-1]['key'] if activeKey == latestDeletePane else activeKey + new_origin_items[-1]['key'] + if activeKey == latestDeletePane + else activeKey, + dash.no_update, + dash.no_update, ] raise PreventUpdate +# 处理侧边菜单栏自动滚动到当前菜单项位置 +app.clientside_callback( + """ + (pathname) => { + + // 处理侧边菜单项滚动 + setTimeout(() => { + // 查找当前页面中name为pathname的元素 + let scrollTarget = document.getElementsByName(pathname) + if (scrollTarget.length > 0) { + // 滚动到该元素 + scrollTarget[0].scrollIntoView({ behavior: "smooth" }); + } + }, 1000) + } + """, + Input('url-container', 'pathname'), +) + + @app.callback( - [Output('tabs-container', 'items', allow_duplicate=True), - Output('tabs-container', 'activeKey', allow_duplicate=True), - Output('trigger-reload-output', 'reload', allow_duplicate=True)], + [ + Output('tabs-container', 'items', allow_duplicate=True), + Output('tabs-container', 'activeKey', allow_duplicate=True), + Output('trigger-reload-output', 'reload', allow_duplicate=True), + ], Input('tabs-container', 'clickedContextMenu'), - [State('tabs-container', 'items'), - State('tabs-container', 'activeKey')], - prevent_initial_call=True + [State('tabs-container', 'items'), State('tabs-container', 'activeKey')], + prevent_initial_call=True, ) def handle_via_context_menu(clickedContextMenu, origin_items, activeKey): """ 基于标签页标题右键菜单的额外标签页控制 """ if clickedContextMenu['menuKey'] == '刷新页面': - - return [ - dash.no_update, - dash.no_update, - True - ] + return [dash.no_update, dash.no_update, True] if '关闭' in clickedContextMenu['menuKey']: new_items = dash.Patch() @@ -223,51 +271,64 @@ def handle_via_context_menu(clickedContextMenu, origin_items, activeKey): { 'key': '刷新页面', 'label': '刷新页面', - 'icon': 'antd-reload' + 'icon': 'antd-reload', }, { 'key': '关闭其他', 'label': '关闭其他', - 'icon': 'antd-close-circle' + 'icon': 'antd-close-circle', }, { 'key': '全部关闭', 'label': '全部关闭', - 'icon': 'antd-close-circle' - } + 'icon': 'antd-close-circle', + }, ] if index == 1: if len(origin_items) == 2: new_items[0]['contextMenu'] = context_menu else: - origin_items[2]['contextMenu'].remove({ - 'key': '关闭左侧', - 'label': '关闭左侧', - 'icon': 'antd-arrow-left' - }) - new_items[2]['contextMenu'] = origin_items[2]['contextMenu'] + origin_items[2]['contextMenu'].remove( + { + 'key': '关闭左侧', + 'label': '关闭左侧', + 'icon': 'antd-arrow-left', + } + ) + new_items[2]['contextMenu'] = origin_items[2][ + 'contextMenu' + ] elif index == 2: if len(origin_items) == 3: - origin_items[1]['contextMenu'].remove({ - 'key': '关闭右侧', - 'label': '关闭右侧', - 'icon': 'antd-arrow-right' - }) - new_items[1]['contextMenu'] = origin_items[1]['contextMenu'] + origin_items[1]['contextMenu'].remove( + { + 'key': '关闭右侧', + 'label': '关闭右侧', + 'icon': 'antd-arrow-right', + } + ) + new_items[1]['contextMenu'] = origin_items[1][ + 'contextMenu' + ] else: if index == len(origin_items) - 1: - new_items[index - 1]['contextMenu'] = item['contextMenu'] + new_items[index - 1]['contextMenu'] = item[ + 'contextMenu' + ] del new_items[index] break new_origin_items = [ - item for item in - origin_items if item['key'] != clickedContextMenu['tabKey'] + item + for item in origin_items + if item['key'] != clickedContextMenu['tabKey'] ] return [ new_items, - new_origin_items[-1]['key'] if activeKey == clickedContextMenu['tabKey'] else activeKey, - dash.no_update + new_origin_items[-1]['key'] + if activeKey == clickedContextMenu['tabKey'] + else activeKey, + dash.no_update, ] elif clickedContextMenu['menuKey'] == '关闭其他': @@ -277,166 +338,108 @@ def handle_via_context_menu(clickedContextMenu, origin_items, activeKey): current_index = index for i in range(1, current_index): del new_items[1] - for j in range(current_index+1, len(origin_items)+1): + for j in range(current_index + 1, len(origin_items) + 1): del new_items[2] context_menu = [ - { - 'key': '刷新页面', - 'label': '刷新页面', - 'icon': 'antd-reload' - }, + {'key': '刷新页面', 'label': '刷新页面', 'icon': 'antd-reload'}, { 'key': '关闭其他', 'label': '关闭其他', - 'icon': 'antd-close-circle' + 'icon': 'antd-close-circle', }, { 'key': '全部关闭', 'label': '全部关闭', - 'icon': 'antd-close-circle' - } + 'icon': 'antd-close-circle', + }, ] - if clickedContextMenu['tabKey'] == '首页': + if clickedContextMenu['tabKey'] == 'Index/': new_items[0]['contextMenu'] = context_menu else: - context_menu.insert(1, { - 'key': '关闭当前', - 'label': '关闭当前', - 'icon': 'antd-close' - }) + context_menu.insert( + 1, + { + 'key': '关闭当前', + 'label': '关闭当前', + 'icon': 'antd-close', + }, + ) new_items[1]['contextMenu'] = context_menu - return [ - new_items, - clickedContextMenu['tabKey'], - dash.no_update - ] + return [new_items, clickedContextMenu['tabKey'], dash.no_update] elif clickedContextMenu['menuKey'] == '关闭左侧': current_index = 0 for index, item in enumerate(origin_items): if item['key'] == clickedContextMenu['tabKey']: current_index = index - item['contextMenu'].remove({ - 'key': '关闭左侧', - 'label': '关闭左侧', - 'icon': 'antd-arrow-left' - }) + item['contextMenu'].remove( + { + 'key': '关闭左侧', + 'label': '关闭左侧', + 'icon': 'antd-arrow-left', + } + ) new_items[index]['contextMenu'] = item['contextMenu'] break for i in range(1, current_index): del new_items[1] - return [ - new_items, - clickedContextMenu['tabKey'], - dash.no_update - ] + return [new_items, clickedContextMenu['tabKey'], dash.no_update] elif clickedContextMenu['menuKey'] == '关闭右侧': current_index = 0 for index, item in enumerate(origin_items): if item['key'] == clickedContextMenu['tabKey']: current_index = index - item['contextMenu'].remove({ - 'key': '关闭右侧', - 'label': '关闭右侧', - 'icon': 'antd-arrow-right' - }) + item['contextMenu'].remove( + { + 'key': '关闭右侧', + 'label': '关闭右侧', + 'icon': 'antd-arrow-right', + } + ) new_items[index]['contextMenu'] = item['contextMenu'] break - for i in range(current_index+1, len(origin_items)+1): - del new_items[current_index+1] + for i in range(current_index + 1, len(origin_items) + 1): + del new_items[current_index + 1] - return [ - new_items, - clickedContextMenu['tabKey'], - dash.no_update - ] + return [new_items, clickedContextMenu['tabKey'], dash.no_update] for i in range(len(origin_items)): del new_items[1] new_items[0]['contextMenu'] = [ - { - 'key': '刷新页面', - 'label': '刷新页面', - 'icon': 'antd-reload' - }, + {'key': '刷新页面', 'label': '刷新页面', 'icon': 'antd-reload'}, { 'key': '关闭其他', 'label': '关闭其他', - 'icon': 'antd-close-circle' + 'icon': 'antd-close-circle', }, { 'key': '全部关闭', 'label': '全部关闭', - 'icon': 'antd-close-circle' - } + 'icon': 'antd-close-circle', + }, ] # 否则则为全部关闭 - return [ - new_items, - '首页', - dash.no_update - ] + return [new_items, 'Index/', dash.no_update] raise PreventUpdate -# 页首面包屑和hash回调 -@app.callback( - [Output('header-breadcrumb', 'items'), - Output('dcc-url', 'pathname', allow_duplicate=True)], +# 标签页点击回调 +app.clientside_callback( + """ + (activeKey, routerList) => { + if (activeKey) { + let currentItem = findByKey(routerList, activeKey); + return currentItem?.props?.href; + } + throw window.dash_clientside.PreventUpdate; + } + """, + Output('dcc-url', 'pathname', allow_duplicate=True), Input('tabs-container', 'activeKey'), - State('menu-info-store-container', 'data'), - prevent_initial_call=True + State('router-list-container', 'data'), + prevent_initial_call=True, ) -def get_current_breadcrumbs(active_key, menu_info): - if active_key: - - if active_key == '首页': - return [ - [ - { - 'title': '首页', - 'icon': 'antd-dashboard', - 'href': '/' - }, - ], - '/' - ] - - elif active_key == '个人资料': - return [ - [ - { - 'title': '首页', - 'icon': 'antd-dashboard', - 'href': '/' - }, - { - 'title': '个人资料', - } - ], - '/user/profile' - ] - - else: - result = find_parents(menu_info.get('menu_info'), active_key) - # 去除result的重复项 - parent_info = list(OrderedDict((json.dumps(d, ensure_ascii=False), d) for d in result).values()) - if parent_info: - current_href = find_href_by_key(menu_info.get('menu_info'), active_key) - - return [ - [ - { - 'title': '首页', - 'icon': 'antd-dashboard', - 'href': '/' - }, - ] + parent_info, - current_href - ] - - raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/login_c.py b/dash-fastapi-frontend/callbacks/login_c.py index e3b2059b075e5878580d0d30951cd2f351e2abb8..2244454c915b8057a27e9bd18ad52a1e562ec556 100644 --- a/dash-fastapi-frontend/callbacks/login_c.py +++ b/dash-fastapi-frontend/callbacks/login_c.py @@ -1,174 +1,234 @@ -import dash -from dash import dcc -import feffery_utils_components as fuc +import feffery_antd_components as fac +import time +from dash import dcc, get_asset_url, no_update from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate from flask import session -import time - +from api.login import LoginApi from server import app -from utils.common import validate_data_not_empty -from api.login import login_api, get_captcha_image_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager @app.callback( output=dict( - username_form_status=Output('login-username-form-item', 'validateStatus'), - password_form_status=Output('login-password-form-item', 'validateStatus'), + username_form_status=Output( + 'login-username-form-item', 'validateStatus' + ), + password_form_status=Output( + 'login-password-form-item', 'validateStatus' + ), captcha_form_status=Output('login-captcha-form-item', 'validateStatus'), username_form_help=Output('login-username-form-item', 'help'), password_form_help=Output('login-password-form-item', 'help'), captcha_form_help=Output('login-captcha-form-item', 'help'), - image_click=Output('login-captcha-image-container', 'n_clicks'), - submit_loading=Output('login-submit', 'loading'), token=Output('token-container', 'data'), - redirect_container=Output('redirect-container', 'children', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) + redirect_container=Output( + 'redirect-container', 'children', allow_duplicate=True + ), + login_success=Output('login-success-container', 'data'), ), inputs=dict( - nClicks=Input('login-submit', 'nClicks') + button_click=Input('login-submit', 'nClicks'), + keyboard_enter_press=Input('keyboard-enter-submit', 'pressedCounts'), ), state=dict( username=State('login-username', 'value'), password=State('login-password', 'value'), input_captcha=State('login-captcha', 'value'), session_id=State('captcha_image-session_id-container', 'data'), - image_click=State('login-captcha-image-container', 'n_clicks'), - captcha_hidden=State('captcha-row-container', 'hidden') + captcha_hidden=State('captcha-row-container', 'hidden'), ), - prevent_initial_call=True + running=[ + [Output('login-submit', 'loading'), True, False], + [Output('login-submit', 'children'), '登录中', '登录'], + [Output('login-captcha-image-container', 'n_clicks'), 0, 1], + ], + prevent_initial_call=True, ) -def login_auth(nClicks, username, password, input_captcha, session_id, image_click, captcha_hidden): - if nClicks: +def login_auth( + button_click, + keyboard_enter_press, + username, + password, + input_captcha, + session_id, + captcha_hidden, +): + if button_click or keyboard_enter_press: + validate_list = [username, password, input_captcha] if captcha_hidden: - input_captcha = 'hidden' + validate_list = [username, password] # 校验全部输入值是否不为空 - if all(validate_data_not_empty(item) for item in [username, password, input_captcha]): - - try: - user_params = dict(username=username, password=password, captcha=input_captcha, session_id=session_id) - userinfo_result = login_api(user_params) - if userinfo_result['code'] == 200: - token = userinfo_result['data']['access_token'] - session['Authorization'] = token - return dict( - username_form_status=None, - password_form_status=None, - captcha_form_status=None, - username_form_help=None, - password_form_help=None, - captcha_form_help=None, - image_click=dash.no_update, - submit_loading=False, - token=token, - redirect_container=dcc.Location(pathname='/', id='login-redirect'), - global_message_container=fuc.FefferyFancyMessage('登录成功', type='success') - ) - - else: - - return dict( - username_form_status=None, - password_form_status=None, - captcha_form_status=None, - username_form_help=None, - password_form_help=None, - captcha_form_help=None, - image_click=image_click + 1, - submit_loading=False, - token=None, - redirect_container=None, - global_message_container=fuc.FefferyFancyMessage(userinfo_result.get('message'), type='error') - ) - except Exception as e: - print(e) - return dict( - username_form_status=None, - password_form_status=None, - captcha_form_status=None, - username_form_help=None, - password_form_help=None, - captcha_form_help=None, - image_click=image_click + 1, - submit_loading=False, - token=None, - redirect_container=None, - global_message_container=fuc.FefferyFancyMessage('接口异常', type='error') - ) + if all(ValidateUtil.not_empty(item) for item in validate_list): + user_params = dict( + username=username, + password=password, + code=input_captcha, + uuid=session_id, + ) + userinfo_result = LoginApi.login(user_params) + token = userinfo_result['token'] + session['Authorization'] = token + MessageManager.success(content='登录成功') + return dict( + username_form_status=None, + password_form_status=None, + captcha_form_status=None, + username_form_help=None, + password_form_help=None, + captcha_form_help=None, + token=token, + redirect_container=dcc.Location( + pathname='/', id='login-redirect' + ), + login_success={'timestamp': time.time()}, + ) return dict( - username_form_status=None if validate_data_not_empty(username) else 'error', - password_form_status=None if validate_data_not_empty(password) else 'error', - captcha_form_status=None if validate_data_not_empty(input_captcha) else 'error', - username_form_help=None if validate_data_not_empty(username) else '请输入用户名!', - password_form_help=None if validate_data_not_empty(password) else '请输入密码!', - captcha_form_help=None if validate_data_not_empty(input_captcha) else '请输入验证码!', - image_click=dash.no_update, - submit_loading=False, + username_form_status=None + if ValidateUtil.not_empty(username) + else 'error', + password_form_status=None + if ValidateUtil.not_empty(password) + else 'error', + captcha_form_status=None + if ValidateUtil.not_empty(input_captcha) + else 'error', + username_form_help=None + if ValidateUtil.not_empty(username) + else '请输入用户名!', + password_form_help=None + if ValidateUtil.not_empty(password) + else '请输入密码!', + captcha_form_help=None + if ValidateUtil.not_empty(input_captcha) + else '请输入验证码!', token=None, redirect_container=None, - global_message_container=None + login_success=None, ) return dict( - username_form_status=dash.no_update, - password_form_status=dash.no_update, - captcha_form_status=dash.no_update, - username_form_help=dash.no_update, - password_form_help=dash.no_update, - captcha_form_help=dash.no_update, - image_click=image_click + 1, - submit_loading=dash.no_update, - token=dash.no_update, - redirect_container=dash.no_update, - global_message_container=dash.no_update + username_form_status=no_update, + password_form_status=no_update, + captcha_form_status=no_update, + username_form_help=no_update, + password_form_help=no_update, + captcha_form_help=no_update, + token=no_update, + redirect_container=no_update, + login_success=None, ) @app.callback( - [Output('login-captcha-image', 'src'), - Output('captcha_image-session_id-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('captcha-row-container', 'hidden'), + Output('register-user-link-container', 'children'), + Output('forget-password-link-container', 'children'), + Output('login-captcha-image', 'src'), + Output('captcha_image-session_id-container', 'data'), + ], Input('login-captcha-image-container', 'n_clicks'), - prevent_initial_call=True + State('login-success-container', 'data'), + prevent_initial_call=True, ) -def change_login_captcha_image(captcha_click): - if captcha_click: - try: - captcha_image_info = get_captcha_image_api() - if captcha_image_info.get('code') == 200: - captcha_image = captcha_image_info.get('data').get('image') - session_id = captcha_image_info.get('data').get('session_id') +def change_login_captcha_image(captcha_click, login_success): + if captcha_click and not login_success: + captcha_image_info = LoginApi.get_code_img() + captcha_enabled = captcha_image_info.get('captcha_enabled') + forget_enabled = captcha_image_info.get('forget_enabled') + register_enabled = captcha_image_info.get('register_enabled') + captcha_image = f"data:image/gif;base64,{captcha_image_info.get('img')}" + session_id = captcha_image_info.get('uuid') - return [ - captcha_image, - session_id, - dash.no_update, - dash.no_update - ] - else: - return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()}, - dash.no_update - ] - except Exception as e: + return [ + not captcha_enabled, + fac.AntdButton( + '注册', + id='register-user-link', + href='/register', + target='_self', + block=True, + size='large', + ) + if register_enabled + else [], + fac.AntdButton( + '忘记密码?', + id='forget-password-link', + type='link', + href='/forget', + target='_self', + style={'padding': 0}, + ) + if forget_enabled + else [], + captcha_image, + session_id, + ] - return [dash.no_update, dash.no_update, {'timestamp': time.time()}, fuc.FefferyFancyMessage('接口异常', type='error')] - - return [dash.no_update] * 4 + raise PreventUpdate @app.callback( - Output('container', 'style'), + Output('login-page', 'style'), Input('url-container', 'pathname'), - State('container', 'style') ) -def random_bg(pathname, old_style): +def random_login_bg(pathname): return { - **old_style, - 'backgroundImage': 'url({})'.format(dash.get_asset_url('imgs/login-background.jpg')), - 'backgroundRepeat': 'no-repeat', - 'backgroundSize': 'cover' + 'backgroundImage': 'url({})'.format( + get_asset_url('imgs/background.png') + ), } + + +app.clientside_callback( + """ + (_) => { + let username = Cookies.get("username"); + let password = Cookies.get("password"); + let remember = Cookies.get("remember"); + return [ + remember === undefined ? false : Boolean(remember), + username === undefined ? '' : username, + password === undefined ? '' : decrypt(password) + ]; + } + """, + [ + Output('login-remember-me', 'checked'), + Output('login-username', 'value'), + Output('login-password', 'value'), + ], + Input('login-page', 'id'), +) + + +app.clientside_callback( + """ + (login_success, remember, username, password) => { + if (login_success) { + if (remember) { + Cookies.set("username", username, { expires: 30 }); + Cookies.set("password", encrypt(password), { expires: 30 }); + Cookies.set("remember", remember, { expires: 30 }); + } else { + Cookies.remove("username"); + Cookies.remove("password"); + Cookies.remove("remember"); + } + } else { + throw window.dash_clientside.PreventUpdate; + } + } + """, + Input('login-success-container', 'data'), + [ + State('login-remember-me', 'checked'), + State('login-username', 'value'), + State('login-password', 'value'), + ], + prevent_initial_call=True, +) diff --git a/dash-fastapi-frontend/callbacks/monitor_c/cache_c/control_c.py b/dash-fastapi-frontend/callbacks/monitor_c/cache_c/control_c.py index d73509237ac95c4d68f3bb7a5aa1e232ec9d2351..11e97af0f20449be1e9e64d49d9a9516189b50f8 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/cache_c/control_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/cache_c/control_c.py @@ -1,5 +1,4 @@ -from dash.dependencies import Input, Output, State, ClientsideFunction - +from dash.dependencies import ClientsideFunction, Input, Output, State from server import app diff --git a/dash-fastapi-frontend/callbacks/monitor_c/cache_c/list_c.py b/dash-fastapi-frontend/callbacks/monitor_c/cache_c/list_c.py index c701177243011170a3d09bad5c0b63403e5f9eb3..32cfefbec93874ff2dc461e163b4206c25127c94 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/cache_c/list_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/cache_c/list_c.py @@ -1,42 +1,39 @@ -import dash -import time import uuid -from dash.dependencies import Input, Output, State, ALL +from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.monitor.cache import CacheApi from server import app -from api.cache import get_cache_name_list_api, get_cache_key_list_api, get_cache_value_api, clear_cache_name_api, clear_cache_key_api, clear_all_cache_api +from utils.feedback_util import MessageManager @app.callback( - [Output('cache_name-list-table', 'data'), - Output('cache_name-list-table', 'key'), - Output('api-check-token', 'data', allow_duplicate=True)], + [ + Output('cache_name-list-table', 'data'), + Output('cache_name-list-table', 'key'), + ], Input('refresh-cache_name', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) def get_cache_name_list(refresh_click): """ 刷新键名列表回调 """ if refresh_click: - - cache_name_res = get_cache_name_list_api() - if cache_name_res.get('code') == 200: - cache_name_list = cache_name_res.get('data') - cache_name_data = [{'key': item.get('cache_name'), 'id': index + 1, 'operation': {'type': 'link', 'icon': 'antd-delete'}, **item} for index, item in enumerate(cache_name_list)] - - return [ - cache_name_data, - str(uuid.uuid4()), - {'timestamp': time.time()} - ] + cache_name_res = CacheApi.list_cache_name() + cache_name_list = cache_name_res.get('data') + cache_name_data = [ + { + 'key': item.get('cache_name'), + 'id': index + 1, + 'operation': {'type': 'link', 'icon': 'antd-delete'}, + **item, + } + for index, item in enumerate(cache_name_list) + ] return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()} + cache_name_data, + str(uuid.uuid4()), ] raise PreventUpdate @@ -47,42 +44,51 @@ def get_cache_name_list(refresh_click): cache_key_table_data=Output('cache_key-list-table', 'data'), cache_key_table_key=Output('cache_key-list-table', 'key'), cache_name_store=Output('current-cache_name-store', 'data'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( - cache_name_table_row_click=Input('cache_name-list-table', 'nClicksCell'), + cache_name_table_row_click=Input( + 'cache_name-list-table', 'nClicksCell' + ), cache_key_refresh_click=Input('refresh-cache_key', 'nClicks'), - operations=Input('cache_list-operations-store', 'data') + operations=Input('cache_list-operations-store', 'data'), ), state=dict( - cache_name_table_click_row_record=State('cache_name-list-table', 'recentlyCellClickRecord'), + cache_name_table_click_row_record=State( + 'cache_name-list-table', 'recentlyCellClickRecord' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_cache_key_list(cache_name_table_row_click, cache_key_refresh_click, operations, cache_name_table_click_row_record): +def get_cache_key_list( + cache_name_table_row_click, + cache_key_refresh_click, + operations, + cache_name_table_click_row_record, +): """ 获取键名列表回调 """ - if cache_name_table_row_click or cache_key_refresh_click or operations: - - cache_key_res = get_cache_key_list_api(cache_name=cache_name_table_click_row_record.get('key')) - if cache_key_res.get('code') == 200: - cache_key_list = cache_key_res.get('data') - cache_key_data = [ - {'key': item, 'id': index + 1, 'cache_key': item, 'operation': {'type': 'link', 'icon': 'antd-delete'}} for index, item in enumerate(cache_key_list)] - - return dict( - cache_key_table_data=cache_key_data, - cache_key_table_key=str(uuid.uuid4()), - cache_name_store=cache_name_table_click_row_record.get('key'), - api_check_token_trigger={'timestamp': time.time()} - ) + if cache_name_table_click_row_record and ( + cache_name_table_row_click or cache_key_refresh_click or operations + ): + cache_key_res = CacheApi.list_cache_key( + cache_name=cache_name_table_click_row_record.get('key') + ) + cache_key_list = cache_key_res.get('data') + cache_key_data = [ + { + 'key': item, + 'id': index + 1, + 'cache_key': item, + 'operation': {'type': 'link', 'icon': 'antd-delete'}, + } + for index, item in enumerate(cache_key_list) + ] return dict( - cache_key_table_data=dash.no_update, - cache_key_table_key=dash.no_update, - cache_name_store=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + cache_key_table_data=cache_key_data, + cache_key_table_key=str(uuid.uuid4()), + cache_name_store=cache_name_table_click_row_record.get('key'), ) raise PreventUpdate @@ -94,73 +100,60 @@ def get_cache_key_list(cache_name_table_row_click, cache_key_refresh_click, oper cache_key=Output('cache_key-input', 'value', allow_duplicate=True), cache_value=Output('cache_value-input', 'value', allow_duplicate=True), cache_key_store=Output('current-cache_key-store', 'data'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( cache_key_table_row_click=Input('cache_key-list-table', 'nClicksCell') ), state=dict( - cache_key_table_click_row_record=State('cache_key-list-table', 'recentlyCellClickRecord'), + cache_key_table_click_row_record=State( + 'cache_key-list-table', 'recentlyCellClickRecord' + ), cache_name_store=State('current-cache_name-store', 'data'), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_cache_value(cache_key_table_row_click, cache_key_table_click_row_record, cache_name_store): +def get_cache_value( + cache_key_table_row_click, + cache_key_table_click_row_record, + cache_name_store, +): """ 获取缓存内容回调 """ if cache_key_table_row_click: - - cache_value_res = get_cache_value_api(cache_name=cache_name_store, cache_key=cache_key_table_click_row_record.get('key')) - if cache_value_res.get('code') == 200: - cache = cache_value_res.get('data') - - return dict( - cache_name=cache.get('cache_name'), - cache_key=cache.get('cache_key'), - cache_value=cache.get('cache_value'), - cache_key_store=cache_key_table_click_row_record.get('key'), - api_check_token_trigger={'timestamp': time.time()} - ) + cache_value_res = CacheApi.get_cache_value( + cache_name=cache_name_store, + cache_key=cache_key_table_click_row_record.get('key'), + ) + cache = cache_value_res.get('data') return dict( - cache_name=dash.no_update, - cache_key=dash.no_update, - cache_value=dash.no_update, - cache_key_store=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + cache_name=cache.get('cache_name'), + cache_key=cache.get('cache_key'), + cache_value=cache.get('cache_value'), + cache_key_store=cache_key_table_click_row_record.get('key'), ) raise PreventUpdate @app.callback( - [Output('cache_list-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('cache_list-operations-store', 'data', allow_duplicate=True), Input('cache_name-list-table', 'nClicksButton'), State('cache_name-list-table', 'recentlyButtonClickedRow'), - prevent_initial_call=True + prevent_initial_call=True, ) def clear_cache_name(clear_click, recently_button_clicked_row): """ 缓存列表表格内部清除缓存回调 """ if clear_click: - clear_cache_name_res = clear_cache_name_api(cache_name=recently_button_clicked_row.get('key')) - if clear_cache_name_res.get('code') == 200: + CacheApi.clear_cache_name( + cache_name=recently_button_clicked_row.get('key') + ) + MessageManager.success(content='清除成功') - return [ - {'type': 'clear_cache_name'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage(clear_cache_name_res.get('message'), type='success') - ] - - return [ - {'type': 'clear_cache_name'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage(clear_cache_name_res.get('message'), type='error') - ] + return {'type': 'clear_cache_name'} raise PreventUpdate @@ -170,53 +163,34 @@ def clear_cache_name(clear_click, recently_button_clicked_row): cache_name=Output('cache_name-input', 'value', allow_duplicate=True), cache_key=Output('cache_key-input', 'value', allow_duplicate=True), cache_value=Output('cache_value-input', 'value', allow_duplicate=True), - operations=Output('cache_list-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - clear_click=Input('cache_key-list-table', 'nClicksButton') + operations=Output( + 'cache_list-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(clear_click=Input('cache_key-list-table', 'nClicksButton')), state=dict( - recently_button_clicked_row=State('cache_key-list-table', 'recentlyButtonClickedRow'), - cache_name_store=State('current-cache_name-store', 'data'), - cache_key_store=State('current-cache_key-store', 'data') + recently_button_clicked_row=State( + 'cache_key-list-table', 'recentlyButtonClickedRow' + ), + cache_key_store=State('current-cache_key-store', 'data'), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def clear_cache_key(clear_click, recently_button_clicked_row, cache_name_store, cache_key_store): +def clear_cache_key(clear_click, recently_button_clicked_row, cache_key_store): """ 键名列表表格内部清除键名回调 """ if clear_click: - clear_cache_key_res = clear_cache_key_api(cache_name=cache_name_store, cache_key=recently_button_clicked_row.get('key')) - if clear_cache_key_res.get('code') == 200: - if cache_key_store == recently_button_clicked_row.get('key'): - return dict( - cache_name=None, - cache_key=None, - cache_value=None, - operations={'type': 'clear_cache_key'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage(clear_cache_key_res.get('message'), type='success') - ) - else: - return dict( - cache_name=dash.no_update, - cache_key=dash.no_update, - cache_value=dash.no_update, - operations={'type': 'clear_cache_key'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage(clear_cache_key_res.get('message'), type='success') - ) + CacheApi.clear_cache_key( + cache_key=recently_button_clicked_row.get('key'), + ) + MessageManager.success(content='清除成功') return dict( - cache_name=dash.no_update, - cache_key=dash.no_update, - cache_value=dash.no_update, + cache_name=None, + cache_key=None, + cache_value=None, operations={'type': 'clear_cache_key'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage(clear_cache_key_res.get('message'), type='error') ) raise PreventUpdate @@ -229,43 +203,34 @@ def clear_cache_key(clear_click, recently_button_clicked_row, cache_name_store, cache_value=Output('cache_value-input', 'value', allow_duplicate=True), refresh_cache_name=Output('refresh-cache_name', 'nClicks'), refresh_cache_key=Output('refresh-cache_key', 'nClicks'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - clear_all_click=Input('clear-all-cache', 'nClicks') ), + inputs=dict(clear_all_click=Input('clear-all-cache', 'nClicks')), state=dict( refresh_cache_name_click=State('refresh-cache_name', 'nClicks'), - refresh_cache_key_click=State('refresh-cache_key', 'nClicks') + refresh_cache_key_click=State('refresh-cache_key', 'nClicks'), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def clear_all_cache(clear_all_click, refresh_cache_name_click, refresh_cache_key_click): +def clear_all_cache( + clear_all_click, refresh_cache_name_click, refresh_cache_key_click +): """ 清除所有缓存回调 """ if clear_all_click: - clear_all_cache_res = clear_all_cache_api() - if clear_all_cache_res.get('code') == 200: - return dict( - cache_name=None, - cache_key=None, - cache_value=None, - refresh_cache_name=refresh_cache_name_click + 1 if refresh_cache_name_click else 1, - refresh_cache_key=refresh_cache_key_click + 1 if refresh_cache_key_click else 1, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage(clear_all_cache_res.get('message'), type='success') - ) + CacheApi.clear_cache_all() + MessageManager.success(content='清除成功') return dict( - cache_name=dash.no_update, - cache_key=dash.no_update, - cache_value=dash.no_update, - refresh_cache_name=dash.no_update, - refresh_cache_key=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage(clear_all_cache_res.get('message'), type='error') + cache_name=None, + cache_key=None, + cache_value=None, + refresh_cache_name=refresh_cache_name_click + 1 + if refresh_cache_name_click + else 1, + refresh_cache_key=refresh_cache_key_click + 1 + if refresh_cache_key_click + else 1, ) raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_c.py b/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_c.py index 24210133a749eecdd9a959aefbe458390b69448b..33702a81755a4bc6096719dd38503fedfd66fc81 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_c.py @@ -1,42 +1,97 @@ -import dash import time import uuid -import json -from dash import dcc -from dash.dependencies import Input, Output, State, ALL +from dash import ctx, dcc, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.monitor.job import JobApi +from config.constant import SysJobStatusConstant from server import app -from utils.common import validate_data_not_empty -from api.job import get_job_list_api, get_job_detail_api, add_job_api, edit_job_api, execute_job_api, delete_job_api, export_job_list_api -from api.dict import query_dict_data_list_api +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil + + +def generate_job_table(query_params: Dict): + """ + 根据查询参数获取定时任务表格数据及分页信息 + + :param query_params: 查询参数 + :return: 定时任务表格数据及分页信息 + """ + table_info = JobApi.list_job(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + if item['status'] == SysJobStatusConstant.NORMAL: + item['status_checked'] = dict(checked=True) + else: + item['status_checked'] = dict(checked=False) + item['job_group_tag'] = DictManager.get_dict_tag( + dict_type='sys_job_group', dict_value=item.get('job_group') + ) + item['key'] = str(item['job_id']) + item['operation'] = [ + {'title': '修改', 'icon': 'antd-edit'} + if PermissionManager.check_perms('monitor:job:edit') + else None, + {'title': '删除', 'icon': 'antd-delete'} + if PermissionManager.check_perms('monitor:job:remove') + else None, + {'title': '执行一次', 'icon': 'antd-rocket'} + if PermissionManager.check_perms('monitor:job:changeStatus') + else None, + {'title': '任务详细', 'icon': 'antd-eye'} + if PermissionManager.check_perms('monitor:job:query') + else None, + {'title': '调度日志', 'icon': 'antd-history'} + if PermissionManager.check_perms('monitor:job:query') + else None, + ] + + return [table_data, table_pagination] @app.callback( output=dict( job_table_data=Output('job-list-table', 'data', allow_duplicate=True), - job_table_pagination=Output('job-list-table', 'pagination', allow_duplicate=True), + job_table_pagination=Output( + 'job-list-table', 'pagination', allow_duplicate=True + ), job_table_key=Output('job-list-table', 'key'), job_table_selectedrowkeys=Output('job-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( search_click=Input('job-search', 'nClicks'), refresh_click=Input('job-refresh', 'nClicks'), pagination=Input('job-list-table', 'pagination'), - operations=Input('job-operations-store', 'data') + operations=Input('job-operations-store', 'data'), ), state=dict( job_name=State('job-job_name-input', 'value'), job_group=State('job-job_group-select', 'value'), status_select=State('job-status-select', 'value'), - button_perms=State('job-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_job_table_data(search_click, refresh_click, pagination, operations, job_name, job_group, status_select, - button_perms): +def get_job_table_data( + search_click, + refresh_click, + pagination, + operations, + job_name, + job_group, + status_select, +): """ 获取定时任务表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ @@ -45,87 +100,23 @@ def get_job_table_data(search_click, refresh_click, pagination, operations, job_ job_group=job_group, status=status_select, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'job-list-table': - query_params = dict( - job_name=job_name, - job_group=job_group, - status=status_select, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - option_table = [] - info = query_dict_data_list_api(dict_type='sys_job_group') - if info.get('code') == 200: - data = info.get('data') - option_table = [ - dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for - item - in data] - option_dict = {item.get('value'): item for item in option_table} - - table_info = get_job_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(checked=True) - else: - item['status'] = dict(checked=False) - if str(item.get('job_group')) in option_dict.keys(): - item['job_group'] = dict( - tag=option_dict.get(str(item.get('job_group'))).get('label'), - color=json.loads(option_dict.get(str(item.get('job_group'))).get('css_class')).get('color') - ) - item['key'] = str(item['job_id']) - item['operation'] = [ - { - 'title': '修改', - 'icon': 'antd-edit' - } if 'monitor:job:edit' in button_perms else None, - { - 'title': '删除', - 'icon': 'antd-delete' - } if 'monitor:job:remove' in button_perms else None, - { - 'title': '执行一次', - 'icon': 'antd-rocket' - } if 'monitor:job:changeStatus' in button_perms else None, - { - 'title': '任务详细', - 'icon': 'antd-eye' - } if 'monitor:job:query' in button_perms else None, - { - 'title': '调度日志', - 'icon': 'antd-history' - } if 'monitor:job:query' in button_perms else None - ] - - return dict( - job_table_data=table_data, - job_table_pagination=table_pagination, - job_table_key=str(uuid.uuid4()), - job_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) - + table_data, table_pagination = generate_job_table(query_params) return dict( - job_table_data=dash.no_update, - job_table_pagination=dash.no_update, - job_table_key=dash.no_update, - job_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + job_table_data=table_data, + job_table_pagination=table_pagination, + job_table_key=str(uuid.uuid4()), + job_table_selectedrowkeys=None, ) raise PreventUpdate @@ -133,26 +124,28 @@ def get_job_table_data(search_click, refresh_click, pagination, operations, job_ # 重置定时任务搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('job-job_name-input', 'value'), - Output('job-job_group-select', 'value'), - Output('job-status-select', 'value'), - Output('job-operations-store', 'data')], + """, + [ + Output('job-job_name-input', 'value'), + Output('job-job_group-select', 'value'), + Output('job-status-select', 'value'), + Output('job-operations-store', 'data'), + ], Input('job-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示定时任务搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -162,92 +155,133 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('job-search-form-container', 'hidden'), - Output('job-hidden-tooltip', 'title')], + """, + [ + Output('job-search-form-container', 'hidden'), + Output('job-hidden-tooltip', 'title'), + ], Input('job-hidden', 'nClicks'), State('job-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'job-operation-button', 'index': 'edit'}, 'disabled'), Input('job-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_job_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - - return False - return True - raise PreventUpdate - - -@app.callback( +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'job-operation-button', 'index': 'delete'}, 'disabled'), Input('job-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_job_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - return True - raise PreventUpdate +# 定时任务表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'job-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'job-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('job-form-store', 'data', allow_duplicate=True), + Output('job-form', 'values'), + ], + [ + Input('job-form-store', 'data'), + Input('job-form', 'values'), + ], + prevent_initial_call=True, +) @app.callback( output=dict( modal_visible=Output('job-modal', 'visible', allow_duplicate=True), modal_title=Output('job-modal', 'title'), - form_value=Output({'type': 'job-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'job-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'job-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('job-edit-id-store', 'data'), - modal_type=Output('job-operations-store-bk', 'data') + form_value=Output('job-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'job-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'job-form', 'helps', allow_duplicate=True + ), + modal_type=Output('job-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'job-operation-button', 'index': ALL}, 'nClicks'), - dropdown_click=Input('job-list-table', 'nClicksDropdownItem') + operation_click=Input( + {'type': 'job-operation-button', 'index': ALL}, 'nClicks' + ), + dropdown_click=Input('job-list-table', 'nClicksDropdownItem'), ), state=dict( selected_row_keys=State('job-list-table', 'selectedRowKeys'), - recently_clicked_dropdown_item_title=State('job-list-table', 'recentlyClickedDropdownItemTitle'), - recently_dropdown_item_clicked_row=State('job-list-table', 'recentlyDropdownItemClickedRow') + recently_clicked_dropdown_item_title=State( + 'job-list-table', 'recentlyClickedDropdownItemTitle' + ), + recently_dropdown_item_clicked_row=State( + 'job-list-table', 'recentlyDropdownItemClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_job_modal(operation_click, dropdown_click, selected_row_keys, recently_clicked_dropdown_item_title, - recently_dropdown_item_clicked_row): +def add_edit_job_modal( + operation_click, + dropdown_click, + selected_row_keys, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ 显示新增或编辑定时任务弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'job-operation-button'} \ - or trigger_id == {'index': 'edit', 'type': 'job-operation-button'} \ - or (trigger_id == 'job-list-table' and recently_clicked_dropdown_item_title == '修改'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'job-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'job-operation-button'} + or ( + trigger_id == 'job-list-table' + and recently_clicked_dropdown_item_title == '修改' + ) + ): if trigger_id == {'index': 'add', 'type': 'job-operation-button'}: job_info = dict( job_name=None, @@ -258,312 +292,306 @@ def add_edit_job_modal(operation_click, dropdown_click, selected_row_keys, recen job_kwargs=None, misfire_policy='1', concurrent='1', - status='0' + status=SysJobStatusConstant.NORMAL, ) return dict( modal_visible=True, modal_title='新增任务', - form_value=[job_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger=dash.no_update, - edit_row_info=None, - modal_type={'type': 'add'} + form_value=job_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, ) - elif trigger_id == {'index': 'edit', 'type': 'job-operation-button'} or (trigger_id == 'job-list-table' and recently_clicked_dropdown_item_title == '修改'): + elif trigger_id == { + 'index': 'edit', + 'type': 'job-operation-button', + } or ( + trigger_id == 'job-list-table' + and recently_clicked_dropdown_item_title == '修改' + ): if trigger_id == {'index': 'edit', 'type': 'job-operation-button'}: job_id = int(','.join(selected_row_keys)) else: job_id = int(recently_dropdown_item_clicked_row['key']) - job_info_res = get_job_detail_api(job_id=job_id) - if job_info_res['code'] == 200: - job_info = job_info_res['data'] - return dict( - modal_visible=True, - modal_title='编辑任务', - form_value=[job_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=job_info if job_info else None, - modal_type={'type': 'edit'} - ) - - return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type=None - ) + job_info_res = JobApi.get_job(job_id=job_id) + job_info = job_info_res['data'] + return dict( + modal_visible=True, + modal_title='编辑任务', + form_value=job_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, + ) raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'job-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'job-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + 'job-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'job-form', 'helps', allow_duplicate=True + ), modal_visible=Output('job-modal', 'visible'), operations=Output('job-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('job-modal', 'okCounts') ), + inputs=dict(confirm_trigger=Input('job-modal', 'okCounts')), state=dict( - modal_type=State('job-operations-store-bk', 'data'), - edit_row_info=State('job-edit-id-store', 'data'), - form_value=State({'type': 'job-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'job-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('job-modal_type-store', 'data'), + form_value=State('job-form-store', 'data'), + form_label=State( + {'type': 'job-form-label', 'index': ALL, 'required': True}, 'label' + ), ), - prevent_initial_call=True + running=[[Output('job-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def job_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label): +def job_confirm(confirm_trigger, modal_type, form_value, form_label): """ 新增或编定时任务弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params_add = form_value_state + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value params_edit = params_add.copy() - params_edit['job_id'] = edit_row_info.get('job_id') if edit_row_info else None - api_res = {} modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_job_api(params_add) + JobApi.add_job(params_add) + if modal_type == 'edit': + JobApi.update_job(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) if modal_type == 'edit': - api_res = edit_job_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + MessageManager.success(content='编辑成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('job-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - [Input('job-list-table', 'recentlySwitchDataIndex'), - Input('job-list-table', 'recentlySwitchStatus'), - Input('job-list-table', 'recentlySwitchRow')], - prevent_initial_call=True + Output('job-operations-store', 'data', allow_duplicate=True), + [ + Input('job-list-table', 'recentlySwitchDataIndex'), + Input('job-list-table', 'recentlySwitchStatus'), + Input('job-list-table', 'recentlySwitchRow'), + ], + prevent_initial_call=True, ) -def table_switch_job_status(recently_switch_data_index, recently_switch_status, recently_switch_row): +def table_switch_job_status( + recently_switch_data_index, recently_switch_status, recently_switch_row +): """ 表格内切换定时任务状态回调 """ if recently_switch_data_index: - if recently_switch_status: - params = dict(job_id=int(recently_switch_row['key']), status='0', type='status') - else: - params = dict(job_id=int(recently_switch_row['key']), status='1', type='status') - edit_button_result = edit_job_api(params) - if edit_button_result['code'] == 200: - - return [ - {'type': 'switch-status'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改成功', type='success') - ] + JobApi.change_job_status( + job_id=int(recently_switch_row['key']), + status='0' if recently_switch_status else '1', + ) + MessageManager.success(content='修改成功') - return [ - {'type': 'switch-status'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error') - ] + return {'type': 'switch-status'} raise PreventUpdate @app.callback( output=dict( - modal_visible=Output('job_detail-modal', 'visible', allow_duplicate=True), + modal_visible=Output( + 'job_detail-modal', 'visible', allow_duplicate=True + ), modal_title=Output('job_detail-modal', 'title'), - form_value=Output({'type': 'job_detail-form-value', 'index': ALL}, 'children'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - dropdown_click=Input('job-list-table', 'nClicksDropdownItem') + form_value=Output( + {'type': 'job_detail-form-value', 'index': ALL}, 'children' + ), ), + inputs=dict(dropdown_click=Input('job-list-table', 'nClicksDropdownItem')), state=dict( - recently_clicked_dropdown_item_title=State('job-list-table', 'recentlyClickedDropdownItemTitle'), - recently_dropdown_item_clicked_row=State('job-list-table', 'recentlyDropdownItemClickedRow') + recently_clicked_dropdown_item_title=State( + 'job-list-table', 'recentlyClickedDropdownItemTitle' + ), + recently_dropdown_item_clicked_row=State( + 'job-list-table', 'recentlyDropdownItemClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_job_detail_modal(dropdown_click, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row): +def get_job_detail_modal( + dropdown_click, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ 显示定时任务详情弹窗回调及执行一次定时任务回调 """ # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[-3]] + form_value_list = [x['id']['index'] for x in ctx.outputs_list[-1]] # 显示定时任务详情弹窗 if dropdown_click and recently_clicked_dropdown_item_title == '任务详细': job_id = int(recently_dropdown_item_clicked_row['key']) - job_info_res = get_job_detail_api(job_id=job_id) - if job_info_res['code'] == 200: - job_info = job_info_res['data'] - if job_info.get('misfire_policy') == '1': - job_info['misfire_policy'] = '立即执行' - elif job_info.get('misfire_policy') == '2': - job_info['misfire_policy'] = '执行一次' - else: - job_info['misfire_policy'] = '放弃执行' - job_info['concurrent'] = '是' if job_info.get('concurrent') == '0' else '否' - job_info['status'] = '正常' if job_info.get('status') == '0' else '停用' - return dict( - modal_visible=True, - modal_title='任务详情', - form_value=[job_info.get(k) for k in form_value_list], - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=None - ) - + job_info_res = JobApi.get_job(job_id=job_id) + job_info = job_info_res['data'] + if job_info.get('misfire_policy') == '1': + job_info['misfire_policy'] = '立即执行' + elif job_info.get('misfire_policy') == '2': + job_info['misfire_policy'] = '执行一次' + else: + job_info['misfire_policy'] = '放弃执行' + job_info['concurrent'] = ( + '是' if job_info.get('concurrent') == '0' else '否' + ) + job_info['job_group'] = DictManager.get_dict_label( + dict_type='sys_job_group', + dict_value=job_info.get('job_group'), + ) + job_info['job_executor'] = DictManager.get_dict_label( + dict_type='sys_job_executor', + dict_value=job_info.get('job_executor'), + ) + job_info['status'] = DictManager.get_dict_label( + dict_type='sys_job_status', + dict_value=job_info.get('status'), + ) + job_info['create_time'] = TimeFormatUtil.format_time( + job_info.get('create_time') + ) return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=None + modal_visible=True, + modal_title='任务详情', + form_value=[job_info.get(k) for k in form_value_list], ) # 执行一次定时任务 if dropdown_click and recently_clicked_dropdown_item_title == '执行一次': - job_id = int(recently_dropdown_item_clicked_row['key']) - job_info_res = execute_job_api(dict(job_id=job_id)) - if job_info_res['code'] == 200: - return dict( - modal_visible=False, - modal_title=None, - form_value=[None] * len(form_value_list), - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('执行成功', type='success') - ) + job_info_res = JobApi.run_job( + job_id=int(recently_dropdown_item_clicked_row['key']), + job_group=recently_dropdown_item_clicked_row['job_group'], + ) + MessageManager.success(content='执行成功') return dict( modal_visible=False, modal_title=None, form_value=[None] * len(form_value_list), - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('执行失败', type='error') ) raise PreventUpdate @app.callback( - [Output('job-delete-text', 'children'), - Output('job-delete-confirm-modal', 'visible'), - Output('job-delete-ids-store', 'data')], - [Input({'type': 'job-operation-button', 'index': ALL}, 'nClicks'), - Input('job-list-table', 'nClicksDropdownItem')], - [State('job-list-table', 'selectedRowKeys'), - State('job-list-table', 'recentlyClickedDropdownItemTitle'), - State('job-list-table', 'recentlyDropdownItemClickedRow')], - prevent_initial_call=True + [ + Output('job-delete-text', 'children'), + Output('job-delete-confirm-modal', 'visible'), + Output('job-delete-ids-store', 'data'), + ], + [ + Input({'type': 'job-operation-button', 'index': ALL}, 'nClicks'), + Input('job-list-table', 'nClicksDropdownItem'), + ], + [ + State('job-list-table', 'selectedRowKeys'), + State('job-list-table', 'recentlyClickedDropdownItemTitle'), + State('job-list-table', 'recentlyDropdownItemClickedRow'), + ], + prevent_initial_call=True, ) -def job_delete_modal(operation_click, dropdown_click, - selected_row_keys, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row): +def job_delete_modal( + operation_click, + dropdown_click, + selected_row_keys, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ 显示删除定时任务二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id == {'index': 'delete', 'type': 'job-operation-button'} or ( - trigger_id == 'job-list-table' and recently_clicked_dropdown_item_title == '删除'): - + trigger_id == 'job-list-table' + and recently_clicked_dropdown_item_title == '删除' + ): if trigger_id == {'index': 'delete', 'type': 'job-operation-button'}: job_ids = ','.join(selected_row_keys) else: if recently_clicked_dropdown_item_title == '删除': job_ids = recently_dropdown_item_clicked_row['key'] else: - return [dash.no_update] * 3 + return [no_update] * 3 return [ f'是否确认删除任务编号为{job_ids}的任务?', True, - {'job_ids': job_ids} + job_ids, ] raise PreventUpdate @app.callback( - [Output('job-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('job-operations-store', 'data', allow_duplicate=True), Input('job-delete-confirm-modal', 'okCounts'), State('job-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def job_delete_confirm(delete_confirm, job_ids_data): """ 删除定时任务弹窗确认回调,实现删除操作 """ if delete_confirm: - params = job_ids_data - delete_button_info = delete_job_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + JobApi.del_job(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @@ -572,86 +600,105 @@ def job_delete_confirm(delete_confirm, job_ids_data): output=dict( job_log_modal_visible=Output('job_to_job_log-modal', 'visible'), job_log_modal_title=Output('job_to_job_log-modal', 'title'), - job_log_job_name=Output('job_log-job_name-input', 'value', allow_duplicate=True), - job_log_job_group_options=Output('job_log-job_group-select', 'options'), + job_log_job_name=Output( + 'job_log-job_name-input', 'value', allow_duplicate=True + ), job_log_search_nclick=Output('job_log-search', 'nClicks'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( - operation_click=Input({'type': 'job-operation-log', 'index': ALL}, 'nClicks'), - dropdown_click=Input('job-list-table', 'nClicksDropdownItem') + operation_click=Input( + {'type': 'job-operation-log', 'index': ALL}, 'nClicks' + ), + dropdown_click=Input('job-list-table', 'nClicksDropdownItem'), ), state=dict( - recently_clicked_dropdown_item_title=State('job-list-table', 'recentlyClickedDropdownItemTitle'), - recently_dropdown_item_clicked_row=State('job-list-table', 'recentlyDropdownItemClickedRow'), - job_log_search_nclick=State('job_log-search', 'nClicks') + recently_clicked_dropdown_item_title=State( + 'job-list-table', 'recentlyClickedDropdownItemTitle' + ), + recently_dropdown_item_clicked_row=State( + 'job-list-table', 'recentlyDropdownItemClickedRow' + ), + job_log_search_nclick=State('job_log-search', 'nClicks'), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def job_to_job_log_modal(operation_click, dropdown_click, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row, job_log_search_nclick): +def job_to_job_log_modal( + operation_click, + dropdown_click, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, + job_log_search_nclick, +): """ 显示定时任务对应调度日志表格弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'log', 'type': 'job-operation-log'} or (trigger_id == 'job-list-table' and recently_clicked_dropdown_item_title == '调度日志'): - option_table = [] - info = query_dict_data_list_api(dict_type='sys_job_group') - if info.get('code') == 200: - data = info.get('data') - option_table = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in data] - - if trigger_id == 'job-list-table' and recently_clicked_dropdown_item_title == '调度日志': - return dict( - job_log_modal_visible=True, - job_log_modal_title='任务调度日志', - job_log_job_name=recently_dropdown_item_clicked_row.get('job_name'), - job_log_job_group_options=option_table, - job_log_search_nclick=job_log_search_nclick + 1 if job_log_search_nclick else 1, - api_check_token_trigger={'timestamp': time.time()} - ) + trigger_id = ctx.triggered_id + if trigger_id == {'index': 'log', 'type': 'job-operation-log'} or ( + trigger_id == 'job-list-table' + and recently_clicked_dropdown_item_title == '调度日志' + ): + if ( + trigger_id == 'job-list-table' + and recently_clicked_dropdown_item_title == '调度日志' + ): + return dict( + job_log_modal_visible=True, + job_log_modal_title='任务调度日志', + job_log_job_name=recently_dropdown_item_clicked_row.get( + 'job_name' + ), + job_log_search_nclick=job_log_search_nclick + 1 + if job_log_search_nclick + else 1, + ) return dict( job_log_modal_visible=True, job_log_modal_title='任务调度日志', job_log_job_name=None, - job_log_job_group_options=option_table, - job_log_search_nclick=job_log_search_nclick + 1 if job_log_search_nclick else 1, - api_check_token_trigger={'timestamp': time.time()} + job_log_search_nclick=job_log_search_nclick + 1 + if job_log_search_nclick + else 1, ) raise PreventUpdate @app.callback( - [Output('job-export-container', 'data', allow_duplicate=True), - Output('job-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('job-export-container', 'data', allow_duplicate=True), + Output('job-export-complete-judge-container', 'data'), + ], Input('job-export', 'nClicks'), - prevent_initial_call=True + [ + State('job-job_name-input', 'value'), + State('job-job_group-select', 'value'), + State('job-status-select', 'value'), + ], + running=[[Output('job-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_job_list(export_click): +def export_job_list(export_click, job_name, job_group, status_select): """ 导出定时任务信息回调 """ if export_click: - export_job_res = export_job_list_api({}) - if export_job_res.status_code == 200: - export_job = export_job_res.content - - return [ - dcc.send_bytes(export_job, f'定时任务信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + export_params = dict( + job_name=job_name, + job_group=job_group, + status=status_select, + ) + export_job_res = JobApi.export_job(export_params) + export_job = export_job_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_job, + f'定时任务信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -660,7 +707,7 @@ def export_job_list(export_click): @app.callback( Output('job-export-container', 'data', allow_duplicate=True), Input('job-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_job_export_status(data): """ diff --git a/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_log_c.py b/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_log_c.py index 7b827ac3bee4527dead485064278e7c161bfddcd..aa28fe1e00fa95391a9c29684df6a9c23171327b 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_log_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/job_c/job_log_c.py @@ -1,122 +1,112 @@ -import dash import time import uuid -import json -from dash import dcc -from dash.dependencies import Input, Output, State, ALL +from dash import ctx, dcc +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.monitor.job_log import JobLogApi from server import app -from api.job import get_job_log_list_api, get_job_log_detail_api, delete_job_log_api, clear_job_log_api, export_job_log_list_api -from api.dict import query_dict_data_list_api +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil @app.callback( output=dict( - job_log_table_data=Output('job_log-list-table', 'data', allow_duplicate=True), - job_log_table_pagination=Output('job_log-list-table', 'pagination', allow_duplicate=True), + job_log_table_data=Output( + 'job_log-list-table', 'data', allow_duplicate=True + ), + job_log_table_pagination=Output( + 'job_log-list-table', 'pagination', allow_duplicate=True + ), job_log_table_key=Output('job_log-list-table', 'key'), - job_log_table_selectedrowkeys=Output('job_log-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + job_log_table_selectedrowkeys=Output( + 'job_log-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('job_log-search', 'nClicks'), refresh_click=Input('job_log-refresh', 'nClicks'), pagination=Input('job_log-list-table', 'pagination'), - operations=Input('job_log-operations-store', 'data') + operations=Input('job_log-operations-store', 'data'), ), state=dict( job_name=State('job_log-job_name-input', 'value'), job_group=State('job_log-job_group-select', 'value'), status_select=State('job_log-status-select', 'value'), create_time_range=State('job_log-create_time-range', 'value'), - button_perms=State('job_log-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_job_log_table_data(search_click, refresh_click, pagination, operations, job_name, job_group, status_select, create_time_range, button_perms): +def get_job_log_table_data( + search_click, + refresh_click, + pagination, + operations, + job_name, + job_group, + status_select, + create_time_range, +): """ 获取定时任务对应调度日志表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - create_time_start = None - create_time_end = None + begin_time = None + end_time = None if create_time_range: - create_time_start = create_time_range[0] - create_time_end = create_time_range[1] + begin_time = create_time_range[0] + end_time = create_time_range[1] query_params = dict( job_name=job_name, job_group=job_group, status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, + begin_time=begin_time, + end_time=end_time, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'job_log-list-table': - query_params = dict( - job_name=job_name, - job_group=job_group, - status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - option_table = [] - info = query_dict_data_list_api(dict_type='sys_job_group') - if info.get('code') == 200: - data = info.get('data') - option_table = [dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for item in data] - option_dict = {item.get('value'): item for item in option_table} - - table_info = get_job_log_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] + table_info = JobLogApi.list_job_log(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status_tag'] = DictManager.get_dict_tag( + dict_type='sys_job_status', dict_value=item.get('status') ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='成功', color='blue') - else: - item['status'] = dict(tag='失败', color='volcano') - if str(item.get('job_group')) in option_dict.keys(): - item['job_group'] = dict( - tag=option_dict.get(str(item.get('job_group'))).get('label'), - color=json.loads(option_dict.get(str(item.get('job_group'))).get('css_class')).get('color') - ) - item['key'] = str(item['job_log_id']) - item['operation'] = [ - { - 'content': '详情', - 'type': 'link', - 'icon': 'antd-eye' - } if 'monitor:job:query' in button_perms else {}, - ] - - return dict( - job_log_table_data=table_data, - job_log_table_pagination=table_pagination, - job_log_table_key=str(uuid.uuid4()), - job_log_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} + item['job_group_tag'] = DictManager.get_dict_tag( + dict_type='sys_job_group', dict_value=item.get('job_group') ) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['job_log_id']) + item['operation'] = [ + {'content': '详情', 'type': 'link', 'icon': 'antd-eye'} + if PermissionManager.check_perms('monitor:job:query') + else {}, + ] return dict( - job_log_table_data=dash.no_update, - job_log_table_pagination=dash.no_update, - job_log_table_key=dash.no_update, - job_log_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + job_log_table_data=table_data, + job_log_table_pagination=table_pagination, + job_log_table_key=str(uuid.uuid4()), + job_log_table_selectedrowkeys=None, ) raise PreventUpdate @@ -124,27 +114,29 @@ def get_job_log_table_data(search_click, refresh_click, pagination, operations, # 重置定时任务调度日志搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('job_log-job_name-input', 'value'), - Output('job_log-job_group-select', 'value'), - Output('job_log-status-select', 'value'), - Output('job_log-create_time-range', 'value'), - Output('job_log-operations-store', 'data')], + """, + [ + Output('job_log-job_name-input', 'value'), + Output('job_log-job_group-select', 'value'), + Output('job_log-status-select', 'value'), + Output('job_log-create_time-range', 'value'), + Output('job_log-operations-store', 'data'), + ], Input('job_log-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示定时任务调度日志搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -154,12 +146,14 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('job_log-search-form-container', 'hidden'), - Output('job_log-hidden-tooltip', 'title')], + """, + [ + Output('job_log-search-form-container', 'hidden'), + Output('job_log-hidden-tooltip', 'title'), + ], Input('job_log-hidden', 'nClicks'), State('job_log-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) @@ -167,77 +161,78 @@ app.clientside_callback( output=dict( modal_visible=Output('job_log-modal', 'visible', allow_duplicate=True), modal_title=Output('job_log-modal', 'title'), - form_value=Output({'type': 'job_log-form-value', 'index': ALL}, 'children'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) - ), - inputs=dict( - button_click=Input('job_log-list-table', 'nClicksButton') + form_value=Output( + {'type': 'job_log-form-value', 'index': ALL}, 'children' + ), ), + inputs=dict(button_click=Input('job_log-list-table', 'nClicksButton')), state=dict( clicked_content=State('job_log-list-table', 'clickedContent'), - recently_button_clicked_row=State('job_log-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'job_log-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_job_log_modal(button_click, clicked_content, recently_button_clicked_row): +def add_edit_job_log_modal( + button_click, clicked_content, recently_button_clicked_row +): if button_click: # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[-2]] - job_log_id = int(recently_button_clicked_row['key']) - job_log_info_res = get_job_log_detail_api(job_log_id=job_log_id) - if job_log_info_res['code'] == 200: - job_log_info = job_log_info_res['data'] - job_log_info['status'] = '成功' if job_log_info.get('status') == '0' else '失败' - return dict( - modal_visible=True, - modal_title='任务执行日志详情', - form_value=[job_log_info.get(k) for k in form_value_list], - api_check_token_trigger={'timestamp': time.time()} - ) - + form_value_list = [x['id']['index'] for x in ctx.outputs_list[-1]] + job_log_info = recently_button_clicked_row + job_log_info['job_group'] = DictManager.get_dict_label( + dict_type='sys_job_group', + dict_value=job_log_info.get('job_group'), + ) + job_log_info['status'] = DictManager.get_dict_label( + dict_type='sys_job_status', + dict_value=job_log_info.get('status'), + ) return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - api_check_token_trigger={'timestamp': time.time()} + modal_visible=True, + modal_title='任务执行日志详情', + form_value=[job_log_info.get(k) for k in form_value_list], ) raise PreventUpdate -@app.callback( +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'job_log-operation-button', 'index': 'delete'}, 'disabled'), Input('job_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_job_log_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - - return True - - raise PreventUpdate @app.callback( - [Output('job_log-delete-text', 'children'), - Output('job_log-delete-confirm-modal', 'visible'), - Output('job_log-delete-ids-store', 'data')], + [ + Output('job_log-delete-text', 'children'), + Output('job_log-delete-confirm-modal', 'visible'), + Output('job_log-delete-ids-store', 'data'), + ], Input({'type': 'job_log-operation-button', 'index': ALL}, 'nClicks'), State('job_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) def job_log_delete_modal(operation_click, selected_row_keys): """ 显示删除或清空定时任务调度日志二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.index in ['delete', 'clear']: if trigger_id.index == 'delete': job_log_ids = ','.join(selected_row_keys) @@ -245,97 +240,91 @@ def job_log_delete_modal(operation_click, selected_row_keys): return [ f'是否确认删除日志编号为{job_log_ids}的任务执行日志?', True, - {'oper_type': 'delete', 'job_log_ids': job_log_ids} + {'oper_type': 'delete', 'job_log_ids': job_log_ids}, ] elif trigger_id.index == 'clear': return [ - f'是否确认清除所有的任务执行日志?', + '是否确认清除所有的任务执行日志?', True, - {'oper_type': 'clear', 'job_log_ids': ''} + {'oper_type': 'clear'}, ] raise PreventUpdate @app.callback( - [Output('job_log-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('job_log-operations-store', 'data', allow_duplicate=True), Input('job_log-delete-confirm-modal', 'okCounts'), State('job_log-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def job_log_delete_confirm(delete_confirm, job_log_ids_data): """ 删除或清空定时任务调度日志弹窗确认回调,实现删除或清空操作 """ if delete_confirm: - oper_type = job_log_ids_data.get('oper_type') if oper_type == 'clear': - params = dict(oper_type=job_log_ids_data.get('oper_type')) - clear_button_info = clear_job_log_api(params) - if clear_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('清除成功', type='success') - ] + JobLogApi.clean_job_log() + MessageManager.success(content='清除成功') + + return {'type': 'clear'} - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('清除失败', type='error') - ] else: - params = dict(job_log_ids=job_log_ids_data.get('job_log_ids')) - delete_button_info = delete_job_log_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + params = job_log_ids_data.get('job_log_ids') + JobLogApi.del_job_log(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('job_log-export-container', 'data', allow_duplicate=True), - Output('job_log-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('job_log-export-container', 'data', allow_duplicate=True), + Output('job_log-export-complete-judge-container', 'data'), + ], Input('job_log-export', 'nClicks'), - prevent_initial_call=True + [ + State('job_log-job_name-input', 'value'), + State('job_log-job_group-select', 'value'), + State('job_log-status-select', 'value'), + State('job_log-create_time-range', 'value'), + ], + running=[[Output('job_log-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_job_log_list(export_click): +def export_job_log_list( + export_click, job_name, job_group, status_select, create_time_range +): """ 导出定时任务调度日志信息回调 """ if export_click: - export_job_log_res = export_job_log_list_api({}) - if export_job_log_res.status_code == 200: - export_job_log = export_job_log_res.content - - return [ - dcc.send_bytes(export_job_log, f'任务执行日志信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + begin_time = None + end_time = None + if create_time_range: + begin_time = create_time_range[0] + end_time = create_time_range[1] + export_params = dict( + job_name=job_name, + job_group=job_group, + status=status_select, + begin_time=begin_time, + end_time=end_time, + ) + export_job_log_res = JobLogApi.export_job_log(export_params) + export_job_log = export_job_log_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_job_log, + f'任务执行日志信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -344,7 +333,7 @@ def export_job_log_list(export_click): @app.callback( Output('job_log-export-container', 'data', allow_duplicate=True), Input('job_log-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_job_log_export_status(data): """ @@ -352,7 +341,6 @@ def reset_job_log_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/monitor_c/logininfor_c.py b/dash-fastapi-frontend/callbacks/monitor_c/logininfor_c.py index 4cec7c0e3cfdf899a6ee94b089939fed9482a831..d68420b16dfc3d340113b976381a92f6db39fc49 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/logininfor_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/logininfor_c.py @@ -1,106 +1,117 @@ -import dash import time import uuid -from dash import dcc +from dash import ctx, dcc, no_update from dash.dependencies import Input, Output, State, ALL from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.monitor.logininfor import LogininforApi from server import app -from api.log import get_login_log_list_api, delete_login_log_api, clear_login_log_api, unlock_user_api, export_login_log_list_api +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.time_format_util import TimeFormatUtil + + +def generate_logininfor_table(query_params: Dict): + """ + 根据查询参数获取登录日志表格数据及分页信息 + + :param query_params: 查询参数 + :return: 登录日志表格数据及分页信息 + """ + table_info = LogininforApi.list_logininfor(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status_tag'] = DictManager.get_dict_tag( + dict_type='sys_common_status', dict_value=item.get('status') + ) + item['login_time'] = TimeFormatUtil.format_time(item.get('login_time')) + item['key'] = str(item['info_id']) + + return [table_data, table_pagination] @app.callback( output=dict( - login_log_table_data=Output('login_log-list-table', 'data', allow_duplicate=True), - login_log_table_pagination=Output('login_log-list-table', 'pagination', allow_duplicate=True), + login_log_table_data=Output( + 'login_log-list-table', 'data', allow_duplicate=True + ), + login_log_table_pagination=Output( + 'login_log-list-table', 'pagination', allow_duplicate=True + ), login_log_table_key=Output('login_log-list-table', 'key'), - login_log_table_selectedrowkeys=Output('login_log-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + login_log_table_selectedrowkeys=Output( + 'login_log-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('login_log-search', 'nClicks'), refresh_click=Input('login_log-refresh', 'nClicks'), sorter=Input('login_log-list-table', 'sorter'), pagination=Input('login_log-list-table', 'pagination'), - operations=Input('login_log-operations-store', 'data') + operations=Input('login_log-operations-store', 'data'), ), state=dict( ipaddr=State('login_log-ipaddr-input', 'value'), user_name=State('login_log-user_name-input', 'value'), status_select=State('login_log-status-select', 'value'), login_time_range=State('login_log-login_time-range', 'value'), - button_perms=State('login_log-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_login_log_table_data(search_click, refresh_click, sorter, pagination, operations, ipaddr, user_name, status_select, login_time_range, button_perms): +def get_login_log_table_data( + search_click, + refresh_click, + sorter, + pagination, + operations, + ipaddr, + user_name, + status_select, + login_time_range, +): """ 获取登录日志表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - login_time_start = None - login_time_end = None + begin_time = None + end_time = None if login_time_range: - login_time_start = login_time_range[0] - login_time_end = login_time_range[1] + begin_time = login_time_range[0] + end_time = login_time_range[1] query_params = dict( ipaddr=ipaddr, user_name=user_name, status=status_select, - login_time_start=login_time_start, - login_time_end=login_time_end, + begin_time=begin_time, + end_time=end_time, order_by_column=sorter.get('columns')[0] if sorter else None, - is_asc=sorter.get('orders')[0] if sorter else None, + is_asc=f"{sorter.get('orders')[0]}ing" if sorter else None, page_num=1, - page_size=10 + page_size=10, ) - triggered_prop = dash.ctx.triggered[0].get('prop_id') + triggered_prop = ctx.triggered[0].get('prop_id') if triggered_prop == 'login_log-list-table.pagination': - query_params = dict( - ipaddr=ipaddr, - user_name=user_name, - status=status_select, - login_time_start=login_time_start, - login_time_end=login_time_end, - order_by_column=sorter.get('columns')[0] if sorter else None, - is_asc=sorter.get('orders')[0] if sorter else None, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_login_log_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='成功', color='blue') - else: - item['status'] = dict(tag='失败', color='volcano') - item['key'] = str(item['info_id']) - - return dict( - login_log_table_data=table_data, - login_log_table_pagination=table_pagination, - login_log_table_key=str(uuid.uuid4()), - login_log_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) - + table_data, table_pagination = generate_logininfor_table(query_params) return dict( - login_log_table_data=dash.no_update, - login_log_table_pagination=dash.no_update, - login_log_table_key=dash.no_update, - login_log_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + login_log_table_data=table_data, + login_log_table_pagination=table_pagination, + login_log_table_key=str(uuid.uuid4()), + login_log_table_selectedrowkeys=None, ) raise PreventUpdate @@ -108,27 +119,29 @@ def get_login_log_table_data(search_click, refresh_click, sorter, pagination, op # 重置登录日志搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('login_log-ipaddr-input', 'value'), - Output('login_log-user_name-input', 'value'), - Output('login_log-status-select', 'value'), - Output('login_log-login_time-range', 'value'), - Output('login_log-operations-store', 'data')], + """, + [ + Output('login_log-ipaddr-input', 'value'), + Output('login_log-user_name-input', 'value'), + Output('login_log-status-select', 'value'), + Output('login_log-login_time-range', 'value'), + Output('login_log-operations-store', 'data'), + ], Input('login_log-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示登录日志搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -138,70 +151,74 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('login_log-search-form-container', 'hidden'), - Output('login_log-hidden-tooltip', 'title')], + """, + [ + Output('login_log-search-form-container', 'hidden'), + Output('login_log-hidden-tooltip', 'title'), + ], Input('login_log-hidden', 'nClicks'), State('login_log-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( - Output({'type': 'login_log-operation-button', 'index': 'delete'}, 'disabled'), +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, + Output( + {'type': 'login_log-operation-button', 'index': 'delete'}, 'disabled' + ), Input('login_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_login_log_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - return True - - raise PreventUpdate - -@app.callback( +# 根据选择的表格数据行数控制解锁按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output('login_log-unlock', 'disabled'), Input('login_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_login_log_unlock_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制解锁按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - - return False - - return True - - raise PreventUpdate @app.callback( - [Output('login_log-delete-text', 'children'), - Output('login_log-delete-confirm-modal', 'visible'), - Output('login_log-delete-ids-store', 'data')], + [ + Output('login_log-delete-text', 'children'), + Output('login_log-delete-confirm-modal', 'visible'), + Output('login_log-delete-ids-store', 'data'), + ], Input({'type': 'login_log-operation-button', 'index': ALL}, 'nClicks'), State('login_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) def login_log_delete_modal(operation_click, selected_row_keys): """ 显示删除或清空登录日志二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.index in ['delete', 'clear']: if trigger_id.index == 'delete': info_ids = ','.join(selected_row_keys) @@ -209,97 +226,93 @@ def login_log_delete_modal(operation_click, selected_row_keys): return [ f'是否确认删除访问编号为{info_ids}的登录日志?', True, - {'oper_type': 'delete', 'info_ids': info_ids} + {'oper_type': 'delete', 'info_ids': info_ids}, ] elif trigger_id.index == 'clear': return [ - f'是否确认清除所有的登录日志?', + '是否确认清除所有的登录日志?', True, - {'oper_type': 'clear', 'info_ids': ''} + {'oper_type': 'clear'}, ] raise PreventUpdate @app.callback( - [Output('login_log-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('login_log-operations-store', 'data', allow_duplicate=True), Input('login_log-delete-confirm-modal', 'okCounts'), State('login_log-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def login_log_delete_confirm(delete_confirm, info_ids_data): """ 删除或清空登录日志弹窗确认回调,实现删除或清空操作 """ if delete_confirm: - oper_type = info_ids_data.get('oper_type') if oper_type == 'clear': - params = dict(oper_type=info_ids_data.get('oper_type')) - clear_button_info = clear_login_log_api(params) - if clear_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('清除成功', type='success') - ] + LogininforApi.clean_logininfor() + MessageManager.success(content='清除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('清除失败', type='error') - ] + return {'type': 'clear'} else: - params = dict(info_ids=info_ids_data.get('info_ids')) - delete_button_info = delete_login_log_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + params = info_ids_data.get('info_ids') + LogininforApi.del_logininfor(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('login_log-export-container', 'data', allow_duplicate=True), - Output('login_log-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('login_log-export-container', 'data', allow_duplicate=True), + Output('login_log-export-complete-judge-container', 'data'), + ], Input('login_log-export', 'nClicks'), - prevent_initial_call=True + [ + State('login_log-list-table', 'sorter'), + State('login_log-ipaddr-input', 'value'), + State('login_log-user_name-input', 'value'), + State('login_log-status-select', 'value'), + State('login_log-login_time-range', 'value'), + ], + running=[[Output('login_log-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_login_log_list(export_click): +def export_login_log_list( + export_click, sorter, ipaddr, user_name, status_select, login_time_range +): """ 导出登录日志信息回调 """ if export_click: - export_login_log_res = export_login_log_list_api({}) - if export_login_log_res.status_code == 200: - export_login_log = export_login_log_res.content - - return [ - dcc.send_bytes(export_login_log, f'登录日志信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + begin_time = None + end_time = None + if login_time_range: + begin_time = login_time_range[0] + end_time = login_time_range[1] + export_params = dict( + ipaddr=ipaddr, + user_name=user_name, + status=status_select, + begin_time=begin_time, + end_time=end_time, + order_by_column=sorter.get('columns')[0] if sorter else None, + is_asc=f"{sorter.get('orders')[0]}ing" if sorter else None, + ) + export_login_log_res = LogininforApi.export_logininfor(export_params) + export_login_log = export_login_log_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_login_log, + f'登录日志信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -308,7 +321,7 @@ def export_login_log_list(export_click): @app.callback( Output('login_log-export-container', 'data', allow_duplicate=True), Input('login_log-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_login_log_export_status(data): """ @@ -316,18 +329,16 @@ def reset_login_log_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate @app.callback( - [Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('login_log-unlock', 'nClicks'), Input('login_log-unlock', 'nClicks'), State('login_log-list-table', 'selectedRows'), - prevent_initial_call=True + prevent_initial_call=True, ) def unlock_user(unlock_click, selected_rows): """ @@ -335,17 +346,9 @@ def unlock_user(unlock_click, selected_rows): """ if unlock_click: user_name = selected_rows[0].get('user_name') - unlock_info_res = unlock_user_api(dict(user_name=user_name)) - if unlock_info_res.get('code') == 200: - - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('解锁成功', type='success') - ] + LogininforApi.unlock_logininfor(user_name=user_name) + MessageManager.success(content='解锁成功') - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('解锁失败', type='error') - ] + return no_update raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/monitor_c/online_c.py b/dash-fastapi-frontend/callbacks/monitor_c/online_c.py index 8a63b4559e305ce66ef201bbca3d23d3b78b13a2..3c07af964b7791ca5e348d76ae93523349bbb9dd 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/online_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/online_c.py @@ -1,89 +1,96 @@ -import dash -import time import uuid -from dash.dependencies import Input, Output, State, ALL +from dash import ctx +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.monitor.online import OnlineApi from server import app -from api.online import get_online_list_api, force_logout_online_api, batch_logout_online_api +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager + + +def generate_online_table(query_params: Dict): + """ + 根据查询参数获取在线用户表格数据及分页信息 + + :param query_params: 查询参数 + :return: 在线用户表格数据及分页信息 + """ + table_info = OnlineApi.list_online(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['key'] = str(item['token_id']) + item['operation'] = [ + {'content': '强退', 'type': 'link', 'icon': 'antd-delete'} + if PermissionManager.check_perms('monitor:online:forceLogout') + else {}, + ] + + return [table_data, table_pagination] @app.callback( output=dict( - online_table_data=Output('online-list-table', 'data', allow_duplicate=True), - online_table_pagination=Output('online-list-table', 'pagination', allow_duplicate=True), + online_table_data=Output( + 'online-list-table', 'data', allow_duplicate=True + ), + online_table_pagination=Output( + 'online-list-table', 'pagination', allow_duplicate=True + ), online_table_key=Output('online-list-table', 'key'), - online_table_selectedrowkeys=Output('online-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + online_table_selectedrowkeys=Output( + 'online-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('online-search', 'nClicks'), refresh_click=Input('online-refresh', 'nClicks'), pagination=Input('online-list-table', 'pagination'), - operations=Input('online-operations-store', 'data') + operations=Input('online-operations-store', 'data'), ), state=dict( ipaddr=State('online-ipaddr-input', 'value'), user_name=State('online-user_name-input', 'value'), - button_perms=State('online-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_online_table_data(search_click, refresh_click, pagination, operations, ipaddr, user_name, button_perms): +def get_online_table_data( + search_click, + refresh_click, + pagination, + operations, + ipaddr, + user_name, +): """ 获取在线用户表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ query_params = dict( - ipaddr=ipaddr, - user_name=user_name, - page_num=1, - page_size=10 + ipaddr=ipaddr, user_name=user_name, page_num=1, page_size=10 ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'online-list-table': - query_params = dict( - ipaddr=ipaddr, - user_name=user_name, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_online_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - item['key'] = str(item['session_id']) - item['operation'] = [ - { - 'content': '强退', - 'type': 'link', - 'icon': 'antd-delete' - } if 'monitor:online:forceLogout' in button_perms else {}, - ] - - return dict( - online_table_data=table_data, - online_table_pagination=table_pagination, - online_table_key=str(uuid.uuid4()), - online_table_selectedrowkeys=None, - api_check_token_trigger= {'timestamp': time.time()} - ) - + table_data, table_pagination = generate_online_table(query_params) return dict( - online_table_data=dash.no_update, - online_table_pagination=dash.no_update, - online_table_key=dash.no_update, - online_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + online_table_data=table_data, + online_table_pagination=table_pagination, + online_table_key=str(uuid.uuid4()), + online_table_selectedrowkeys=None, ) raise PreventUpdate @@ -91,25 +98,27 @@ def get_online_table_data(search_click, refresh_click, pagination, operations, i # 重置在线用户搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('online-ipaddr-input', 'value'), - Output('online-user_name-input', 'value'), - Output('online-operations-store', 'data')], + """, + [ + Output('online-ipaddr-input', 'value'), + Output('online-user_name-input', 'value'), + Output('online-operations-store', 'data'), + ], Input('online-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示在线用户搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -119,105 +128,100 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('online-search-form-container', 'hidden'), - Output('online-hidden-tooltip', 'title')], + """, + [ + Output('online-search-form-container', 'hidden'), + Output('online-hidden-tooltip', 'title'), + ], Input('online-hidden', 'nClicks'), State('online-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制批量强退按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'online-operation-button', 'index': 'delete'}, 'disabled'), Input('online-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_online_edit_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制批量强退按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - - return True - - raise PreventUpdate @app.callback( - [Output('online-delete-text', 'children'), - Output('online-delete-confirm-modal', 'visible'), - Output('online-delete-ids-store', 'data')], - [Input({'type': 'online-operation-button', 'index': ALL}, 'nClicks'), - Input('online-list-table', 'nClicksButton')], - [State('online-list-table', 'selectedRowKeys'), - State('online-list-table', 'clickedContent'), - State('online-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('online-delete-text', 'children'), + Output('online-delete-confirm-modal', 'visible'), + Output('online-delete-ids-store', 'data'), + ], + [ + Input({'type': 'online-operation-button', 'index': ALL}, 'nClicks'), + Input('online-list-table', 'nClicksButton'), + ], + [ + State('online-list-table', 'selectedRowKeys'), + State('online-list-table', 'clickedContent'), + State('online-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def online_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def online_delete_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示强退在线用户二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id == {'index': 'delete', 'type': 'online-operation-button'} or ( - trigger_id == 'online-list-table' and clicked_content == '强退'): - logout_type = '' - + trigger_id == 'online-list-table' and clicked_content == '强退' + ): if trigger_id == {'index': 'delete', 'type': 'online-operation-button'}: session_ids = ','.join(selected_row_keys) else: if clicked_content == '强退': session_ids = recently_button_clicked_row['key'] - logout_type = 'force' else: - return dash.no_update + raise PreventUpdate return [ f'是否确认强退会话编号为{session_ids}的会话?', True, - {'session_ids': session_ids, 'logout_type': logout_type} + session_ids, ] raise PreventUpdate @app.callback( - [Output('online-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('online-operations-store', 'data', allow_duplicate=True), Input('online-delete-confirm-modal', 'okCounts'), State('online-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def online_delete_confirm(delete_confirm, session_ids_data): """ 强退在线用户弹窗确认回调,实现强退操作 """ if delete_confirm: + params = session_ids_data + OnlineApi.force_logout(params) + MessageManager.success(content='强退成功') - params = dict(session_ids=session_ids_data.get('session_ids')) - logout_type = session_ids_data.get('logout_type') - if logout_type == 'force': - delete_button_info = force_logout_online_api(params) - else: - delete_button_info = batch_logout_online_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'force'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('强退成功', type='success') - ] - - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('强退失败', type='error') - ] + return {'type': 'force'} raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/monitor_c/operlog_c.py b/dash-fastapi-frontend/callbacks/monitor_c/operlog_c.py index b1e6252502f2fb05d5b169e5dc72e5932de13762..9eee00f8592b6d873d2853891e29bce99c7e3887 100644 --- a/dash-fastapi-frontend/callbacks/monitor_c/operlog_c.py +++ b/dash-fastapi-frontend/callbacks/monitor_c/operlog_c.py @@ -1,31 +1,76 @@ -import dash import time import uuid -import json -from dash import dcc +from dash import ctx, dcc from dash.dependencies import Input, Output, State, ALL from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.monitor.operlog import OperlogApi from server import app -from api.log import get_operation_log_list_api, get_operation_log_detail_api, delete_operation_log_api, clear_operation_log_api, export_operation_log_list_api -from api.dict import query_dict_data_list_api +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil + + +def generate_operlog_table(query_params: Dict): + """ + 根据查询参数获取操作日志表格数据及分页信息 + + :param query_params: 查询参数 + :return: 操作日志表格数据及分页信息 + """ + table_info = OperlogApi.list_operlog(query_params) + if table_info['code'] == 200: + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status_tag'] = DictManager.get_dict_tag( + dict_type='sys_common_status', dict_value=item.get('status') + ) + item['business_type_tag'] = DictManager.get_dict_tag( + dict_type='sys_oper_type', + dict_value=item.get('business_type'), + ) + item['oper_time'] = TimeFormatUtil.format_time( + item.get('oper_time') + ) + item['key'] = str(item['oper_id']) + item['cost_time'] = f"{item['cost_time']}毫秒" + item['operation'] = [ + {'content': '详情', 'type': 'link', 'icon': 'antd-eye'} + if PermissionManager.check_perms('monitor:operlog:query') + else {}, + ] + + return [table_data, table_pagination] @app.callback( output=dict( - operation_log_table_data=Output('operation_log-list-table', 'data', allow_duplicate=True), - operation_log_table_pagination=Output('operation_log-list-table', 'pagination', allow_duplicate=True), + operation_log_table_data=Output( + 'operation_log-list-table', 'data', allow_duplicate=True + ), + operation_log_table_pagination=Output( + 'operation_log-list-table', 'pagination', allow_duplicate=True + ), operation_log_table_key=Output('operation_log-list-table', 'key'), - operation_log_table_selectedrowkeys=Output('operation_log-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + operation_log_table_selectedrowkeys=Output( + 'operation_log-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('operation_log-search', 'nClicks'), refresh_click=Input('operation_log-refresh', 'nClicks'), sorter=Input('operation_log-list-table', 'sorter'), pagination=Input('operation_log-list-table', 'pagination'), - operations=Input('operation_log-operations-store', 'data') + operations=Input('operation_log-operations-store', 'data'), ), state=dict( title=State('operation_log-title-input', 'value'), @@ -33,99 +78,57 @@ from api.dict import query_dict_data_list_api business_type=State('operation_log-business_type-select', 'value'), status_select=State('operation_log-status-select', 'value'), oper_time_range=State('operation_log-oper_time-range', 'value'), - button_perms=State('operation_log-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_operation_log_table_data(search_click, refresh_click, sorter, pagination, operations, title, oper_name, business_type, status_select, oper_time_range, button_perms): +def get_operation_log_table_data( + search_click, + refresh_click, + sorter, + pagination, + operations, + title, + oper_name, + business_type, + status_select, + oper_time_range, +): """ 获取操作日志表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - oper_time_start = None - oper_time_end = None + begin_time = None + end_time = None if oper_time_range: - oper_time_start = oper_time_range[0] - oper_time_end = oper_time_range[1] + begin_time = oper_time_range[0] + end_time = oper_time_range[1] query_params = dict( title=title, oper_name=oper_name, business_type=business_type, status=status_select, - oper_time_start=oper_time_start, - oper_time_end=oper_time_end, + begin_time=begin_time, + end_time=end_time, order_by_column=sorter.get('columns')[0] if sorter else None, - is_asc=sorter.get('orders')[0] if sorter else None, + is_asc=f"{sorter.get('orders')[0]}ing" if sorter else None, page_num=1, - page_size=10 + page_size=10, ) - triggered_prop = dash.ctx.triggered[0].get('prop_id') + triggered_prop = ctx.triggered[0].get('prop_id') if triggered_prop == 'operation_log-list-table.pagination': - query_params = dict( - title=title, - oper_name=oper_name, - business_type=business_type, - status=status_select, - oper_time_start=oper_time_start, - oper_time_end=oper_time_end, - order_by_column=sorter.get('columns')[0] if sorter else None, - is_asc=sorter.get('orders')[0] if sorter else None, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - option_table = [] - info = query_dict_data_list_api(dict_type='sys_oper_type') - if info.get('code') == 200: - data = info.get('data') - option_table = [dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for item in data] - option_dict = {item.get('value'): item for item in option_table} - - table_info = get_operation_log_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == 0: - item['status'] = dict(tag='成功', color='blue') - else: - item['status'] = dict(tag='失败', color='volcano') - if str(item.get('business_type')) in option_dict.keys(): - item['business_type'] = dict( - tag=option_dict.get(str(item.get('business_type'))).get('label'), - color=json.loads(option_dict.get(str(item.get('business_type'))).get('css_class')).get('color') - ) - item['key'] = str(item['oper_id']) - item['cost_time'] = f"{item['cost_time']}毫秒" - item['operation'] = [ - { - 'content': '详情', - 'type': 'link', - 'icon': 'antd-eye' - } if 'monitor:operlog:query' in button_perms else {}, - ] - - return dict( - operation_log_table_data=table_data, - operation_log_table_pagination=table_pagination, - operation_log_table_key=str(uuid.uuid4()), - operation_log_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) - + table_data, table_pagination = generate_operlog_table(query_params) return dict( - operation_log_table_data=dash.no_update, - operation_log_table_pagination=dash.no_update, - operation_log_table_key=dash.no_update, - operation_log_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + operation_log_table_data=table_data, + operation_log_table_pagination=table_pagination, + operation_log_table_key=str(uuid.uuid4()), + operation_log_table_selectedrowkeys=None, ) raise PreventUpdate @@ -133,28 +136,30 @@ def get_operation_log_table_data(search_click, refresh_click, sorter, pagination # 重置操作日志搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('operation_log-title-input', 'value'), - Output('operation_log-oper_name-input', 'value'), - Output('operation_log-business_type-select', 'value'), - Output('operation_log-status-select', 'value'), - Output('operation_log-oper_time-range', 'value'), - Output('operation_log-operations-store', 'data')], + """, + [ + Output('operation_log-title-input', 'value'), + Output('operation_log-oper_name-input', 'value'), + Output('operation_log-business_type-select', 'value'), + Output('operation_log-status-select', 'value'), + Output('operation_log-oper_time-range', 'value'), + Output('operation_log-operations-store', 'data'), + ], Input('operation_log-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示操作日志搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -164,98 +169,117 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('operation_log-search-form-container', 'hidden'), - Output('operation_log-hidden-tooltip', 'title')], + """, + [ + Output('operation_log-search-form-container', 'hidden'), + Output('operation_log-hidden-tooltip', 'title'), + ], Input('operation_log-hidden', 'nClicks'), State('operation_log-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) @app.callback( output=dict( - modal_visible=Output('operation_log-modal', 'visible', allow_duplicate=True), + modal_visible=Output( + 'operation_log-modal', 'visible', allow_duplicate=True + ), modal_title=Output('operation_log-modal', 'title'), - form_value=Output({'type': 'operation_log-form-value', 'index': ALL}, 'children'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + form_value=Output( + {'type': 'operation_log-form-value', 'index': ALL}, 'children' + ), ), inputs=dict( button_click=Input('operation_log-list-table', 'nClicksButton') ), state=dict( clicked_content=State('operation_log-list-table', 'clickedContent'), - recently_button_clicked_row=State('operation_log-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'operation_log-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_operation_log_modal(button_click, clicked_content, recently_button_clicked_row): +def add_edit_operation_log_modal( + button_click, clicked_content, recently_button_clicked_row +): """ 显示操作日志详情弹窗回调 """ if button_click: # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[-2]] - oper_id = int(recently_button_clicked_row['key']) - operation_log_info_res = get_operation_log_detail_api(oper_id=oper_id) - if operation_log_info_res['code'] == 200: - operation_log_info = operation_log_info_res['data'] - oper_name = operation_log_info.get('oper_name') if operation_log_info.get('oper_name') else '' - oper_ip = operation_log_info.get('oper_ip') if operation_log_info.get('oper_ip') else '' - oper_location = operation_log_info.get('oper_location') if operation_log_info.get('oper_location') else '' - operation_log_info['login_info'] = f'{oper_name} / {oper_ip} / {oper_location}' - operation_log_info['status'] = '正常' if operation_log_info.get('status') == 0 else '失败' - operation_log_info['cost_time'] = f"{operation_log_info.get('cost_time')}毫秒" - return dict( - modal_visible=True, - modal_title='操作日志详情', - form_value=[operation_log_info.get(k) for k in form_value_list], - api_check_token_trigger={'timestamp': time.time()} - ) - + form_value_list = [x['id']['index'] for x in ctx.outputs_list[-1]] + operation_log_info = recently_button_clicked_row + oper_name = ( + operation_log_info.get('oper_name') + if operation_log_info.get('oper_name') + else '' + ) + oper_ip = ( + operation_log_info.get('oper_ip') + if operation_log_info.get('oper_ip') + else '' + ) + oper_location = ( + operation_log_info.get('oper_location') + if operation_log_info.get('oper_location') + else '' + ) + operation_log_info['login_info'] = ( + f'{oper_name} / {oper_ip} / {oper_location}' + ) + operation_log_info['status'] = DictManager.get_dict_label( + dict_type='sys_common_status', + dict_value=operation_log_info.get('status'), + ) return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - api_check_token_trigger={'timestamp': time.time()} + modal_visible=True, + modal_title='操作日志详情', + form_value=[operation_log_info.get(k) for k in form_value_list], ) raise PreventUpdate -@app.callback( - Output({'type': 'operation_log-operation-button', 'index': 'delete'}, 'disabled'), +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, + Output( + {'type': 'operation_log-operation-button', 'index': 'delete'}, + 'disabled', + ), Input('operation_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_operation_log_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - - return True - - raise PreventUpdate @app.callback( - [Output('operation_log-delete-text', 'children'), - Output('operation_log-delete-confirm-modal', 'visible'), - Output('operation_log-delete-ids-store', 'data')], + [ + Output('operation_log-delete-text', 'children'), + Output('operation_log-delete-confirm-modal', 'visible'), + Output('operation_log-delete-ids-store', 'data'), + ], Input({'type': 'operation_log-operation-button', 'index': ALL}, 'nClicks'), State('operation_log-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) def operation_log_delete_modal(operation_click, selected_row_keys): """ 显示删除或清空操作日志二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.index in ['delete', 'clear']: if trigger_id.index == 'delete': oper_ids = ','.join(selected_row_keys) @@ -263,97 +287,101 @@ def operation_log_delete_modal(operation_click, selected_row_keys): return [ f'是否确认删除日志编号为{oper_ids}的操作日志?', True, - {'oper_type': 'delete', 'oper_ids': oper_ids} + {'oper_type': 'delete', 'oper_ids': oper_ids}, ] elif trigger_id.index == 'clear': return [ - f'是否确认清除所有的操作日志?', + '是否确认清除所有的操作日志?', True, - {'oper_type': 'clear', 'oper_ids': ''} + {'oper_type': 'clear'}, ] raise PreventUpdate @app.callback( - [Output('operation_log-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('operation_log-operations-store', 'data', allow_duplicate=True), Input('operation_log-delete-confirm-modal', 'okCounts'), State('operation_log-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def operation_log_delete_confirm(delete_confirm, oper_ids_data): """ 删除或清空操作日志弹窗确认回调,实现删除或清空操作 """ if delete_confirm: - oper_type = oper_ids_data.get('oper_type') if oper_type == 'clear': - params = dict(oper_type=oper_ids_data.get('oper_type')) - clear_button_info = clear_operation_log_api(params) - if clear_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('清除成功', type='success') - ] + OperlogApi.clean_operlog() + MessageManager.success(content='清除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('清除失败', type='error') - ] + return {'type': 'clear'} else: - params = dict(oper_ids=oper_ids_data.get('oper_ids')) - delete_button_info = delete_operation_log_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + params = oper_ids_data.get('oper_ids') + OperlogApi.del_operlog(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('operation_log-export-container', 'data', allow_duplicate=True), - Output('operation_log-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('operation_log-export-container', 'data', allow_duplicate=True), + Output('operation_log-export-complete-judge-container', 'data'), + ], Input('operation_log-export', 'nClicks'), - prevent_initial_call=True + [ + State('operation_log-list-table', 'sorter'), + State('operation_log-title-input', 'value'), + State('operation_log-oper_name-input', 'value'), + State('operation_log-business_type-select', 'value'), + State('operation_log-status-select', 'value'), + State('operation_log-oper_time-range', 'value'), + ], + running=[[Output('operation_log-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_operation_log_list(export_click): +def export_operation_log_list( + export_click, + sorter, + title, + oper_name, + business_type, + status_select, + oper_time_range, +): """ 导出操作日志信息回调 """ if export_click: - export_operation_log_res = export_operation_log_list_api({}) - if export_operation_log_res.status_code == 200: - export_operation_log = export_operation_log_res.content - - return [ - dcc.send_bytes(export_operation_log, f'操作日志信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + begin_time = None + end_time = None + if oper_time_range: + begin_time = oper_time_range[0] + end_time = oper_time_range[1] + export_params = dict( + title=title, + oper_name=oper_name, + business_type=business_type, + status=status_select, + begin_time=begin_time, + end_time=end_time, + order_by_column=sorter.get('columns')[0] if sorter else None, + is_asc=f"{sorter.get('orders')[0]}ing" if sorter else None, + ) + export_operation_log_res = OperlogApi.export_operlog(export_params) + export_operation_log = export_operation_log_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_operation_log, + f'操作日志信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -362,7 +390,7 @@ def export_operation_log_list(export_click): @app.callback( Output('operation_log-export-container', 'data', allow_duplicate=True), Input('operation_log-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_operation_log_export_status(data): """ @@ -370,7 +398,6 @@ def reset_operation_log_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/register_c.py b/dash-fastapi-frontend/callbacks/register_c.py new file mode 100644 index 0000000000000000000000000000000000000000..e97c583121ec887142e7fe9192d94387f218cd28 --- /dev/null +++ b/dash-fastapi-frontend/callbacks/register_c.py @@ -0,0 +1,195 @@ +import time +from dash import dcc, get_asset_url, no_update +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate +from api.login import LoginApi +from api.register import RegisterApi +from server import app +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager + + +@app.callback( + output=dict( + username_form_status=Output( + 'register-username-form-item', 'validateStatus' + ), + password_form_status=Output( + 'register-password-form-item', 'validateStatus' + ), + confirm_password_form_status=Output( + 'register-confirm_password-form-item', 'validateStatus' + ), + captcha_form_status=Output( + 'register-captcha-form-item', 'validateStatus' + ), + username_form_help=Output('register-username-form-item', 'help'), + password_form_help=Output('register-password-form-item', 'help'), + confirm_password_form_help=Output( + 'register-confirm_password-form-item', 'help' + ), + captcha_form_help=Output('register-captcha-form-item', 'help'), + redirect_container=Output( + 'redirect-container', 'children', allow_duplicate=True + ), + register_success=Output('register-success-container', 'data'), + ), + inputs=dict( + button_click=Input('register-submit', 'nClicks'), + keyboard_enter_press=Input( + 'register-keyboard-enter-submit', 'pressedCounts' + ), + ), + state=dict( + username=State('register-username', 'value'), + password=State('register-password', 'value'), + confirm_password=State('register-confirm_password', 'value'), + input_captcha=State('register-captcha', 'value'), + session_id=State('register-captcha_image-session_id-container', 'data'), + captcha_hidden=State('register-captcha-row-container', 'hidden'), + ), + running=[ + [Output('register-submit', 'loading'), True, False], + [Output('register-submit', 'children'), '注册中', '注册'], + [Output('register-captcha-image-container', 'n_clicks'), 0, 1], + ], + prevent_initial_call=True, +) +def register( + button_click, + keyboard_enter_press, + username, + password, + confirm_password, + input_captcha, + session_id, + captcha_hidden, +): + if button_click or keyboard_enter_press: + validate_list = [username, password, confirm_password, input_captcha] + if captcha_hidden: + validate_list = [username, password, confirm_password] + # 校验全部输入值是否不为空 + if all(ValidateUtil.not_empty(item) for item in validate_list): + if password == confirm_password: + register_params = dict( + username=username, + password=password, + confirm_password=confirm_password, + code=input_captcha, + uuid=session_id, + ) + RegisterApi.register(register_params) + MessageManager.success(content='注册成功') + return dict( + username_form_status=None, + password_form_status=None, + confirm_password_form_status=None, + captcha_form_status=None, + username_form_help=None, + password_form_help=None, + confirm_password_form_help=None, + captcha_form_help=None, + redirect_container=dcc.Location( + pathname='/', id='register-redirect' + ), + register_success={'timestamp': time.time()}, + ) + else: + MessageManager.warning(content='两次输入的密码不一致!') + return dict( + username_form_status=None, + password_form_status='error', + confirm_password_form_status='error', + captcha_form_status=None, + username_form_help=None, + password_form_help='两次输入的密码不一致!', + confirm_password_form_help='两次输入的密码不一致!', + captcha_form_help=None, + redirect_container=None, + register_success=None, + ) + + return dict( + username_form_status=None + if ValidateUtil.not_empty(username) + else 'error', + password_form_status=None + if ValidateUtil.not_empty(password) + else 'error', + confirm_password_form_status=None + if ValidateUtil.not_empty(confirm_password) + else 'error', + captcha_form_status=None + if ValidateUtil.not_empty(input_captcha) + else 'error', + username_form_help=None + if ValidateUtil.not_empty(username) + else '请输入用户名!', + password_form_help=None + if ValidateUtil.not_empty(password) + else '请输入密码!', + confirm_password_form_help=None + if ValidateUtil.not_empty(confirm_password) + else '请再次输入密码!', + captcha_form_help=None + if ValidateUtil.not_empty(input_captcha) + else '请输入验证码!', + redirect_container=None, + register_success=None, + ) + + return dict( + username_form_status=no_update, + password_form_status=no_update, + confirm_password_form_status=no_update, + captcha_form_status=no_update, + username_form_help=no_update, + password_form_help=no_update, + confirm_password_form_help=no_update, + captcha_form_help=no_update, + redirect_container=no_update, + register_success=None, + ) + + +@app.callback( + [ + Output('register-captcha-row-container', 'hidden'), + Output('register-captcha-image', 'src'), + Output('register-captcha_image-session_id-container', 'data'), + ], + Input('register-captcha-image-container', 'n_clicks'), + State('register-success-container', 'data'), + prevent_initial_call=True, +) +def change_register_captcha_image(captcha_click, login_success): + if captcha_click and not login_success: + captcha_image_info = LoginApi.get_code_img() + captcha_enabled = captcha_image_info.get('captcha_enabled') + captcha_image = f"data:image/gif;base64,{captcha_image_info.get('img')}" + session_id = captcha_image_info.get('uuid') + + return [ + not captcha_enabled, + captcha_image, + session_id, + ] + + raise PreventUpdate + + +@app.callback( + Output('register-page', 'style'), + Input('url-container', 'pathname'), +) +def random_register_bg(pathname): + return { + 'height': '100vh', + 'overflow': 'auto', + 'WebkitBackgroundSize': '100% 100%', + 'backgroundSize': '100% 100', + 'backgroundImage': 'url({})'.format( + get_asset_url('imgs/background.png') + ), + } diff --git a/dash-fastapi-frontend/callbacks/router_c.py b/dash-fastapi-frontend/callbacks/router_c.py new file mode 100644 index 0000000000000000000000000000000000000000..ba3f14f6b7865539ecc36de4f4d307d56df77e59 --- /dev/null +++ b/dash-fastapi-frontend/callbacks/router_c.py @@ -0,0 +1,135 @@ +from dash import dcc, no_update +from dash.dependencies import Input, Output, State +from flask import session +from importlib import import_module +import views +from api.login import LoginApi +from api.router import RouterApi +from config.router import RouterConfig +from server import app +from utils.cache_util import CacheManager +from utils.router_util import RouterUtil + + +@app.callback( + output=dict( + app_mount=Output('app-mount', 'children'), + redirect_container=Output( + 'redirect-container', 'children', allow_duplicate=True + ), + current_pathname=Output('current-pathname-container', 'data'), + router_list=Output('router-list-container', 'data'), + search_panel_data=Output('search-panel', 'data'), + ), + inputs=dict(pathname=Input('url-container', 'pathname')), + state=dict( + url_trigger=State('url-container', 'trigger'), + session_token=State('token-container', 'data'), + ), + prevent_initial_call=True, +) +def router(pathname, url_trigger, session_token): + """ + 全局路由回调 + """ + # 检查当前会话是否已经登录 + token_result = session.get('Authorization') + # 若已登录 + if token_result and session_token and token_result == session_token: + if url_trigger == 'load': + current_user = LoginApi.get_info() + router_list_result = RouterApi.get_routers() + router_list = router_list_result['data'] + router_list = RouterConfig.CONSTANT_ROUTES + router_list + menu_info = RouterUtil.generate_menu_tree(router_list) + permissions = { + 'perms': current_user['permissions'], + 'roles': current_user['roles'], + } + cache_obj = dict( + user_info=current_user['user'], + permissions=permissions, + menu_info=menu_info, + ) + CacheManager.set(cache_obj) + menu_info = CacheManager.get('menu_info') + valid_pathname_list = RouterUtil.generate_validate_pathname_list( + menu_info + ) + if pathname in valid_pathname_list: + if url_trigger == 'load': + # 根据pathname控制渲染行为 + if pathname in [ + route.get('path') + for route in RouterConfig.WHITE_ROUTES_LIST + ]: + # 重定向到主页面 + return dict( + app_mount=no_update, + redirect_container=dcc.Location( + pathname='/', id='router-redirect' + ), + current_pathname=no_update, + router_list=no_update, + search_panel_data=no_update, + ) + + user_menu_info = RouterUtil.generate_menu_tree( + RouterUtil.get_visible_routers(router_list) + ) + search_panel_data = RouterUtil.generate_search_panel_data( + user_menu_info + ) + # 否则正常渲染主页面 + return dict( + app_mount=views.layout.render(user_menu_info), + redirect_container=None, + current_pathname=pathname, + router_list=menu_info, + search_panel_data=search_panel_data, + ) + + else: + return dict( + app_mount=no_update, + redirect_container=None, + current_pathname=pathname, + router_list=no_update, + search_panel_data=no_update, + ) + + else: + # 渲染404状态页 + return dict( + app_mount=views.page_404.render(), + redirect_container=None, + current_pathname=no_update, + router_list=no_update, + search_panel_data=no_update, + ) + + else: + # 若未登录 + # 根据pathname控制渲染行为 + for route in RouterConfig.WHITE_ROUTES_LIST: + if pathname == route.get('path'): + component = route.get('component') or 'page_404' + + return dict( + app_mount=import_module('views.' + component).render(), + redirect_container=None, + current_pathname=no_update, + router_list=no_update, + search_panel_data=no_update, + ) + + # 否则重定向到登录页 + return dict( + app_mount=no_update, + redirect_container=dcc.Location( + pathname='/login', id='router-redirect' + ), + current_pathname=no_update, + router_list=no_update, + search_panel_data=no_update, + ) diff --git a/dash-fastapi-frontend/callbacks/system_c/config_c.py b/dash-fastapi-frontend/callbacks/system_c/config_c.py index 99a25ac70cc95304d047e7e4c2be39464d390cf9..e516c47c18732c2d78f06b84b60673c8b66c11b5 100644 --- a/dash-fastapi-frontend/callbacks/system_c/config_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/config_c.py @@ -1,114 +1,127 @@ -import dash import time import uuid -from dash import dcc -from dash.dependencies import Input, Output, State, ALL +from dash import ctx, dcc, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.system.config import ConfigApi +from config.constant import SysYesNoConstant from server import app -from utils.common import validate_data_not_empty -from api.config import get_config_list_api, get_config_detail_api, add_config_api, edit_config_api, delete_config_api, export_config_list_api, refresh_config_api +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil + + +def generate_config_table(query_params: Dict): + """ + 根据查询参数获取参数设置表格数据及分页信息 + + :param query_params: 查询参数 + :return: 参数设置表格数据及分页信息 + """ + table_info = ConfigApi.list_config(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['config_type'] = DictManager.get_dict_tag( + dict_type='sys_yes_no', dict_value=item.get('config_type') + ) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['config_id']) + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:config:edit') + else {}, + {'content': '删除', 'type': 'link', 'icon': 'antd-delete'} + if PermissionManager.check_perms('system:config:remove') + else {}, + ] + + return [table_data, table_pagination] @app.callback( output=dict( - config_table_data=Output('config-list-table', 'data', allow_duplicate=True), - config_table_pagination=Output('config-list-table', 'pagination', allow_duplicate=True), + config_table_data=Output( + 'config-list-table', 'data', allow_duplicate=True + ), + config_table_pagination=Output( + 'config-list-table', 'pagination', allow_duplicate=True + ), config_table_key=Output('config-list-table', 'key'), - config_table_selectedrowkeys=Output('config-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + config_table_selectedrowkeys=Output( + 'config-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('config-search', 'nClicks'), refresh_click=Input('config-refresh', 'nClicks'), pagination=Input('config-list-table', 'pagination'), - operations=Input('config-operations-store', 'data') + operations=Input('config-operations-store', 'data'), ), state=dict( config_name=State('config-config_name-input', 'value'), config_key=State('config-config_key-input', 'value'), config_type=State('config-config_type-select', 'value'), create_time_range=State('config-create_time-range', 'value'), - button_perms=State('config-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_config_table_data(search_click, refresh_click, pagination, operations, config_name, config_key, config_type, create_time_range, button_perms): +def get_config_table_data( + search_click, + refresh_click, + pagination, + operations, + config_name, + config_key, + config_type, + create_time_range, +): """ 获取参数设置表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - create_time_start = None - create_time_end = None + begin_time = None + end_time = None if create_time_range: - create_time_start = create_time_range[0] - create_time_end = create_time_range[1] + begin_time = create_time_range[0] + end_time = create_time_range[1] query_params = dict( config_name=config_name, config_key=config_key, config_type=config_type, - create_time_start=create_time_start, - create_time_end=create_time_end, + begin_time=begin_time, + end_time=end_time, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'config-list-table': - query_params = dict( - config_name=config_name, - config_key=config_key, - config_type=config_type, - create_time_start=create_time_start, - create_time_end=create_time_end, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_config_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['config_type'] == 'Y': - item['config_type'] = dict(tag='是', color='blue') - else: - item['config_type'] = dict(tag='否', color='volcano') - item['key'] = str(item['config_id']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:config:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:config:remove' in button_perms else {}, - ] - - return dict( - config_table_data=table_data, - config_table_pagination=table_pagination, - config_table_key=str(uuid.uuid4()), - config_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) + table_data, table_pagination = generate_config_table(query_params) return dict( - config_table_data=dash.no_update, - config_table_pagination=dash.no_update, - config_table_key=dash.no_update, - config_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + config_table_data=table_data, + config_table_pagination=table_pagination, + config_table_key=str(uuid.uuid4()), + config_table_selectedrowkeys=None, ) raise PreventUpdate @@ -116,27 +129,29 @@ def get_config_table_data(search_click, refresh_click, pagination, operations, c # 重置参数设置搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('config-config_name-input', 'value'), - Output('config-config_key-input', 'value'), - Output('config-config_type-select', 'value'), - Output('config-create_time-range', 'value'), - Output('config-operations-store', 'data')], + """, + [ + Output('config-config_name-input', 'value'), + Output('config-config_key-input', 'value'), + Output('config-config_type-select', 'value'), + Output('config-create_time-range', 'value'), + Output('config-operations-store', 'data'), + ], Input('config-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示参数设置搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -146,317 +161,374 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('config-search-form-container', 'hidden'), - Output('config-hidden-tooltip', 'title')], + """, + [ + Output('config-search-form-container', 'hidden'), + Output('config-hidden-tooltip', 'title'), + ], Input('config-hidden', 'nClicks'), State('config-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'config-operation-button', 'index': 'edit'}, 'disabled'), Input('config-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_config_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - - return False - return True - raise PreventUpdate - - -@app.callback( +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'config-operation-button', 'index': 'delete'}, 'disabled'), Input('config-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_config_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - return False - return True - - raise PreventUpdate +# 参数配置表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'config-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'config-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('config-form-store', 'data', allow_duplicate=True), + Output('config-form', 'values'), + ], + [ + Input('config-form-store', 'data'), + Input('config-form', 'values'), + ], + prevent_initial_call=True, +) @app.callback( output=dict( modal_visible=Output('config-modal', 'visible', allow_duplicate=True), modal_title=Output('config-modal', 'title'), - form_value=Output({'type': 'config-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'config-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'config-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('config-edit-id-store', 'data'), - modal_type=Output('config-operations-store-bk', 'data') + form_value=Output('config-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'config-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'config-form', 'helps', allow_duplicate=True + ), + modal_type=Output('config-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'config-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('config-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'config-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('config-list-table', 'nClicksButton'), ), state=dict( selected_row_keys=State('config-list-table', 'selectedRowKeys'), clicked_content=State('config-list-table', 'clickedContent'), - recently_button_clicked_row=State('config-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'config-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_config_modal(operation_click, button_click, selected_row_keys, clicked_content, recently_button_clicked_row): +def add_edit_config_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示新增或编辑参数设置弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'config-operation-button'} \ - or trigger_id == {'index': 'edit', 'type': 'config-operation-button'} \ - or (trigger_id == 'config-list-table' and clicked_content == '修改'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'config-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'config-operation-button'} + or (trigger_id == 'config-list-table' and clicked_content == '修改') + ): if trigger_id == {'index': 'add', 'type': 'config-operation-button'}: - config_info = dict(config_name=None, config_key=None, config_value=None, config_type='Y', remark=None) + config_info = dict( + config_name=None, + config_key=None, + config_value=None, + config_type=SysYesNoConstant.YES, + remark=None, + ) return dict( modal_visible=True, modal_title='新增参数', - form_value=[config_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger=dash.no_update, - edit_row_info=None, - modal_type={'type': 'add'} + form_value=config_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, ) - elif trigger_id == {'index': 'edit', 'type': 'config-operation-button'} or (trigger_id == 'config-list-table' and clicked_content == '修改'): - if trigger_id == {'index': 'edit', 'type': 'config-operation-button'}: + elif trigger_id == { + 'index': 'edit', + 'type': 'config-operation-button', + } or (trigger_id == 'config-list-table' and clicked_content == '修改'): + if trigger_id == { + 'index': 'edit', + 'type': 'config-operation-button', + }: config_id = int(','.join(selected_row_keys)) else: config_id = int(recently_button_clicked_row['key']) - config_info_res = get_config_detail_api(config_id=config_id) - if config_info_res['code'] == 200: - config_info = config_info_res['data'] - return dict( - modal_visible=True, - modal_title='编辑参数', - form_value=[config_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=config_info if config_info else None, - modal_type={'type': 'edit'} - ) - - return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type=None - ) + config_info_res = ConfigApi.get_config(config_id=config_id) + config_info = config_info_res['data'] + return dict( + modal_visible=True, + modal_title='编辑参数', + form_value=config_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, + ) raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'config-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'config-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + 'config-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'config-form', 'helps', allow_duplicate=True + ), modal_visible=Output('config-modal', 'visible'), - operations=Output('config-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('config-modal', 'okCounts') + operations=Output( + 'config-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('config-modal', 'okCounts')), state=dict( - modal_type=State('config-operations-store-bk', 'data'), - edit_row_info=State('config-edit-id-store', 'data'), - form_value=State({'type': 'config-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'config-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('config-modal_type-store', 'data'), + form_value=State('config-form-store', 'data'), + form_label=State( + {'type': 'config-form-label', 'index': ALL, 'required': True}, + 'label', + ), ), - prevent_initial_call=True + running=[[Output('config-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def config_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label): +def config_confirm(confirm_trigger, modal_type, form_value, form_label): """ 新增或编辑参数设置弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params_add = form_value_state + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value params_edit = params_add.copy() - params_edit['config_id'] = edit_row_info.get('config_id') if edit_row_info else None - api_res = {} modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_config_api(params_add) + ConfigApi.add_config(params_add) if modal_type == 'edit': - api_res = edit_config_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + ConfigApi.update_config(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('config-delete-text', 'children'), - Output('config-delete-confirm-modal', 'visible'), - Output('config-delete-ids-store', 'data')], - [Input({'type': 'config-operation-button', 'index': ALL}, 'nClicks'), - Input('config-list-table', 'nClicksButton')], - [State('config-list-table', 'selectedRowKeys'), - State('config-list-table', 'clickedContent'), - State('config-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('config-delete-text', 'children'), + Output('config-delete-confirm-modal', 'visible'), + Output('config-delete-ids-store', 'data'), + ], + [ + Input({'type': 'config-operation-button', 'index': ALL}, 'nClicks'), + Input('config-list-table', 'nClicksButton'), + ], + [ + State('config-list-table', 'selectedRowKeys'), + State('config-list-table', 'clickedContent'), + State('config-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def config_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def config_delete_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示删除参数设置二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id == {'index': 'delete', 'type': 'config-operation-button'} or ( - trigger_id == 'config-list-table' and clicked_content == '删除'): - + trigger_id == 'config-list-table' and clicked_content == '删除' + ): if trigger_id == {'index': 'delete', 'type': 'config-operation-button'}: config_ids = ','.join(selected_row_keys) else: if clicked_content == '删除': config_ids = recently_button_clicked_row['key'] else: - return dash.no_update + return no_update return [ f'是否确认删除参数编号为{config_ids}的参数设置?', True, - {'config_ids': config_ids} + config_ids, ] raise PreventUpdate @app.callback( - [Output('config-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('config-operations-store', 'data', allow_duplicate=True), Input('config-delete-confirm-modal', 'okCounts'), State('config-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def config_delete_confirm(delete_confirm, config_ids_data): """ 删除参数设置弹窗确认回调,实现删除操作 """ if delete_confirm: - params = config_ids_data - delete_button_info = delete_config_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + ConfigApi.del_config(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('config-export-container', 'data', allow_duplicate=True), - Output('config-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('config-export-container', 'data', allow_duplicate=True), + Output('config-export-complete-judge-container', 'data'), + ], Input('config-export', 'nClicks'), - prevent_initial_call=True + [ + State('config-config_name-input', 'value'), + State('config-config_key-input', 'value'), + State('config-config_type-select', 'value'), + State('config-create_time-range', 'value'), + ], + running=[[Output('config-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_config_list(export_click): +def export_config_list( + export_click, config_name, config_key, config_type, create_time_range +): """ 导出参数设置信息回调 """ if export_click: - export_config_res = export_config_list_api({}) - if export_config_res.status_code == 200: - export_config = export_config_res.content - - return [ - dcc.send_bytes(export_config, f'参数配置信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + begin_time = None + end_time = None + if create_time_range: + begin_time = create_time_range[0] + end_time = create_time_range[1] + export_params = dict( + config_name=config_name, + config_key=config_key, + config_type=config_type, + begin_time=begin_time, + end_time=end_time, + ) + export_config_res = ConfigApi.export_config(export_params) + export_config = export_config_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_config, + f'参数配置信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -465,7 +537,7 @@ def export_config_list(export_click): @app.callback( Output('config-export-container', 'data', allow_duplicate=True), Input('config-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_config_export_status(data): """ @@ -473,33 +545,25 @@ def reset_config_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate @app.callback( - [Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('config-refresh-cache', 'nClicks'), Input('config-refresh-cache', 'nClicks'), - prevent_initial_call=True + running=[[Output('config-refresh-cache', 'loading'), True, False]], + prevent_initial_call=True, ) def refresh_config_cache(refresh_click): """ 刷新缓存回调 """ if refresh_click: - refresh_info_res = refresh_config_api({}) - if refresh_info_res.get('code') == 200: - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('刷新成功', type='success') - ] + ConfigApi.refresh_cache() + MessageManager.success(content='刷新成功') - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('刷新失败', type='error') - ] + return no_update raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/dept_c.py b/dash-fastapi-frontend/callbacks/system_c/dept_c.py index 877f587132965a96afc1da825cd97d2411220df9..8252663f03563a7d1e9de22a320de47e07f634ef 100644 --- a/dash-fastapi-frontend/callbacks/system_c/dept_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/dept_c.py @@ -1,161 +1,168 @@ -import dash -import time import uuid +from dash import ctx, no_update from dash.dependencies import Input, Output, State, ALL from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.system.dept import DeptApi +from config.constant import SysNormalDisableConstant from server import app -from utils.common import validate_data_not_empty -from utils.tree_tool import list_to_tree -from api.dept import get_dept_tree_api, get_dept_list_api, add_dept_api, edit_dept_api, delete_dept_api, \ - get_dept_detail_api, get_dept_tree_for_edit_option_api +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil +from utils.tree_util import TreeUtil + + +def generate_dept_table(query_params: Dict): + """ + 根据查询参数获取部门表格数据及展开信息 + + :param query_params: 查询参数 + :return: 部门表格数据及展开信息 + """ + table_info = DeptApi.list_dept(query_params) + default_expanded_row_keys = [] + table_data = table_info['data'] + for item in table_data: + default_expanded_row_keys.append(str(item['dept_id'])) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['dept_id']) + if item['parent_id'] == 0: + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:dept:edit') + else {}, + {'content': '新增', 'type': 'link', 'icon': 'antd-plus'} + if PermissionManager.check_perms('system:dept:add') + else {}, + ] + elif item['status'] == '1': + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:dept:edit') + else {}, + { + 'content': '删除', + 'type': 'link', + 'icon': 'antd-delete', + } + if PermissionManager.check_perms('system:dept:remove') + else {}, + ] + else: + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:dept:edit') + else {}, + {'content': '新增', 'type': 'link', 'icon': 'antd-plus'} + if PermissionManager.check_perms('system:dept:add') + else {}, + { + 'content': '删除', + 'type': 'link', + 'icon': 'antd-delete', + } + if PermissionManager.check_perms('system:dept:remove') + else {}, + ] + item['status'] = DictManager.get_dict_tag( + dict_type='sys_normal_disable', dict_value=item.get('status') + ) + table_data_new = TreeUtil.list_to_tree(table_data, 'dept_id', 'parent_id') + + return [table_data_new, default_expanded_row_keys] @app.callback( output=dict( dept_table_data=Output('dept-list-table', 'data', allow_duplicate=True), dept_table_key=Output('dept-list-table', 'key'), - dept_table_defaultexpandedrowkeys=Output('dept-list-table', 'defaultExpandedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - fold_click=Output('dept-fold', 'nClicks') + dept_table_defaultexpandedrowkeys=Output( + 'dept-list-table', 'defaultExpandedRowKeys' + ), + fold_click=Output('dept-fold', 'nClicks'), ), inputs=dict( search_click=Input('dept-search', 'nClicks'), refresh_click=Input('dept-refresh', 'nClicks'), operations=Input('dept-operations-store', 'data'), - fold_click=Input('dept-fold', 'nClicks') + fold_click=Input('dept-fold', 'nClicks'), ), state=dict( dept_name=State('dept-dept_name-input', 'value'), status_select=State('dept-status-select', 'value'), - in_default_expanded_row_keys=State('dept-list-table', 'defaultExpandedRowKeys'), - button_perms=State('dept-button-perms-container', 'data') + in_default_expanded_row_keys=State( + 'dept-list-table', 'defaultExpandedRowKeys' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_dept_table_data(search_click, refresh_click, operations, fold_click, dept_name, status_select, - in_default_expanded_row_keys, button_perms): +def get_dept_table_data( + search_click, + refresh_click, + operations, + fold_click, + dept_name, + status_select, + in_default_expanded_row_keys, +): """ 获取部门表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - query_params = dict( - dept_name=dept_name, - status=status_select - ) + query_params = dict(dept_name=dept_name, status=status_select) if search_click or refresh_click or operations or fold_click: - table_info = get_dept_list_api(query_params) - default_expanded_row_keys = [] - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - for item in table_data: - default_expanded_row_keys.append(str(item['dept_id'])) - item['key'] = str(item['dept_id']) - if item['parent_id'] == 0: - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dept:edit' in button_perms else {}, - { - 'content': '新增', - 'type': 'link', - 'icon': 'antd-plus' - } if 'system:dept:add' in button_perms else {}, - ] - elif item['status'] == '1': - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dept:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dept:remove' in button_perms else {}, - ] - else: - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dept:edit' in button_perms else {}, - { - 'content': '新增', - 'type': 'link', - 'icon': 'antd-plus' - } if 'system:dept:add' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dept:remove' in button_perms else {}, - ] - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - table_data_new = list_to_tree(table_data, 'dept_id', 'parent_id') - - if fold_click: - if in_default_expanded_row_keys: - return dict( - dept_table_data=table_data_new, - dept_table_key=str(uuid.uuid4()), - dept_table_defaultexpandedrowkeys=[], - api_check_token_trigger={'timestamp': time.time()}, - fold_click=None - ) - - return dict( - dept_table_data=table_data_new, - dept_table_key=str(uuid.uuid4()), - dept_table_defaultexpandedrowkeys=default_expanded_row_keys, - api_check_token_trigger={'timestamp': time.time()}, - fold_click=None - ) + table_data, default_expanded_row_keys = generate_dept_table( + query_params + ) + if fold_click: + if in_default_expanded_row_keys: + return dict( + dept_table_data=table_data, + dept_table_key=str(uuid.uuid4()), + dept_table_defaultexpandedrowkeys=[], + fold_click=None, + ) return dict( - dept_table_data=dash.no_update, - dept_table_key=dash.no_update, - dept_table_defaultexpandedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - fold_click=None + dept_table_data=table_data, + dept_table_key=str(uuid.uuid4()), + dept_table_defaultexpandedrowkeys=default_expanded_row_keys, + fold_click=None, ) return dict( - dept_table_data=dash.no_update, - dept_table_key=dash.no_update, - dept_table_defaultexpandedrowkeys=dash.no_update, - api_check_token_trigger=dash.no_update, - fold_click=None + dept_table_data=no_update, + dept_table_key=no_update, + dept_table_defaultexpandedrowkeys=no_update, + fold_click=None, ) # 重置部门搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('dept-dept_name-input', 'value'), - Output('dept-status-select', 'value'), - Output('dept-operations-store', 'data')], + """, + [ + Output('dept-dept_name-input', 'value'), + Output('dept-status-select', 'value'), + Output('dept-operations-store', 'data'), + ], Input('dept-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示部门搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -165,12 +172,41 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('dept-search-form-container', 'hidden'), - Output('dept-hidden-tooltip', 'title')], + """, + [ + Output('dept-search-form-container', 'hidden'), + Output('dept-hidden-tooltip', 'title'), + ], Input('dept-hidden', 'nClicks'), State('dept-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, +) + + +# 部门表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'dept-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'dept-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('dept-form-store', 'data', allow_duplicate=True), + Output('dept-form', 'values'), + ], + [ + Input('dept-form-store', 'data'), + Input('dept-form', 'values'), + ], + prevent_initial_call=True, ) @@ -179,239 +215,236 @@ app.clientside_callback( modal_visible=Output('dept-modal', 'visible', allow_duplicate=True), modal_title=Output('dept-modal', 'title'), parent_id_div_ishidden=Output('dept-parent_id-div', 'hidden'), - parent_id_tree=Output({'type': 'dept-form-value', 'index': 'parent_id'}, 'treeData'), - form_value=Output({'type': 'dept-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'dept-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'dept-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('dept-edit-id-store', 'data'), - modal_type=Output('dept-operations-store-bk', 'data') + parent_id_tree=Output('dept-tree-select', 'treeData'), + form_value=Output('dept-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'dept-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'dept-form', 'helps', allow_duplicate=True + ), + modal_type=Output('dept-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'dept-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('dept-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'dept-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('dept-list-table', 'nClicksButton'), ), state=dict( clicked_content=State('dept-list-table', 'clickedContent'), - recently_button_clicked_row=State('dept-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'dept-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_dept_modal(operation_click, button_click, clicked_content, recently_button_clicked_row): +def add_edit_dept_modal( + operation_click, button_click, clicked_content, recently_button_clicked_row +): """ 显示新增或编辑部门弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id == {'index': 'add', 'type': 'dept-operation-button'} or ( - trigger_id == 'dept-list-table' and clicked_content != '删除'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[4]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[5]] - dept_params = dict(dept_name='') + trigger_id == 'dept-list-table' and clicked_content != '删除' + ): if trigger_id == 'dept-list-table' and clicked_content == '修改': - dept_params['dept_id'] = int(recently_button_clicked_row['key']) - tree_info = get_dept_tree_for_edit_option_api(dept_params) + tree_info = DeptApi.list_dept_exclude_child( + dept_id=int(recently_button_clicked_row['key']) + ) else: - tree_info = get_dept_tree_api(dept_params) - if tree_info['code'] == 200: - tree_data = tree_info['data'] - - if trigger_id == {'index': 'add', 'type': 'dept-operation-button'} or (trigger_id == 'dept-list-table' and clicked_content == '新增'): - dept_info = dict( - parent_id=None if trigger_id == {'index': 'add', 'type': 'dept-operation-button'} else str(recently_button_clicked_row['key']), - dept_name=None, - order_num=None, - leader=None, - phone=None, - email=None, - status='0', - ) - return dict( - modal_visible=True, - modal_title='新增部门', - parent_id_div_ishidden=False, - parent_id_tree=tree_data, - form_value=[dept_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type={'type': 'add'} - ) - elif trigger_id == 'dept-list-table' and clicked_content == '修改': - dept_id = int(recently_button_clicked_row['key']) - dept_info_res = get_dept_detail_api(dept_id=dept_id) - if dept_info_res['code'] == 200: - dept_info = dept_info_res['data'] - return dict( - modal_visible=True, - modal_title='编辑部门', - parent_id_div_ishidden=dept_info.get('parent_id') == 0, - parent_id_tree=tree_data, - form_value=[dept_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=dept_info, - modal_type={'type': 'edit'} - ) - - return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - parent_id_div_ishidden=dash.no_update, - parent_id_tree=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type=None + dept_params = dict(dept_name='') + tree_info = DeptApi.list_dept(dept_params) + tree_data = TreeUtil.list_to_tree_select( + tree_info['data'], 'dept_name', 'dept_id', 'dept_id', 'parent_id' ) + if trigger_id == { + 'index': 'add', + 'type': 'dept-operation-button', + } or (trigger_id == 'dept-list-table' and clicked_content == '新增'): + dept_info = dict( + parent_id=None + if trigger_id + == {'index': 'add', 'type': 'dept-operation-button'} + else str(recently_button_clicked_row['key']), + dept_name=None, + order_num=None, + leader=None, + phone=None, + email=None, + status=SysNormalDisableConstant.NORMAL, + ) + return dict( + modal_visible=True, + modal_title='新增部门', + parent_id_div_ishidden=False, + parent_id_tree=tree_data, + form_value=dept_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, + ) + elif trigger_id == 'dept-list-table' and clicked_content == '修改': + dept_id = int(recently_button_clicked_row['key']) + dept_info_res = DeptApi.get_dept(dept_id=dept_id) + dept_info = dept_info_res['data'] + return dict( + modal_visible=True, + modal_title='编辑部门', + parent_id_div_ishidden=dept_info.get('parent_id') == 0, + parent_id_tree=tree_data, + form_value=dept_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, + ) + raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'dept-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'dept-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + 'dept-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'dept-form', 'helps', allow_duplicate=True + ), modal_visible=Output('dept-modal', 'visible'), - operations=Output('dept-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('dept-modal', 'okCounts') + operations=Output( + 'dept-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('dept-modal', 'okCounts')), state=dict( - modal_type=State('dept-operations-store-bk', 'data'), - edit_row_info=State('dept-edit-id-store', 'data'), - form_value=State({'type': 'dept-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'dept-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('dept-modal_type-store', 'data'), + form_value=State('dept-form-store', 'data'), + form_label=State( + {'type': 'dept-form-label', 'index': ALL, 'required': True}, 'label' + ), ), - prevent_initial_call=True + running=[[Output('dept-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def dept_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label): +def dept_confirm(confirm_trigger, modal_type, form_value, form_label): """ 新增或编辑部门弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params_add = form_value_state + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value params_edit = params_add.copy() - params_edit['dept_id'] = edit_row_info.get('dept_id') if edit_row_info else None - api_res = {} modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_dept_api(params_add) + DeptApi.add_dept(params_add) if modal_type == 'edit': - api_res = edit_dept_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + DeptApi.update_dept(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('dept-delete-text', 'children'), - Output('dept-delete-confirm-modal', 'visible'), - Output('dept-delete-ids-store', 'data')], + [ + Output('dept-delete-text', 'children'), + Output('dept-delete-confirm-modal', 'visible'), + Output('dept-delete-ids-store', 'data'), + ], [Input('dept-list-table', 'nClicksButton')], - [State('dept-list-table', 'clickedContent'), - State('dept-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + State('dept-list-table', 'clickedContent'), + State('dept-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def dept_delete_modal(button_click, clicked_content, recently_button_clicked_row): +def dept_delete_modal( + button_click, clicked_content, recently_button_clicked_row +): """ 显示删除部门二次确认弹窗回调 """ if button_click: - if clicked_content == '删除': dept_ids = recently_button_clicked_row['key'] else: - return dash.no_update + raise PreventUpdate return [ f'是否确认删除部门编号为{dept_ids}的部门?', True, - {'dept_ids': dept_ids} + dept_ids, ] raise PreventUpdate @app.callback( - [Output('dept-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('dept-operations-store', 'data', allow_duplicate=True), Input('dept-delete-confirm-modal', 'okCounts'), State('dept-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def dept_delete_confirm(delete_confirm, dept_ids_data): """ 删除部门弹窗确认回调,实现删除操作 """ if delete_confirm: - params = dept_ids_data - delete_button_info = delete_dept_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + DeptApi.del_dept(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_c.py b/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_c.py index a4698f1cc952e35ad1347d66e48fc38c113c1b24..0db17a475b23a4125ffea53c4a40285655b2c630 100644 --- a/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_c.py @@ -1,118 +1,131 @@ -import dash import time import uuid -from dash import dcc +from dash import ctx, dcc, no_update from dash.dependencies import Input, Output, State, ALL from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.system.dict.type import DictTypeApi +from config.constant import SysNormalDisableConstant from server import app -from utils.common import validate_data_not_empty -from api.dict import get_dict_type_list_api, get_all_dict_type_api, get_dict_type_detail_api, add_dict_type_api, edit_dict_type_api, delete_dict_type_api, export_dict_type_list_api, refresh_dict_api +from utils.cache_util import TTLCacheManager +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil + + +def generate_dict_type_table(query_params: Dict): + """ + 根据查询参数获取字典类型表格数据及分页信息 + + :param query_params: 查询参数 + :return: 字典类型表格数据及分页信息 + """ + table_info = DictTypeApi.list_type(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status'] = DictManager.get_dict_tag( + dict_type='sys_normal_disable', dict_value=item.get('status') + ) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['dict_id']) + item['dict_type'] = { + 'content': item['dict_type'], + 'type': 'link', + } + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:dict:edit') + else {}, + {'content': '删除', 'type': 'link', 'icon': 'antd-delete'} + if PermissionManager.check_perms('system:dict:remove') + else {}, + ] + + return [table_data, table_pagination] @app.callback( output=dict( - dict_type_table_data=Output('dict_type-list-table', 'data', allow_duplicate=True), - dict_type_table_pagination=Output('dict_type-list-table', 'pagination', allow_duplicate=True), + dict_type_table_data=Output( + 'dict_type-list-table', 'data', allow_duplicate=True + ), + dict_type_table_pagination=Output( + 'dict_type-list-table', 'pagination', allow_duplicate=True + ), dict_type_table_key=Output('dict_type-list-table', 'key'), - dict_type_table_selectedrowkeys=Output('dict_type-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + dict_type_table_selectedrowkeys=Output( + 'dict_type-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('dict_type-search', 'nClicks'), refresh_click=Input('dict_type-refresh', 'nClicks'), pagination=Input('dict_type-list-table', 'pagination'), - operations=Input('dict_type-operations-store', 'data') + operations=Input('dict_type-operations-store', 'data'), ), state=dict( dict_name=State('dict_type-dict_name-input', 'value'), dict_type=State('dict_type-dict_type-input', 'value'), status_select=State('dict_type-status-select', 'value'), create_time_range=State('dict_type-create_time-range', 'value'), - button_perms=State('dict_type-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_dict_type_table_data(search_click, refresh_click, pagination, operations, dict_name, dict_type, status_select, create_time_range, button_perms): +def get_dict_type_table_data( + search_click, + refresh_click, + pagination, + operations, + dict_name, + dict_type, + status_select, + create_time_range, +): """ 获取字典类型表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - create_time_start = None - create_time_end = None + begin_time = None + end_time = None if create_time_range: - create_time_start = create_time_range[0] - create_time_end = create_time_range[1] + begin_time = create_time_range[0] + end_time = create_time_range[1] query_params = dict( dict_name=dict_name, dict_type=dict_type, status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, + begin_time=begin_time, + end_time=end_time, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'dict_type-list-table': - query_params = dict( - dict_name=dict_name, - dict_type=dict_type, - status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_dict_type_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['dict_id']) - item['dict_type'] = { - 'content': item['dict_type'], - 'type': 'link', - } - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dict:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dict:remove' in button_perms else {}, - ] - - return dict( - dict_type_table_data=table_data, - dict_type_table_pagination=table_pagination, - dict_type_table_key=str(uuid.uuid4()), - dict_type_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) - + table_data, table_pagination = generate_dict_type_table(query_params) return dict( - dict_type_table_data=dash.no_update, - dict_type_table_pagination=dash.no_update, - dict_type_table_key=dash.no_update, - dict_type_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + dict_type_table_data=table_data, + dict_type_table_pagination=table_pagination, + dict_type_table_key=str(uuid.uuid4()), + dict_type_table_selectedrowkeys=None, ) raise PreventUpdate @@ -120,27 +133,29 @@ def get_dict_type_table_data(search_click, refresh_click, pagination, operations # 重置字典类型搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('dict_type-dict_name-input', 'value'), - Output('dict_type-dict_type-input', 'value'), - Output('dict_type-status-select', 'value'), - Output('dict_type-create_time-range', 'value'), - Output('dict_type-operations-store', 'data')], + """, + [ + Output('dict_type-dict_name-input', 'value'), + Output('dict_type-dict_type-input', 'value'), + Output('dict_type-status-select', 'value'), + Output('dict_type-create_time-range', 'value'), + Output('dict_type-operations-store', 'data'), + ], Input('dict_type-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示字典类型搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -150,368 +165,454 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('dict_type-search-form-container', 'hidden'), - Output('dict_type-hidden-tooltip', 'title')], + """, + [ + Output('dict_type-search-form-container', 'hidden'), + Output('dict_type-hidden-tooltip', 'title'), + ], Input('dict_type-hidden', 'nClicks'), State('dict_type-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'dict_type-operation-button', 'index': 'edit'}, 'disabled'), Input('dict_type-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_dict_type_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - - return False - - return True - - raise PreventUpdate -@app.callback( - Output({'type': 'dict_type-operation-button', 'index': 'delete'}, 'disabled'), +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, + Output( + {'type': 'dict_type-operation-button', 'index': 'delete'}, 'disabled' + ), Input('dict_type-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_dict_type_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - return True - raise PreventUpdate +# 字典类型表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'dict_type-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'dict_type-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('dict_type-form-store', 'data', allow_duplicate=True), + Output('dict_type-form', 'values'), + ], + [ + Input('dict_type-form-store', 'data'), + Input('dict_type-form', 'values'), + ], + prevent_initial_call=True, +) @app.callback( output=dict( - modal_visible=Output('dict_type-modal', 'visible', allow_duplicate=True), + modal_visible=Output( + 'dict_type-modal', 'visible', allow_duplicate=True + ), modal_title=Output('dict_type-modal', 'title'), - form_value=Output({'type': 'dict_type-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'dict_type-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'dict_type-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('dict_type-edit-id-store', 'data'), - modal_type=Output('dict_type-operations-store-bk', 'data') + form_value=Output('dict_type-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'dict_type-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'dict_type-form', 'helps', allow_duplicate=True + ), + modal_type=Output('dict_type-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'dict_type-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('dict_type-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'dict_type-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('dict_type-list-table', 'nClicksButton'), ), state=dict( selected_row_keys=State('dict_type-list-table', 'selectedRowKeys'), clicked_content=State('dict_type-list-table', 'clickedContent'), - recently_button_clicked_row=State('dict_type-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'dict_type-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_dict_type_modal(operation_click, button_click, selected_row_keys, clicked_content, recently_button_clicked_row): +def add_edit_dict_type_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示新增或编辑字典类型弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'dict_type-operation-button'} \ - or trigger_id == {'index': 'edit', 'type': 'dict_type-operation-button'} \ - or (trigger_id == 'dict_type-list-table' and clicked_content == '修改'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'dict_type-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'dict_type-operation-button'} + or (trigger_id == 'dict_type-list-table' and clicked_content == '修改') + ): if trigger_id == {'index': 'add', 'type': 'dict_type-operation-button'}: - dict_type_info = dict(dict_name=None, dict_type=None, status='0', remark=None,) + dict_type_info = dict( + dict_name=None, + dict_type=None, + status=SysNormalDisableConstant.NORMAL, + remark=None, + ) return dict( modal_visible=True, modal_title='新增字典类型', - form_value=[dict_type_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger=dash.no_update, - edit_row_info=None, - modal_type={'type': 'add'} + form_value=dict_type_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, ) - elif trigger_id == {'index': 'edit', 'type': 'dict_type-operation-button'} or (trigger_id == 'dict_type-list-table' and clicked_content == '修改'): - if trigger_id == {'index': 'edit', 'type': 'dict_type-operation-button'}: + elif trigger_id == { + 'index': 'edit', + 'type': 'dict_type-operation-button', + } or ( + trigger_id == 'dict_type-list-table' and clicked_content == '修改' + ): + if trigger_id == { + 'index': 'edit', + 'type': 'dict_type-operation-button', + }: dict_id = int(','.join(selected_row_keys)) else: dict_id = int(recently_button_clicked_row['key']) - dict_type_info_res = get_dict_type_detail_api(dict_id=dict_id) - if dict_type_info_res['code'] == 200: - dict_type_info = dict_type_info_res['data'] - return dict( - modal_visible=True, - modal_title='编辑字典类型', - form_value=[dict_type_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=dict_type_info if dict_type_info else None, - modal_type={'type': 'edit'} - ) - - return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type=None - ) + dict_type_info_res = DictTypeApi.get_type(dict_id=dict_id) + dict_type_info = dict_type_info_res['data'] + return dict( + modal_visible=True, + modal_title='编辑字典类型', + form_value=dict_type_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, + ) raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'dict_type-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'dict_type-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + 'dict_type-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'dict_type-form', 'helps', allow_duplicate=True + ), modal_visible=Output('dict_type-modal', 'visible'), - operations=Output('dict_type-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('dict_type-modal', 'okCounts') + operations=Output( + 'dict_type-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('dict_type-modal', 'okCounts')), state=dict( - modal_type=State('dict_type-operations-store-bk', 'data'), - edit_row_info=State('dict_type-edit-id-store', 'data'), - form_value=State({'type': 'dict_type-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'dict_type-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('dict_type-modal_type-store', 'data'), + form_value=State('dict_type-form-store', 'data'), + form_label=State( + {'type': 'dict_type-form-label', 'index': ALL, 'required': True}, + 'label', + ), ), - prevent_initial_call=True + running=[[Output('dict_type-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def dict_type_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label): +def dict_type_confirm(confirm_trigger, modal_type, form_value, form_label): """ 新增或编字典类型弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params_add = form_value_state + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value params_edit = params_add.copy() - params_edit['dict_id'] = edit_row_info.get('dict_id') if edit_row_info else None - api_res = {} modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_dict_type_api(params_add) + DictTypeApi.add_type(params_add) if modal_type == 'edit': - api_res = edit_dict_type_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + DictTypeApi.update_type(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + TTLCacheManager.delete(form_value.get('dict_type')) + MessageManager.success(content='编辑成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('dict_type-delete-text', 'children'), - Output('dict_type-delete-confirm-modal', 'visible'), - Output('dict_type-delete-ids-store', 'data')], - [Input({'type': 'dict_type-operation-button', 'index': ALL}, 'nClicks'), - Input('dict_type-list-table', 'nClicksButton')], - [State('dict_type-list-table', 'selectedRowKeys'), - State('dict_type-list-table', 'clickedContent'), - State('dict_type-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('dict_type-delete-text', 'children'), + Output('dict_type-delete-confirm-modal', 'visible'), + Output('dict_type-delete-ids-store', 'data'), + ], + [ + Input({'type': 'dict_type-operation-button', 'index': ALL}, 'nClicks'), + Input('dict_type-list-table', 'nClicksButton'), + ], + [ + State('dict_type-list-table', 'selectedRows'), + State('dict_type-list-table', 'clickedContent'), + State('dict_type-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def dict_type_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def dict_type_delete_modal( + operation_click, + button_click, + selected_rows, + clicked_content, + recently_button_clicked_row, +): """ 显示删除字典类型二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'delete', 'type': 'dict_type-operation-button'} or ( - trigger_id == 'dict_type-list-table' and clicked_content == '删除'): - - if trigger_id == {'index': 'delete', 'type': 'dict_type-operation-button'}: - dict_ids = ','.join(selected_row_keys) + trigger_id = ctx.triggered_id + if trigger_id == { + 'index': 'delete', + 'type': 'dict_type-operation-button', + } or (trigger_id == 'dict_type-list-table' and clicked_content == '删除'): + if trigger_id == { + 'index': 'delete', + 'type': 'dict_type-operation-button', + }: + dict_ids = ','.join( + [str(item.get('dict_id')) for item in selected_rows] + ) + dict_types = ','.join( + [item.get('dict_type').get('content') for item in selected_rows] + ) else: if clicked_content == '删除': dict_ids = recently_button_clicked_row['key'] + dict_types = recently_button_clicked_row['dict_type'].get( + 'content' + ) else: - return dash.no_update + return no_update return [ f'是否确认删除字典编号为{dict_ids}的字典类型?', True, - {'dict_ids': dict_ids} + {'dict_ids': dict_ids, 'dict_types': dict_types}, ] raise PreventUpdate @app.callback( - [Output('dict_type-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('dict_type-operations-store', 'data', allow_duplicate=True), Input('dict_type-delete-confirm-modal', 'okCounts'), State('dict_type-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def dict_type_delete_confirm(delete_confirm, dict_ids_data): """ 删除字典类型弹窗确认回调,实现删除操作 """ if delete_confirm: + params = dict_ids_data.get('dict_ids') + dict_types = dict_ids_data.get('dict_types') + DictTypeApi.del_type(params) + TTLCacheManager.delete(dict_types) + MessageManager.success(content='删除成功') - params = dict_ids_data - delete_button_info = delete_dict_type_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] - - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( output=dict( - dict_data_modal_visible=Output('dict_type_to_dict_data-modal', 'visible'), + dict_data_modal_visible=Output( + 'dict_type_to_dict_data-modal', 'visible' + ), dict_data_modal_title=Output('dict_type_to_dict_data-modal', 'title'), - dict_data_select_options=Output('dict_data-dict_type-select', 'options'), - dict_data_select_value=Output('dict_data-dict_type-select', 'value', allow_duplicate=True), + dict_data_select_options=Output( + 'dict_data-dict_type-select', 'options' + ), + dict_data_select_value=Output( + 'dict_data-dict_type-select', 'value', allow_duplicate=True + ), dict_data_search_nclick=Output('dict_data-search', 'nClicks'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) - ), - inputs=dict( - button_click=Input('dict_type-list-table', 'nClicksButton') ), + inputs=dict(button_click=Input('dict_type-list-table', 'nClicksButton')), state=dict( clicked_content=State('dict_type-list-table', 'clickedContent'), - recently_button_clicked_row=State('dict_type-list-table', 'recentlyButtonClickedRow'), - dict_data_search_nclick=State('dict_data-search', 'nClicks') + recently_button_clicked_row=State( + 'dict_type-list-table', 'recentlyButtonClickedRow' + ), + dict_data_search_nclick=State('dict_data-search', 'nClicks'), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def dict_type_to_dict_data_modal(button_click, clicked_content, recently_button_clicked_row, dict_data_search_nclick): +def dict_type_to_dict_data_modal( + button_click, + clicked_content, + recently_button_clicked_row, + dict_data_search_nclick, +): """ 显示字典类型对应数据表格弹窗回调 """ - if button_click and clicked_content == recently_button_clicked_row.get('dict_type').get('content'): - all_dict_type_info = get_all_dict_type_api({}) - if all_dict_type_info.get('code') == 200: - all_dict_type = all_dict_type_info.get('data') - dict_data_options = [dict(label=item.get('dict_name'), value=item.get('dict_type')) for item in all_dict_type] - - return dict( - dict_data_modal_visible=True, - dict_data_modal_title='字典数据', - dict_data_select_options=dict_data_options, - dict_data_select_value=recently_button_clicked_row.get('dict_type').get('content'), - dict_data_search_nclick=dict_data_search_nclick + 1 if dict_data_search_nclick else 1, - api_check_token_trigger={'timestamp': time.time()} - ) + if button_click and clicked_content == recently_button_clicked_row.get( + 'dict_type' + ).get('content'): + all_dict_type_info = DictTypeApi.optionselect() + all_dict_type = all_dict_type_info.get('data') + dict_data_options = [ + dict(label=item.get('dict_name'), value=item.get('dict_type')) + for item in all_dict_type + ] return dict( dict_data_modal_visible=True, dict_data_modal_title='字典数据', - dict_data_select_options=[], - dict_data_select_value=recently_button_clicked_row.get('dict_type').get('content'), - dict_data_search_nclick=dict_data_search_nclick + 1 if dict_data_search_nclick else 1, - api_check_token_trigger={'timestamp': time.time()} + dict_data_select_options=dict_data_options, + dict_data_select_value=recently_button_clicked_row.get( + 'dict_type' + ).get('content'), + dict_data_search_nclick=dict_data_search_nclick + 1 + if dict_data_search_nclick + else 1, ) raise PreventUpdate @app.callback( - [Output('dict_type-export-container', 'data', allow_duplicate=True), - Output('dict_type-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('dict_type-export-container', 'data', allow_duplicate=True), + Output('dict_type-export-complete-judge-container', 'data'), + ], Input('dict_type-export', 'nClicks'), - prevent_initial_call=True + [ + State('dict_type-dict_name-input', 'value'), + State('dict_type-dict_type-input', 'value'), + State('dict_type-status-select', 'value'), + State('dict_type-create_time-range', 'value'), + ], + running=[[Output('dict_type-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_dict_type_list(export_click): +def export_dict_type_list( + export_click, dict_name, dict_type, status_select, create_time_range +): """ 导出字典类型信息回调 """ if export_click: - export_dict_type_res = export_dict_type_list_api({}) - if export_dict_type_res.status_code == 200: - export_dict_type = export_dict_type_res.content + begin_time = None + end_time = None + if create_time_range: + begin_time = create_time_range[0] + end_time = create_time_range[1] - return [ - dcc.send_bytes(export_dict_type, f'字典类型信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + export_params = dict( + dict_name=dict_name, + dict_type=dict_type, + status=status_select, + begin_time=begin_time, + end_time=end_time, + ) + export_dict_type_res = DictTypeApi.export_type(export_params) + export_dict_type = export_dict_type_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_dict_type, + f'字典类型信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -520,7 +621,7 @@ def export_dict_type_list(export_click): @app.callback( Output('dict_type-export-container', 'data', allow_duplicate=True), Input('dict_type-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_dict_type_export_status(data): """ @@ -528,33 +629,25 @@ def reset_dict_type_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate @app.callback( - [Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('dict_type-refresh-cache', 'nClicks'), Input('dict_type-refresh-cache', 'nClicks'), - prevent_initial_call=True + running=[[Output('dict_type-refresh-cache', 'loading'), True, False]], + prevent_initial_call=True, ) def refresh_dict_cache(refresh_click): """ 刷新缓存回调 """ if refresh_click: - refresh_info_res = refresh_dict_api({}) - if refresh_info_res.get('code') == 200: - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('刷新成功', type='success') - ] + DictTypeApi.refresh_cache() + MessageManager.success(content='刷新成功') - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('刷新失败', type='error') - ] + return no_update raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_data_c.py b/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_data_c.py index cf4ae9dd3b59a94a61af1a30b3e7fe2eb0fad757..05d51ea9db2be3377e4123e9bfedd88419900a56 100644 --- a/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_data_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/dict_c/dict_data_c.py @@ -1,39 +1,54 @@ -import dash import time import uuid -from dash import dcc +from dash import ctx, dcc, no_update from dash.dependencies import Input, Output, State, ALL from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.dict.data import DictDataApi +from config.constant import SysNormalDisableConstant from server import app -from utils.common import validate_data_not_empty -from api.dict import get_dict_data_list_api, get_dict_data_detail_api, add_dict_data_api, edit_dict_data_api, delete_dict_data_api, export_dict_data_list_api +from utils.cache_util import TTLCacheManager +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil @app.callback( output=dict( - dict_data_table_data=Output('dict_data-list-table', 'data', allow_duplicate=True), - dict_data_table_pagination=Output('dict_data-list-table', 'pagination', allow_duplicate=True), + dict_data_table_data=Output( + 'dict_data-list-table', 'data', allow_duplicate=True + ), + dict_data_table_pagination=Output( + 'dict_data-list-table', 'pagination', allow_duplicate=True + ), dict_data_table_key=Output('dict_data-list-table', 'key'), - dict_data_table_selectedrowkeys=Output('dict_data-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + dict_data_table_selectedrowkeys=Output( + 'dict_data-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('dict_data-search', 'nClicks'), refresh_click=Input('dict_data-refresh', 'nClicks'), pagination=Input('dict_data-list-table', 'pagination'), - operations=Input('dict_data-operations-store', 'data') + operations=Input('dict_data-operations-store', 'data'), ), state=dict( dict_type=State('dict_data-dict_type-select', 'value'), dict_label=State('dict_data-dict_label-input', 'value'), status_select=State('dict_data-status-select', 'value'), - button_perms=State('dict_data-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_dict_data_table_data(search_click, refresh_click, pagination, operations, dict_type, dict_label, status_select, button_perms): +def get_dict_data_table_data( + search_click, + refresh_click, + pagination, + operations, + dict_type, + dict_label, + status_select, +): """ 获取字典数据表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ @@ -43,62 +58,49 @@ def get_dict_data_table_data(search_click, refresh_click, pagination, operations dict_label=dict_label, status=status_select, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'dict_data-list-table': - query_params = dict( - dict_type=dict_type, - dict_label=dict_label, - status=status_select, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_dict_data_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] + table_info = DictDataApi.list_data(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status'] = DictManager.get_dict_tag( + dict_type='sys_normal_disable', dict_value=item.get('status') ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['dict_code']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dict:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dict:remove' in button_perms else {}, - ] - - return dict( - dict_data_table_data=table_data, - dict_data_table_pagination=table_pagination, - dict_data_table_key=str(uuid.uuid4()), - dict_data_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') ) + item['key'] = str(item['dict_code']) + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:dict:edit') + else {}, + {'content': '删除', 'type': 'link', 'icon': 'antd-delete'} + if PermissionManager.check_perms('system:dict:remove') + else {}, + ] return dict( - dict_data_table_data=dash.no_update, - dict_data_table_pagination=dash.no_update, - dict_data_table_key=dash.no_update, - dict_data_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + dict_data_table_data=table_data, + dict_data_table_pagination=table_pagination, + dict_data_table_key=str(uuid.uuid4()), + dict_data_table_selectedrowkeys=None, ) raise PreventUpdate @@ -106,25 +108,27 @@ def get_dict_data_table_data(search_click, refresh_click, pagination, operations # 重置字典数据搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('dict_data-dict_label-input', 'value'), - Output('dict_data-status-select', 'value'), - Output('dict_data-operations-store', 'data')], + """, + [ + Output('dict_data-dict_label-input', 'value'), + Output('dict_data-status-select', 'value'), + Output('dict_data-operations-store', 'data'), + ], Input('dict_data-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示字典数据搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -134,93 +138,134 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('dict_data-search-form-container', 'hidden'), - Output('dict_data-hidden-tooltip', 'title')], + """, + [ + Output('dict_data-search-form-container', 'hidden'), + Output('dict_data-hidden-tooltip', 'title'), + ], Input('dict_data-hidden', 'nClicks'), State('dict_data-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'dict_data-operation-button', 'index': 'edit'}, 'disabled'), Input('dict_data-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_dict_data_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - return False - return True - - raise PreventUpdate - - -@app.callback( - Output({'type': 'dict_data-operation-button', 'index': 'delete'}, 'disabled'), +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, + Output( + {'type': 'dict_data-operation-button', 'index': 'delete'}, 'disabled' + ), Input('dict_data-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_dict_data_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - return True - raise PreventUpdate +# 字典数据表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'dict_data-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'dict_data-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('dict_data-form-store', 'data', allow_duplicate=True), + Output('dict_data-form', 'values'), + ], + [ + Input('dict_data-form-store', 'data'), + Input('dict_data-form', 'values'), + ], + prevent_initial_call=True, +) @app.callback( output=dict( - modal_visible=Output('dict_data-modal', 'visible', allow_duplicate=True), + modal_visible=Output( + 'dict_data-modal', 'visible', allow_duplicate=True + ), modal_title=Output('dict_data-modal', 'title'), - form_value=Output({'type': 'dict_data-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'dict_data-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'dict_data-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('dict_data-edit-id-store', 'data'), - modal_type=Output('dict_data-operations-store-bk', 'data') + form_value=Output('dict_data-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'dict_data-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'dict_data-form', 'helps', allow_duplicate=True + ), + modal_type=Output('dict_data-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'dict_data-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('dict_data-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'dict_data-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('dict_data-list-table', 'nClicksButton'), ), state=dict( selected_row_keys=State('dict_data-list-table', 'selectedRowKeys'), clicked_content=State('dict_data-list-table', 'clickedContent'), - recently_button_clicked_row=State('dict_data-list-table', 'recentlyButtonClickedRow'), - dict_type_select=State('dict_data-dict_type-select', 'value') + recently_button_clicked_row=State( + 'dict_data-list-table', 'recentlyButtonClickedRow' + ), + dict_type_select=State('dict_data-dict_type-select', 'value'), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_dict_data_modal(operation_click, button_click, selected_row_keys, clicked_content, - recently_button_clicked_row, dict_type_select): +def add_edit_dict_data_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, + dict_type_select, +): """ 显示新增或编辑字典数据弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'dict_data-operation-button'} \ - or trigger_id == {'index': 'edit', 'type': 'dict_data-operation-button'} \ - or (trigger_id == 'dict_data-list-table' and clicked_content == '修改'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'dict_data-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'dict_data-operation-button'} + or (trigger_id == 'dict_data-list-table' and clicked_content == '修改') + ): if trigger_id == {'index': 'add', 'type': 'dict_data-operation-button'}: dict_data_info = dict( dict_type=dict_type_select, @@ -229,234 +274,253 @@ def add_edit_dict_data_modal(operation_click, button_click, selected_row_keys, c css_class=None, dict_sort=0, list_class='default', - status='0', - remark=None + status=SysNormalDisableConstant.NORMAL, + remark=None, ) return dict( modal_visible=True, modal_title='新增字典数据', - form_value=[dict_data_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger=dash.no_update, - edit_row_info=None, - modal_type={'type': 'add'} + form_value=dict_data_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, ) - elif trigger_id == {'index': 'edit', 'type': 'dict_data-operation-button'} or (trigger_id == 'dict_data-list-table' and clicked_content == '修改'): - if trigger_id == {'index': 'edit', 'type': 'dict_data-operation-button'}: + elif trigger_id == { + 'index': 'edit', + 'type': 'dict_data-operation-button', + } or ( + trigger_id == 'dict_data-list-table' and clicked_content == '修改' + ): + if trigger_id == { + 'index': 'edit', + 'type': 'dict_data-operation-button', + }: dict_code = int(','.join(selected_row_keys)) else: dict_code = int(recently_button_clicked_row['key']) - dict_data_info_res = get_dict_data_detail_api(dict_code=dict_code) - if dict_data_info_res['code'] == 200: - dict_data_info = dict_data_info_res['data'] - return dict( - modal_visible=True, - modal_title='编辑字典数据', - form_value=[dict_data_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=dict_data_info if dict_data_info else None, - modal_type={'type': 'edit'} - ) - - return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type=None - ) + dict_data_info_res = DictDataApi.get_data(dict_code=dict_code) + dict_data_info = dict_data_info_res['data'] + return dict( + modal_visible=True, + modal_title='编辑字典数据', + form_value=dict_data_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, + ) raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'dict_data-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'dict_data-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + 'dict_data-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'dict_data-form', 'helps', allow_duplicate=True + ), modal_visible=Output('dict_data-modal', 'visible'), - operations=Output('dict_data-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('dict_data-modal', 'okCounts') + operations=Output( + 'dict_data-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('dict_data-modal', 'okCounts')), state=dict( - modal_type=State('dict_data-operations-store-bk', 'data'), - edit_row_info=State('dict_data-edit-id-store', 'data'), - form_value=State({'type': 'dict_data-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'dict_data-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('dict_data-modal_type-store', 'data'), + form_value=State('dict_data-form-store', 'data'), + form_label=State( + {'type': 'dict_data-form-label', 'index': ALL, 'required': True}, + 'label', + ), ), - prevent_initial_call=True + running=[[Output('dict_data-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def dict_data_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label): +def dict_data_confirm(confirm_trigger, modal_type, form_value, form_label): """ 新增或编字典数据弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params_add = form_value_state + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value params_edit = params_add.copy() - params_edit['dict_code'] = edit_row_info.get('dict_code') if edit_row_info else None - api_res = {} modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_dict_data_api(params_add) + DictDataApi.add_data(params_add) if modal_type == 'edit': - api_res = edit_dict_data_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + DictDataApi.update_data(params_edit) + if modal_type == 'add': + TTLCacheManager.delete(form_value.get('dict_type')) + MessageManager.success('新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + TTLCacheManager.delete(form_value.get('dict_type')) + MessageManager.success('编辑成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('dict_data-delete-text', 'children'), - Output('dict_data-delete-confirm-modal', 'visible'), - Output('dict_data-delete-ids-store', 'data')], - [Input({'type': 'dict_data-operation-button', 'index': ALL}, 'nClicks'), - Input('dict_data-list-table', 'nClicksButton')], - [State('dict_data-list-table', 'selectedRowKeys'), - State('dict_data-list-table', 'clickedContent'), - State('dict_data-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('dict_data-delete-text', 'children'), + Output('dict_data-delete-confirm-modal', 'visible'), + Output('dict_data-delete-ids-store', 'data'), + ], + [ + Input({'type': 'dict_data-operation-button', 'index': ALL}, 'nClicks'), + Input('dict_data-list-table', 'nClicksButton'), + ], + [ + State('dict_data-list-table', 'selectedRows'), + State('dict_data-list-table', 'clickedContent'), + State('dict_data-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def dict_data_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def dict_data_delete_modal( + operation_click, + button_click, + selected_rows, + clicked_content, + recently_button_clicked_row, +): """ 显示删除字典数据二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'delete', 'type': 'dict_data-operation-button'} or ( - trigger_id == 'dict_data-list-table' and clicked_content == '删除'): - - if trigger_id == {'index': 'delete', 'type': 'dict_data-operation-button'}: - dict_codes = ','.join(selected_row_keys) + trigger_id = ctx.triggered_id + if trigger_id == { + 'index': 'delete', + 'type': 'dict_data-operation-button', + } or (trigger_id == 'dict_data-list-table' and clicked_content == '删除'): + if trigger_id == { + 'index': 'delete', + 'type': 'dict_data-operation-button', + }: + dict_codes = ','.join( + [str(item.get('dict_code')) for item in selected_rows] + ) + dict_types = ','.join( + list(set([item.get('dict_type') for item in selected_rows])) + ) else: if clicked_content == '删除': dict_codes = recently_button_clicked_row['key'] + dict_types = recently_button_clicked_row['dict_type'] else: - return dash.no_update + return no_update return [ f'是否确认删除字典编码为{dict_codes}的数据?', True, - {'dict_codes': dict_codes} + {'dict_codes': dict_codes, 'dict_types': dict_types}, ] raise PreventUpdate @app.callback( - [Output('dict_data-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('dict_data-operations-store', 'data', allow_duplicate=True), Input('dict_data-delete-confirm-modal', 'okCounts'), State('dict_data-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def dict_data_delete_confirm(delete_confirm, dict_codes_data): """ 删除字典数据弹窗确认回调,实现删除操作 """ if delete_confirm: + params = dict_codes_data.get('dict_codes') + dict_types = dict_codes_data.get('dict_types') + DictDataApi.del_data(params) + TTLCacheManager.delete(dict_types) + MessageManager.success('删除成功') - params = dict_codes_data - delete_button_info = delete_dict_data_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] - - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('dict_data-export-container', 'data', allow_duplicate=True), - Output('dict_data-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('dict_data-export-container', 'data', allow_duplicate=True), + Output('dict_data-export-complete-judge-container', 'data'), + ], Input('dict_data-export', 'nClicks'), - State('dict_data-dict_type-select', 'value'), - prevent_initial_call=True + [ + State('dict_data-dict_type-select', 'value'), + State('dict_data-dict_label-input', 'value'), + State('dict_data-status-select', 'value'), + ], + running=[[Output('dict_data-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_dict_data_list(export_click, dict_type): +def export_dict_data_list(export_click, dict_type, dict_label, status_select): """ 导出字典数据信息回调 """ if export_click: - export_dict_data_res = export_dict_data_list_api(dict(dict_type=dict_type)) - if export_dict_data_res.status_code == 200: - export_dict_data = export_dict_data_res.content - - return [ - dcc.send_bytes(export_dict_data, f'字典数据信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + export_params = dict( + dict_type=dict_type, + dict_label=dict_label, + status=status_select, + ) + export_dict_data_res = DictDataApi.export_data(export_params) + export_dict_data = export_dict_data_res.content + MessageManager.success('导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_dict_data, + f'字典数据信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -465,7 +529,7 @@ def export_dict_data_list(export_click, dict_type): @app.callback( Output('dict_data-export-container', 'data', allow_duplicate=True), Input('dict_data-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_dict_data_export_status(data): """ @@ -473,7 +537,6 @@ def reset_dict_data_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/button_type_c.py b/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/button_type_c.py index a39764c1b2d72b8eee2a3880b6563ff039a1e2bb..0c8e15c53ba7e9243536a57bff06bf938cb0df39 100644 --- a/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/button_type_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/button_type_c.py @@ -1,106 +1,143 @@ -import dash -import time +from dash import no_update from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.menu import MenuApi from server import app -from utils.common import validate_data_not_empty -from api.menu import add_menu_api, edit_menu_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager @app.callback( output=dict( form_validate=[ - Output('menu-parent_id-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-menu_name-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-order_num-form-item', 'validateStatus', allow_duplicate=True), + Output( + 'menu-parent_id-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-menu_name-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-order_num-form-item', + 'validateStatus', + allow_duplicate=True, + ), Output('menu-parent_id-form-item', 'help', allow_duplicate=True), Output('menu-menu_name-form-item', 'help', allow_duplicate=True), - Output('menu-order_num-form-item', 'help', allow_duplicate=True) + Output('menu-order_num-form-item', 'help', allow_duplicate=True), ], modal_visible=Output('menu-modal', 'visible', allow_duplicate=True), - operations=Output('menu-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) + operations=Output( + 'menu-operations-store', 'data', allow_duplicate=True + ), ), inputs=dict( confirm_trigger=Input('menu-modal-F-trigger', 'data'), ), state=dict( - modal_type=State('menu-operations-store-bk', 'data'), + modal_type=State('menu-modal_type-store', 'data'), edit_row_info=State('menu-edit-id-store', 'data'), parent_id=State('menu-parent_id', 'value'), menu_type=State('menu-menu_type', 'value'), icon=State('menu-icon', 'value'), menu_name=State('menu-menu_name', 'value'), order_num=State('menu-order_num', 'value'), - perms=State('button-menu-perms', 'value') + perms=State('button-menu-perms', 'value'), ), - prevent_initial_call=True + running=[[Output('menu-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def menu_confirm_button(confirm_trigger, modal_type, edit_row_info, parent_id, menu_type, icon, menu_name, order_num, perms): +def menu_confirm_button( + confirm_trigger, + modal_type, + edit_row_info, + parent_id, + menu_type, + icon, + menu_name, + order_num, + perms, +): """ 菜单类型为按钮时新增或编辑弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - if all(validate_data_not_empty(item) for item in [parent_id, menu_name, order_num]): - params_add = dict(parent_id=parent_id, menu_type=menu_type, icon=icon, menu_name=menu_name, order_num=order_num, perms=perms) - params_edit = dict(menu_id=edit_row_info.get('menu_id') if edit_row_info else None, parent_id=parent_id, menu_type=menu_type, icon=icon, - menu_name=menu_name, order_num=order_num, perms=perms) - api_res = {} + if all( + ValidateUtil.not_empty(item) + for item in [parent_id, menu_name, order_num] + ): + params_add = dict( + parent_id=parent_id, + menu_type=menu_type, + icon=icon, + menu_name=menu_name, + order_num=order_num, + perms=perms, + ) + params_edit = dict( + menu_id=edit_row_info.get('menu_id') if edit_row_info else None, + parent_id=parent_id, + menu_type=menu_type, + icon=icon, + menu_name=menu_name, + order_num=order_num, + perms=perms, + ) modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_menu_api(params_add) + MenuApi.add_menu(params_add) if modal_type == 'edit': - api_res = edit_menu_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_validate=[None] * 6, - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_validate=[None] * 6, - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + MenuApi.update_menu(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_validate=[None] * 6, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_validate=[None] * 6, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( form_validate=[None] * 6, - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) return dict( form_validate=[ - None if validate_data_not_empty(parent_id) else 'error', - None if validate_data_not_empty(menu_name) else 'error', - None if validate_data_not_empty(order_num) else 'error', - None if validate_data_not_empty(parent_id) else '请选择上级菜单!', - None if validate_data_not_empty(menu_name) else '请输入菜单名称!', - None if validate_data_not_empty(order_num) else '请输入显示排序!' + None if ValidateUtil.not_empty(parent_id) else 'error', + None if ValidateUtil.not_empty(menu_name) else 'error', + None if ValidateUtil.not_empty(order_num) else 'error', + None + if ValidateUtil.not_empty(parent_id) + else '请选择上级菜单!', + None + if ValidateUtil.not_empty(menu_name) + else '请输入菜单名称!', + None + if ValidateUtil.not_empty(order_num) + else '请输入显示排序!', ], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - Output('button-menu-perms', 'value'), - Input('menu-edit-id-store', 'data') + Output('button-menu-perms', 'value'), Input('menu-edit-id-store', 'data') ) def set_edit_info(edit_info): """ diff --git a/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/content_type_c.py b/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/content_type_c.py index c1404ae466ec693787d1e2f0373395611161c9e5..971cc588d91f2c5dcd93a4698ed9efab44acf237 100644 --- a/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/content_type_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/content_type_c.py @@ -1,20 +1,30 @@ -import dash -import time +from dash import no_update from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.menu import MenuApi from server import app -from utils.common import validate_data_not_empty -from api.menu import add_menu_api, edit_menu_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager @app.callback( output=dict( form_validate=[ - Output('menu-parent_id-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-menu_name-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-order_num-form-item', 'validateStatus', allow_duplicate=True), + Output( + 'menu-parent_id-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-menu_name-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-order_num-form-item', + 'validateStatus', + allow_duplicate=True, + ), Output('content-menu-path-form-item', 'validateStatus'), Output('menu-parent_id-form-item', 'help', allow_duplicate=True), Output('menu-menu_name-form-item', 'help', allow_duplicate=True), @@ -22,15 +32,13 @@ from api.menu import add_menu_api, edit_menu_api Output('content-menu-path-form-item', 'help'), ], modal_visible=Output('menu-modal', 'visible', allow_duplicate=True), - operations=Output('menu-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('menu-modal-M-trigger', 'data') + operations=Output( + 'menu-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('menu-modal-M-trigger', 'data')), state=dict( - modal_type=State('menu-operations-store-bk', 'data'), + modal_type=State('menu-modal_type-store', 'data'), edit_row_info=State('menu-edit-id-store', 'data'), parent_id=State('menu-parent_id', 'value'), menu_type=State('menu-menu_type', 'value'), @@ -40,78 +48,116 @@ from api.menu import add_menu_api, edit_menu_api is_frame=State('content-menu-is_frame', 'value'), path=State('content-menu-path', 'value'), visible=State('content-menu-visible', 'value'), - status=State('content-menu-status', 'value') + status=State('content-menu-status', 'value'), ), - prevent_initial_call=True + running=[[Output('menu-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def menu_confirm_content(confirm_trigger, modal_type, edit_row_info, parent_id, menu_type, icon, menu_name, order_num, is_frame, path, visible, status): +def menu_confirm_content( + confirm_trigger, + modal_type, + edit_row_info, + parent_id, + menu_type, + icon, + menu_name, + order_num, + is_frame, + path, + visible, + status, +): """ 菜单类型为目录时新增或编辑弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - if all(validate_data_not_empty(item) for item in [parent_id, menu_name, order_num, path]): - params_add = dict(parent_id=parent_id, menu_type=menu_type, icon=icon, menu_name=menu_name, order_num=order_num, - is_frame=is_frame, path=path, visible=visible, status=status) - params_edit = dict(menu_id=edit_row_info.get('menu_id') if edit_row_info else None, parent_id=parent_id, menu_type=menu_type, icon=icon, - menu_name=menu_name, order_num=order_num, is_frame=is_frame, path=path, visible=visible, status=status) - api_res = {} + if all( + ValidateUtil.not_empty(item) + for item in [parent_id, menu_name, order_num, path] + ): + params_add = dict( + parent_id=parent_id, + menu_type=menu_type, + icon=icon, + menu_name=menu_name, + order_num=order_num, + is_frame=is_frame, + path=path, + visible=visible, + status=status, + ) + params_edit = dict( + menu_id=edit_row_info.get('menu_id') if edit_row_info else None, + parent_id=parent_id, + menu_type=menu_type, + icon=icon, + menu_name=menu_name, + order_num=order_num, + is_frame=is_frame, + path=path, + visible=visible, + status=status, + ) modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_menu_api(params_add) + MenuApi.add_menu(params_add) if modal_type == 'edit': - api_res = edit_menu_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_validate=[None] * 8, - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_validate=[None] * 8, - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + MenuApi.update_menu(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_validate=[None] * 8, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_validate=[None] * 8, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( form_validate=[None] * 8, - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) return dict( form_validate=[ - None if validate_data_not_empty(parent_id) else 'error', - None if validate_data_not_empty(menu_name) else 'error', - None if validate_data_not_empty(order_num) else 'error', - None if validate_data_not_empty(path) else 'error', - None if validate_data_not_empty(parent_id) else '请选择上级菜单!', - None if validate_data_not_empty(menu_name) else '请输入菜单名称!', - None if validate_data_not_empty(order_num) else '请输入显示排序!', - None if validate_data_not_empty(path) else '请输入路由地址!', + None if ValidateUtil.not_empty(parent_id) else 'error', + None if ValidateUtil.not_empty(menu_name) else 'error', + None if ValidateUtil.not_empty(order_num) else 'error', + None if ValidateUtil.not_empty(path) else 'error', + None + if ValidateUtil.not_empty(parent_id) + else '请选择上级菜单!', + None + if ValidateUtil.not_empty(menu_name) + else '请输入菜单名称!', + None + if ValidateUtil.not_empty(order_num) + else '请输入显示排序!', + None if ValidateUtil.not_empty(path) else '请输入路由地址!', ], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('content-menu-is_frame', 'value'), - Output('content-menu-path', 'value'), - Output('content-menu-visible', 'value'), - Output('content-menu-status', 'value')], - Input('menu-edit-id-store', 'data') + [ + Output('content-menu-is_frame', 'value'), + Output('content-menu-path', 'value'), + Output('content-menu-visible', 'value'), + Output('content-menu-status', 'value'), + ], + Input('menu-edit-id-store', 'data'), ) def set_edit_info(edit_info): """ @@ -122,7 +168,7 @@ def set_edit_info(edit_info): edit_info.get('is_frame'), edit_info.get('path'), edit_info.get('visible'), - edit_info.get('status') + edit_info.get('status'), ] raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/menu_type_c.py b/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/menu_type_c.py index 2fcb149a836ccbf3fcfbb90e08a42604bcb525f5..450ee324cce89df78429afe55b513fe9a18e56df 100644 --- a/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/menu_type_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/menu_c/components_c/menu_type_c.py @@ -1,36 +1,48 @@ -import dash -import time +from dash import no_update from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.menu import MenuApi from server import app -from utils.common import validate_data_not_empty -from api.menu import add_menu_api, edit_menu_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager @app.callback( output=dict( form_validate=[ - Output('menu-parent_id-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-menu_name-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-order_num-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-menu-path-form-item', 'validateStatus', allow_duplicate=True), + Output( + 'menu-parent_id-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-menu_name-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-order_num-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-menu-path-form-item', + 'validateStatus', + allow_duplicate=True, + ), Output('menu-parent_id-form-item', 'help', allow_duplicate=True), Output('menu-menu_name-form-item', 'help', allow_duplicate=True), Output('menu-order_num-form-item', 'help', allow_duplicate=True), Output('menu-menu-path-form-item', 'help', allow_duplicate=True), ], modal_visible=Output('menu-modal', 'visible', allow_duplicate=True), - operations=Output('menu-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('menu-modal-C-trigger', 'data') + operations=Output( + 'menu-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('menu-modal-C-trigger', 'data')), state=dict( - modal_type=State('menu-operations-store-bk', 'data'), + modal_type=State('menu-modal_type-store', 'data'), edit_row_info=State('menu-edit-id-store', 'data'), parent_id=State('menu-parent_id', 'value'), menu_type=State('menu-menu_type', 'value'), @@ -39,89 +51,142 @@ from api.menu import add_menu_api, edit_menu_api order_num=State('menu-order_num', 'value'), is_frame=State('menu-menu-is_frame', 'value'), path=State('menu-menu-path', 'value'), + route_name=State('menu-menu-route_name', 'value'), component=State('menu-menu-component', 'value'), perms=State('menu-menu-perms', 'value'), query=State('menu-menu-query', 'value'), is_cache=State('menu-menu-is_cache', 'value'), visible=State('menu-menu-visible', 'value'), - status=State('menu-menu-status', 'value') + status=State('menu-menu-status', 'value'), ), - prevent_initial_call=True + running=[[Output('menu-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def menu_confirm_menu(confirm_trigger, modal_type, edit_row_info, parent_id, menu_type, icon, menu_name, order_num, is_frame, path, - component, perms, query, is_cache, visible, status): +def menu_confirm_menu( + confirm_trigger, + modal_type, + edit_row_info, + parent_id, + menu_type, + icon, + menu_name, + order_num, + is_frame, + path, + route_name, + component, + perms, + query, + is_cache, + visible, + status, +): """ 菜单类型为菜单时新增或编辑弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - if all(validate_data_not_empty(item) for item in [parent_id, menu_name, order_num, path]): - params_add = dict(parent_id=parent_id, menu_type=menu_type, icon=icon, menu_name=menu_name, order_num=order_num, is_frame=is_frame, - path=path, component=component, perms=perms, query=query, is_cache=is_cache, visible=visible, status=status) - params_edit = dict(menu_id=edit_row_info.get('menu_id') if edit_row_info else None, parent_id=parent_id, menu_type=menu_type, icon=icon, - menu_name=menu_name, order_num=order_num, is_frame=is_frame, path=path, component=component, - perms=perms, query=query, is_cache=is_cache, visible=visible, status=status) - api_res = {} + if all( + ValidateUtil.not_empty(item) + for item in [parent_id, menu_name, order_num, path] + ): + params_add = dict( + parent_id=parent_id, + menu_type=menu_type, + icon=icon, + menu_name=menu_name, + order_num=order_num, + is_frame=is_frame, + path=path, + route_name=route_name, + component=component, + perms=perms, + query=query, + is_cache=is_cache, + visible=visible, + status=status, + ) + params_edit = dict( + menu_id=edit_row_info.get('menu_id') if edit_row_info else None, + parent_id=parent_id, + menu_type=menu_type, + icon=icon, + menu_name=menu_name, + order_num=order_num, + is_frame=is_frame, + path=path, + route_name=route_name, + component=component, + perms=perms, + query=query, + is_cache=is_cache, + visible=visible, + status=status, + ) modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_menu_api(params_add) + MenuApi.add_menu(params_add) if modal_type == 'edit': - api_res = edit_menu_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_validate=[None] * 8, - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_validate=[None] * 8, - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + MenuApi.update_menu(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_validate=[None] * 8, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_validate=[None] * 8, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( form_validate=[None] * 8, - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) return dict( form_validate=[ - None if validate_data_not_empty(parent_id) else 'error', - None if validate_data_not_empty(menu_name) else 'error', - None if validate_data_not_empty(order_num) else 'error', - None if validate_data_not_empty(path) else 'error', - None if validate_data_not_empty(parent_id) else '请选择上级菜单!', - None if validate_data_not_empty(menu_name) else '请输入菜单名称!', - None if validate_data_not_empty(order_num) else '请输入显示排序!', - None if validate_data_not_empty(path) else '请输入路由地址!' + None if ValidateUtil.not_empty(parent_id) else 'error', + None if ValidateUtil.not_empty(menu_name) else 'error', + None if ValidateUtil.not_empty(order_num) else 'error', + None if ValidateUtil.not_empty(path) else 'error', + None + if ValidateUtil.not_empty(parent_id) + else '请选择上级菜单!', + None + if ValidateUtil.not_empty(menu_name) + else '请输入菜单名称!', + None + if ValidateUtil.not_empty(order_num) + else '请输入显示排序!', + None if ValidateUtil.not_empty(path) else '请输入路由地址!', ], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('menu-menu-is_frame', 'value'), - Output('menu-menu-path', 'value'), - Output('menu-menu-component', 'value'), - Output('menu-menu-perms', 'value'), - Output('menu-menu-query', 'value'), - Output('menu-menu-is_cache', 'value'), - Output('menu-menu-visible', 'value'), - Output('menu-menu-status', 'value')], - Input('menu-edit-id-store', 'data') + [ + Output('menu-menu-is_frame', 'value'), + Output('menu-menu-path', 'value'), + Output('menu-menu-route_name', 'value'), + Output('menu-menu-component', 'value'), + Output('menu-menu-perms', 'value'), + Output('menu-menu-query', 'value'), + Output('menu-menu-is_cache', 'value'), + Output('menu-menu-visible', 'value'), + Output('menu-menu-status', 'value'), + ], + Input('menu-edit-id-store', 'data'), ) def set_edit_info(edit_info): """ @@ -131,12 +196,13 @@ def set_edit_info(edit_info): return [ edit_info.get('is_frame'), edit_info.get('path'), + edit_info.get('route_name'), edit_info.get('component'), edit_info.get('perms'), edit_info.get('query'), edit_info.get('is_cache'), edit_info.get('visible'), - edit_info.get('status') + edit_info.get('status'), ] raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/menu_c/menu_c.py b/dash-fastapi-frontend/callbacks/system_c/menu_c/menu_c.py index 36efee91334b19f84814d6ef17fb5d2fb4defb95..250c2232154ae371265a1bf62e712ff7dc3b1aef 100644 --- a/dash-fastapi-frontend/callbacks/system_c/menu_c/menu_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/menu_c/menu_c.py @@ -1,159 +1,171 @@ -import dash +import feffery_antd_components as fac import time import uuid +from dash import ctx, no_update from dash.dependencies import Input, Output, State, ALL from dash.exceptions import PreventUpdate -import feffery_antd_components as fac -import feffery_utils_components as fuc - +from typing import Dict +from api.system.menu import MenuApi +from config.constant import MenuConstant, SysNormalDisableConstant from server import app -from utils.tree_tool import list_to_tree -from views.system.menu.components import content_type, menu_type, button_type -from api.menu import get_menu_tree_api, get_menu_tree_for_edit_option_api, get_menu_list_api, delete_menu_api, get_menu_detail_api +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil +from utils.tree_util import TreeUtil +from views.system.menu.components import button_type, content_type, menu_type + + +def generate_menu_table(query_params: Dict): + """ + 根据查询参数获取菜单表格数据及展开信息 + + :param query_params: 查询参数 + :return: 菜单表格数据及展开信息 + """ + table_info = MenuApi.list_menu(query_params) + default_expanded_row_keys = [] + table_data = table_info['data'] + for item in table_data: + default_expanded_row_keys.append(str(item['menu_id'])) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['menu_id']) + item['icon'] = [ + { + 'type': 'link', + 'icon': item['icon'], + 'disabled': True, + 'style': {'color': 'rgba(0, 0, 0, 0.8)'}, + }, + ] + if item['status'] == SysNormalDisableConstant.DISABLE: + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:menu:edit') + else {}, + { + 'content': '删除', + 'type': 'link', + 'icon': 'antd-delete', + } + if PermissionManager.check_perms('system:menu:remove') + else {}, + ] + else: + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:menu:edit') + else {}, + {'content': '新增', 'type': 'link', 'icon': 'antd-plus'} + if PermissionManager.check_perms('system:menu:add') + else {}, + { + 'content': '删除', + 'type': 'link', + 'icon': 'antd-delete', + } + if PermissionManager.check_perms('system:menu:remove') + else {}, + ] + item['status'] = DictManager.get_dict_tag( + dict_type='sys_normal_disable', dict_value=item.get('status') + ) + table_data_new = TreeUtil.list_to_tree(table_data, 'menu_id', 'parent_id') + + return [table_data_new, default_expanded_row_keys] @app.callback( output=dict( menu_table_data=Output('menu-list-table', 'data', allow_duplicate=True), menu_table_key=Output('menu-list-table', 'key'), - menu_table_defaultexpandedrowkeys=Output('menu-list-table', 'defaultExpandedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - fold_click=Output('menu-fold', 'nClicks') + menu_table_defaultexpandedrowkeys=Output( + 'menu-list-table', 'defaultExpandedRowKeys' + ), + fold_click=Output('menu-fold', 'nClicks'), ), inputs=dict( search_click=Input('menu-search', 'nClicks'), refresh_click=Input('menu-refresh', 'nClicks'), operations=Input('menu-operations-store', 'data'), - fold_click=Input('menu-fold', 'nClicks') + fold_click=Input('menu-fold', 'nClicks'), ), state=dict( menu_name=State('menu-menu_name-input', 'value'), status_select=State('menu-status-select', 'value'), - in_default_expanded_row_keys=State('menu-list-table', 'defaultExpandedRowKeys'), - button_perms=State('menu-button-perms-container', 'data') + in_default_expanded_row_keys=State( + 'menu-list-table', 'defaultExpandedRowKeys' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_menu_table_data(search_click, refresh_click, operations, fold_click, menu_name, status_select, in_default_expanded_row_keys, button_perms): +def get_menu_table_data( + search_click, + refresh_click, + operations, + fold_click, + menu_name, + status_select, + in_default_expanded_row_keys, +): """ 获取菜单表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - query_params = dict( - menu_name=menu_name, - status=status_select - ) + query_params = dict(menu_name=menu_name, status=status_select) if search_click or refresh_click or operations or fold_click: - table_info = get_menu_list_api(query_params) - default_expanded_row_keys = [] - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - for item in table_data: - default_expanded_row_keys.append(str(item['menu_id'])) - item['key'] = str(item['menu_id']) - item['icon'] = [ - { - 'type': 'link', - 'icon': item['icon'], - 'disabled': True, - 'style': { - 'color': 'rgba(0, 0, 0, 0.8)' - } - }, - ] - if item['status'] == '1': - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:menu:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:menu:remove' in button_perms else {}, - ] - else: - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:menu:edit' in button_perms else {}, - { - 'content': '新增', - 'type': 'link', - 'icon': 'antd-plus' - } if 'system:menu:add' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:menu:remove' in button_perms else {}, - ] - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - table_data_new = list_to_tree(table_data, 'menu_id', 'parent_id') - - if fold_click: - if not in_default_expanded_row_keys: - return dict( - menu_table_data=table_data_new, - menu_table_key=str(uuid.uuid4()), - menu_table_defaultexpandedrowkeys=default_expanded_row_keys, - api_check_token_trigger={'timestamp': time.time()}, - fold_click=None - ) - - return dict( - menu_table_data=table_data_new, - menu_table_key=str(uuid.uuid4()), - menu_table_defaultexpandedrowkeys=[], - api_check_token_trigger={'timestamp': time.time()}, - fold_click=None - ) + table_data, default_expanded_row_keys = generate_menu_table( + query_params + ) + if fold_click: + if not in_default_expanded_row_keys: + return dict( + menu_table_data=table_data, + menu_table_key=str(uuid.uuid4()), + menu_table_defaultexpandedrowkeys=default_expanded_row_keys, + fold_click=None, + ) return dict( - menu_table_data=dash.no_update, - menu_table_key=dash.no_update, - menu_table_defaultexpandedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - fold_click=None + menu_table_data=table_data, + menu_table_key=str(uuid.uuid4()), + menu_table_defaultexpandedrowkeys=[], + fold_click=None, ) return dict( - menu_table_data=dash.no_update, - menu_table_key=dash.no_update, - menu_table_defaultexpandedrowkeys=dash.no_update, - api_check_token_trigger=dash.no_update, - fold_click=None + menu_table_data=no_update, + menu_table_key=no_update, + menu_table_defaultexpandedrowkeys=no_update, + fold_click=None, ) # 重置菜单搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('menu-menu_name-input', 'value'), - Output('menu-status-select', 'value'), - Output('menu-operations-store', 'data')], + """, + [ + Output('menu-menu_name-input', 'value'), + Output('menu-status-select', 'value'), + Output('menu-operations-store', 'data'), + ], Input('menu-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示菜单搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -163,175 +175,231 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('menu-search-form-container', 'hidden'), - Output('menu-hidden-tooltip', 'title')], + """, + [ + Output('menu-search-form-container', 'hidden'), + Output('menu-hidden-tooltip', 'title'), + ], Input('menu-hidden', 'nClicks'), State('menu-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) @app.callback( - [Output('menu-icon', 'value'), - Output('menu-icon', 'prefix')], + [Output('menu-icon', 'value'), Output('menu-icon', 'prefix')], Input('icon-category', 'value'), - prevent_initial_call=True + prevent_initial_call=True, ) def get_select_icon(icon): """ 获取新增或编辑表单中选择的icon回调 """ if icon: - return [ - icon, - fac.AntdIcon(icon=icon) - ] + return [icon, fac.AntdIcon(icon=icon)] raise PreventUpdate @app.callback( output=dict( - modal=dict(visible=Output('menu-modal', 'visible', allow_duplicate=True), title=Output('menu-modal', 'title')), + modal=dict( + visible=Output('menu-modal', 'visible', allow_duplicate=True), + title=Output('menu-modal', 'title'), + ), form_value=dict( - parent_tree=Output('menu-parent_id', 'treeData'), parent_id=Output('menu-parent_id', 'value'), - menu_type=Output('menu-menu_type', 'value'), icon=Output('menu-icon', 'value', allow_duplicate=True), - icon_prefix=Output('menu-icon', 'prefix', allow_duplicate=True), icon_category=Output('icon-category', 'value'), - menu_name=Output('menu-menu_name', 'value'), order_num=Output('menu-order_num', 'value') + parent_tree=Output('menu-parent_id', 'treeData'), + parent_id=Output('menu-parent_id', 'value'), + menu_type=Output('menu-menu_type', 'value'), + icon=Output('menu-icon', 'value', allow_duplicate=True), + icon_prefix=Output('menu-icon', 'prefix', allow_duplicate=True), + icon_category=Output('icon-category', 'value'), + menu_name=Output('menu-menu_name', 'value'), + order_num=Output('menu-order_num', 'value'), ), form_validate=[ - Output('menu-parent_id-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-menu_name-form-item', 'validateStatus', allow_duplicate=True), - Output('menu-order_num-form-item', 'validateStatus', allow_duplicate=True), + Output( + 'menu-parent_id-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-menu_name-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'menu-order_num-form-item', + 'validateStatus', + allow_duplicate=True, + ), Output('menu-parent_id-form-item', 'help', allow_duplicate=True), Output('menu-menu_name-form-item', 'help', allow_duplicate=True), - Output('menu-order_num-form-item', 'help', allow_duplicate=True) + Output('menu-order_num-form-item', 'help', allow_duplicate=True), ], other=dict( - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), edit_row_info=Output('menu-edit-id-store', 'data'), - modal_type=Output('menu-operations-store-bk', 'data') - ) + modal_type=Output('menu-modal_type-store', 'data'), + ), ), inputs=dict( - operation_click=Input({'type': 'menu-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('menu-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'menu-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('menu-list-table', 'nClicksButton'), ), state=dict( clicked_content=State('menu-list-table', 'clickedContent'), - recently_button_clicked_row=State('menu-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'menu-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_menu_modal(operation_click, button_click, clicked_content, recently_button_clicked_row): +def add_edit_menu_modal( + operation_click, button_click, clicked_content, recently_button_clicked_row +): """ 显示新增或编辑菜单弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'menu-operation-button'} or (trigger_id == 'menu-list-table' and clicked_content != '删除'): + trigger_id = ctx.triggered_id + if trigger_id == {'index': 'add', 'type': 'menu-operation-button'} or ( + trigger_id == 'menu-list-table' and clicked_content != '删除' + ): menu_params = dict(menu_name='') - if clicked_content == '修改': - tree_info = get_menu_tree_for_edit_option_api(menu_params) - else: - tree_info = get_menu_tree_api(menu_params) - if tree_info['code'] == 200: - tree_data = tree_info['data'] + tree_info = MenuApi.list_menu(menu_params) + tree_data = [dict(title='主类目', value='0', key='0', children=[])] + tree_data[0]['children'] = TreeUtil.list_to_tree_select( + tree_info['data'], 'menu_name', 'menu_id', 'menu_id', 'parent_id' + ) - if trigger_id == {'index': 'add', 'type': 'menu-operation-button'}: + if trigger_id == {'index': 'add', 'type': 'menu-operation-button'}: + return dict( + modal=dict(visible=True, title='新增菜单'), + form_value=dict( + parent_tree=tree_data, + parent_id='0', + menu_type=MenuConstant.TYPE_DIR, + icon=None, + icon_prefix=None, + icon_category=None, + menu_name=None, + order_num=None, + ), + form_validate=[None] * 6, + other=dict( + edit_row_info=None, + modal_type={'type': 'add'}, + ), + ) + elif trigger_id == 'menu-list-table' and clicked_content == '新增': + return dict( + modal=dict(visible=True, title='新增菜单'), + form_value=dict( + parent_tree=tree_data, + parent_id=str(recently_button_clicked_row['key']), + menu_type=MenuConstant.TYPE_DIR, + icon=None, + icon_prefix=None, + icon_category=None, + menu_name=None, + order_num=None, + ), + form_validate=[None] * 6, + other=dict( + edit_row_info=None, + modal_type={'type': 'add'}, + ), + ) + elif trigger_id == 'menu-list-table' and clicked_content == '修改': + menu_id = int(recently_button_clicked_row['key']) + menu_info_res = MenuApi.get_menu(menu_id=menu_id) + if menu_info_res['code'] == 200: + menu_info = menu_info_res['data'] return dict( - modal=dict(visible=True, title='新增菜单'), + modal=dict(visible=True, title='编辑菜单'), form_value=dict( - parent_tree=tree_data, parent_id='0', menu_type='M', icon=None, - icon_prefix=None, icon_category=None, menu_name=None, order_num=None + parent_tree=tree_data, + parent_id=str(menu_info.get('parent_id')), + menu_type=menu_info.get('menu_type'), + icon=menu_info.get('icon'), + icon_prefix=fac.AntdIcon(icon=menu_info.get('icon')), + icon_category=menu_info.get('icon'), + menu_name=menu_info.get('menu_name'), + order_num=menu_info.get('order_num'), ), form_validate=[None] * 6, other=dict( - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type={'type': 'add'} - ) - ) - elif trigger_id == 'menu-list-table' and clicked_content == '新增': - return dict( - modal=dict(visible=True, title='新增菜单'), - form_value=dict( - parent_tree=tree_data, parent_id=str(recently_button_clicked_row['key']), menu_type='M', - icon=None, icon_prefix=None, icon_category=None, menu_name=None, order_num=None + edit_row_info=menu_info, + modal_type={'type': 'edit'}, ), - form_validate=[None] * 6, - other=dict( - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type={'type': 'add'} - ) ) - elif trigger_id == 'menu-list-table' and clicked_content == '修改': - menu_id = int(recently_button_clicked_row['key']) - menu_info_res = get_menu_detail_api(menu_id=menu_id) - if menu_info_res['code'] == 200: - menu_info = menu_info_res['data'] - return dict( - modal=dict(visible=True, title='编辑菜单'), - form_value=dict( - parent_tree=tree_data, parent_id=str(menu_info.get('parent_id')), - menu_type=menu_info.get('menu_type'), icon=menu_info.get('icon'), - icon_prefix=fac.AntdIcon(icon=menu_info.get('icon')), icon_category=menu_info.get('icon'), - menu_name=menu_info.get('menu_name'), order_num=menu_info.get('order_num') - ), - form_validate=[None] * 6, - other=dict( - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=menu_info, - modal_type={'type': 'edit'} - ) - ) return dict( - modal=dict(visible=dash.no_update, title=dash.no_update), + modal=dict(visible=no_update, title=no_update), form_value=dict( - parent_tree=dash.no_update, parent_id=dash.no_update, menu_type=dash.no_update, - icon=dash.no_update, icon_prefix=dash.no_update, icon_category=dash.no_update, - menu_name=dash.no_update, order_num=dash.no_update + parent_tree=no_update, + parent_id=no_update, + menu_type=no_update, + icon=no_update, + icon_prefix=no_update, + icon_category=no_update, + menu_name=no_update, + order_num=no_update, ), - form_validate=[dash.no_update] * 6, + form_validate=[no_update] * 6, other=dict( - api_check_token_trigger={'timestamp': time.time()}, edit_row_info=None, - modal_type=None - ) + modal_type=None, + ), ) raise PreventUpdate @app.callback( - [Output('content-by-menu-type', 'children'), - Output('content-by-menu-type', 'key'), - Output('menu-modal-menu-type-store', 'data')], + [ + Output('content-by-menu-type', 'children'), + Output('content-by-menu-type', 'key'), + Output('menu-modal-menu-type-store', 'data'), + ], Input('menu-menu_type', 'value'), - prevent_initial_call=True + prevent_initial_call=True, ) def get_bottom_content(menu_value): """ 根据不同菜单类型渲染不同的子区域 """ - if menu_value == 'M': - return [content_type.render(), str(uuid.uuid4()), {'type': 'M'}] + if menu_value == MenuConstant.TYPE_DIR: + return [ + content_type.render(), + str(uuid.uuid4()), + {'type': MenuConstant.TYPE_DIR}, + ] - elif menu_value == 'C': - return [menu_type.render(), str(uuid.uuid4()), {'type': 'C'}] + elif menu_value == MenuConstant.TYPE_MENU: + return [ + menu_type.render(), + str(uuid.uuid4()), + {'type': MenuConstant.TYPE_MENU}, + ] - elif menu_value == 'F': - return [button_type.render(), str(uuid.uuid4()), {'type': 'F'}] + elif menu_value == MenuConstant.TYPE_BUTTON: + return [ + button_type.render(), + str(uuid.uuid4()), + {'type': MenuConstant.TYPE_BUTTON}, + ] raise PreventUpdate @app.callback( - [Output('menu-modal-M-trigger', 'data'), - Output('menu-modal-C-trigger', 'data'), - Output('menu-modal-F-trigger', 'data')], + [ + Output('menu-modal-M-trigger', 'data'), + Output('menu-modal-C-trigger', 'data'), + Output('menu-modal-F-trigger', 'data'), + ], Input('menu-modal', 'okCounts'), State('menu-modal-menu-type-store', 'data'), ) @@ -340,85 +408,66 @@ def modal_confirm_trigger(confirm, menu_type): 增加触发器,根据不同菜单类型触发不同的回调,解决组件不存在回调异常的问题 """ if confirm: - if menu_type.get('type') == 'M': - return [ - {'timestamp': time.time()}, - dash.no_update, - dash.no_update - ] - if menu_type.get('type') == 'C': - return [ - dash.no_update, - {'timestamp': time.time()}, - dash.no_update - ] - - if menu_type.get('type') == 'F': - return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()} - ] + if menu_type.get('type') == MenuConstant.TYPE_DIR: + return [{'timestamp': time.time()}, no_update, no_update] + if menu_type.get('type') == MenuConstant.TYPE_MENU: + return [no_update, {'timestamp': time.time()}, no_update] + + if menu_type.get('type') == MenuConstant.TYPE_BUTTON: + return [no_update, no_update, {'timestamp': time.time()}] raise PreventUpdate @app.callback( - [Output('menu-delete-text', 'children'), - Output('menu-delete-confirm-modal', 'visible'), - Output('menu-delete-ids-store', 'data')], + [ + Output('menu-delete-text', 'children'), + Output('menu-delete-confirm-modal', 'visible'), + Output('menu-delete-ids-store', 'data'), + ], [Input('menu-list-table', 'nClicksButton')], - [State('menu-list-table', 'clickedContent'), - State('menu-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + State('menu-list-table', 'clickedContent'), + State('menu-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def menu_delete_modal(button_click, clicked_content, recently_button_clicked_row): +def menu_delete_modal( + button_click, clicked_content, recently_button_clicked_row +): """ 显示删除菜单二次确认弹窗回调 """ if button_click: - if clicked_content == '删除': menu_ids = recently_button_clicked_row['key'] else: - return dash.no_update + raise PreventUpdate return [ f'是否确认删除菜单编号为{menu_ids}的菜单?', True, - {'menu_ids': menu_ids} + menu_ids, ] raise PreventUpdate @app.callback( - [Output('menu-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('menu-operations-store', 'data', allow_duplicate=True), Input('menu-delete-confirm-modal', 'okCounts'), State('menu-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def menu_delete_confirm(delete_confirm, menu_ids_data): """ 删除菜单弹窗确认回调,实现删除操作 """ if delete_confirm: - params = menu_ids_data - delete_button_info = delete_menu_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + MenuApi.del_menu(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/notice_c.py b/dash-fastapi-frontend/callbacks/system_c/notice_c.py index 74b3bb9449f90fa4b3d8f6422cff9975697e77b9..95323134defadee5db54440821bc172b2898a274 100644 --- a/dash-fastapi-frontend/callbacks/system_c/notice_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/notice_c.py @@ -1,129 +1,131 @@ -import dash -import time import uuid -import json -from dash.dependencies import Input, Output, State, ALL +from dash import ctx, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.system.notice import NoticeApi +from config.constant import SysNoticeStatusConstant, SysNoticeTypeConstant +from config.env import ApiConfig from server import app -from utils.common import validate_data_not_empty -from api.notice import get_notice_list_api, add_notice_api, edit_notice_api, delete_notice_api, get_notice_detail_api -from api.dict import query_dict_data_list_api +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.string_util import StringUtil +from utils.time_format_util import TimeFormatUtil + + +def generate_notice_table(query_params: Dict): + """ + 根据查询参数获取通知公告表格数据及分页信息 + + :param query_params: 查询参数 + :return: 通知公告表格数据及分页信息 + """ + table_info = NoticeApi.list_notice(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status'] = DictManager.get_dict_tag( + dict_type='sys_notice_status', dict_value=item.get('status') + ) + item['notice_type'] = DictManager.get_dict_tag( + dict_type='sys_notice_type', dict_value=item.get('notice_type') + ) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['notice_id']) + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:notice:edit') + else {}, + {'content': '删除', 'type': 'link', 'icon': 'antd-delete'} + if PermissionManager.check_perms('system:notice:remove') + else {}, + ] + + return [table_data, table_pagination] @app.callback( output=dict( - notice_table_data=Output('notice-list-table', 'data', allow_duplicate=True), - notice_table_pagination=Output('notice-list-table', 'pagination', allow_duplicate=True), + notice_table_data=Output( + 'notice-list-table', 'data', allow_duplicate=True + ), + notice_table_pagination=Output( + 'notice-list-table', 'pagination', allow_duplicate=True + ), notice_table_key=Output('notice-list-table', 'key'), - notice_table_selectedrowkeys=Output('notice-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + notice_table_selectedrowkeys=Output( + 'notice-list-table', 'selectedRowKeys' + ), ), inputs=dict( search_click=Input('notice-search', 'nClicks'), refresh_click=Input('notice-refresh', 'nClicks'), pagination=Input('notice-list-table', 'pagination'), - operations=Input('notice-operations-store', 'data') + operations=Input('notice-operations-store', 'data'), ), state=dict( notice_title=State('notice-notice_title-input', 'value'), update_by=State('notice-update_by-input', 'value'), notice_type=State('notice-notice_type-select', 'value'), create_time_range=State('notice-create_time-range', 'value'), - button_perms=State('notice-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_notice_table_data(search_click, refresh_click, pagination, operations, notice_title, update_by, notice_type, create_time_range, - button_perms): +def get_notice_table_data( + search_click, + refresh_click, + pagination, + operations, + notice_title, + update_by, + notice_type, + create_time_range, +): """ 获取通知公告表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - create_time_start = None - create_time_end = None + begin_time = None + end_time = None if create_time_range: - create_time_start = create_time_range[0] - create_time_end = create_time_range[1] + begin_time = create_time_range[0] + end_time = create_time_range[1] query_params = dict( notice_title=notice_title, update_by=update_by, notice_type=notice_type, - create_time_start=create_time_start, - create_time_end=create_time_end, + begin_time=begin_time, + end_time=end_time, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'notice-list-table': - query_params = dict( - notice_title=notice_title, - update_by=update_by, - notice_type=notice_type, - create_time_start=create_time_start, - create_time_end=create_time_end, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - option_table = [] - info = query_dict_data_list_api(dict_type='sys_notice_type') - if info.get('code') == 200: - data = info.get('data') - option_table = [ - dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for - item in data] - option_dict = {item.get('value'): item for item in option_table} - - table_info = get_notice_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='关闭', color='volcano') - item['notice_type'] = dict( - tag=option_dict.get(str(item.get('notice_type'))).get('label'), - color=json.loads(option_dict.get(str(item.get('notice_type'))).get('css_class')).get('color') - ) - item['key'] = str(item['notice_id']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:notice:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:notice:remove' in button_perms else {}, - ] - - return dict( - notice_table_data=table_data, - notice_table_pagination=table_pagination, - notice_table_key=str(uuid.uuid4()), - notice_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) + table_data, table_pagination = generate_notice_table(query_params) return dict( - notice_table_data=dash.no_update, - notice_table_pagination=dash.no_update, - notice_table_key=dash.no_update, - notice_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + notice_table_data=table_data, + notice_table_pagination=table_pagination, + notice_table_key=str(uuid.uuid4()), + notice_table_selectedrowkeys=None, ) raise PreventUpdate @@ -131,27 +133,29 @@ def get_notice_table_data(search_click, refresh_click, pagination, operations, n # 重置通知公告搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('notice-notice_title-input', 'value'), - Output('notice-update_by-input', 'value'), - Output('notice-notice_type-select', 'value'), - Output('notice-create_time-range', 'value'), - Output('notice-operations-store', 'data')], + """, + [ + Output('notice-notice_title-input', 'value'), + Output('notice-update_by-input', 'value'), + Output('notice-notice_type-select', 'value'), + Output('notice-create_time-range', 'value'), + Output('notice-operations-store', 'data'), + ], Input('notice-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示通知公告搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -161,152 +165,187 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('notice-search-form-container', 'hidden'), - Output('notice-hidden-tooltip', 'title')], + """, + [ + Output('notice-search-form-container', 'hidden'), + Output('notice-hidden-tooltip', 'title'), + ], Input('notice-hidden', 'nClicks'), State('notice-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'notice-operation-button', 'index': 'edit'}, 'disabled'), Input('notice-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_notice_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - - return False - return True - - raise PreventUpdate - -@app.callback( +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'notice-operation-button', 'index': 'delete'}, 'disabled'), Input('notice-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_notice_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - - return True - - raise PreventUpdate @app.callback( output=dict( - modal=dict(visible=Output('notice-modal', 'visible', allow_duplicate=True), title=Output('notice-modal', 'title')), + modal=dict( + visible=Output('notice-modal', 'visible', allow_duplicate=True), + title=Output('notice-modal', 'title'), + ), form_value=dict( notice_title=Output('notice-notice_title', 'value'), notice_type=Output('notice-notice_type', 'value'), status=Output('notice-status', 'value'), notice_content=Output('notice-content', 'htmlValue'), - editor_key=Output('notice-content', 'key') + editor_key=Output('notice-content', 'key'), ), form_validate=[ - Output('notice-notice_title-form-item', 'validateStatus', allow_duplicate=True), - Output('notice-notice_type-form-item', 'validateStatus', allow_duplicate=True), - Output('notice-notice_title-form-item', 'help', allow_duplicate=True), - Output('notice-notice_type-form-item', 'help', allow_duplicate=True) + Output( + 'notice-notice_title-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'notice-notice_type-form-item', + 'validateStatus', + allow_duplicate=True, + ), + Output( + 'notice-notice_title-form-item', 'help', allow_duplicate=True + ), + Output( + 'notice-notice_type-form-item', 'help', allow_duplicate=True + ), ], other=dict( - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), edit_row_info=Output('notice-edit-id-store', 'data'), - modal_type=Output('notice-operations-store-bk', 'data') - ) + modal_type=Output('notice-modal_type-store', 'data'), + ), ), inputs=dict( - operation_click=Input({'type': 'notice-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('notice-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'notice-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('notice-list-table', 'nClicksButton'), ), state=dict( selected_row_keys=State('notice-list-table', 'selectedRowKeys'), clicked_content=State('notice-list-table', 'clickedContent'), - recently_button_clicked_row=State('notice-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'notice-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_notice_modal(operation_click, button_click, selected_row_keys, clicked_content, - recently_button_clicked_row): +def add_edit_notice_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示新增或编辑通知公告弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'notice-operation-button'} \ - or trigger_id == {'index': 'edit', 'type': 'notice-operation-button'} \ - or (trigger_id == 'notice-list-table' and clicked_content == '修改'): + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'notice-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'notice-operation-button'} + or (trigger_id == 'notice-list-table' and clicked_content == '修改') + ): if trigger_id == {'index': 'add', 'type': 'notice-operation-button'}: return dict( modal=dict(visible=True, title='新增通知公告'), - form_value=dict(notice_title=None, notice_type=None, status='0', notice_content=None, editor_key=str(uuid.uuid4())), + form_value=dict( + notice_title=None, + notice_type=SysNoticeTypeConstant.NOTICE, + status=SysNoticeStatusConstant.NORMAL, + notice_content=None, + editor_key=str(uuid.uuid4()), + ), form_validate=[None] * 4, other=dict( - api_check_token_trigger=dash.no_update, edit_row_info=None, - modal_type={'type': 'add'} - ) + modal_type={'type': 'add'}, + ), ) - elif trigger_id == {'index': 'edit', 'type': 'notice-operation-button'} or (trigger_id == 'notice-list-table' and clicked_content == '修改'): - if trigger_id == {'index': 'edit', 'type': 'notice-operation-button'}: + elif trigger_id == { + 'index': 'edit', + 'type': 'notice-operation-button', + } or (trigger_id == 'notice-list-table' and clicked_content == '修改'): + if trigger_id == { + 'index': 'edit', + 'type': 'notice-operation-button', + }: notice_id = int(','.join(selected_row_keys)) else: notice_id = int(recently_button_clicked_row['key']) - notice_info_res = get_notice_detail_api(notice_id=notice_id) - if notice_info_res['code'] == 200: - notice_info = notice_info_res['data'] - notice_content = notice_info.get('notice_content') + notice_info_res = NoticeApi.get_notice(notice_id=notice_id) + notice_info = notice_info_res['data'] + notice_content = notice_info.get('notice_content') - return dict( - modal=dict(visible=True, title='编辑通知公告'), - form_value=dict( - notice_title=notice_info.get('notice_title'), - notice_type=notice_info.get('notice_type'), - status=notice_info.get('status'), - notice_content=notice_content, - editor_key=str(uuid.uuid4()) + return dict( + modal=dict(visible=True, title='编辑通知公告'), + form_value=dict( + notice_title=notice_info.get('notice_title'), + notice_type=notice_info.get('notice_type'), + status=notice_info.get('status'), + notice_content=StringUtil.insert_before_substring( + notice_content, '/profile/upload/', ApiConfig.BaseUrl ), - form_validate=[None] * 4, - other=dict( - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=notice_info if notice_info else None, - modal_type={'type': 'edit'} - ) - ) + editor_key=str(uuid.uuid4()), + ), + form_validate=[None] * 4, + other=dict( + edit_row_info=notice_info if notice_info else None, + modal_type={'type': 'edit'}, + ), + ) return dict( - modal=dict(visible=dash.no_update, title=dash.no_update), + modal=dict(visible=no_update, title=no_update), form_value=dict( - notice_title=dash.no_update, - notice_type=dash.no_update, - status=dash.no_update, - notice_content=dash.no_update, - editor_key=dash.no_update + notice_title=no_update, + notice_type=no_update, + status=no_update, + notice_content=no_update, + editor_key=no_update, ), - form_validate=[dash.no_update] * 4, + form_validate=[no_update] * 4, other=dict( - api_check_token_trigger={'timestamp': time.time()}, edit_row_info=None, - modal_type=None - ) + modal_type=None, + ), ) raise PreventUpdate @@ -314,114 +353,160 @@ def add_edit_notice_modal(operation_click, button_click, selected_row_keys, clic @app.callback( output=dict( - notice_title_form_status=Output('notice-notice_title-form-item', 'validateStatus', allow_duplicate=True), - notice_type_form_status=Output('notice-notice_type-form-item', 'validateStatus', allow_duplicate=True), - notice_title_form_help=Output('notice-notice_title-form-item', 'help', allow_duplicate=True), - notice_type_form_help=Output('notice-notice_type-form-item', 'help', allow_duplicate=True), + notice_title_form_status=Output( + 'notice-notice_title-form-item', + 'validateStatus', + allow_duplicate=True, + ), + notice_type_form_status=Output( + 'notice-notice_type-form-item', + 'validateStatus', + allow_duplicate=True, + ), + notice_title_form_help=Output( + 'notice-notice_title-form-item', 'help', allow_duplicate=True + ), + notice_type_form_help=Output( + 'notice-notice_type-form-item', 'help', allow_duplicate=True + ), modal_visible=Output('notice-modal', 'visible'), - operations=Output('notice-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('notice-modal', 'okCounts') + operations=Output( + 'notice-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('notice-modal', 'okCounts')), state=dict( - modal_type=State('notice-operations-store-bk', 'data'), + modal_type=State('notice-modal_type-store', 'data'), edit_row_info=State('notice-edit-id-store', 'data'), notice_title=State('notice-notice_title', 'value'), notice_type=State('notice-notice_type', 'value'), status=State('notice-status', 'value'), - notice_content=State('notice-content', 'htmlValue') + notice_content=State('notice-content', 'htmlValue'), ), - prevent_initial_call=True + running=[[Output('notice-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def notice_confirm(confirm_trigger, modal_type, edit_row_info, notice_title, notice_type, status, notice_content): +def notice_confirm( + confirm_trigger, + modal_type, + edit_row_info, + notice_title, + notice_type, + status, + notice_content, +): """ 新增或编辑通知公告弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - if all(validate_data_not_empty(item) for item in [notice_title, notice_type]): - params_add = dict(notice_title=notice_title, notice_type=notice_type, status=status, - notice_content=notice_content) - params_edit = dict(notice_id=edit_row_info.get('notice_id') if edit_row_info else None, - notice_title=notice_title, - notice_type=notice_type, status=status, notice_content=notice_content) - api_res = {} + if all( + ValidateUtil.not_empty(item) + for item in [notice_title, notice_type] + ): + params_add = dict( + notice_title=notice_title, + notice_type=notice_type, + status=status, + notice_content=notice_content.replace(ApiConfig.BaseUrl, ''), + ) + params_edit = dict( + notice_id=edit_row_info.get('notice_id') + if edit_row_info + else None, + notice_title=notice_title, + notice_type=notice_type, + status=status, + notice_content=notice_content.replace(ApiConfig.BaseUrl, ''), + ) modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_notice_api(params_add) + NoticeApi.add_notice(params_add) if modal_type == 'edit': - api_res = edit_notice_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - notice_title_form_status=None, - notice_type_form_status=None, - notice_title_form_help=None, - notice_type_form_help=None, - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - notice_title_form_status=None, - notice_type_form_status=None, - notice_title_form_help=None, - notice_type_form_help=None, - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + NoticeApi.update_notice(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + notice_title_form_status=None, + notice_type_form_status=None, + notice_title_form_help=None, + notice_type_form_help=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + notice_title_form_status=None, + notice_type_form_status=None, + notice_title_form_help=None, + notice_type_form_help=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( notice_title_form_status=None, notice_type_form_status=None, notice_title_form_help=None, notice_type_form_help=None, - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) return dict( - notice_title_form_status=None if validate_data_not_empty(notice_title) else 'error', - notice_type_form_status=None if validate_data_not_empty(notice_type) else 'error', - notice_title_form_help=None if validate_data_not_empty(notice_title) else '请输入公告标题!', - notice_type_form_help=None if validate_data_not_empty(notice_type) else '请输入公告类型!', - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + notice_title_form_status=None + if ValidateUtil.not_empty(notice_title) + else 'error', + notice_type_form_status=None + if ValidateUtil.not_empty(notice_type) + else 'error', + notice_title_form_help=None + if ValidateUtil.not_empty(notice_title) + else '请输入公告标题!', + notice_type_form_help=None + if ValidateUtil.not_empty(notice_type) + else '请输入公告类型!', + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('notice-delete-text', 'children'), - Output('notice-delete-confirm-modal', 'visible'), - Output('notice-delete-ids-store', 'data')], - [Input({'type': 'notice-operation-button', 'index': ALL}, 'nClicks'), - Input('notice-list-table', 'nClicksButton')], - [State('notice-list-table', 'selectedRowKeys'), - State('notice-list-table', 'clickedContent'), - State('notice-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('notice-delete-text', 'children'), + Output('notice-delete-confirm-modal', 'visible'), + Output('notice-delete-ids-store', 'data'), + ], + [ + Input({'type': 'notice-operation-button', 'index': ALL}, 'nClicks'), + Input('notice-list-table', 'nClicksButton'), + ], + [ + State('notice-list-table', 'selectedRowKeys'), + State('notice-list-table', 'clickedContent'), + State('notice-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def notice_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def notice_delete_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示删除通知公告二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id == {'index': 'delete', 'type': 'notice-operation-button'} or ( - trigger_id == 'notice-list-table' and clicked_content == '删除'): - trigger_id = dash.ctx.triggered_id + trigger_id == 'notice-list-table' and clicked_content == '删除' + ): + trigger_id = ctx.triggered_id if trigger_id == {'index': 'delete', 'type': 'notice-operation-button'}: notice_ids = ','.join(selected_row_keys) @@ -429,44 +514,28 @@ def notice_delete_modal(operation_click, button_click, if clicked_content == '删除': notice_ids = recently_button_clicked_row['key'] else: - return dash.no_update + return no_update - return [ - f'是否确认删除序号为{notice_ids}的通知公告?', - True, - {'notice_ids': notice_ids} - ] + return [f'是否确认删除序号为{notice_ids}的通知公告?', True, notice_ids] raise PreventUpdate @app.callback( - [Output('notice-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('notice-operations-store', 'data', allow_duplicate=True), Input('notice-delete-confirm-modal', 'okCounts'), State('notice-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def notice_delete_confirm(delete_confirm, notice_ids_data): """ 删除岗通知公告弹窗确认回调,实现删除操作 """ if delete_confirm: - params = notice_ids_data - delete_button_info = delete_notice_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + NoticeApi.del_notice(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return ({'type': 'delete'},) raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/post_c.py b/dash-fastapi-frontend/callbacks/system_c/post_c.py index fa0fd279f07309d572de4818f795afccfd6f7b76..03b71c76057b28e9f20d212896d7131158497b26 100644 --- a/dash-fastapi-frontend/callbacks/system_c/post_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/post_c.py @@ -1,39 +1,87 @@ -import dash import time import uuid -from dash import dcc -from dash.dependencies import Input, Output, State, ALL +from dash import ctx, dcc, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.system.post import PostApi +from config.constant import SysNormalDisableConstant from server import app -from utils.common import validate_data_not_empty -from api.post import get_post_list_api, get_post_detail_api, add_post_api, edit_post_api, delete_post_api, export_post_list_api +from utils.common_util import ValidateUtil +from utils.dict_util import DictManager +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil + + +def generate_post_table(query_params: Dict): + """ + 根据查询参数获取岗位表格数据及分页信息 + + :param query_params: 查询参数 + :return: 岗位表格数据及分页信息 + """ + table_info = PostApi.list_post(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + item['status'] = DictManager.get_dict_tag( + dict_type='sys_normal_disable', dict_value=item.get('status') + ) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['post_id']) + item['operation'] = [ + {'content': '修改', 'type': 'link', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:post:edit') + else {}, + {'content': '删除', 'type': 'link', 'icon': 'antd-delete'} + if PermissionManager.check_perms('system:post:remove') + else {}, + ] + + return [table_data, table_pagination] @app.callback( output=dict( post_table_data=Output('post-list-table', 'data', allow_duplicate=True), - post_table_pagination=Output('post-list-table', 'pagination', allow_duplicate=True), + post_table_pagination=Output( + 'post-list-table', 'pagination', allow_duplicate=True + ), post_table_key=Output('post-list-table', 'key'), post_table_selectedrowkeys=Output('post-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( search_click=Input('post-search', 'nClicks'), refresh_click=Input('post-refresh', 'nClicks'), pagination=Input('post-list-table', 'pagination'), - operations=Input('post-operations-store', 'data') + operations=Input('post-operations-store', 'data'), ), state=dict( post_code=State('post-post_code-input', 'value'), post_name=State('post-post_name-input', 'value'), status_select=State('post-status-select', 'value'), - button_perms=State('post-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_post_table_data(search_click, refresh_click, pagination, operations, post_code, post_name, status_select, button_perms): +def get_post_table_data( + search_click, + refresh_click, + pagination, + operations, + post_code, + post_name, + status_select, +): """ 获取岗位表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ @@ -43,61 +91,23 @@ def get_post_table_data(search_click, refresh_click, pagination, operations, pos post_name=post_name, status=status_select, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'post-list-table': - query_params = dict( - post_code=post_code, - post_name=post_name, - status=status_select, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_post_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['post_id']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:post:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:post:remove' in button_perms else {}, - ] - return dict( - post_table_data=table_data, - post_table_pagination=table_pagination, - post_table_key=str(uuid.uuid4()), - post_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) - + table_data, table_pagination = generate_post_table(query_params) return dict( - post_table_data=dash.no_update, - post_table_pagination=dash.no_update, - post_table_key=dash.no_update, - post_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + post_table_data=table_data, + post_table_pagination=table_pagination, + post_table_key=str(uuid.uuid4()), + post_table_selectedrowkeys=None, ) raise PreventUpdate @@ -105,26 +115,28 @@ def get_post_table_data(search_click, refresh_click, pagination, operations, pos # 重置岗位搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('post-post_code-input', 'value'), - Output('post-post_name-input', 'value'), - Output('post-status-select', 'value'), - Output('post-operations-store', 'data')], + """, + [ + Output('post-post_code-input', 'value'), + Output('post-post_name-input', 'value'), + Output('post-status-select', 'value'), + Output('post-operations-store', 'data'), + ], Input('post-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示岗位搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -134,238 +146,288 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('post-search-form-container', 'hidden'), - Output('post-hidden-tooltip', 'title')], + """, + [ + Output('post-search-form-container', 'hidden'), + Output('post-hidden-tooltip', 'title'), + ], Input('post-hidden', 'nClicks'), State('post-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) - -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'post-operation-button', 'index': 'edit'}, 'disabled'), Input('post-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_post_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1: - return True - return False - return True - - raise PreventUpdate - - -@app.callback( +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'post-operation-button', 'index': 'delete'}, 'disabled'), Input('post-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_post_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - - return False - return True - raise PreventUpdate +# 岗位表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'post-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'post-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('post-form-store', 'data', allow_duplicate=True), + Output('post-form', 'values'), + ], + [ + Input('post-form-store', 'data'), + Input('post-form', 'values'), + ], + prevent_initial_call=True, +) @app.callback( output=dict( modal_visible=Output('post-modal', 'visible', allow_duplicate=True), modal_title=Output('post-modal', 'title'), - form_value=Output({'type': 'post-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'post-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'post-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('post-edit-id-store', 'data'), - modal_type=Output('post-operations-store-bk', 'data') + form_value=Output('post-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'post-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'post-form', 'helps', allow_duplicate=True + ), + modal_type=Output('post-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'post-operation-button', 'index': ALL}, 'nClicks'), - button_click=Input('post-list-table', 'nClicksButton') + operation_click=Input( + {'type': 'post-operation-button', 'index': ALL}, 'nClicks' + ), + button_click=Input('post-list-table', 'nClicksButton'), ), state=dict( selected_row_keys=State('post-list-table', 'selectedRowKeys'), clicked_content=State('post-list-table', 'clickedContent'), - recently_button_clicked_row=State('post-list-table', 'recentlyButtonClickedRow') + recently_button_clicked_row=State( + 'post-list-table', 'recentlyButtonClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_edit_post_modal(operation_click, button_click, selected_row_keys, clicked_content, recently_button_clicked_row): +def add_edit_post_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示新增或编辑岗位弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'add', 'type': 'post-operation-button'} \ - or trigger_id == {'index': 'edit', 'type': 'post-operation-button'} \ - or (trigger_id == 'post-list-table' and clicked_content == '修改'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'post-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'post-operation-button'} + or (trigger_id == 'post-list-table' and clicked_content == '修改') + ): if trigger_id == {'index': 'add', 'type': 'post-operation-button'}: - post_info = dict(post_name=None, post_code=None, post_sort=0, status='0', remark=None) + post_info = dict( + post_name=None, + post_code=None, + post_sort=0, + status=SysNormalDisableConstant.NORMAL, + remark=None, + ) return dict( modal_visible=True, modal_title='新增岗位', - form_value=[post_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger=dash.no_update, - edit_row_info=None, - modal_type={'type': 'add'} + form_value=post_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, ) - elif trigger_id == {'index': 'edit', 'type': 'post-operation-button'} or (trigger_id == 'post-list-table' and clicked_content == '修改'): + elif trigger_id == { + 'index': 'edit', + 'type': 'post-operation-button', + } or (trigger_id == 'post-list-table' and clicked_content == '修改'): if trigger_id == {'index': 'edit', 'type': 'post-operation-button'}: post_id = int(','.join(selected_row_keys)) else: post_id = int(recently_button_clicked_row['key']) - post_info_res = get_post_detail_api(post_id=post_id) - if post_info_res['code'] == 200: - post_info = post_info_res['data'] - return dict( - modal_visible=True, - modal_title='编辑岗位', - form_value=[post_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=post_info if post_info else None, - modal_type={'type': 'edit'} - ) - - return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type=None - ) + post_info_res = PostApi.get_post(post_id=post_id) + post_info = post_info_res['data'] + return dict( + modal_visible=True, + modal_title='编辑岗位', + form_value=post_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, + ) raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'post-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'post-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + 'post-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'post-form', 'helps', allow_duplicate=True + ), modal_visible=Output('post-modal', 'visible'), - operations=Output('post-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('post-modal', 'okCounts') + operations=Output( + 'post-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('post-modal', 'okCounts')), state=dict( - modal_type=State('post-operations-store-bk', 'data'), - edit_row_info=State('post-edit-id-store', 'data'), - form_value=State({'type': 'post-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'post-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('post-modal_type-store', 'data'), + form_value=State('post-form-store', 'data'), + form_label=State( + {'type': 'post-form-label', 'index': ALL, 'required': True}, 'label' + ), ), - prevent_initial_call=True + running=[[Output('post-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def post_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label): +def post_confirm(confirm_trigger, modal_type, form_value, form_label): """ 新增或编辑岗位弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params_add = form_value_state + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value params_edit = params_add.copy() - params_edit['post_id'] = edit_row_info.get('post_id') if edit_row_info else None - api_res = {} modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_post_api(params_add) + PostApi.add_post(params_add) if modal_type == 'edit': - api_res = edit_post_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + PostApi.update_post(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('post-delete-text', 'children'), - Output('post-delete-confirm-modal', 'visible'), - Output('post-delete-ids-store', 'data')], - [Input({'type': 'post-operation-button', 'index': ALL}, 'nClicks'), - Input('post-list-table', 'nClicksButton')], - [State('post-list-table', 'selectedRowKeys'), - State('post-list-table', 'clickedContent'), - State('post-list-table', 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('post-delete-text', 'children'), + Output('post-delete-confirm-modal', 'visible'), + Output('post-delete-ids-store', 'data'), + ], + [ + Input({'type': 'post-operation-button', 'index': ALL}, 'nClicks'), + Input('post-list-table', 'nClicksButton'), + ], + [ + State('post-list-table', 'selectedRowKeys'), + State('post-list-table', 'clickedContent'), + State('post-list-table', 'recentlyButtonClickedRow'), + ], + prevent_initial_call=True, ) -def post_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def post_delete_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示删除岗位二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'delete', 'type': 'post-operation-button'} or (trigger_id == 'post-list-table' and clicked_content == '删除'): - + trigger_id = ctx.triggered_id + if trigger_id == {'index': 'delete', 'type': 'post-operation-button'} or ( + trigger_id == 'post-list-table' and clicked_content == '删除' + ): if trigger_id == {'index': 'delete', 'type': 'post-operation-button'}: post_ids = ','.join(selected_row_keys) else: @@ -374,76 +436,63 @@ def post_delete_modal(operation_click, button_click, else: raise PreventUpdate - return [ - f'是否确认删除岗位编号为{post_ids}的岗位?', - True, - {'post_ids': post_ids} - ] + return [f'是否确认删除岗位编号为{post_ids}的岗位?', True, post_ids] raise PreventUpdate @app.callback( - [Output('post-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('post-operations-store', 'data', allow_duplicate=True), Input('post-delete-confirm-modal', 'okCounts'), State('post-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def post_delete_confirm(delete_confirm, post_ids_data): """ 删除岗位弹窗确认回调,实现删除操作 """ if delete_confirm: - params = post_ids_data - delete_button_info = delete_post_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + PostApi.del_post(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('post-export-container', 'data', allow_duplicate=True), - Output('post-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('post-export-container', 'data', allow_duplicate=True), + Output('post-export-complete-judge-container', 'data'), + ], Input('post-export', 'nClicks'), - prevent_initial_call=True + [ + State('post-post_code-input', 'value'), + State('post-post_name-input', 'value'), + State('post-status-select', 'value'), + ], + running=[[Output('post-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_post_list(export_click): +def export_post_list(export_click, post_code, post_name, status): """ 导出岗位信息回调 """ if export_click: - export_post_res = export_post_list_api({}) - if export_post_res.status_code == 200: - export_post = export_post_res.content - - return [ - dcc.send_bytes(export_post, f'岗位信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + export_params = dict( + post_code=post_code, post_name=post_name, status=status + ) + export_post_res = PostApi.export_post(export_params) + export_post = export_post_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_post, + f'岗位信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -452,7 +501,7 @@ def export_post_list(export_click): @app.callback( Output('post-export-container', 'data', allow_duplicate=True), Input('post-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_post_export_status(data): """ @@ -460,7 +509,6 @@ def reset_post_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/role_c/allocate_user_c.py b/dash-fastapi-frontend/callbacks/system_c/role_c/allocate_user_c.py index 9c3a9255fa031ad18056414e7c6e50d2d8120111..0729cd1d4a1a03c45b47b268da9af3efa70ebfb9 100644 --- a/dash-fastapi-frontend/callbacks/system_c/role_c/allocate_user_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/role_c/allocate_user_c.py @@ -1,30 +1,63 @@ -import dash -import time import uuid -from dash.dependencies import Input, Output, State, ALL, MATCH +from dash import ctx, no_update +from dash.dependencies import ALL, Input, MATCH, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.role import RoleApi from server import app -from api.role import get_allocated_user_list_api, get_unallocated_user_list_api, auth_user_select_all_api, auth_user_cancel_api +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil @app.callback( - [Output({'type': 'allocate_user-list-table', 'index': MATCH}, 'data', allow_duplicate=True), - Output({'type': 'allocate_user-list-table', 'index': MATCH}, 'pagination', allow_duplicate=True), - Output({'type': 'allocate_user-list-table', 'index': MATCH}, 'key'), - Output({'type': 'allocate_user-list-table', 'index': MATCH}, 'selectedRowKeys')], - [Input({'type': 'allocate_user-search', 'index': MATCH}, 'nClicks'), - Input({'type': 'allocate_user-refresh', 'index': MATCH}, 'nClicks'), - Input({'type': 'allocate_user-list-table', 'index': MATCH}, 'pagination'), - Input({'type': 'allocate_user-operations-container', 'index': MATCH}, 'data')], - [State({'type': 'allocate_user-user_name-input', 'index': MATCH}, 'value'), - State({'type': 'allocate_user-phonenumber-input', 'index': MATCH}, 'value'), - State('allocate_user-role_id-container', 'data'), - State('allocate_user-button-perms-container', 'data')], - prevent_initial_call=True + [ + Output( + {'type': 'allocate_user-list-table', 'index': MATCH}, + 'data', + allow_duplicate=True, + ), + Output( + {'type': 'allocate_user-list-table', 'index': MATCH}, + 'pagination', + allow_duplicate=True, + ), + Output({'type': 'allocate_user-list-table', 'index': MATCH}, 'key'), + Output( + {'type': 'allocate_user-list-table', 'index': MATCH}, + 'selectedRowKeys', + ), + ], + [ + Input({'type': 'allocate_user-search', 'index': MATCH}, 'nClicks'), + Input({'type': 'allocate_user-refresh', 'index': MATCH}, 'nClicks'), + Input( + {'type': 'allocate_user-list-table', 'index': MATCH}, 'pagination' + ), + Input( + {'type': 'allocate_user-operations-container', 'index': MATCH}, + 'data', + ), + ], + [ + State( + {'type': 'allocate_user-user_name-input', 'index': MATCH}, 'value' + ), + State( + {'type': 'allocate_user-phonenumber-input', 'index': MATCH}, 'value' + ), + State('allocate_user-role_id-container', 'data'), + ], + prevent_initial_call=True, ) -def get_allocate_user_table_data(search_click, refresh_click, pagination, operations, user_name, phonenumber, role_id, button_perms): +def get_allocate_user_table_data( + search_click, + refresh_click, + pagination, + operations, + user_name, + phonenumber, + role_id, +): """ 使用模式匹配回调MATCH模式,根据不同类型获取角色已分配用户列表及未分配用户列表(进行表格相关增删查改操作后均会触发此回调) """ @@ -34,76 +67,86 @@ def get_allocate_user_table_data(search_click, refresh_click, pagination, operat user_name=user_name, phonenumber=phonenumber, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id.type == 'allocate_user-list-table': - query_params = dict( - role_id=int(role_id), - user_name=user_name, - phonenumber=phonenumber, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: table_info = {} if triggered_id.index == 'allocated': - table_info = get_allocated_user_list_api(query_params) + table_info = RoleApi.allocated_user_list(query_params) if triggered_id.index == 'unallocated': - table_info = get_unallocated_user_list_api(query_params) - if table_info.get('code') == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] + table_info = RoleApi.unallocated_user_list(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + if item['status'] == '0': + item['status'] = dict(tag='正常', color='blue') + else: + item['status'] = dict(tag='停用', color='volcano') + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['user_id']) - if triggered_id.index == 'allocated': - item['operation'] = [ - { - 'content': '取消授权', - 'type': 'link', - 'icon': 'antd-close-circle' - } if 'system:role:edit' in button_perms else {}, - ] - - return [table_data, table_pagination, str(uuid.uuid4()), None] + item['key'] = str(item['user_id']) + if triggered_id.index == 'allocated': + item['operation'] = [ + { + 'content': '取消授权', + 'type': 'link', + 'icon': 'antd-close-circle', + } + if PermissionManager.check_perms('system:role:remove') + else {}, + ] - return [dash.no_update, dash.no_update, dash.no_update, dash.no_update] + return [table_data, table_pagination, str(uuid.uuid4()), None] raise PreventUpdate # 重置分配用户搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output({'type': 'allocate_user-user_name-input', 'index': MATCH}, 'value'), - Output({'type': 'allocate_user-phonenumber-input', 'index': MATCH}, 'value'), - Output({'type': 'allocate_user-operations-container', 'index': MATCH}, 'data')], + """, + [ + Output( + {'type': 'allocate_user-user_name-input', 'index': MATCH}, 'value' + ), + Output( + {'type': 'allocate_user-phonenumber-input', 'index': MATCH}, 'value' + ), + Output( + {'type': 'allocate_user-operations-container', 'index': MATCH}, + 'data', + ), + ], Input({'type': 'allocate_user-reset', 'index': MATCH}, 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示分配用户搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -113,25 +156,41 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output({'type': 'allocate_user-search-form-container', 'index': MATCH}, 'hidden'), - Output({'type': 'allocate_user-hidden-tooltip', 'index': MATCH}, 'title')], + """, + [ + Output( + {'type': 'allocate_user-search-form-container', 'index': MATCH}, + 'hidden', + ), + Output( + {'type': 'allocate_user-hidden-tooltip', 'index': MATCH}, 'title' + ), + ], Input({'type': 'allocate_user-hidden', 'index': MATCH}, 'nClicks'), - State({'type': 'allocate_user-search-form-container', 'index': MATCH}, 'hidden'), - prevent_initial_call=True + State( + {'type': 'allocate_user-search-form-container', 'index': MATCH}, + 'hidden', + ), + prevent_initial_call=True, ) @app.callback( - Output({'type': 'allocate_user-operation-button', 'index': 'delete'}, 'disabled'), - Input({'type': 'allocate_user-list-table', 'index': 'allocated'}, 'selectedRowKeys'), - prevent_initial_call=True + Output( + {'type': 'allocate_user-operation-button', 'index': 'delete'}, + 'disabled', + ), + Input( + {'type': 'allocate_user-list-table', 'index': 'allocated'}, + 'selectedRowKeys', + ), + prevent_initial_call=True, ) def change_allocated_user_delete_button_status(table_rows_selected): """ 根据选择的表格数据行数控制取批量消授权按钮状态回调 """ - outputs_list = dash.ctx.outputs_list + outputs_list = ctx.outputs_list if outputs_list: if table_rows_selected: return False @@ -142,32 +201,55 @@ def change_allocated_user_delete_button_status(table_rows_selected): @app.callback( - [Output('allocate_user-modal', 'visible'), - Output({'type': 'allocate_user-search', 'index': 'unallocated'}, 'nClicks')], + [ + Output('allocate_user-modal', 'visible'), + Output( + {'type': 'allocate_user-search', 'index': 'unallocated'}, 'nClicks' + ), + ], Input('allocate_user-add', 'nClicks'), State({'type': 'allocate_user-search', 'index': 'unallocated'}, 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) def allocate_user_modal(add_click, unallocated_user): """ 分配用户弹框中添加用户按钮回调 """ if add_click: - return [True, unallocated_user + 1 if unallocated_user else 1] raise PreventUpdate @app.callback( - [Output({'type': 'allocate_user-operations-container', 'index': 'allocated'}, 'data', allow_duplicate=True), - Output({'type': 'allocate_user-operations-container', 'index': 'unallocated'}, 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output( + { + 'type': 'allocate_user-operations-container', + 'index': 'allocated', + }, + 'data', + allow_duplicate=True, + ), + Output( + { + 'type': 'allocate_user-operations-container', + 'index': 'unallocated', + }, + 'data', + allow_duplicate=True, + ), + ], Input('allocate_user-modal', 'okCounts'), - [State({'type': 'allocate_user-list-table', 'index': 'unallocated'}, 'selectedRowKeys'), - State('allocate_user-role_id-container', 'data')], - prevent_initial_call=True + [ + State( + {'type': 'allocate_user-list-table', 'index': 'unallocated'}, + 'selectedRowKeys', + ), + State('allocate_user-role_id-container', 'data'), + ], + running=[[Output('allocate_user-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) def allocate_user_add_confirm(add_confirm, selected_row_keys, role_id): """ @@ -175,99 +257,122 @@ def allocate_user_add_confirm(add_confirm, selected_row_keys, role_id): """ if add_confirm: if selected_row_keys: - - params = {'user_ids': ','.join(selected_row_keys), 'role_ids': role_id} - add_button_info = auth_user_select_all_api(params) - if add_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('授权成功', type='success') - ] + params = { + 'user_ids': ','.join(selected_row_keys), + 'role_id': int(role_id), + } + RoleApi.auth_user_select_all(params) + MessageManager.success(content='授权成功') return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('授权失败', type='error') + {'type': 'add'}, + {'type': 'add'}, ] + MessageManager.error(content='请选择用户') return [ - dash.no_update, - dash.no_update, - dash.no_update, - fuc.FefferyFancyMessage('请选择用户', type='error') + no_update, + no_update, ] raise PreventUpdate @app.callback( - [Output('allocate_user-delete-text', 'children'), - Output('allocate_user-delete-confirm-modal', 'visible'), - Output('allocate_user-delete-ids-store', 'data')], - [Input({'type': 'allocate_user-operation-button', 'index': ALL}, 'nClicks'), - Input({'type': 'allocate_user-list-table', 'index': 'allocated'}, 'nClicksButton')], - [State({'type': 'allocate_user-list-table', 'index': 'allocated'}, 'selectedRowKeys'), - State({'type': 'allocate_user-list-table', 'index': 'allocated'}, 'clickedContent'), - State({'type': 'allocate_user-list-table', 'index': 'allocated'}, 'recentlyButtonClickedRow')], - prevent_initial_call=True + [ + Output('allocate_user-delete-text', 'children'), + Output('allocate_user-delete-confirm-modal', 'visible'), + Output('allocate_user-delete-ids-store', 'data'), + ], + [ + Input( + {'type': 'allocate_user-operation-button', 'index': ALL}, 'nClicks' + ), + Input( + {'type': 'allocate_user-list-table', 'index': 'allocated'}, + 'nClicksButton', + ), + ], + [ + State( + {'type': 'allocate_user-list-table', 'index': 'allocated'}, + 'selectedRowKeys', + ), + State( + {'type': 'allocate_user-list-table', 'index': 'allocated'}, + 'clickedContent', + ), + State( + {'type': 'allocate_user-list-table', 'index': 'allocated'}, + 'recentlyButtonClickedRow', + ), + ], + prevent_initial_call=True, ) -def allocate_user_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): +def allocate_user_delete_modal( + operation_click, + button_click, + selected_row_keys, + clicked_content, + recently_button_clicked_row, +): """ 显示取消授权二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.type == 'allocate_user-operation-button' or ( - trigger_id.type == 'allocate_user-list-table' and clicked_content == '取消授权'): - + trigger_id.type == 'allocate_user-list-table' + and clicked_content == '取消授权' + ): if trigger_id.type == 'allocate_user-operation-button': user_ids = ','.join(selected_row_keys) + oper = {'oper_type': 'clear', 'user_ids': user_ids} else: if clicked_content == '取消授权': user_ids = recently_button_clicked_row['key'] + oper = {'oper_type': 'delete', 'user_ids': user_ids} else: - return dash.no_update + return no_update - return [ - f'是否确认取消用户id为{user_ids}的授权?', - True, - user_ids - ] + return [f'是否确认取消用户id为{user_ids}的授权?', True, oper] raise PreventUpdate @app.callback( - [Output({'type': 'allocate_user-operations-container', 'index': 'allocated'}, 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output( + { + 'type': 'allocate_user-operations-container', + 'index': 'allocated', + }, + 'data', + allow_duplicate=True, + ), Input('allocate_user-delete-confirm-modal', 'okCounts'), - [State('allocate_user-delete-ids-store', 'data'), - State('allocate_user-role_id-container', 'data')], - prevent_initial_call=True + [ + State('allocate_user-delete-ids-store', 'data'), + State('allocate_user-role_id-container', 'data'), + ], + prevent_initial_call=True, ) def allocate_user_delete_confirm(delete_confirm, user_ids_data, role_id): """ 取消授权弹窗确认回调,实现取消授权操作 """ if delete_confirm: + oper_type = user_ids_data.get('oper_type') + user_ids = user_ids_data.get('user_ids') + if oper_type == 'clear': + params = {'user_ids': user_ids, 'role_id': int(role_id)} + RoleApi.auth_user_cancel_all(params) + MessageManager.success(content='取消授权成功') + + return {'type': 'clear'} + else: + params = {'user_id': int(user_ids), 'role_id': int(role_id)} + RoleApi.auth_user_cancel(params) + MessageManager.success(content='取消授权成功') - params = {'user_ids': user_ids_data, 'role_ids': role_id} - delete_button_info = auth_user_cancel_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('取消授权成功', type='success') - ] - - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('取消授权失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/role_c/data_scope_c.py b/dash-fastapi-frontend/callbacks/system_c/role_c/data_scope_c.py index 808dab993da6e8ff9e2ffe4ebb042a2de948b3d5..8604e5de8697461dd3054cb981b13ef1b6728f3e 100644 --- a/dash-fastapi-frontend/callbacks/system_c/role_c/data_scope_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/role_c/data_scope_c.py @@ -1,197 +1,154 @@ -import dash -import time -from dash.dependencies import Input, Output, State, ALL +import uuid +from dash import ctx, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.role import RoleApi from server import app -from api.role import get_role_detail_api, role_datascope_api -from api.dept import get_dept_tree_api, get_dept_list_api +from utils.feedback_util import MessageManager +from utils.tree_util import TreeUtil @app.callback( Output('role-dept-perms', 'expandedKeys'), Input('role-dept-perms-radio-fold-unfold', 'checked'), - State('role-dept-store', 'data'), - prevent_initial_call=True + State('role-dept-perms', 'treeData'), + prevent_initial_call=True, ) -def fold_unfold_role_dept(fold_unfold, dept_info): +def fold_unfold_role_dept(fold_unfold, dept_tree): """ 数据权限表单中展开/折叠checkbox回调 """ - if dept_info: - default_expanded_keys = [] - for item in dept_info: - if fold_unfold: - default_expanded_keys.append(str(item.get('dept_id'))) - else: - if item.get('parent_id') == 0: - default_expanded_keys.append(str(item.get('dept_id'))) - - return default_expanded_keys + if dept_tree and fold_unfold is not None: + expanded_keys = [ + dept.get('key') for dept in dept_tree[0].get('children') + ] + expanded_keys.append(dept_tree[0].get('key')) + if fold_unfold: + return expanded_keys + else: + return [] - return dash.no_update + return no_update @app.callback( Output('role-dept-perms', 'checkedKeys', allow_duplicate=True), Input('role-dept-perms-radio-all-none', 'checked'), - State('role-dept-store', 'data'), - prevent_initial_call=True + State('role-dept-perms', 'treeData'), + prevent_initial_call=True, ) -def all_none_role_dept_mode(all_none, dept_info): +def all_none_role_dept_mode(all_none, dept_tree): """ 数据权限表单中全选/全不选checkbox回调 """ - if dept_info: - default_expanded_keys = [] - for item in dept_info: - if item.get('parent_id') == 0: - default_expanded_keys.append(str(item.get('dept_id'))) - + if dept_tree and all_none is not None: if all_none: - return [str(item.get('dept_id')) for item in dept_info] + all_keys = TreeUtil.find_tree_all_keys(dept_tree, []) + return all_keys else: return [] - return dash.no_update + return no_update @app.callback( - [Output('role-dept-perms', 'checkStrictly'), - Output('role-dept-perms', 'checkedKeys', allow_duplicate=True)], + [ + Output('role-dept-perms', 'checkStrictly'), + Output('role-dept-perms', 'keys', allow_duplicate=True), + ], Input('role-dept-perms-radio-parent-children', 'checked'), - State('current-role-dept-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_role_dept_mode(parent_children, current_role_dept): +def change_role_dept_mode(parent_children): """ 数据权限表单中父子联动checkbox回调 """ - checked_dept = [] - if parent_children: - if current_role_dept: - for item in current_role_dept: - has_children = False - for other_item in current_role_dept: - if other_item['parent_id'] == item['dept_id']: - has_children = True - break - if not has_children: - checked_dept.append(str(item.get('dept_id'))) - return [False, checked_dept] - else: - if current_role_dept: - checked_dept = [str(item.get('dept_id')) for item in current_role_dept if item] or [] - return [True, checked_dept] + return [not parent_children, str(uuid.uuid4())] @app.callback( output=dict( dept_div=Output('role-dept-perms-div', 'hidden'), dept_perms_tree=Output('role-dept-perms', 'treeData'), - dept_perms_expanded_check=Output('role-dept-perms-radio-fold-unfold', 'checked'), - dept_perms_checkedkeys=Output('role-dept-perms', 'checkedKeys', allow_duplicate=True), - dept_perms_halfcheckedkeys=Output('role-dept-perms', 'halfCheckedKeys', allow_duplicate=True), - role_dept=Output('role-dept-store', 'data'), - current_role_dept=Output('current-role-dept-store', 'data') + dept_perms_expanded_check=Output( + 'role-dept-perms-radio-fold-unfold', 'checked' + ), + dept_perms_checkedkeys=Output( + 'role-dept-perms', 'checkedKeys', allow_duplicate=True + ), + radio_parent_children=Output( + 'role-dept-perms-radio-parent-children', 'checked' + ), ), inputs=dict( - data_scope=Input({'type': 'datascope-form-value', 'index': 'data_scope'}, 'value'), - ), - state=dict( - role_info=State('role-edit-id-store', 'data') + data_scope=Input( + {'type': 'datascope-form-value', 'index': 'data_scope'}, 'value' + ), ), - prevent_initial_call=True + state=dict(role_info=State('role-edit-id-store', 'data')), + prevent_initial_call=True, ) def get_role_dept_info(data_scope, role_info): if data_scope == '2': - tree_info = get_dept_tree_api({}) - dept_list_info = get_dept_list_api({}) - if tree_info.get('code') == 200 and dept_list_info.get('code') == 200: - tree_data = tree_info['data'] - dept_list = [item for item in dept_list_info['data']['rows'] if item.get('status') == '0'] - checked_dept = [] - checked_dept_all = [] - if role_info.get('dept')[0]: - for item in role_info.get('dept'): - checked_dept_all.append(str(item.get('dept_id'))) - has_children = False - for other_item in role_info.get('dept'): - if other_item['parent_id'] == item['dept_id']: - has_children = True - break - if not has_children: - checked_dept.append(str(item.get('dept_id'))) - half_checked_dept = [x for x in checked_dept_all if x not in checked_dept] - - return dict( - dept_div=False, - dept_perms_tree=tree_data, - dept_perms_expanded_check=True, - dept_perms_checkedkeys=checked_dept, - dept_perms_halfcheckedkeys=half_checked_dept, - role_dept=dept_list, - current_role_dept=role_info.get('dept') - ) + tree_info = RoleApi.dept_tree_select( + role_id=int(role_info.get('role_id')) + ) + tree_data = tree_info['depts'] + checked_keys = [str(item) for item in tree_info['checked_keys']] return dict( dept_div=False, - dept_perms_tree=dash.no_update, - dept_perms_expanded_check=dash.no_update, - dept_perms_checkedkeys=dash.no_update, - dept_perms_halfcheckedkeys=dash.no_update, - role_dept=dash.no_update, - current_role_dept=dash.no_update + dept_perms_tree=tree_data, + dept_perms_expanded_check=True, + dept_perms_checkedkeys=checked_keys, + radio_parent_children=role_info.get('dept_check_strictly'), ) return dict( dept_div=True, - dept_perms_tree=dash.no_update, - dept_perms_expanded_check=dash.no_update, - dept_perms_checkedkeys=dash.no_update, - dept_perms_halfcheckedkeys=dash.no_update, - role_dept=dash.no_update, - current_role_dept=dash.no_update + dept_perms_tree=no_update, + dept_perms_expanded_check=no_update, + dept_perms_checkedkeys=no_update, + radio_parent_children=no_update, ) @app.callback( output=dict( - modal_visible=Output('role-datascope-modal', 'visible', allow_duplicate=True), - form_value=Output({'type': 'datascope-form-value', 'index': ALL}, 'value'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - edit_row_info=Output('role-edit-id-store', 'data', allow_duplicate=True) + modal_visible=Output( + 'role-datascope-modal', 'visible', allow_duplicate=True + ), + form_value=Output( + {'type': 'datascope-form-value', 'index': ALL}, 'value' + ), + edit_row_info=Output( + 'role-edit-id-store', 'data', allow_duplicate=True + ), ), inputs=dict( - button_click=Input({'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, 'nClicks') + button_click=Input( + {'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, + 'nClicks', + ) ), - prevent_initial_call=True + prevent_initial_call=True, ) def edit_role_datascope_modal(button_click): """ 显示角色数据权限弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.operation == 'datascope': # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[1]] + form_value_list = [x['id']['index'] for x in ctx.outputs_list[1]] role_id = int(trigger_id.index) - role_info_res = get_role_detail_api(role_id=role_id) - if role_info_res['code'] == 200: - role_info = role_info_res['data'] - return dict( - modal_visible=True, - form_value=[role_info.get('role').get(k) for k in form_value_list], - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=role_info - ) + role_info_res = RoleApi.get_role(role_id=role_id) + role_info = role_info_res['data'] return dict( - modal_visible=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None + modal_visible=True, + form_value=[role_info.get(k) for k in form_value_list], + edit_row_info=role_info, ) raise PreventUpdate @@ -200,52 +157,63 @@ def edit_role_datascope_modal(button_click): @app.callback( output=dict( modal_visible=Output('role-datascope-modal', 'visible'), - operations=Output('role-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('role-datascope-modal', 'okCounts') + operations=Output( + 'role-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('role-datascope-modal', 'okCounts')), state=dict( edit_row_info=State('role-edit-id-store', 'data'), - form_value=State({'type': 'datascope-form-value', 'index': ALL}, 'value'), + form_value=State( + {'type': 'datascope-form-value', 'index': ALL}, 'value' + ), dept_checked_keys=State('role-dept-perms', 'checkedKeys'), dept_half_checked_keys=State('role-dept-perms', 'halfCheckedKeys'), - parent_checked=State('role-dept-perms-radio-parent-children', 'checked') + parent_checked=State( + 'role-dept-perms-radio-parent-children', 'checked' + ), ), - prevent_initial_call=True + running=[[Output('role-datascope-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def role_datascope_confirm(confirm_trigger, edit_row_info, form_value, dept_checked_keys, dept_half_checked_keys, parent_checked): +def role_datascope_confirm( + confirm_trigger, + edit_row_info, + form_value, + dept_checked_keys, + dept_half_checked_keys, + parent_checked, +): """ 角色数据权限弹窗确认回调,实现分配数据权限的操作 """ if confirm_trigger: # 获取所有输入表单项对应的value - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[1]} - dept_half_checked_keys = dept_half_checked_keys if dept_half_checked_keys else [] + form_value_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[1] + } + dept_half_checked_keys = ( + dept_half_checked_keys if dept_half_checked_keys else [] + ) dept_checked_keys = dept_checked_keys if dept_checked_keys else [] if parent_checked: dept_perms = dept_half_checked_keys + dept_checked_keys else: dept_perms = dept_checked_keys params_datascope = form_value_state - params_datascope['dept_id'] = ','.join(dept_perms) if dept_perms else None - params_datascope['role_id'] = edit_row_info.get('role').get('role_id') if edit_row_info else None - api_res = role_datascope_api(params_datascope) - if api_res.get('code') == 200: - return dict( - modal_visible=False, - operations={'type': 'datascope'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('分配成功', type='success') - ) + params_datascope['dept_ids'] = ( + [int(item) for item in dept_perms] if dept_perms else [] + ) + params_datascope['dept_check_strictly'] = parent_checked + params_datascope['role_id'] = ( + edit_row_info.get('role_id') if edit_row_info else None + ) + RoleApi.data_scope(params_datascope) + MessageManager.success(content='分配成功') return dict( - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('分配失败', type='error') + modal_visible=False, + operations={'type': 'datascope'}, ) raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/role_c/role_c.py b/dash-fastapi-frontend/callbacks/system_c/role_c/role_c.py index 11a9757db7aafa3d630ac77dad187e6e7a0091ae..a0b77349182f6489e382a650e8f555eeeb36a8e2 100644 --- a/dash-fastapi-frontend/callbacks/system_c/role_c/role_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/role_c/role_c.py @@ -1,191 +1,190 @@ -import dash +import feffery_antd_components as fac import time import uuid -from dash import dcc -from dash.dependencies import Input, Output, State, ALL +from dash import ctx, dcc, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_antd_components as fac -import feffery_utils_components as fuc - +from typing import Dict +from api.system.menu import MenuApi +from api.system.role import RoleApi +from config.constant import SysNormalDisableConstant from server import app -from utils.common import validate_data_not_empty -from api.role import get_role_list_api, get_role_detail_api, add_role_api, edit_role_api, delete_role_api, export_role_list_api -from api.menu import get_menu_tree_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil +from utils.tree_util import TreeUtil + + +def generate_role_table(query_params: Dict): + """ + 根据查询参数获取角色表格数据及分页信息 + + :param query_params: 查询参数 + :return: 角色表格数据及分页信息 + """ + table_info = RoleApi.list_role(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + if item['status'] == SysNormalDisableConstant.NORMAL: + item['status'] = dict(checked=True, disabled=item['role_id'] == 1) + else: + item['status'] = dict(checked=False, disabled=item['role_id'] == 1) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['role_id']) + if item['role_id'] == 1: + item['operation'] = [] + else: + item['operation'] = fac.AntdSpace( + [ + fac.AntdButton( + '修改', + id={ + 'type': 'role-operation-table', + 'operation': 'edit', + 'index': str(item['role_id']), + }, + type='link', + icon=fac.AntdIcon(icon='antd-edit'), + style={'padding': 0}, + ) + if PermissionManager.check_perms('system:role:edit') + else [], + fac.AntdButton( + '删除', + id={ + 'type': 'role-operation-table', + 'operation': 'delete', + 'index': str(item['role_id']), + }, + type='link', + icon=fac.AntdIcon(icon='antd-delete'), + style={'padding': 0}, + ) + if PermissionManager.check_perms('system:role:remove') + else [], + fac.AntdPopover( + fac.AntdButton( + '更多', + type='link', + icon=fac.AntdIcon(icon='antd-more'), + style={'padding': 0}, + ), + content=fac.AntdSpace( + [ + fac.AntdButton( + '数据权限', + id={ + 'type': 'role-operation-table', + 'operation': 'datascope', + 'index': str(item['role_id']), + }, + type='text', + block=True, + icon=fac.AntdIcon(icon='antd-check-circle'), + style={'padding': 0}, + ), + fac.AntdButton( + '分配用户', + id={ + 'type': 'role-operation-table', + 'operation': 'allocation', + 'index': str(item['role_id']), + }, + type='text', + block=True, + icon=fac.AntdIcon(icon='antd-user'), + style={'padding': 0}, + ), + ], + direction='vertical', + ), + placement='bottomRight', + ) + if PermissionManager.check_perms('system:role:edit') + else [], + ] + ) + + return [table_data, table_pagination] @app.callback( output=dict( role_table_data=Output('role-list-table', 'data', allow_duplicate=True), - role_table_pagination=Output('role-list-table', 'pagination', allow_duplicate=True), + role_table_pagination=Output( + 'role-list-table', 'pagination', allow_duplicate=True + ), role_table_key=Output('role-list-table', 'key'), role_table_selectedrowkeys=Output('role-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( search_click=Input('role-search', 'nClicks'), refresh_click=Input('role-refresh', 'nClicks'), pagination=Input('role-list-table', 'pagination'), - operations=Input('role-operations-store', 'data') + operations=Input('role-operations-store', 'data'), ), state=dict( role_name=State('role-role_name-input', 'value'), role_key=State('role-role_key-input', 'value'), status_select=State('role-status-select', 'value'), create_time_range=State('role-create_time-range', 'value'), - button_perms=State('role-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_role_table_data(search_click, refresh_click, pagination, operations, role_name, role_key, status_select, create_time_range, button_perms): +def get_role_table_data( + search_click, + refresh_click, + pagination, + operations, + role_name, + role_key, + status_select, + create_time_range, +): """ 获取角色表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ - create_time_start = None - create_time_end = None + begin_time = None + end_time = None if create_time_range: - create_time_start = create_time_range[0] - create_time_end = create_time_range[1] + begin_time = create_time_range[0] + end_time = create_time_range[1] query_params = dict( role_name=role_name, role_key=role_key, status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, + begin_time=begin_time, + end_time=end_time, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'role-list-table': - query_params = dict( - role_name=role_name, - role_key=role_key, - status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) if search_click or refresh_click or pagination or operations: - table_info = get_role_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(checked=True, disabled=item['role_id'] == 1) - else: - item['status'] = dict(checked=False, disabled=item['role_id'] == 1) - item['key'] = str(item['role_id']) - if item['role_id'] == 1: - item['operation'] = [] - else: - item['operation'] = fac.AntdSpace( - [ - fac.AntdButton( - '修改', - id={ - 'type': 'role-operation-table', - 'operation': 'edit', - 'index': str(item['role_id']) - }, - type='link', - icon=fac.AntdIcon( - icon='antd-edit' - ), - style={ - 'padding': 0 - } - ) if 'system:role:edit' in button_perms else [], - fac.AntdButton( - '删除', - id={ - 'type': 'role-operation-table', - 'operation': 'delete', - 'index': str(item['role_id']) - }, - type='link', - icon=fac.AntdIcon( - icon='antd-delete' - ), - style={ - 'padding': 0 - } - ) if 'system:role:remove' in button_perms else [], - fac.AntdPopover( - fac.AntdButton( - '更多', - type='link', - icon=fac.AntdIcon( - icon='antd-more' - ), - style={ - 'padding': 0 - } - ), - content=fac.AntdSpace( - [ - fac.AntdButton( - '数据权限', - id={ - 'type': 'role-operation-table', - 'operation': 'datascope', - 'index': str(item['role_id']) - }, - type='text', - block=True, - icon=fac.AntdIcon( - icon='antd-check-circle' - ), - style={ - 'padding': 0 - } - ), - fac.AntdButton( - '分配用户', - id={ - 'type': 'role-operation-table', - 'operation': 'allocation', - 'index': str(item['role_id']) - }, - type='text', - block=True, - icon=fac.AntdIcon( - icon='antd-user' - ), - style={ - 'padding': 0 - } - ), - ], - direction='vertical' - ), - placement='bottomRight' - ) if 'system:role:edit' in button_perms else [] - ] - ) - - return dict( - role_table_data=table_data, - role_table_pagination=table_pagination, - role_table_key=str(uuid.uuid4()), - role_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) - + table_data, table_pagination = generate_role_table(query_params) return dict( - role_table_data=dash.no_update, - role_table_pagination=dash.no_update, - role_table_key=dash.no_update, - role_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + role_table_data=table_data, + role_table_pagination=table_pagination, + role_table_key=str(uuid.uuid4()), + role_table_selectedrowkeys=None, ) raise PreventUpdate @@ -193,27 +192,29 @@ def get_role_table_data(search_click, refresh_click, pagination, operations, rol # 重置角色搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('role-role_name-input', 'value'), - Output('role-role_key-input', 'value'), - Output('role-status-select', 'value'), - Output('role-create_time-range', 'value'), - Output('role-operations-store', 'data')], + """, + [ + Output('role-role_name-input', 'value'), + Output('role-role_key-input', 'value'), + Output('role-status-select', 'value'), + Output('role-create_time-range', 'value'), + Output('role-operations-store', 'data'), + ], Input('role-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示角色搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -223,247 +224,231 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('role-search-form-container', 'hidden'), - Output('role-hidden-tooltip', 'title')], + """, + [ + Output('role-search-form-container', 'hidden'), + Output('role-hidden-tooltip', 'title'), + ], Input('role-hidden', 'nClicks'), State('role-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'role-operation-button', 'operation': 'edit'}, 'disabled'), Input('role-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_role_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1 or '1' in table_rows_selected: - return True - - return False - - return True - - return dash.no_update -@app.callback( - Output({'type': 'role-operation-button', 'operation': 'delete'}, 'disabled'), +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, + Output( + {'type': 'role-operation-button', 'operation': 'delete'}, 'disabled' + ), Input('role-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_role_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if '1' in table_rows_selected: - return True - - return False - - return True - - return dash.no_update @app.callback( - Output('role-menu-perms', 'expandedKeys', allow_duplicate=True), + Output('role-menu-perms', 'expandedKeys'), Input('role-menu-perms-radio-fold-unfold', 'checked'), - State('role-menu-store', 'data'), - prevent_initial_call=True + State('role-menu-perms', 'treeData'), + prevent_initial_call=True, ) -def fold_unfold_role_menu(fold_unfold, menu_info): +def fold_unfold_role_menu(fold_unfold, menu_tree): """ 新增和编辑表单中展开/折叠checkbox回调 """ - if menu_info: - default_expanded_keys = [] - for item in menu_info: - if item.get('parent_id') == 0: - default_expanded_keys.append(str(item.get('menu_id'))) - + if menu_tree and fold_unfold is not None: + expanded_keys = [menu.get('key') for menu in menu_tree] if fold_unfold: - return default_expanded_keys + return expanded_keys else: return [] - - return dash.no_update + + return no_update @app.callback( Output('role-menu-perms', 'checkedKeys', allow_duplicate=True), Input('role-menu-perms-radio-all-none', 'checked'), - State('role-menu-store', 'data'), - prevent_initial_call=True + State('role-menu-perms', 'treeData'), + prevent_initial_call=True, ) -def all_none_role_menu_mode(all_none, menu_info): +def all_none_role_menu_mode(all_none, menu_tree): """ 新增和编辑表单中全选/全不选checkbox回调 """ - if menu_info: - default_expanded_keys = [] - for item in menu_info: - if item.get('parent_id') == 0: - default_expanded_keys.append(str(item.get('menu_id'))) - + if menu_tree and all_none is not None: if all_none: - return [str(item.get('menu_id')) for item in menu_info] + all_keys = TreeUtil.find_tree_all_keys(menu_tree, []) + return all_keys else: return [] - - return dash.no_update + + return no_update @app.callback( - [Output('role-menu-perms', 'checkStrictly'), - Output('role-menu-perms', 'checkedKeys', allow_duplicate=True)], + [ + Output('role-menu-perms', 'checkStrictly'), + Output('role-menu-perms', 'key', allow_duplicate=True), + ], Input('role-menu-perms-radio-parent-children', 'checked'), - State('current-role-menu-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_role_menu_mode(parent_children, current_role_menu): +def change_role_menu_mode(parent_children): """ 新增和编辑表单中父子联动checkbox回调 """ - checked_menu = [] - if parent_children: - if current_role_menu: - for item in current_role_menu: - has_children = False - for other_item in current_role_menu: - if other_item['parent_id'] == item['menu_id']: - has_children = True - break - if not has_children: - checked_menu.append(str(item.get('menu_id'))) - return [False, checked_menu] - else: - if current_role_menu: - checked_menu = [str(item.get('menu_id')) for item in current_role_menu if item] or [] - return [True, checked_menu] + return [not parent_children, str(uuid.uuid4())] @app.callback( output=dict( modal_visible=Output('role-modal', 'visible', allow_duplicate=True), modal_title=Output('role-modal', 'title'), - form_value=Output({'type': 'role-form-value', 'index': ALL, 'required': ALL}, 'value'), - form_label_validate_status=Output({'type': 'role-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'role-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), + form_value=Output( + {'type': 'role-form-value', 'index': ALL, 'required': ALL}, 'value' + ), + form_label_validate_status=Output( + {'type': 'role-form-label', 'index': ALL, 'required': True}, + 'validateStatus', + allow_duplicate=True, + ), + form_label_validate_info=Output( + {'type': 'role-form-label', 'index': ALL, 'required': True}, + 'help', + allow_duplicate=True, + ), menu_perms_tree=Output('role-menu-perms', 'treeData'), - menu_perms_expandedkeys=Output('role-menu-perms', 'expandedKeys', allow_duplicate=True), - menu_perms_checkedkeys=Output('role-menu-perms', 'checkedKeys', allow_duplicate=True), - menu_perms_halfcheckedkeys=Output('role-menu-perms', 'halfCheckedKeys', allow_duplicate=True), - role_menu=Output('role-menu-store', 'data'), - current_role_menu=Output('current-role-menu-store', 'data'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), + menu_perms_checkedkeys=Output( + 'role-menu-perms', 'checkedKeys', allow_duplicate=True + ), + fold_unfold=Output('role-menu-perms-radio-fold-unfold', 'checked'), + all_none=Output('role-menu-perms-radio-all-none', 'checked'), + radio_parent_children=Output( + 'role-menu-perms-radio-parent-children', 'checked' + ), edit_row_info=Output('role-edit-id-store', 'data'), - modal_type=Output('role-operations-store-bk', 'data') + modal_type=Output('role-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'role-operation-button', 'operation': ALL}, 'nClicks'), - button_click=Input({'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, 'nClicks') + operation_click=Input( + {'type': 'role-operation-button', 'operation': ALL}, 'nClicks' + ), + button_click=Input( + {'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, + 'nClicks', + ), ), - state=dict( - selected_row_keys=State('role-list-table', 'selectedRowKeys') - ), - prevent_initial_call=True + state=dict(selected_row_keys=State('role-list-table', 'selectedRowKeys')), + prevent_initial_call=True, ) def add_edit_role_modal(operation_click, button_click, selected_row_keys): """ 显示新增或编辑角色弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.operation in ['add', 'edit']: # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] + form_value_list = [x['id']['index'] for x in ctx.outputs_list[2]] # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] - menu_params = dict(menu_name='', type='role') - tree_info = get_menu_tree_api(menu_params) - if tree_info.get('code') == 200: + form_label_list = [x['id']['index'] for x in ctx.outputs_list[3]] + if ( + trigger_id.type == 'role-operation-button' + and trigger_id.operation == 'add' + ): + tree_info = MenuApi.treeselect() tree_data = tree_info['data'] - if trigger_id.type == 'role-operation-button' and trigger_id.operation == 'add': - role_info = dict(role_name=None, role_key=None, role_sort=None, status='0', remark=None) - return dict( - modal_visible=True, - modal_title='新增角色', - form_value=[role_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - menu_perms_tree=tree_data[0], - menu_perms_expandedkeys=[], - menu_perms_checkedkeys=None, - menu_perms_halfcheckedkeys=None, - role_menu=tree_data[1], - current_role_menu=None, - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=None, - modal_type={'type': 'add'} - ) - elif trigger_id.operation == 'edit': - if trigger_id.type == 'role-operation-button': - role_id = int(','.join(selected_row_keys)) - else: - role_id = int(trigger_id.index) - role_info_res = get_role_detail_api(role_id=role_id) - if role_info_res['code'] == 200: - role_info = role_info_res['data'] - checked_menu = [] - checked_menu_all = [] - if role_info.get('menu')[0]: - for item in role_info.get('menu'): - checked_menu_all.append(str(item.get('menu_id'))) - has_children = False - for other_item in role_info.get('menu'): - if other_item['parent_id'] == item['menu_id']: - has_children = True - break - if not has_children: - checked_menu.append(str(item.get('menu_id'))) - half_checked_menu = [x for x in checked_menu_all if x not in checked_menu] - return dict( - modal_visible=True, - modal_title='编辑角色', - form_value=[role_info.get('role').get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - menu_perms_tree=tree_data[0], - menu_perms_expandedkeys=[], - menu_perms_checkedkeys=checked_menu, - menu_perms_halfcheckedkeys=half_checked_menu, - role_menu=tree_data[1], - current_role_menu=role_info.get('menu'), - api_check_token_trigger={'timestamp': time.time()}, - edit_row_info=role_info.get('role') if role_info else None, - modal_type={'type': 'edit'} - ) + role_info = dict( + role_name=None, + role_key=None, + role_sort=0, + status=SysNormalDisableConstant.NORMAL, + remark=None, + ) + return dict( + modal_visible=True, + modal_title='新增角色', + form_value=[role_info.get(k) for k in form_value_list], + form_label_validate_status=[None] * len(form_label_list), + form_label_validate_info=[None] * len(form_label_list), + menu_perms_tree=tree_data, + menu_perms_checkedkeys=None, + fold_unfold=None, + all_none=None, + radio_parent_children=no_update, + edit_row_info=None, + modal_type={'type': 'add'}, + ) + elif trigger_id.operation == 'edit': + if trigger_id.type == 'role-operation-button': + role_id = int(','.join(selected_row_keys)) + else: + role_id = int(trigger_id.index) + role_info_res = RoleApi.get_role(role_id=role_id) + tree_info = MenuApi.role_menu_treeselect(role_id=role_id) + role_info = role_info_res['data'] + menu_check_strictly = role_info['menu_check_strictly'] + tree_data = tree_info['menus'] + checked_keys = [str(item) for item in tree_info['checked_keys']] + return dict( + modal_visible=True, + modal_title='编辑角色', + form_value=[role_info.get(k) for k in form_value_list], + form_label_validate_status=[None] * len(form_label_list), + form_label_validate_info=[None] * len(form_label_list), + menu_perms_tree=tree_data, + menu_perms_checkedkeys=checked_keys, + fold_unfold=None, + all_none=None, + radio_parent_children=menu_check_strictly, + edit_row_info=role_info if role_info else None, + modal_type={'type': 'edit'}, + ) return dict( - modal_visible=dash.no_update, - modal_title=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_value_list), - form_label_validate_info=[dash.no_update] * len(form_value_list), - menu_perms_tree=dash.no_update, - menu_perms_expandedkeys=dash.no_update, - menu_perms_checkedkeys=dash.no_update, - menu_perms_halfcheckedkeys=dash.no_update, - role_menu=dash.no_update, - current_role_menu=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, + modal_visible=no_update, + modal_title=no_update, + form_value=[no_update] * len(form_value_list), + form_label_validate_status=[no_update] * len(form_value_list), + form_label_validate_info=[no_update] * len(form_value_list), + menu_perms_tree=no_update, + menu_perms_checkedkeys=no_update, + fold_unfold=no_update, + all_none=no_update, + radio_parent_children=no_update, edit_row_info=None, - modal_type=None + modal_type=None, ) raise PreventUpdate @@ -471,149 +456,191 @@ def add_edit_role_modal(operation_click, button_click, selected_row_keys): @app.callback( output=dict( - form_label_validate_status=Output({'type': 'role-form-label', 'index': ALL, 'required': True}, 'validateStatus', - allow_duplicate=True), - form_label_validate_info=Output({'type': 'role-form-label', 'index': ALL, 'required': True}, 'help', - allow_duplicate=True), + form_label_validate_status=Output( + {'type': 'role-form-label', 'index': ALL, 'required': True}, + 'validateStatus', + allow_duplicate=True, + ), + form_label_validate_info=Output( + {'type': 'role-form-label', 'index': ALL, 'required': True}, + 'help', + allow_duplicate=True, + ), modal_visible=Output('role-modal', 'visible'), - operations=Output('role-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - confirm_trigger=Input('role-modal', 'okCounts') + operations=Output( + 'role-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('role-modal', 'okCounts')), state=dict( - modal_type=State('role-operations-store-bk', 'data'), + modal_type=State('role-modal_type-store', 'data'), edit_row_info=State('role-edit-id-store', 'data'), - form_value=State({'type': 'role-form-value', 'index': ALL, 'required': ALL}, 'value'), - form_label=State({'type': 'role-form-value', 'index': ALL, 'required': True}, 'placeholder'), + form_value=State( + {'type': 'role-form-value', 'index': ALL, 'required': ALL}, 'value' + ), + form_label=State( + {'type': 'role-form-label', 'index': ALL, 'required': True}, + 'label', + ), menu_checked_keys=State('role-menu-perms', 'checkedKeys'), menu_half_checked_keys=State('role-menu-perms', 'halfCheckedKeys'), - parent_checked=State('role-menu-perms-radio-parent-children', 'checked') + parent_checked=State( + 'role-menu-perms-radio-parent-children', 'checked' + ), ), - prevent_initial_call=True + running=[[Output('role-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def role_confirm(confirm_trigger, modal_type, edit_row_info, form_value, form_label, menu_checked_keys, menu_half_checked_keys, parent_checked): +def role_confirm( + confirm_trigger, + modal_type, + edit_row_info, + form_value, + form_label, + menu_checked_keys, + menu_half_checked_keys, + parent_checked, +): """ 新增或编辑角色弹窗确认回调,实现新增或编辑操作 """ if confirm_trigger: # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] + form_label_output_list = [x['id']['index'] for x in ctx.outputs_list[0]] # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[3]} - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - menu_half_checked_keys = menu_half_checked_keys if menu_half_checked_keys else [] + form_value_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[2] + } + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[3] + } + if all( + ValidateUtil.not_empty(item) + for item in [ + form_value_state.get(k) for k in form_label_output_list + ] + ): + menu_half_checked_keys = ( + menu_half_checked_keys if menu_half_checked_keys else [] + ) menu_checked_keys = menu_checked_keys if menu_checked_keys else [] if parent_checked: menu_perms = menu_half_checked_keys + menu_checked_keys else: menu_perms = menu_checked_keys params_add = form_value_state - params_add['menu_id'] = ','.join(menu_perms) if menu_perms else None + params_add['menu_ids'] = ( + [int(item) for item in menu_perms] if menu_perms else [] + ) + params_add['menu_check_strictly'] = parent_checked params_edit = params_add.copy() - params_edit['role_id'] = edit_row_info.get('role_id') if edit_row_info else None - api_res = {} + params_edit['role_id'] = ( + edit_row_info.get('role_id') if edit_row_info else None + ) modal_type = modal_type.get('type') if modal_type == 'add': - api_res = add_role_api(params_add) + RoleApi.add_role(params_add) if modal_type == 'edit': - api_res = edit_role_api(params_edit) - if api_res.get('code') == 200: - if modal_type == 'add': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - if modal_type == 'edit': - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') - ) + RoleApi.update_role(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=[None] + * len(form_label_output_list), + form_label_validate_info=[None] + * len(form_label_output_list), + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + + return dict( + form_label_validate_status=[None] + * len(form_label_output_list), + form_label_validate_info=[None] + * len(form_label_output_list), + modal_visible=False, + operations={'type': 'edit'}, + ) return dict( form_label_validate_status=[None] * len(form_label_output_list), form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else form_label_state.get(k) for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('处理失败', type='error') + form_label_validate_status=[ + None + if ValidateUtil.not_empty(form_value_state.get(k)) + else 'error' + for k in form_label_output_list + ], + form_label_validate_info=[ + None + if ValidateUtil.not_empty(form_value_state.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_output_list + ], + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('role-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - [Input('role-list-table', 'recentlySwitchDataIndex'), - Input('role-list-table', 'recentlySwitchStatus'), - Input('role-list-table', 'recentlySwitchRow')], - prevent_initial_call=True + Output('role-operations-store', 'data', allow_duplicate=True), + [ + Input('role-list-table', 'recentlySwitchDataIndex'), + Input('role-list-table', 'recentlySwitchStatus'), + Input('role-list-table', 'recentlySwitchRow'), + ], + prevent_initial_call=True, ) -def table_switch_role_status(recently_switch_data_index, recently_switch_status, recently_switch_row): +def table_switch_role_status( + recently_switch_data_index, recently_switch_status, recently_switch_row +): """ 表格内切换角色状态回调 """ if recently_switch_data_index: - if recently_switch_status: - params = dict(role_id=int(recently_switch_row['key']), status='0', type='status') - else: - params = dict(role_id=int(recently_switch_row['key']), status='1', type='status') - edit_button_result = edit_role_api(params) - if edit_button_result['code'] == 200: - - return [ - {'type': 'switch-status'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改成功', type='success') - ] + RoleApi.change_role_status( + role_id=int(recently_switch_row['key']), + status='0' if recently_switch_status else '1', + ) + MessageManager.success(content='修改成功') - return [ - {'type': 'switch-status'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error') - ] + return {'type': 'switch-status'} raise PreventUpdate @app.callback( - [Output('role-delete-text', 'children'), - Output('role-delete-confirm-modal', 'visible'), - Output('role-delete-ids-store', 'data')], - [Input({'type': 'role-operation-button', 'operation': ALL}, 'nClicks'), - Input({'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, 'nClicks')], + [ + Output('role-delete-text', 'children'), + Output('role-delete-confirm-modal', 'visible'), + Output('role-delete-ids-store', 'data'), + ], + [ + Input({'type': 'role-operation-button', 'operation': ALL}, 'nClicks'), + Input( + {'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, + 'nClicks', + ), + ], State('role-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) def role_delete_modal(operation_click, button_click, selected_row_keys): """ 显示删除角色二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.operation == 'delete': - if trigger_id.type == 'role-operation-button': role_ids = ','.join(selected_row_keys) else: @@ -625,96 +652,108 @@ def role_delete_modal(operation_click, button_click, selected_row_keys): return [ f'是否确认删除角色编号为{role_ids}的角色?', True, - {'role_ids': role_ids} + role_ids, ] raise PreventUpdate @app.callback( - [Output('role-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('role-operations-store', 'data', allow_duplicate=True), Input('role-delete-confirm-modal', 'okCounts'), State('role-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def role_delete_confirm(delete_confirm, role_ids_data): """ 删除角色弹窗确认回调,实现删除操作 """ if delete_confirm: - params = role_ids_data - delete_button_info = delete_role_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + RoleApi.del_role(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('role_to_allocated_user-modal', 'visible'), - Output({'type': 'allocate_user-search', 'index': 'allocated'}, 'nClicks'), - Output('allocate_user-role_id-container', 'data')], - Input({'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, 'nClicks'), + [ + Output('role_to_allocated_user-modal', 'visible'), + Output( + {'type': 'allocate_user-search', 'index': 'allocated'}, 'nClicks' + ), + Output('allocate_user-role_id-container', 'data'), + ], + Input( + {'type': 'role-operation-table', 'operation': ALL, 'index': ALL}, + 'nClicks', + ), State({'type': 'allocate_user-search', 'index': 'allocated'}, 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) def role_to_allocated_user_modal(allocated_click, allocated_user_search_nclick): """ 显示角色分配用户弹窗回调 """ - trigger_id = dash.ctx.triggered_id + trigger_id = ctx.triggered_id if trigger_id.operation == 'allocation': return [ True, - allocated_user_search_nclick + 1 if allocated_user_search_nclick else 1, - trigger_id.index + allocated_user_search_nclick + 1 + if allocated_user_search_nclick + else 1, + trigger_id.index, ] raise PreventUpdate @app.callback( - [Output('role-export-container', 'data', allow_duplicate=True), - Output('role-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('role-export-container', 'data', allow_duplicate=True), + Output('role-export-complete-judge-container', 'data'), + ], Input('role-export', 'nClicks'), - prevent_initial_call=True + [ + State('role-role_name-input', 'value'), + State('role-role_key-input', 'value'), + State('role-status-select', 'value'), + State('role-create_time-range', 'value'), + ], + running=[[Output('role-export', 'loading'), True, False]], + prevent_initial_call=True, ) -def export_role_list(export_click): +def export_role_list( + export_click, role_name, role_key, status_select, create_time_range +): """ 导出角色信息回调 """ if export_click: - export_role_res = export_role_list_api({}) - if export_role_res.status_code == 200: - export_role = export_role_res.content - - return [ - dcc.send_bytes(export_role, f'角色信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + begin_time = None + end_time = None + if create_time_range: + begin_time = create_time_range[0] + end_time = create_time_range[1] + export_params = dict( + role_name=role_name, + role_key=role_key, + status=status_select, + begin_time=begin_time, + end_time=end_time, + ) + export_role_res = RoleApi.export_role(export_params) + export_role = export_role_res.content + MessageManager.success(content='导出成功') return [ - dash.no_update, - dash.no_update, + dcc.send_bytes( + export_role, + f'角色信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') ] raise PreventUpdate @@ -723,7 +762,7 @@ def export_role_list(export_click): @app.callback( Output('role-export-container', 'data', allow_duplicate=True), Input('role-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_role_export_status(data): """ @@ -731,7 +770,6 @@ def reset_role_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/user_c/allocate_role_c.py b/dash-fastapi-frontend/callbacks/system_c/user_c/allocate_role_c.py index 5ddf787de2774f834e147a43961116a4691607b7..9e99763aa22936e5cf0281ee59da16cde0837acd 100644 --- a/dash-fastapi-frontend/callbacks/system_c/user_c/allocate_role_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/user_c/allocate_role_c.py @@ -1,273 +1,49 @@ -import dash -import time -import uuid -from dash.dependencies import Input, Output, State, ALL, MATCH +from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from api.system.user import UserApi from server import app -from api.user import get_allocated_role_list_api, get_unallocated_role_list_api, auth_role_select_all_api, auth_role_cancel_api +from utils.feedback_util import MessageManager @app.callback( - [Output({'type': 'allocate_role-list-table', 'index': MATCH}, 'data', allow_duplicate=True), - Output({'type': 'allocate_role-list-table', 'index': MATCH}, 'pagination', allow_duplicate=True), - Output({'type': 'allocate_role-list-table', 'index': MATCH}, 'key'), - Output({'type': 'allocate_role-list-table', 'index': MATCH}, 'selectedRowKeys')], - [Input({'type': 'allocate_role-search', 'index': MATCH}, 'nClicks'), - Input({'type': 'allocate_role-refresh', 'index': MATCH}, 'nClicks'), - Input({'type': 'allocate_role-list-table', 'index': MATCH}, 'pagination'), - Input({'type': 'allocate_role-operations-container', 'index': MATCH}, 'data')], - [State({'type': 'allocate_role-role_name-input', 'index': MATCH}, 'value'), - State({'type': 'allocate_role-role_key-input', 'index': MATCH}, 'value'), - State('allocate_role-user_id-container', 'data'), - State('allocate_role-button-perms-container', 'data')], - prevent_initial_call=True -) -def get_allocate_role_table_data(search_click, refresh_click, pagination, operations, role_name, role_key, user_id, button_perms): - """ - 使用模式匹配回调MATCH模式,根据不同类型获取用户已分配角色列表及未分配角色列表(进行表格相关增删查改操作后均会触发此回调) - """ - - query_params = dict( - user_id=int(user_id), - role_name=role_name, - role_key=role_key, - page_num=1, - page_size=10 - ) - triggered_id = dash.ctx.triggered_id - if triggered_id.type == 'allocate_role-list-table': - query_params = dict( - user_id=int(user_id), - role_name=role_name, - role_key=role_key, - page_num=pagination['current'], - page_size=pagination['pageSize'] - ) - if search_click or refresh_click or pagination or operations: - table_info = {} - if triggered_id.index == 'allocated': - table_info = get_allocated_role_list_api(query_params) - if triggered_id.index == 'unallocated': - table_info = get_unallocated_role_list_api(query_params) - if table_info.get('code') == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['role_id']) - if triggered_id.index == 'allocated': - item['operation'] = [ - { - 'content': '取消授权', - 'type': 'link', - 'icon': 'antd-close-circle' - } if 'system:user:edit' in button_perms else {}, - ] - - return [table_data, table_pagination, str(uuid.uuid4()), None] - - return [dash.no_update, dash.no_update, dash.no_update, dash.no_update] - - raise PreventUpdate - - -# 重置分配角色搜索表单数据回调 -app.clientside_callback( - ''' - (reset_click) => { - if (reset_click) { - return [null, null, {'type': 'reset'}] - } - return window.dash_clientside.no_update; - } - ''', - [Output({'type': 'allocate_role-role_name-input', 'index': MATCH}, 'value'), - Output({'type': 'allocate_role-role_key-input', 'index': MATCH}, 'value'), - Output({'type': 'allocate_role-operations-container', 'index': MATCH}, 'data')], - Input({'type': 'allocate_role-reset', 'index': MATCH}, 'nClicks'), - prevent_initial_call=True -) - - -# 隐藏/显示分配角色搜索表单回调 -app.clientside_callback( - ''' - (hidden_click, hidden_status) => { - if (hidden_click) { - return [ - !hidden_status, - hidden_status ? '隐藏搜索' : '显示搜索' - ] - } - return window.dash_clientside.no_update; - } - ''', - [Output({'type': 'allocate_role-search-form-container', 'index': MATCH}, 'hidden'), - Output({'type': 'allocate_role-hidden-tooltip', 'index': MATCH}, 'title')], - Input({'type': 'allocate_role-hidden', 'index': MATCH}, 'nClicks'), - State({'type': 'allocate_role-search-form-container', 'index': MATCH}, 'hidden'), - prevent_initial_call=True -) - - -@app.callback( - Output({'type': 'allocate_role-operation-button', 'index': 'delete'}, 'disabled'), - Input({'type': 'allocate_role-list-table', 'index': 'allocated'}, 'selectedRowKeys'), - prevent_initial_call=True -) -def change_allocated_role_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制取批量消授权按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - return False - - return True - - raise PreventUpdate - - -@app.callback( - [Output('allocate_role-modal', 'visible'), - Output({'type': 'allocate_role-search', 'index': 'unallocated'}, 'nClicks')], - Input('allocate_role-add', 'nClicks'), - State({'type': 'allocate_role-search', 'index': 'unallocated'}, 'nClicks'), - prevent_initial_call=True -) -def allocate_role_modal(add_click, unallocated_role): - """ - 分配角色弹框中添加角色按钮回调 - """ - if add_click: - - return [True, unallocated_role + 1 if unallocated_role else 1] - - raise PreventUpdate - - -@app.callback( - [Output({'type': 'allocate_role-operations-container', 'index': 'allocated'}, 'data', allow_duplicate=True), - Output({'type': 'allocate_role-operations-container', 'index': 'unallocated'}, 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - Input('allocate_role-modal', 'okCounts'), - [State({'type': 'allocate_role-list-table', 'index': 'unallocated'}, 'selectedRowKeys'), - State('allocate_role-user_id-container', 'data')], - prevent_initial_call=True + Output('user_to_allocated_role-modal', 'visible', allow_duplicate=True), + Input('allocate_role-submit-button', 'nClicks'), + [ + State('allocate_role-list-table', 'selectedRowKeys'), + State('allocate_role-user_id-container', 'data'), + ], + running=[[Output('allocate_role-submit-button', 'loading'), True, False]], + prevent_initial_call=True, ) def allocate_user_add_confirm(add_confirm, selected_row_keys, user_id): """ 添加角色确认回调,实现给用户分配角色操作 """ if add_confirm: - if selected_row_keys: - - params = {'user_ids': user_id, 'role_ids': ','.join(selected_row_keys)} - add_button_info = auth_role_select_all_api(params) - if add_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('授权成功', type='success') - ] - - return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('授权失败', type='error') - ] - - return [ - dash.no_update, - dash.no_update, - dash.no_update, - fuc.FefferyFancyMessage('请选择角色', type='error') - ] - - raise PreventUpdate - - -@app.callback( - [Output('allocate_role-delete-text', 'children'), - Output('allocate_role-delete-confirm-modal', 'visible'), - Output('allocate_role-delete-ids-store', 'data')], - [Input({'type': 'allocate_role-operation-button', 'index': ALL}, 'nClicks'), - Input({'type': 'allocate_role-list-table', 'index': 'allocated'}, 'nClicksButton')], - [State({'type': 'allocate_role-list-table', 'index': 'allocated'}, 'selectedRowKeys'), - State({'type': 'allocate_role-list-table', 'index': 'allocated'}, 'clickedContent'), - State({'type': 'allocate_role-list-table', 'index': 'allocated'}, 'recentlyButtonClickedRow')], - prevent_initial_call=True -) -def allocate_role_delete_modal(operation_click, button_click, - selected_row_keys, clicked_content, recently_button_clicked_row): - """ - 显示取消授权二次确认弹窗回调 - """ - trigger_id = dash.ctx.triggered_id - if trigger_id.type == 'allocate_role-operation-button' or ( - trigger_id.type == 'allocate_role-list-table' and clicked_content == '取消授权'): - - if trigger_id.type == 'allocate_role-operation-button': - role_ids = ','.join(selected_row_keys) - else: - if clicked_content == '取消授权': - role_ids = recently_button_clicked_row['key'] - else: - return dash.no_update + params = { + 'user_id': int(user_id), + 'role_ids': ','.join(selected_row_keys) + if selected_row_keys + else '', + } + UserApi.update_auth_role(params) + MessageManager.success(content='分配成功') - return [ - f'是否确认取消角色id为{role_ids}的授权?', - True, - role_ids - ] + return False raise PreventUpdate @app.callback( - [Output({'type': 'allocate_role-operations-container', 'index': 'allocated'}, 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - Input('allocate_role-delete-confirm-modal', 'okCounts'), - [State('allocate_role-delete-ids-store', 'data'), - State('allocate_role-user_id-container', 'data')], - prevent_initial_call=True + Output('user_to_allocated_role-modal', 'visible', allow_duplicate=True), + Input('allocate_role-back-button', 'nClicks'), + prevent_initial_call=True, ) -def allocate_role_delete_confirm(delete_confirm, role_ids_data, user_id): +def allocate_user_back_confirm(back_confirm): """ - 取消授权弹窗确认回调,实现取消授权操作 + 关闭分配角色弹框操作 """ - if delete_confirm: - - params = {'user_ids': user_id, 'role_ids': role_ids_data} - delete_button_info = auth_role_cancel_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('取消授权成功', type='success') - ] - - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('取消授权失败', type='error') - ] + if back_confirm: + return False raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/avatar_c.py b/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/avatar_c.py index 45c886b8e110ba654c6afbade1083c79663cce76..0d18a2d41779967ccee3d5e7afdb61b1778ad218 100644 --- a/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/avatar_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/avatar_c.py @@ -1,20 +1,21 @@ -import dash -import feffery_utils_components as fuc -import time +import base64 import uuid from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate +from io import BytesIO +from api.system.user import UserApi from server import app - -from api.user import change_user_avatar_api +from utils.feedback_util import MessageManager @app.callback( - [Output('avatar-cropper-modal', 'visible', allow_duplicate=True), - Output('avatar-cropper', 'src', allow_duplicate=True)], + [ + Output('avatar-cropper-modal', 'visible', allow_duplicate=True), + Output('avatar-cropper', 'src', allow_duplicate=True), + ], Input('avatar-edit-click', 'n_clicks'), State('user-avatar-image-info', 'src'), - prevent_initial_call=True + prevent_initial_call=True, ) def avatar_cropper_modal_visible(n_clicks, user_avatar_image_info): """ @@ -28,16 +29,15 @@ def avatar_cropper_modal_visible(n_clicks, user_avatar_image_info): @app.callback( Output('avatar-cropper', 'src', allow_duplicate=True), - Input('avatar-upload-choose', 'listUploadTaskRecord'), - prevent_initial_call=True + Input('avatar-upload-choose', 'contents'), + prevent_initial_call=True, ) -def upload_user_avatar(list_upload_task_record): +def upload_user_avatar(contents): """ 上传用户头像获取后端url回调 """ - if list_upload_task_record: - - return list_upload_task_record[-1].get('url') + if contents: + return contents raise PreventUpdate @@ -64,25 +64,27 @@ app.clientside_callback( } } """, - [Output('avatar-cropper', 'zoom'), - Output('avatar-cropper', 'rotate')], - [Input('zoom-out', 'nClicks'), - Input('zoom-in', 'nClicks'), - Input('rotate-left', 'nClicks'), - Input('rotate-right', 'nClicks')], - prevent_initial_call=True + [Output('avatar-cropper', 'zoom'), Output('avatar-cropper', 'rotate')], + [ + Input('zoom-out', 'nClicks'), + Input('zoom-in', 'nClicks'), + Input('rotate-left', 'nClicks'), + Input('rotate-right', 'nClicks'), + ], + prevent_initial_call=True, ) @app.callback( - [Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True), - Output('avatar-cropper-modal', 'visible', allow_duplicate=True), - Output('user-avatar-image-info', 'key'), - Output('avatar-info', 'key')], + [ + Output('avatar-cropper-modal', 'visible', allow_duplicate=True), + Output('user-avatar-image-info', 'key'), + Output('avatar-info', 'key'), + ], Input('change-avatar-submit', 'nClicks'), State('avatar-cropper', 'croppedImageData'), - prevent_initial_call=True + running=[[Output('change-avatar-submit', 'loading'), True, False]], + prevent_initial_call=True, ) def change_user_avatar_callback(submit_click, avatar_data): """ @@ -90,24 +92,16 @@ def change_user_avatar_callback(submit_click, avatar_data): """ if submit_click: - params = dict(type='avatar', avatar=avatar_data) - change_avatar_result = change_user_avatar_api(params) - if change_avatar_result.get('code') == 200: - - return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改成功', type='success'), - False, - str(uuid.uuid4()), - str(uuid.uuid4()) - ] + params = dict( + avatarfile=BytesIO(base64.b64decode(avatar_data.split(',', 1)[1])) + ) + UserApi.upload_avatar(params) + MessageManager.success(content='修改成功') return [ - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error'), - dash.no_update, - dash.no_update, - dash.no_update + False, + str(uuid.uuid4()), + str(uuid.uuid4()), ] raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/reset_pwd_c.py b/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/reset_pwd_c.py index 7fe72373ffa566e039cd43bda47b6a47fb1305d9..d75b94e0339870e3cbe1c467d8cb35b9f3df5048 100644 --- a/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/reset_pwd_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/reset_pwd_c.py @@ -1,62 +1,43 @@ -import dash -import feffery_utils_components as fuc -import time from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate +from api.system.user import UserApi from server import app - -from api.user import reset_user_password_api +from utils.feedback_util import MessageManager @app.callback( - [Output('reset-old-password-form-item', 'validateStatus'), - Output('reset-new-password-form-item', 'validateStatus'), - Output('reset-confirm-password-form-item', 'validateStatus'), - Output('reset-old-password-form-item', 'help'), - Output('reset-new-password-form-item', 'help'), - Output('reset-confirm-password-form-item', 'help'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('reset-old-password-form-item', 'validateStatus'), + Output('reset-new-password-form-item', 'validateStatus'), + Output('reset-confirm-password-form-item', 'validateStatus'), + Output('reset-old-password-form-item', 'help'), + Output('reset-new-password-form-item', 'help'), + Output('reset-confirm-password-form-item', 'help'), + ], Input('reset-password-submit', 'nClicks'), - [State('reset-old-password', 'value'), - State('reset-new-password', 'value'), - State('reset-confirm-password', 'value')], - prevent_initial_call=True + [ + State('reset-old-password', 'value'), + State('reset-new-password', 'value'), + State('reset-confirm-password', 'value'), + ], + running=[[Output('reset-password-submit', 'loading'), True, False]], + prevent_initial_call=True, ) -def reset_submit_user_info(reset_click, old_password, new_password, confirm_password): +def reset_submit_user_info( + reset_click, old_password, new_password, confirm_password +): """ 重置当前用户密码回调 """ if reset_click: if all([old_password, new_password, confirm_password]): - if new_password == confirm_password: + UserApi.update_user_pwd( + old_password=old_password, new_password=new_password + ) + MessageManager.success(content='修改成功') - params = dict(type='avatar', old_password=old_password, password=new_password) - reset_password_result = reset_user_password_api(params) - if reset_password_result.get('code') == 200: - - return [ - None, - None, - None, - None, - None, - None, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改成功', type='success'), - ] - - return [ - None, - None, - None, - None, - None, - None, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error'), - ] + return [None] * 6 return [ None, @@ -65,8 +46,6 @@ def reset_submit_user_info(reset_click, old_password, new_password, confirm_pass None, None if new_password else '前后两次密码不一致!', None if confirm_password else '前后两次密码不一致!', - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error'), ] return [ @@ -76,25 +55,27 @@ def reset_submit_user_info(reset_click, old_password, new_password, confirm_pass None if old_password else '请输入旧密码!', None if new_password else '请输入新密码!', None if confirm_password else '请输入确认密码!', - dash.no_update, - fuc.FefferyFancyMessage('修改失败', type='error'), ] raise PreventUpdate @app.callback( - [Output('tabs-container', 'latestDeletePane', allow_duplicate=True), - Output('tabs-container', 'tabCloseCounts', allow_duplicate=True)], + [ + Output('tabs-container', 'latestDeletePane', allow_duplicate=True), + Output('tabs-container', 'tabCloseCounts', allow_duplicate=True), + ], Input('reset-password-close', 'nClicks'), State('tabs-container', 'tabCloseCounts'), - prevent_initial_call=True + prevent_initial_call=True, ) def close_personal_info_modal(close_click, tab_close_counts): """ 关闭当前个人资料标签页回调 """ if close_click: - - return ['个人资料', tab_close_counts + 1 if tab_close_counts else 1] + return [ + 'Profile/user/profile', + tab_close_counts + 1 if tab_close_counts else 1, + ] raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/user_info_c.py b/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/user_info_c.py index 087434d9e950a4992dee78dc933ae761b5b52570..0b79b565b3245c839693a187c734cfbd38984baa 100644 --- a/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/user_info_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/user_c/profile_c/user_info_c.py @@ -1,28 +1,28 @@ -import dash -import feffery_utils_components as fuc -import time from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate +from api.system.user import UserApi from server import app - -from api.user import change_user_info_api +from utils.feedback_util import MessageManager @app.callback( - [Output('reset-user-nick_name-form-item', 'validateStatus'), - Output('reset-user-phonenumber-form-item', 'validateStatus'), - Output('reset-user-email-form-item', 'validateStatus'), - Output('reset-user-nick_name-form-item', 'help'), - Output('reset-user-phonenumber-form-item', 'help'), - Output('reset-user-email-form-item', 'help'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('reset-user-nick_name-form-item', 'validateStatus'), + Output('reset-user-phonenumber-form-item', 'validateStatus'), + Output('reset-user-email-form-item', 'validateStatus'), + Output('reset-user-nick_name-form-item', 'help'), + Output('reset-user-phonenumber-form-item', 'help'), + Output('reset-user-email-form-item', 'help'), + ], Input('reset-submit', 'nClicks'), - [State('reset-user-nick_name', 'value'), - State('reset-user-phonenumber', 'value'), - State('reset-user-email', 'value'), - State('reset-user-sex', 'value')], - prevent_initial_call=True + [ + State('reset-user-nick_name', 'value'), + State('reset-user-phonenumber', 'value'), + State('reset-user-email', 'value'), + State('reset-user-sex', 'value'), + ], + running=[[Output('reset-submit', 'loading'), True, False]], + prevent_initial_call=True, ) def reset_submit_user_info(reset_click, nick_name, phonenumber, email, sex): """ @@ -30,32 +30,16 @@ def reset_submit_user_info(reset_click, nick_name, phonenumber, email, sex): """ if reset_click: if all([nick_name, phonenumber, email]): + params = dict( + nick_name=nick_name, + phonenumber=phonenumber, + email=email, + sex=sex, + ) + UserApi.update_user_profile(params) + MessageManager.success(content='修改成功') - params = dict(type='avatar', nick_name=nick_name, phonenumber=phonenumber, email=email, sex=sex) - change_user_info_result = change_user_info_api(params) - if change_user_info_result.get('code') == 200: - - return [ - None, - None, - None, - None, - None, - None, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改成功', type='success'), - ] - - return [ - None, - None, - None, - None, - None, - None, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error'), - ] + return [None] * 6 return [ None if nick_name else 'error', @@ -64,25 +48,24 @@ def reset_submit_user_info(reset_click, nick_name, phonenumber, email, sex): None if nick_name else '请输入用户昵称!', None if phonenumber else '请输入手机号码!', None if email else '请输入邮箱!', - dash.no_update, - fuc.FefferyFancyMessage('修改失败', type='error'), ] raise PreventUpdate @app.callback( - [Output('tabs-container', 'latestDeletePane', allow_duplicate=True), - Output('tabs-container', 'tabCloseCounts', allow_duplicate=True)], + [ + Output('tabs-container', 'latestDeletePane', allow_duplicate=True), + Output('tabs-container', 'tabCloseCounts', allow_duplicate=True), + ], Input('reset-close', 'nClicks'), State('tabs-container', 'tabCloseCounts'), - prevent_initial_call=True + prevent_initial_call=True, ) def close_personal_info_modal(close_click, tab_close_counts): """ 关闭当前个人资料标签页回调 """ if close_click: - - return ['个人资料', tab_close_counts + 1 if tab_close_counts else 1] + return ['Profile/user/profile', tab_close_counts + 1 if tab_close_counts else 1] raise PreventUpdate diff --git a/dash-fastapi-frontend/callbacks/system_c/user_c/user_c.py b/dash-fastapi-frontend/callbacks/system_c/user_c/user_c.py index d2c6e36581ad9cf62fbee538649a68fb618b82dc..cf493fcc3832fd88864377f320203c36e911c9a3 100644 --- a/dash-fastapi-frontend/callbacks/system_c/user_c/user_c.py +++ b/dash-fastapi-frontend/callbacks/system_c/user_c/user_c.py @@ -1,71 +1,122 @@ -import dash +import base64 import time import uuid -from dash import dcc -from dash.dependencies import Input, Output, State, ALL +from io import BytesIO +from dash import ctx, dcc, no_update +from dash.dependencies import ALL, Input, Output, State from dash.exceptions import PreventUpdate -import feffery_utils_components as fuc - +from typing import Dict +from api.system.user import UserApi +from config.constant import SysNormalDisableConstant from server import app -from utils.common import validate_data_not_empty -from api.dept import get_dept_tree_api -from api.user import get_user_list_api, get_user_detail_api, add_user_api, edit_user_api, delete_user_api, reset_user_password_api, batch_import_user_api, download_user_import_template_api, export_user_list_api -from api.role import get_role_select_option_api -from api.post import get_post_select_option_api +from utils.common_util import ValidateUtil +from utils.feedback_util import MessageManager +from utils.permission_util import PermissionManager +from utils.time_format_util import TimeFormatUtil -@app.callback( - [Output('dept-tree', 'treeData'), - Output('api-check-token', 'data', allow_duplicate=True)], - Input('dept-input-search', 'value'), - prevent_initial_call=True -) -def get_search_dept_tree(dept_input): - dept_params = dict(dept_name=dept_input) - tree_info = get_dept_tree_api(dept_params) - if tree_info['code'] == 200: - tree_data = tree_info['data'] +def generate_user_table(query_params: Dict): + """ + 根据查询参数获取用户表格数据及分页信息 + + :param query_params: 查询参数 + :return: 用户表格数据及分页信息 + """ + table_info = UserApi.list_user(query_params) + table_data = table_info['rows'] + table_pagination = dict( + pageSize=table_info['page_size'], + current=table_info['page_num'], + showSizeChanger=True, + pageSizeOptions=[10, 30, 50, 100], + showQuickJumper=True, + total=table_info['total'], + ) + for item in table_data: + if item['status'] == SysNormalDisableConstant.NORMAL: + item['status'] = dict(checked=True, disabled=item['user_id'] == 1) + else: + item['status'] = dict(checked=False, disabled=item['user_id'] == 1) + item['dept_name'] = ( + item.get('dept').get('dept_name') if item.get('dept') else None + ) + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['user_id']) + if item['user_id'] == 1: + item['operation'] = [] + else: + item['operation'] = [ + {'title': '修改', 'icon': 'antd-edit'} + if PermissionManager.check_perms('system:user:edit') + else None, + {'title': '删除', 'icon': 'antd-delete'} + if PermissionManager.check_perms('system:user:remove') + else None, + {'title': '重置密码', 'icon': 'antd-key'} + if PermissionManager.check_perms('system:user:resetPwd') + else None, + {'title': '分配角色', 'icon': 'antd-check-circle'} + if PermissionManager.check_perms('system:user:edit') + else None, + ] + + return [table_data, table_pagination] - return [tree_data, {'timestamp': time.time()}] - return [dash.no_update, {'timestamp': time.time()}] +app.clientside_callback( + """(dept_input) => dept_input""", + Output('dept-tree', 'searchKeyword'), + Input('dept-input-search', 'value'), + prevent_initial_call=True, +) @app.callback( output=dict( user_table_data=Output('user-list-table', 'data', allow_duplicate=True), - user_table_pagination=Output('user-list-table', 'pagination', allow_duplicate=True), + user_table_pagination=Output( + 'user-list-table', 'pagination', allow_duplicate=True + ), user_table_key=Output('user-list-table', 'key'), user_table_selectedrowkeys=Output('user-list-table', 'selectedRowKeys'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) ), inputs=dict( selected_dept_tree=Input('dept-tree', 'selectedKeys'), search_click=Input('user-search', 'nClicks'), refresh_click=Input('user-refresh', 'nClicks'), pagination=Input('user-list-table', 'pagination'), - operations=Input('user-operations-store', 'data') + operations=Input('user-operations-store', 'data'), ), state=dict( user_name=State('user-user_name-input', 'value'), phone_number=State('user-phone_number-input', 'value'), status_select=State('user-status-select', 'value'), create_time_range=State('user-create_time-range', 'value'), - button_perms=State('user-button-perms-container', 'data') ), - prevent_initial_call=True + prevent_initial_call=True, ) -def get_user_table_data_by_dept_tree(selected_dept_tree, search_click, refresh_click, pagination, operations, - user_name, phone_number, status_select, create_time_range, button_perms): +def get_user_table_data_by_dept_tree( + selected_dept_tree, + search_click, + refresh_click, + pagination, + operations, + user_name, + phone_number, + status_select, + create_time_range, +): """ 获取用户表格数据回调(进行表格相关增删查改操作后均会触发此回调) """ dept_id = None - create_time_start = None - create_time_end = None + begin_time = None + end_time = None if create_time_range: - create_time_start = create_time_range[0] - create_time_end = create_time_range[1] + begin_time = create_time_range[0] + end_time = create_time_range[1] if selected_dept_tree: dept_id = int(selected_dept_tree[0]) query_params = dict( @@ -73,77 +124,33 @@ def get_user_table_data_by_dept_tree(selected_dept_tree, search_click, refresh_c user_name=user_name, phonenumber=phone_number, status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, + begin_time=begin_time, + end_time=end_time, page_num=1, - page_size=10 + page_size=10, ) - triggered_id = dash.ctx.triggered_id + triggered_id = ctx.triggered_id if triggered_id == 'user-list-table': - query_params = dict( - dept_id=dept_id, - user_name=user_name, - phonenumber=phone_number, - status=status_select, - create_time_start=create_time_start, - create_time_end=create_time_end, - page_num=pagination['current'], - page_size=pagination['pageSize'] + query_params.update( + { + 'page_num': pagination['current'], + 'page_size': pagination['pageSize'], + } ) - if selected_dept_tree or search_click or refresh_click or pagination or operations: - table_info = get_user_list_api(query_params) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - table_pagination = dict( - pageSize=table_info['data']['page_size'], - current=table_info['data']['page_num'], - showSizeChanger=True, - pageSizeOptions=[10, 30, 50, 100], - showQuickJumper=True, - total=table_info['data']['total'] - ) - for item in table_data: - if item['status'] == '0': - item['status'] = dict(checked=True, disabled=item['user_id'] == 1) - else: - item['status'] = dict(checked=False, disabled=item['user_id'] == 1) - item['key'] = str(item['user_id']) - if item['user_id'] == 1: - item['operation'] = [] - else: - item['operation'] = [ - { - 'title': '修改', - 'icon': 'antd-edit' - } if 'system:user:edit' in button_perms else None, - { - 'title': '删除', - 'icon': 'antd-delete' - } if 'system:user:remove' in button_perms else None, - { - 'title': '重置密码', - 'icon': 'antd-key' - } if 'system:user:resetPwd' in button_perms else None, - { - 'title': '分配角色', - 'icon': 'antd-check-circle' - } if 'system:user:edit' in button_perms else None - ] - - return dict( - user_table_data=table_data, - user_table_pagination=table_pagination, - user_table_key=str(uuid.uuid4()), - user_table_selectedrowkeys=None, - api_check_token_trigger={'timestamp': time.time()} - ) + if ( + selected_dept_tree + or search_click + or refresh_click + or pagination + or operations + ): + table_data, table_pagination = generate_user_table(query_params) return dict( - user_table_data=dash.no_update, - user_table_pagination=dash.no_update, - user_table_key=dash.no_update, - user_table_selectedrowkeys=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} + user_table_data=table_data, + user_table_pagination=table_pagination, + user_table_key=str(uuid.uuid4()), + user_table_selectedrowkeys=None, ) raise PreventUpdate @@ -151,28 +158,30 @@ def get_user_table_data_by_dept_tree(selected_dept_tree, search_click, refresh_c # 重置用户搜索表单数据回调 app.clientside_callback( - ''' + """ (reset_click) => { if (reset_click) { return [null, null, null, null, null, {'type': 'reset'}] } return window.dash_clientside.no_update; } - ''', - [Output('dept-tree', 'selectedKeys'), - Output('user-user_name-input', 'value'), - Output('user-phone_number-input', 'value'), - Output('user-status-select', 'value'), - Output('user-create_time-range', 'value'), - Output('user-operations-store', 'data')], + """, + [ + Output('dept-tree', 'selectedKeys'), + Output('user-user_name-input', 'value'), + Output('user-phone_number-input', 'value'), + Output('user-status-select', 'value'), + Output('user-create_time-range', 'value'), + Output('user-operations-store', 'data'), + ], Input('user-reset', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) # 隐藏/显示用户搜索表单回调 app.clientside_callback( - ''' + """ (hidden_click, hidden_status) => { if (hidden_click) { return [ @@ -182,400 +191,397 @@ app.clientside_callback( } return window.dash_clientside.no_update; } - ''', - [Output('user-search-form-container', 'hidden'), - Output('user-hidden-tooltip', 'title')], + """, + [ + Output('user-search-form-container', 'hidden'), + Output('user-hidden-tooltip', 'title'), + ], Input('user-hidden', 'nClicks'), State('user-search-form-container', 'hidden'), - prevent_initial_call=True + prevent_initial_call=True, ) -@app.callback( +# 根据选择的表格数据行数控制修改按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length === 1) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'user-operation-button', 'index': 'edit'}, 'disabled'), Input('user-list-table', 'selectedRowKeys'), - prevent_initial_call=True + prevent_initial_call=True, ) -def change_user_edit_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制编辑按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if len(table_rows_selected) > 1 or '1' in table_rows_selected: - return True - - return False - return True - raise PreventUpdate - - -@app.callback( +# 根据选择的表格数据行数控制删除按钮状态回调 +app.clientside_callback( + """ + (table_rows_selected) => { + outputs_list = window.dash_clientside.callback_context.outputs_list; + if (outputs_list) { + if (table_rows_selected?.length > 0) { + return false; + } + return true; + } + throw window.dash_clientside.PreventUpdate; + } + """, Output({'type': 'user-operation-button', 'index': 'delete'}, 'disabled'), Input('user-list-table', 'selectedRowKeys'), - prevent_initial_call=True -) -def change_user_delete_button_status(table_rows_selected): - """ - 根据选择的表格数据行数控制删除按钮状态回调 - """ - outputs_list = dash.ctx.outputs_list - if outputs_list: - if table_rows_selected: - if '1' in table_rows_selected: - return True - - return False - - return True - - raise PreventUpdate - - -@app.callback( - output=dict( - modal_visible=Output('user-add-modal', 'visible', allow_duplicate=True), - dept_tree=Output({'type': 'user_add-form-value', 'index': 'dept_id'}, 'treeData'), - form_value=Output({'type': 'user_add-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'user_add-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'user_add-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - user_post=Output('user-add-post', 'value'), - user_role=Output('user-add-role', 'value'), - post_option=Output('user-add-post', 'options'), - role_option=Output('user-add-role', 'options'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) - ), - inputs=dict( - add_click=Input('user-add', 'nClicks') - ), - prevent_initial_call=True + prevent_initial_call=True, ) -def add_user_modal(add_click): - """ - 显示新增用户弹窗回调 - """ - if add_click: - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] - dept_params = dict(dept_name='') - tree_info = get_dept_tree_api(dept_params) - post_option_info = get_post_select_option_api() - role_option_info = get_role_select_option_api() - if tree_info['code'] == 200 and post_option_info['code'] == 200 and role_option_info['code'] == 200: - tree_data = tree_info['data'] - post_option = post_option_info['data'] - role_option = role_option_info['data'] - user_info = dict(nick_name=None, dept_id=None, phonenumber=None, email=None, user_name=None, password=None, sex=None, status='0', remark=None) - - return dict( - modal_visible=True, - dept_tree=tree_data, - form_value=[user_info.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - user_post=None, - user_role=None, - post_option=[dict(label=item['post_name'], value=item['post_id']) for item in post_option], - role_option=[dict(label=item['role_name'], value=item['role_id']) for item in role_option], - api_check_token_trigger={'timestamp': time.time()} - ) - - return dict( - modal_visible=dash.no_update, - dept_tree=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - user_post=dash.no_update, - user_role=dash.no_update, - post_option=dash.no_update, - role_option=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} - ) - - raise PreventUpdate -@app.callback( - output=dict( - form_label_validate_status=Output({'type': 'user_add-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'user_add-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - modal_visible=Output('user-add-modal', 'visible', allow_duplicate=True), - operations=Output('user-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - add_confirm=Input('user-add-modal', 'okCounts') - ), - state=dict( - post=State('user-add-post', 'value'), - role=State('user-add-role', 'value'), - form_value=State({'type': 'user_add-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'user_add-form-label', 'index': ALL, 'required': True}, 'label') - ), - prevent_initial_call=True +# 用户表单数据双向绑定回调 +app.clientside_callback( + """ + (row_data, form_value) => { + trigger_id = window.dash_clientside.callback_context.triggered_id; + if (trigger_id === 'user-form-store') { + return [window.dash_clientside.no_update, row_data]; + } + if (trigger_id === 'user-form') { + Object.assign(row_data, form_value); + return [row_data, window.dash_clientside.no_update]; + } + throw window.dash_clientside.PreventUpdate; + } + """, + [ + Output('user-form-store', 'data', allow_duplicate=True), + Output('user-form', 'values'), + ], + [ + Input('user-form-store', 'data'), + Input('user-form', 'values'), + ], + prevent_initial_call=True, ) -def usr_add_confirm(add_confirm, post, role, form_value, form_label): - if add_confirm: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params = form_value_state - params['post_id'] = ','.join(map(str, post)) if post else '' - params['role_id'] = ','.join(map(str, role)) if role else '' - add_button_result = add_user_api(params) - - if add_button_result['code'] == 200: - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=False, - operations={'type': 'add'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增成功', type='success') - ) - - return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('新增失败', type='error') - ) - - return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('新增失败', type='error') - ) - - raise PreventUpdate @app.callback( output=dict( - modal_visible=Output('user-edit-modal', 'visible', allow_duplicate=True), - dept_tree=Output({'type': 'user_edit-form-value', 'index': 'dept_id'}, 'treeData'), - form_value=Output({'type': 'user_edit-form-value', 'index': ALL}, 'value'), - form_label_validate_status=Output({'type': 'user_edit-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'user_edit-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - user_post=Output('user-edit-post', 'value'), - user_role=Output('user-edit-role', 'value'), - post_option=Output('user-edit-post', 'options'), - role_option=Output('user-edit-role', 'options'), - edit_row_info=Output('user-edit-id-store', 'data'), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True) + modal_visible=Output('user-modal', 'visible', allow_duplicate=True), + modal_title=Output('user-modal', 'title'), + dept_tree=Output('user-dpet-tree', 'treeData'), + post_option=Output('user-post', 'options'), + role_option=Output('user-role', 'options'), + user_name_disabled=Output('user-form-user_name', 'disabled'), + password_disabled=Output('user-form-password', 'disabled'), + user_name_password_container=Output( + 'user-user_name-password-container', 'hidden' + ), + form_value=Output('user-form-store', 'data', allow_duplicate=True), + form_label_validate_status=Output( + 'user-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'user-form', 'helps', allow_duplicate=True + ), + modal_type=Output('user-modal_type-store', 'data'), ), inputs=dict( - operation_click=Input({'type': 'user-operation-button', 'index': ALL}, 'nClicks'), - dropdown_click=Input('user-list-table', 'nClicksDropdownItem') + operation_click=Input( + {'type': 'user-operation-button', 'index': ALL}, 'nClicks' + ), + dropdown_click=Input('user-list-table', 'nClicksDropdownItem'), ), state=dict( selected_row_keys=State('user-list-table', 'selectedRowKeys'), - recently_clicked_dropdown_item_title=State('user-list-table', 'recentlyClickedDropdownItemTitle'), - recently_dropdown_item_clicked_row=State('user-list-table', 'recentlyDropdownItemClickedRow') + recently_clicked_dropdown_item_title=State( + 'user-list-table', 'recentlyClickedDropdownItemTitle' + ), + recently_dropdown_item_clicked_row=State( + 'user-list-table', 'recentlyDropdownItemClickedRow' + ), ), - prevent_initial_call=True + prevent_initial_call=True, ) -def user_edit_modal(operation_click, dropdown_click, - selected_row_keys, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row): +def add_edit_user_modal( + operation_click, + dropdown_click, + selected_row_keys, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ - 显示编辑用户弹窗回调 + 显示新增或编辑用户弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'edit', 'type': 'user-operation-button'} or (trigger_id == 'user-list-table' and recently_clicked_dropdown_item_title == '修改'): - # 获取所有输出表单项对应value的index - form_value_list = [x['id']['index'] for x in dash.ctx.outputs_list[2]] - # 获取所有输出表单项对应label的index - form_label_list = [x['id']['index'] for x in dash.ctx.outputs_list[3]] - - dept_params = dict(dept_name='') - tree_data = get_dept_tree_api(dept_params)['data'] - post_option = get_post_select_option_api()['data'] - role_option = get_role_select_option_api()['data'] - - if trigger_id == {'index': 'edit', 'type': 'user-operation-button'}: - user_id = int(selected_row_keys[0]) - else: - if recently_clicked_dropdown_item_title == '修改': - user_id = int(recently_dropdown_item_clicked_row['key']) + trigger_id = ctx.triggered_id + if ( + trigger_id == {'index': 'add', 'type': 'user-operation-button'} + or trigger_id == {'index': 'edit', 'type': 'user-operation-button'} + or ( + trigger_id == 'user-list-table' + and recently_clicked_dropdown_item_title == '修改' + ) + ): + tree_info = UserApi.dept_tree_select() + tree_data = tree_info['data'] + if trigger_id == {'index': 'add', 'type': 'user-operation-button'}: + detail_info = UserApi.get_user(user_id='') + post_option = detail_info['posts'] + role_option = detail_info['roles'] + user_info = dict( + nick_name=None, + dept_id=None, + phonenumber=None, + email=None, + user_name=None, + password=None, + post_ids=None, + user_ids=None, + sex=None, + status=SysNormalDisableConstant.NORMAL, + remark=None, + ) + return dict( + modal_visible=True, + modal_title='新增用户', + dept_tree=tree_data, + post_option=[ + dict(label=item['post_name'], value=item['post_id']) + for item in post_option + if item + ] + or [], + role_option=[ + dict(label=item['role_name'], value=item['role_id']) + for item in role_option + if item + ] + or [], + user_name_disabled=False, + password_disabled=False, + user_name_password_container=False, + form_value=user_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'add'}, + ) + elif trigger_id == { + 'index': 'edit', + 'type': 'user-operation-button', + } or ( + trigger_id == 'user-list-table' + and recently_clicked_dropdown_item_title == '修改' + ): + if trigger_id == {'index': 'edit', 'type': 'user-operation-button'}: + user_id = int(','.join(selected_row_keys)) else: - raise PreventUpdate - - edit_button_info = get_user_detail_api(user_id) - if edit_button_info['code'] == 200: - edit_button_result = edit_button_info['data'] - user = edit_button_result['user'] - role = edit_button_result['role'] - post = edit_button_result['post'] - + user_id = int(recently_dropdown_item_clicked_row['key']) + user_info_res = UserApi.get_user(user_id=user_id) + user_info = user_info_res['data'] + post_option = user_info_res['posts'] + role_option = user_info_res['roles'] + post_ids = user_info['post_ids'] + role_ids = user_info['role_ids'] + user_info['post_ids'] = ( + [int(item) for item in post_ids.split(',')] if post_ids else [] + ) + user_info['role_ids'] = ( + [int(item) for item in role_ids.split(',')] if role_ids else [] + ) return dict( modal_visible=True, + modal_title='编辑用户', dept_tree=tree_data, - form_value=[user.get(k) for k in form_value_list], - form_label_validate_status=[None] * len(form_label_list), - form_label_validate_info=[None] * len(form_label_list), - user_post=[item['post_id'] for item in post if item] or [], - user_role=[item['role_id'] for item in role if item] or [], - post_option=[dict(label=item['post_name'], value=item['post_id']) for item in post_option if item] or [], - role_option=[dict(label=item['role_name'], value=item['role_id']) for item in role_option if item] or [], - edit_row_info={'user_id': user_id}, - api_check_token_trigger={'timestamp': time.time()} + post_option=[ + dict(label=item['post_name'], value=item['post_id']) + for item in post_option + if item + ] + or [], + role_option=[ + dict(label=item['role_name'], value=item['role_id']) + for item in role_option + if item + ] + or [], + user_name_disabled=True, + password_disabled=True, + user_name_password_container=True, + form_value=user_info, + form_label_validate_status=None, + form_label_validate_info=None, + modal_type={'type': 'edit'}, ) - return dict( - modal_visible=dash.no_update, - dept_tree=dash.no_update, - form_value=[dash.no_update] * len(form_value_list), - form_label_validate_status=[dash.no_update] * len(form_label_list), - form_label_validate_info=[dash.no_update] * len(form_label_list), - user_post=dash.no_update, - user_role=dash.no_update, - post_option=dash.no_update, - role_option=dash.no_update, - edit_row_info=dash.no_update, - api_check_token_trigger={'timestamp': time.time()} - ) - raise PreventUpdate @app.callback( output=dict( - form_label_validate_status=Output({'type': 'user_edit-form-label', 'index': ALL, 'required': True}, 'validateStatus', allow_duplicate=True), - form_label_validate_info=Output({'type': 'user_edit-form-label', 'index': ALL, 'required': True}, 'help', allow_duplicate=True), - modal_visible=Output('user-edit-modal', 'visible', allow_duplicate=True), - operations=Output('user-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - edit_confirm=Input('user-edit-modal', 'okCounts') + form_label_validate_status=Output( + 'user-form', 'validateStatuses', allow_duplicate=True + ), + form_label_validate_info=Output( + 'user-form', 'helps', allow_duplicate=True + ), + modal_visible=Output('user-modal', 'visible'), + operations=Output( + 'user-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(confirm_trigger=Input('user-modal', 'okCounts')), state=dict( - post=State('user-edit-post', 'value'), - role=State('user-edit-role', 'value'), - edit_row_info=State('user-edit-id-store', 'data'), - form_value=State({'type': 'user_edit-form-value', 'index': ALL}, 'value'), - form_label=State({'type': 'user_edit-form-label', 'index': ALL, 'required': True}, 'label') + modal_type=State('user-modal_type-store', 'data'), + form_value=State('user-form-store', 'data'), + form_label=State( + {'type': 'user-form-label', 'index': ALL, 'required': True}, 'label' + ), ), - prevent_initial_call=True + running=[[Output('user-modal', 'confirmLoading'), True, False]], + prevent_initial_call=True, ) -def usr_edit_confirm(edit_confirm, edit_row_info, post, role, form_value, form_label): - if edit_confirm: - # 获取所有输出表单项对应label的index - form_label_output_list = [x['id']['index'] for x in dash.ctx.outputs_list[0]] - # 获取所有输入表单项对应的value及label - form_value_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-2]} - form_label_state = {x['id']['index']: x.get('value') for x in dash.ctx.states_list[-1]} - - if all(validate_data_not_empty(item) for item in [form_value_state.get(k) for k in form_label_output_list]): - params = form_value_state - params['user_id'] = edit_row_info.get('user_id') if edit_row_info else None - params['post_id'] = ','.join(map(str, post)) if post else '' - params['role_id'] = ','.join(map(str, role)) if role else '' - edit_button_result = edit_user_api(params) - - if edit_button_result['code'] == 200: +def user_confirm(confirm_trigger, modal_type, form_value, form_label): + """ + 新增或编辑用户弹窗确认回调,实现新增或编辑操作 + """ + if confirm_trigger: + # 获取所有必填表单项对应label的index + form_label_list = [x['id']['index'] for x in ctx.states_list[-1]] + # 获取所有输入必填表单项对应的label + form_label_state = { + x['id']['index']: x.get('value') for x in ctx.states_list[-1] + } + if all( + ValidateUtil.not_empty(item) + for item in [form_value.get(k) for k in form_label_list] + ): + params_add = form_value + params_add['post_ids'] = ( + [int(item) for item in params_add.get('post_ids')] + if params_add.get('post_ids') + else [] + ) + params_add['role_ids'] = ( + [int(item) for item in params_add.get('role_ids')] + if params_add.get('role_ids') + else [] + ) + params_edit = params_add.copy() + params_edit['role'] = [] + modal_type = modal_type.get('type') + if modal_type == 'add': + UserApi.add_user(params_add) + if modal_type == 'edit': + UserApi.update_user(params_edit) + if modal_type == 'add': + MessageManager.success(content='新增成功') + + return dict( + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=False, + operations={'type': 'add'}, + ) + if modal_type == 'edit': + MessageManager.success(content='编辑成功') + return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), + form_label_validate_status=None, + form_label_validate_info=None, modal_visible=False, operations={'type': 'edit'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑成功', type='success') ) return dict( - form_label_validate_status=[None] * len(form_label_output_list), - form_label_validate_info=[None] * len(form_label_output_list), - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('编辑失败', type='error') + form_label_validate_status=None, + form_label_validate_info=None, + modal_visible=no_update, + operations=no_update, ) return dict( - form_label_validate_status=[None if validate_data_not_empty(form_value_state.get(k)) else 'error' for k in form_label_output_list], - form_label_validate_info=[None if validate_data_not_empty(form_value_state.get(k)) else f'{form_label_state.get(k)}不能为空!' for k in form_label_output_list], - modal_visible=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('编辑失败', type='error') + form_label_validate_status={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else 'error' + for k in form_label_list + }, + form_label_validate_info={ + form_label_state.get(k): None + if ValidateUtil.not_empty(form_value.get(k)) + else f'{form_label_state.get(k)}不能为空!' + for k in form_label_list + }, + modal_visible=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('user-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - [Input('user-list-table', 'recentlySwitchDataIndex'), - Input('user-list-table', 'recentlySwitchStatus'), - Input('user-list-table', 'recentlySwitchRow')], - prevent_initial_call=True + Output('user-operations-store', 'data', allow_duplicate=True), + [ + Input('user-list-table', 'recentlySwitchDataIndex'), + Input('user-list-table', 'recentlySwitchStatus'), + Input('user-list-table', 'recentlySwitchRow'), + ], + prevent_initial_call=True, ) -def table_switch_user_status(recently_switch_data_index, recently_switch_status, recently_switch_row): +def table_switch_user_status( + recently_switch_data_index, recently_switch_status, recently_switch_row +): """ 表格内切换用户状态回调 """ if recently_switch_data_index: - if recently_switch_status: - params = dict(user_id=int(recently_switch_row['key']), status='0', type='status') - else: - params = dict(user_id=int(recently_switch_row['key']), status='1', type='status') - edit_button_result = edit_user_api(params) - if edit_button_result['code'] == 200: - - return [ - {'type': 'switch-status'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改成功', type='success') - ] + UserApi.change_user_status( + user_id=int(recently_switch_row['key']), + status='0' if recently_switch_status else '1', + ) + MessageManager.success(content='修改成功') - return [ - {'type': 'switch-status'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('修改失败', type='error') - ] + return {'type': 'switch-status'} raise PreventUpdate @app.callback( - [Output('user-delete-text', 'children'), - Output('user-delete-confirm-modal', 'visible'), - Output('user-delete-ids-store', 'data')], - [Input({'type': 'user-operation-button', 'index': ALL}, 'nClicks'), - Input('user-list-table', 'nClicksDropdownItem')], - [State('user-list-table', 'selectedRowKeys'), - State('user-list-table', 'recentlyClickedDropdownItemTitle'), - State('user-list-table', 'recentlyDropdownItemClickedRow')], - prevent_initial_call=True + [ + Output('user-delete-text', 'children'), + Output('user-delete-confirm-modal', 'visible'), + Output('user-delete-ids-store', 'data'), + ], + [ + Input({'type': 'user-operation-button', 'index': ALL}, 'nClicks'), + Input('user-list-table', 'nClicksDropdownItem'), + ], + [ + State('user-list-table', 'selectedRowKeys'), + State('user-list-table', 'recentlyClickedDropdownItemTitle'), + State('user-list-table', 'recentlyDropdownItemClickedRow'), + ], + prevent_initial_call=True, ) -def user_delete_modal(operation_click, dropdown_click, - selected_row_keys, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row): +def user_delete_modal( + operation_click, + dropdown_click, + selected_row_keys, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ 显示删除用户二次确认弹窗回调 """ - trigger_id = dash.ctx.triggered_id - if trigger_id == {'index': 'delete', 'type': 'user-operation-button'} or (trigger_id == 'user-list-table' and recently_clicked_dropdown_item_title == '删除'): - + trigger_id = ctx.triggered_id + if trigger_id == {'index': 'delete', 'type': 'user-operation-button'} or ( + trigger_id == 'user-list-table' + and recently_clicked_dropdown_item_title == '删除' + ): if trigger_id == {'index': 'delete', 'type': 'user-operation-button'}: user_ids = ','.join(selected_row_keys) else: @@ -587,54 +593,52 @@ def user_delete_modal(operation_click, dropdown_click, return [ f'是否确认删除用户编号为{user_ids}的用户?', True, - {'user_ids': user_ids} + user_ids, ] raise PreventUpdate @app.callback( - [Output('user-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + Output('user-operations-store', 'data', allow_duplicate=True), Input('user-delete-confirm-modal', 'okCounts'), State('user-delete-ids-store', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def user_delete_confirm(delete_confirm, user_ids_data): """ 删除用户弹窗确认回调,实现删除操作 """ if delete_confirm: - params = user_ids_data - delete_button_info = delete_user_api(params) - if delete_button_info['code'] == 200: - return [ - {'type': 'delete'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除成功', type='success') - ] + UserApi.del_user(params) + MessageManager.success(content='删除成功') - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('删除失败', type='error') - ] + return {'type': 'delete'} raise PreventUpdate @app.callback( - [Output('user-reset-password-confirm-modal', 'visible'), - Output('reset-password-row-key-store', 'data'), - Output('reset-password-input', 'value')], + [ + Output( + 'user-reset-password-confirm-modal', 'visible', allow_duplicate=True + ), + Output('reset-password-row-key-store', 'data'), + Output('reset-password-input', 'value'), + ], Input('user-list-table', 'nClicksDropdownItem'), - [State('user-list-table', 'recentlyClickedDropdownItemTitle'), - State('user-list-table', 'recentlyDropdownItemClickedRow')], - prevent_initial_call=True + [ + State('user-list-table', 'recentlyClickedDropdownItemTitle'), + State('user-list-table', 'recentlyDropdownItemClickedRow'), + ], + prevent_initial_call=True, ) -def user_reset_password_modal(dropdown_click, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row): +def user_reset_password_modal( + dropdown_click, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ 显示重置用户密码弹窗回调 """ @@ -644,69 +648,90 @@ def user_reset_password_modal(dropdown_click, recently_clicked_dropdown_item_tit else: raise PreventUpdate - return [ - True, - {'user_id': user_id}, - None - ] + return [True, user_id, None] raise PreventUpdate @app.callback( - [Output('user-operations-store', 'data', allow_duplicate=True), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], + [ + Output('user-operations-store', 'data', allow_duplicate=True), + Output( + 'user-reset-password-confirm-modal', 'visible', allow_duplicate=True + ), + ], Input('user-reset-password-confirm-modal', 'okCounts'), - [State('reset-password-row-key-store', 'data'), - State('reset-password-input', 'value')], - prevent_initial_call=True + [ + State('reset-password-row-key-store', 'data'), + State('reset-password-input', 'value'), + ], + running=[ + [ + Output('user-reset-password-confirm-modal', 'confirmLoading'), + True, + False, + ] + ], + prevent_initial_call=True, ) def user_reset_password_confirm(reset_confirm, user_id_data, reset_password): """ 重置用户密码弹窗确认回调,实现重置密码操作 """ if reset_confirm: + UserApi.reset_user_pwd( + user_id=int(user_id_data), password=reset_password + ) + MessageManager.success(content='重置成功') - user_id_data['password'] = reset_password - params = user_id_data - reset_button_info = reset_user_password_api(params) - if reset_button_info['code'] == 200: - return [ - {'type': 'reset-password'}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('重置成功', type='success') - ] - - return [ - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('重置失败', type='error') - ] + return [{'type': 'reset-password'}, False] raise PreventUpdate @app.callback( - [Output('user_to_allocated_role-modal', 'visible'), - Output({'type': 'allocate_role-search', 'index': 'allocated'}, 'nClicks'), - Output('allocate_role-user_id-container', 'data')], + [ + Output('user_to_allocated_role-modal', 'visible'), + Output('allocate_role-user_id-container', 'data'), + Output('allocate_role-nick_name-input', 'value'), + Output('allocate_role-user_name-input', 'value'), + Output('allocate_role-list-table', 'selectedRowKeys'), + Output('allocate_role-list-table', 'data'), + ], Input('user-list-table', 'nClicksDropdownItem'), - [State('user-list-table', 'recentlyClickedDropdownItemTitle'), - State('user-list-table', 'recentlyDropdownItemClickedRow'), - State({'type': 'allocate_role-search', 'index': 'allocated'}, 'nClicks')], - prevent_initial_call=True + [ + State('user-list-table', 'recentlyClickedDropdownItemTitle'), + State('user-list-table', 'recentlyDropdownItemClickedRow'), + ], + prevent_initial_call=True, ) -def role_to_allocated_user_modal(dropdown_click, recently_clicked_dropdown_item_title, recently_dropdown_item_clicked_row, allocated_role_search_nclick): +def role_to_allocated_user_modal( + dropdown_click, + recently_clicked_dropdown_item_title, + recently_dropdown_item_clicked_row, +): """ 显示用户分配角色弹窗回调 """ if dropdown_click and recently_clicked_dropdown_item_title == '分配角色': - + user_id = int(recently_dropdown_item_clicked_row['key']) + allocated_role_info = UserApi.get_auth_role(user_id=user_id) + table_data = allocated_role_info.get('roles') + selected_row_keys = [] + for item in table_data: + item['create_time'] = TimeFormatUtil.format_time( + item.get('create_time') + ) + item['key'] = str(item['role_id']) + if item.get('flag'): + selected_row_keys.append(str(item['role_id'])) return [ True, - allocated_role_search_nclick + 1 if allocated_role_search_nclick else 1, - recently_dropdown_item_clicked_row['key'] + user_id, + allocated_role_info.get('user').get('nick_name'), + allocated_role_info.get('user').get('user_name'), + selected_row_keys, + table_data, ] raise PreventUpdate @@ -714,144 +739,179 @@ def role_to_allocated_user_modal(dropdown_click, recently_clicked_dropdown_item_ # 显示用户导入弹窗及重置上传弹窗组件状态回调 app.clientside_callback( - ''' + """ (nClicks) => { if (nClicks) { return [ true, - [], - [], + null, false ]; } return [ false, window.dash_clientside.no_update, - window.dash_clientside.no_update, window.dash_clientside.no_update ]; } - ''', - [Output('user-import-confirm-modal', 'visible'), - Output('user-upload-choose', 'listUploadTaskRecord'), - Output('user-upload-choose', 'defaultFileList'), - Output('user-import-update-check', 'checked')], + """, + [ + Output('user-import-confirm-modal', 'visible', allow_duplicate=True), + Output('user-upload-choose', 'contents'), + Output('user-import-update-check', 'checked'), + ], Input('user-import', 'nClicks'), - prevent_initial_call=True + prevent_initial_call=True, ) @app.callback( output=dict( - confirm_loading=Output('user-import-confirm-modal', 'confirmLoading'), - modal_visible=Output('batch-result-modal', 'visible'), + result_modal_visible=Output('batch-result-modal', 'visible'), + import_modal_visible=Output( + 'user-import-confirm-modal', 'visible', allow_duplicate=True + ), batch_result=Output('batch-result-content', 'children'), - operations=Output('user-operations-store', 'data', allow_duplicate=True), - api_check_token_trigger=Output('api-check-token', 'data', allow_duplicate=True), - global_message_container=Output('global-message-container', 'children', allow_duplicate=True) - ), - inputs=dict( - import_confirm=Input('user-import-confirm-modal', 'okCounts') + operations=Output( + 'user-operations-store', 'data', allow_duplicate=True + ), ), + inputs=dict(import_confirm=Input('user-import-confirm-modal', 'okCounts')), state=dict( - list_upload_task_record=State('user-upload-choose', 'listUploadTaskRecord'), - is_update=State('user-import-update-check', 'checked') + contents=State('user-upload-choose', 'contents'), + is_update=State('user-import-update-check', 'checked'), ), - prevent_initial_call=True + running=[ + [Output('user-import-confirm-modal', 'confirmLoading'), True, False], + [Output('user-import-confirm-modal', 'okText'), '导入中', '导入'], + ], + prevent_initial_call=True, ) -def user_import_confirm(import_confirm, list_upload_task_record, is_update): +def user_import_confirm(import_confirm, contents, is_update): """ 用户导入弹窗确认回调,实现批量导入用户操作 """ if import_confirm: - if list_upload_task_record: - url = list_upload_task_record[-1].get('url') - batch_param = dict(url=url, is_update=is_update) - batch_import_result = batch_import_user_api(batch_param) - if batch_import_result.get('code') == 200: - return dict( - confirm_loading=False, - modal_visible=True if batch_import_result.get('message') else False, - batch_result=batch_import_result.get('message'), - operations={'type': 'batch-import'}, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('导入成功', type='success') - ) + if contents: + # url = list_upload_task_record[-1].get('url') + # batch_param = dict(url=url, is_update=is_update) + batch_import_result = UserApi.import_user( + file=BytesIO(base64.b64decode(contents.split(',', 1)[1])), + update_support=is_update, + ) + MessageManager.success(content='导入成功') return dict( - confirm_loading=False, - modal_visible=True, - batch_result=batch_import_result.get('message'), - operations=dash.no_update, - api_check_token_trigger={'timestamp': time.time()}, - global_message_container=fuc.FefferyFancyMessage('导入失败', type='error') + result_modal_visible=True + if batch_import_result.get('msg') + else False, + import_modal_visible=True + if batch_import_result.get('msg') + else False, + batch_result=batch_import_result.get('msg'), + operations={'type': 'batch-import'}, ) else: + MessageManager.warning(content='请上传需要导入的文件') + return dict( - confirm_loading=False, - modal_visible=dash.no_update, - batch_result=dash.no_update, - operations=dash.no_update, - api_check_token_trigger=dash.no_update, - global_message_container=fuc.FefferyFancyMessage('请上传需要导入的文件', type='error') + result_modal_visible=no_update, + import_modal_visible=True, + batch_result=no_update, + operations=no_update, ) raise PreventUpdate @app.callback( - [Output('user-export-container', 'data', allow_duplicate=True), - Output('user-export-complete-judge-container', 'data'), - Output('api-check-token', 'data', allow_duplicate=True), - Output('global-message-container', 'children', allow_duplicate=True)], - [Input('user-export', 'nClicks'), - Input('download-user-import-template', 'nClicks')], - prevent_initial_call=True + [ + Output('user-export-container', 'data', allow_duplicate=True), + Output( + 'user-export-complete-judge-container', 'data', allow_duplicate=True + ), + ], + Input('download-user-import-template', 'nClicks'), + prevent_initial_call=True, ) -def export_user_list(export_click, download_click): +def download_user_template(download_click): """ - 导出用户信息回调 + 下载导入用户模板回调 """ - trigger_id = dash.ctx.triggered_id - if export_click or download_click: + if download_click: + download_template_res = UserApi.download_template() + download_template = download_template_res.content + MessageManager.success(content='下载成功') - if trigger_id == 'user-export': - export_user_res = export_user_list_api({}) - if export_user_res.status_code == 200: - export_user = export_user_res.content + return [ + dcc.send_bytes( + download_template, + f'用户导入模板_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), + {'timestamp': time.time()}, + ] - return [ - dcc.send_bytes(export_user, f'用户信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出成功', type='success') - ] + raise PreventUpdate - return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('导出失败', type='error') - ] - if trigger_id == 'download-user-import-template': - download_template_res = download_user_import_template_api() - if download_template_res.status_code == 200: - download_template = download_template_res.content +@app.callback( + [ + Output('user-export-container', 'data', allow_duplicate=True), + Output( + 'user-export-complete-judge-container', 'data', allow_duplicate=True + ), + ], + Input('user-export', 'nClicks'), + [ + State('dept-tree', 'selectedKeys'), + State('user-user_name-input', 'value'), + State('user-phone_number-input', 'value'), + State('user-status-select', 'value'), + State('user-create_time-range', 'value'), + ], + running=[[Output('user-export', 'loading'), True, False]], + prevent_initial_call=True, +) +def export_user_list( + export_click, + selected_dept_tree, + user_name, + phone_number, + status_select, + create_time_range, +): + """ + 导出用户信息回调 + """ + if export_click: + dept_id = None + begin_time = None + end_time = None + if create_time_range: + begin_time = create_time_range[0] + end_time = create_time_range[1] + if selected_dept_tree: + dept_id = int(selected_dept_tree[0]) + export_params = dict( + dept_id=dept_id, + user_name=user_name, + phonenumber=phone_number, + status=status_select, + begin_time=begin_time, + end_time=end_time, + ) + export_user_res = UserApi.export_user(export_params) + MessageManager.success(content='导出成功') - return [ - dcc.send_bytes(download_template, f'用户导入模板_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx'), - {'timestamp': time.time()}, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('下载成功', type='success') - ] + export_user = export_user_res.content - return [ - dash.no_update, - dash.no_update, - {'timestamp': time.time()}, - fuc.FefferyFancyMessage('下载失败', type='error') - ] + return [ + dcc.send_bytes( + export_user, + f'用户信息_{time.strftime("%Y%m%d%H%M%S", time.localtime())}.xlsx', + ), + {'timestamp': time.time()}, + ] raise PreventUpdate @@ -859,7 +919,7 @@ def export_user_list(export_click, download_click): @app.callback( Output('user-export-container', 'data', allow_duplicate=True), Input('user-export-complete-judge-container', 'data'), - prevent_initial_call=True + prevent_initial_call=True, ) def reset_user_export_status(data): """ @@ -867,7 +927,6 @@ def reset_user_export_status(data): """ time.sleep(0.5) if data: - return None raise PreventUpdate diff --git a/dash-fastapi-frontend/components/ApiRadioGroup/__init__.py b/dash-fastapi-frontend/components/ApiRadioGroup/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3b1631162fa0664c2fa7e61788fbf61849626af2 --- /dev/null +++ b/dash-fastapi-frontend/components/ApiRadioGroup/__init__.py @@ -0,0 +1,55 @@ +from feffery_antd_components import AntdRadioGroup +from typing import Any, Dict, List, Literal, Optional, Union +from uuid import uuid4 +from utils.dict_util import DictManager + + +class ApiRadioGroup(AntdRadioGroup): + def __init__( + self, + dict_type: str, + id: Optional[Union[str, Dict]] = str(uuid4()), + key: Optional[str] = None, + style: Optional[Dict] = None, + className: Optional[Union[str, Dict]] = None, + name: Optional[str] = None, + direction: Optional[Literal['horizontal', 'vertical']] = 'horizontal', + disabled: Optional[bool] = False, + size: Optional[Literal['small', 'middle', 'large']] = 'middle', + value: Optional[Any] = None, + defaultValue: Optional[Any] = None, + optionType: Optional[Literal['default', 'button']] = 'default', + buttonStyle: Optional[Literal['outline', 'solid']] = 'outline', + readOnly: Optional[bool] = False, + batchPropsNames: Optional[List] = None, + batchPropsValues: Optional[Dict] = None, + loading_state: Optional[Any] = None, + persistence: Optional[Any] = None, + persisted_props: Optional[Any] = None, + persistence_type: Optional[Any] = None, + **kwargs: Any, + ): + self.options = DictManager.get_dict_options(dict_type=dict_type)[0] + super().__init__( + id=id, + key=key, + style=style, + className=className, + name=name, + direction=direction, + options=self.options, + disabled=disabled, + size=size, + value=value, + defaultValue=defaultValue, + optionType=optionType, + buttonStyle=buttonStyle, + readOnly=readOnly, + batchPropsNames=batchPropsNames, + batchPropsValues=batchPropsValues, + loading_state=loading_state, + persistence=persistence, + persisted_props=persisted_props, + persistence_type=persistence_type, + **kwargs, + ) diff --git a/dash-fastapi-frontend/components/ApiSelect/__init__.py b/dash-fastapi-frontend/components/ApiSelect/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..74535e1359010b1cffe5742a95ec49a7141c3150 --- /dev/null +++ b/dash-fastapi-frontend/components/ApiSelect/__init__.py @@ -0,0 +1,109 @@ +from feffery_antd_components import AntdSelect +from typing import Any, Dict, List, Literal, Optional, Union +from uuid import uuid4 +from utils.dict_util import DictManager + + +class ApiSelect(AntdSelect): + def __init__( + self, + dict_type: str, + id: Optional[Union[str, Dict]] = str(uuid4()), + key: Optional[str] = None, + style: Optional[Dict] = None, + className: Optional[Union[str, Dict]] = None, + popupClassName: Optional[str] = None, + name: Optional[str] = None, + locale: Optional[Literal['zh-cn', 'en-us', 'de-de']] = 'zh-cn', + listHeight: Optional[Union[int, float]] = 256, + colorsMode: Optional[Literal['sequential', 'diverging']] = 'sequential', + colorsNameWidth: Optional[Union[int, float]] = 40, + mode: Optional[Literal['multiple', 'tags']] = None, + disabled: Optional[bool] = False, + size: Optional[Literal['small', 'middle', 'large']] = 'middle', + bordered: Optional[bool] = True, + variant: Optional[Literal['outlined', 'borderless', 'filled']] = None, + placeholder: Optional[str] = None, + placement: Optional[ + Literal['bottomLeft', 'bottomRight', 'topLeft', 'topRight'] + ] = 'bottomLeft', + value: Optional[Any] = None, + defaultValue: Optional[Any] = None, + maxTagCount: Optional[Union[int, float, Literal['responsive']]] = 5, + status: Optional[Literal['error', 'warning']] = None, + optionFilterProp: Optional[Literal['value', 'label']] = 'value', + searchValue: Optional[str] = None, + optionFilterMode: Optional[ + Literal[ + 'case-insensitive', 'case-sensitive', 'regex', 'remote-match' + ] + ] = 'case-insensitive', + debounceSearchValue: Optional[Any] = None, + debounceWait: Optional[Union[int, float]] = 0, + autoSpin: Optional[bool] = False, + autoClearSearchValue: Optional[bool] = True, + emptyContent: Optional[Any] = None, + loadingEmptyContent: Optional[Any] = None, + dropdownBefore: Optional[Any] = None, + dropdownAfter: Optional[Any] = None, + allowClear: Optional[bool] = True, + autoFocus: Optional[bool] = False, + popupMatchSelectWidth: Optional[bool] = True, + readOnly: Optional[bool] = False, + popupContainer: Optional[Literal['parent', 'body']] = 'body', + batchPropsNames: Optional[List] = None, + batchPropsValues: Optional[Dict] = None, + loading_state: Optional[Any] = None, + persistence: Optional[Any] = None, + persisted_props: Optional[Any] = None, + persistence_type: Optional[Any] = None, + **kwargs: Any, + ): + self.options = DictManager.get_dict_options(dict_type=dict_type)[0] + super().__init__( + id=id, + key=key, + style=style, + className=className, + popupClassName=popupClassName, + name=name, + locale=locale, + options=self.options, + listHeight=listHeight, + colorsMode=colorsMode, + colorsNameWidth=colorsNameWidth, + mode=mode, + disabled=disabled, + size=size, + bordered=bordered, + variant=variant, + placeholder=placeholder, + placement=placement, + value=value, + defaultValue=defaultValue, + maxTagCount=maxTagCount, + status=status, + optionFilterProp=optionFilterProp, + searchValue=searchValue, + optionFilterMode=optionFilterMode, + debounceSearchValue=debounceSearchValue, + debounceWait=debounceWait, + autoSpin=autoSpin, + autoClearSearchValue=autoClearSearchValue, + emptyContent=emptyContent, + loadingEmptyContent=loadingEmptyContent, + dropdownBefore=dropdownBefore, + dropdownAfter=dropdownAfter, + allowClear=allowClear, + autoFocus=autoFocus, + popupMatchSelectWidth=popupMatchSelectWidth, + readOnly=readOnly, + popupContainer=popupContainer, + batchPropsNames=batchPropsNames, + batchPropsValues=batchPropsValues, + loading_state=loading_state, + persistence=persistence, + persisted_props=persisted_props, + persistence_type=persistence_type, + **kwargs, + ) diff --git a/dash-fastapi-frontend/components/ManuallyUpload/__init__.py b/dash-fastapi-frontend/components/ManuallyUpload/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..69b1eee4917fd53bc0523e688f333ba7f2b5469e --- /dev/null +++ b/dash-fastapi-frontend/components/ManuallyUpload/__init__.py @@ -0,0 +1,126 @@ +import feffery_antd_components as fac +from dash import dcc, html +from feffery_utils_components import FefferyShadowDom, FefferyStyle +from typing import Dict, Optional, Union +from uuid import uuid4 + + +class ManuallyUpload(FefferyShadowDom): + def __init__( + self, + id: Optional[Union[str, Dict]] = str(uuid4()), + accept: Optional[str] = None, + disabled: Optional[bool] = False, + max_size: Optional[Union[float, int]] = -1, + min_size: Optional[Union[float, int]] = 0, + multiple: Optional[bool] = False, + ): + children = [ + dcc.Upload( + html.Div( + html.Span( + html.Div( + html.Span( + html.Div( + [ + html.P( + fac.AntdIcon( + icon='antd-cloud-upload', + ), + className='ant-upload-drag-icon', + ), + html.P( + '用户导入', + className='ant-upload-text', + ), + html.P( + '点击或拖拽文件至此处进行上传', + className='ant-upload-hint', + ), + ], + className='ant-upload-drag-container', + ), + tabIndex='0', + role='button', + className='ant-upload ant-upload-btn', + ), + className='ant-upload ant-upload-drag', + ), + className='ant-upload-wrapper', + ), + className='ant-droag-upload-container', + ), + id=id, + accept=accept, + disabled=disabled, + max_size=max_size, + min_size=min_size, + multiple=multiple, + ), + FefferyStyle( + rawStyle=""" + .ant-droag-upload-container { + border: 1px solid transparent; + transition: border 0.3s; + } + .ant-upload-wrapper { + box-sizing: border-box; + margin: 0; + padding: 0; + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + line-height: 1.5714285714285714; + list-style: none; + font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji'; + } + .ant-upload-wrapper .ant-upload-drag { + position: relative; + width: 100%; + height: 100%; + text-align: center; + background: rgba(0, 0, 0, 0.02); + border: 1px dashed #d9d9d9; + border-radius: 8px; + cursor: pointer; + transition: border-color 0.3s; + } + .ant-upload-wrapper .ant-upload-drag:not(.ant-upload-disabled):hover, .ant-upload-wrapper .ant-upload-drag-hover:not(.ant-upload-disabled) { + border-color: #4096ff; + } + .ant-upload-wrapper .ant-upload-drag .ant-upload-btn { + display: table; + width: 100%; + height: 100%; + outline: none; + border-radius: 8px; + } + .ant-upload-wrapper .ant-upload-drag .ant-upload { + padding: 16px; + } + .ant-upload-wrapper .ant-upload-drag .ant-upload-drag-container { + display: table-cell; + vertical-align: middle; + } + .ant-upload-wrapper .ant-upload-drag p.ant-upload-drag-icon { + margin-bottom: 16px; + } + .ant-upload-wrapper .ant-upload-drag p.ant-upload-drag-icon .anticon { + color: #1677ff; + font-size: 48px; + } + .ant-upload-wrapper .ant-upload-drag p.ant-upload-text { + margin: 0 0 4px; + color: rgba(0, 0, 0, 0.88); + font-size: 16px; + } + .ant-upload-wrapper .ant-upload-drag p.ant-upload-hint { + color: rgba(0, 0, 0, 0.45); + font-size: 14px; + } + .ant-upload-wrapper [class^="ant-upload"], .ant-upload-wrapper [class*=" ant-upload"] { + box-sizing: border-box; + } + """ + ), + ] + super().__init__(children=children) diff --git a/dash-fastapi-frontend/components/__init__.py b/dash-fastapi-frontend/components/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0f0cf325db9341ef924a13ae8c564b43f56d4739 --- /dev/null +++ b/dash-fastapi-frontend/components/__init__.py @@ -0,0 +1,3 @@ +from .ApiRadioGroup import ApiRadioGroup # noqa: F401 +from .ApiSelect import ApiSelect # noqa: F401 +from .ManuallyUpload import ManuallyUpload # noqa: F401 diff --git a/dash-fastapi-frontend/config/constant.py b/dash-fastapi-frontend/config/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..0c1613bfb43d4aa83f61fc35ba44494bbf1d81f7 --- /dev/null +++ b/dash-fastapi-frontend/config/constant.py @@ -0,0 +1,158 @@ +class CommonConstant: + """ + 常用常量 + + WWW: www主域 + HTTP: http请求 + HTTPS: https请求 + """ + + WWW = 'www.' + HTTP = 'http://' + HTTPS = 'https://' + + +class HttpStatusConstant: + """ + 返回状态码 + + SUCCESS: 操作成功 + CREATED: 对象创建成功 + ACCEPTED: 请求已经被接受 + NO_CONTENT: 操作已经执行成功,但是没有返回数据 + MOVED_PERM: 资源已被移除 + SEE_OTHER: 重定向 + NOT_MODIFIED: 资源没有被修改 + BAD_REQUEST: 参数列表错误(缺少,格式不匹配) + UNAUTHORIZED: 未授权 + FORBIDDEN: 访问受限,授权过期 + NOT_FOUND: 资源,服务未找到 + BAD_METHOD: 不允许的http方法 + CONFLICT: 资源冲突,或者资源被锁 + UNSUPPORTED_TYPE: 不支持的数据,媒体类型 + ERROR: 系统内部错误 + NOT_IMPLEMENTED: 接口未实现 + WARN: 系统警告消息 + """ + + SUCCESS = 200 + CREATED = 201 + ACCEPTED = 202 + NO_CONTENT = 204 + MOVED_PERM = 301 + SEE_OTHER = 303 + NOT_MODIFIED = 304 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + BAD_METHOD = 405 + CONFLICT = 409 + UNSUPPORTED_TYPE = 415 + ERROR = 500 + NOT_IMPLEMENTED = 501 + WARN = 601 + + +class MenuConstant: + """ + 菜单常量 + + TYPE_DIR: 菜单类型(目录) + TYPE_MENU: 菜单类型(菜单) + TYPE_BUTTON: 菜单类型(按钮) + YES_FRAME: 是否菜单外链(是) + NO_FRAME: 是否菜单外链(否) + YES_CACHE: 是否缓存菜单(是) + NO_CACHE: 是否缓存菜单(否) + LAYOUT: Layout组件标识 + PARENT_VIEW: ParentView组件标识 + INNER_LINK: InnerLink组件标识 + SUB_MENU: 菜单类型(目录)标识 + ITEM: 菜单类型(菜单)标识 + """ + + TYPE_DIR = 'M' + TYPE_MENU = 'C' + TYPE_BUTTON = 'F' + YES_FRAME = 0 + NO_FRAME = 1 + YES_CACHE = 0 + NO_CACHE = 1 + LAYOUT = 'Layout' + PARENT_VIEW = 'ParentView' + INNER_LINK = 'InnerLink' + SUB_MENU = 'SubMenu' + ITEM = 'Item' + + +class SysJobStatusConstant: + """ + 任务状态常量 + + NORMAL: 正常 + DISABLE: 暂停 + """ + + NORMAL = '0' + DISABLE = '1' + + +class SysNormalDisableConstant: + """ + 系统开关常量 + + NORMAL: 正常 + DISABLE: 停用 + """ + + NORMAL = '0' + DISABLE = '1' + + +class SysNoticeStatusConstant: + """ + 通知状态常量 + + NORMAL: 正常 + DISABLE: 关闭 + """ + + NORMAL = '0' + DISABLE = '1' + + +class SysNoticeTypeConstant: + """ + 通知类型常量 + + NORMAL: 正常 + DISABLE: 关闭 + """ + + NOTICE = '1' + BULLETIN = '2' + + +class SysShowHideConstant: + """ + 菜单显隐常量 + + SHOW: 正常 + HIDE: 暂停 + """ + + SHOW = '0' + HIDE = '1' + + +class SysYesNoConstant: + """ + 系统是否常量 + + YES: 是 + NO: 否 + """ + + YES = 'Y' + NO = 'N' diff --git a/dash-fastapi-frontend/config/enums.py b/dash-fastapi-frontend/config/enums.py new file mode 100644 index 0000000000000000000000000000000000000000..283fbb95bb4968f6eb5cd2af7d7f06343b5c9723 --- /dev/null +++ b/dash-fastapi-frontend/config/enums.py @@ -0,0 +1,19 @@ +from enum import Enum + + +class ApiMethod(Enum): + """ + Api请求方法 + + GET: get方法 + POST: post方法 + DELETE: delete方法 + PUT: put方法 + PATCH: patch方法 + """ + + GET = 'get' + POST = 'post' + DELETE = 'delete' + PUT = 'put' + PATCH = 'patch' diff --git a/dash-fastapi-frontend/config/env.py b/dash-fastapi-frontend/config/env.py index afa30bbf1529c01671370a20b612ca43c74f17c6..e654b7322e1099f2a8dcc44a5ae500ceaa9af589 100644 --- a/dash-fastapi-frontend/config/env.py +++ b/dash-fastapi-frontend/config/env.py @@ -1,16 +1,17 @@ -import os import argparse -from pydantic import BaseSettings -from functools import lru_cache +import os from dotenv import load_dotenv +from functools import lru_cache +from pydantic_settings import BaseSettings class AppSettings(BaseSettings): """ 应用配置 """ + app_env: str = 'dev' - app_name: str = '通用后台管理系统' + app_name: str = 'DF Admin' app_base_url: str = 'http://127.0.0.1:9099' app_proxy_path: str = '/dev-api' app_is_proxy: bool = False @@ -18,8 +19,19 @@ class AppSettings(BaseSettings): app_host: str = '0.0.0.0' app_port: int = 8088 app_debug: bool = True - app_compress_algorithm = 'br' - app_compress_br_level = 11 + app_compress_algorithm: str = 'br' + app_compress_br_level: int = 11 + + +class CacheSettings(BaseSettings): + """ + 缓存配置 + """ + + lru_cache_maxsize: int = 10000 + lru_cache_capacity: int = 10000 + ttl_cache_maxsize: int = 0 + ttl_cache_expire: int = 600 class GetConfig: @@ -38,6 +50,14 @@ class GetConfig: # 实例化应用配置模型 return AppSettings() + @lru_cache() + def get_cache_config(self): + """ + 获取缓存配置 + """ + # 实例化缓存配置模型 + return CacheSettings() + @staticmethod def parse_cli_args(): """ @@ -65,3 +85,496 @@ class GetConfig: get_config = GetConfig() # 应用配置 AppConfig = get_config.get_app_config() +# 缓存配置 +CacheConfig = get_config.get_cache_config() + + +class ApiConfig: + """ + Api配置 + + BaseUrl: Api请求地址 + """ + + BaseUrl = ( + AppConfig.app_base_url + AppConfig.app_proxy_path + if AppConfig.app_is_proxy + else AppConfig.app_base_url + ) + + +class PathConfig: + """ + 路径配置 + + ABS_ROOT_PATH: 项目绝对根目录 + """ + + ABS_ROOT_PATH = os.path.abspath(os.getcwd()) + + +class IconConfig: + ICON_LIST = [ + 'antd-carry-out', + 'antd-car', + 'antd-bulb', + 'antd-build', + 'antd-bug', + 'antd-bar-code', + 'antd-branches', + 'antd-aim', + 'antd-issues-close', + 'antd-ellipsis', + 'antd-user', + 'antd-unlock', + 'antd-repair', + 'antd-team', + 'antd-sync', + 'antd-setting', + 'antd-send', + 'antd-schedule', + 'antd-save', + 'antd-rocket', + 'antd-reload', + 'antd-read', + 'antd-qrcode', + 'antd-power-off', + 'antd-number', + 'antd-notification', + 'antd-menu', + 'antd-mail', + 'antd-lock', + 'antd-loading', + 'antd-key', + 'antd-hourglass', + 'antd-global', + 'antd-function', + 'antd-import', + 'antd-export', + 'antd-dashboard', + 'antd-control', + 'antd-console-sql', + 'antd-compass', + 'antd-comment', + 'antd-code', + 'antd-cluster', + 'antd-clear', + 'antd-camera', + 'antd-book', + 'antd-catalog', + 'antd-api', + 'antd-alert', + 'antd-account-book', + 'antd-alipay', + 'antd-alipay-circle', + 'antd-weibo', + 'antd-github', + 'antd-fall', + 'antd-rise', + 'antd-stock', + 'antd-home', + 'antd-fund', + 'antd-area-chart', + 'antd-radar-chart', + 'antd-bar-chart', + 'antd-pie-chart', + 'antd-box-plot', + 'antd-dot-chart', + 'antd-line-chart', + 'antd-field-binary', + 'antd-field-number', + 'antd-field-string', + 'antd-field-time', + 'antd-file-add', + 'antd-file-done', + 'antd-file', + 'antd-file-image', + 'antd-file-markdown', + 'antd-file-pdf', + 'antd-file-protect', + 'antd-file-sync', + 'antd-file-text', + 'antd-file-word', + 'antd-file-zip', + 'antd-filter', + 'antd-fire', + 'antd-woman', + 'antd-arrow-up', + 'antd-arrow-down', + 'antd-arrow-left', + 'antd-arrow-right', + 'antd-flag', + 'antd-user-add', + 'antd-folder-add', + 'antd-man', + 'antd-tag', + 'antd-folder', + 'antd-user-delete', + 'antd-trophy', + 'antd-shopping-cart', + 'antd-folder-open', + 'antd-fork', + 'antd-select', + 'antd-tags', + 'antd-thunderbolt', + 'antd-sound', + 'antd-fund-projection-screen', + 'antd-funnel-plot', + 'antd-gift', + 'antd-robot', + 'antd-pushpin', + 'antd-printer', + 'antd-phone', + 'antd-picture', + 'antd-idcard', + 'antd-partition', + 'antd-monitor', + 'antd-more', + 'antd-apartment', + 'antd-money-collect', + 'antd-experiment', + 'antd-link', + 'antd-mobile', + 'antd-coffee', + 'antd-layout', + 'antd-eye', + 'antd-eye-invisible', + 'antd-exception', + 'antd-dollar', + 'antd-euro', + 'antd-download', + 'antd-environment', + 'antd-deployment-unit', + 'antd-crown', + 'antd-desktop', + 'antd-like', + 'antd-dislike', + 'antd-disconnect', + 'antd-app-store', + 'antd-app-store-add', + 'antd-bell', + 'antd-calculator', + 'antd-calendar', + 'antd-database', + 'antd-history', + 'antd-search', + 'antd-file-search', + 'antd-cloud', + 'antd-cloud-upload', + 'antd-cloud-download', + 'antd-cloud-server', + 'antd-cloud-sync', + 'antd-swap', + 'antd-rollback', + 'antd-login', + 'antd-logout', + 'antd-menu-fold', + 'antd-menu-unfold', + 'antd-full-screen', + 'antd-full-screen-exit', + 'antd-question-circle', + 'antd-plus-circle', + 'antd-minus-circle', + 'antd-info-circle', + 'antd-exclamation-circle', + 'antd-close-circle', + 'antd-check-circle', + 'antd-clock-circle', + 'antd-stop', + 'antd-edit', + 'antd-delete', + 'antd-highlight', + 'antd-redo', + 'antd-undo', + 'antd-zoom-in', + 'antd-zoom-out', + 'antd-sort-ascending', + 'antd-sort-descending', + 'antd-table', + 'antd-question', + 'antd-plus', + 'antd-minus', + 'antd-close', + 'antd-check', + 'antd-sketch', + 'antd-bank', + 'antd-block', + 'antd-insurance', + 'antd-smile', + 'antd-skin', + 'antd-star', + 'antd-right-circle-two-tone', + 'antd-left-circle-two-tone', + 'antd-up-circle-two-tone', + 'antd-down-circle-two-tone', + 'antd-up-square-two-tone', + 'antd-down-square-two-tone', + 'antd-left-square-two-tone', + 'antd-right-square-two-tone', + 'antd-question-circle-two-tone', + 'antd-plus-circle-two-tone', + 'antd-minus-circle-two-tone', + 'antd-plus-square-two-tone', + 'antd-minus-square-two-tone', + 'antd-info-circle-two-tone', + 'antd-exclamation-circle-two-tone', + 'antd-close-circle-two-tone', + 'antd-close-square-two-tone', + 'antd-check-circle-two-tone', + 'antd-check-square-two-tone', + 'antd-edit-two-tone', + 'antd-delete-two-tone', + 'antd-highlight-two-tone', + 'antd-pie-chart-two-tone', + 'antd-box-chart-two-tone', + 'antd-fund-two-tone', + 'antd-sliders-two-tone', + 'antd-api-two-tone', + 'antd-cloud-two-tone', + 'antd-hourglass-two-tone', + 'antd-notification-two-tone', + 'antd-tool-two-tone', + 'antd-down', + 'antd-up', + 'antd-left', + 'antd-right', + 'md-star-half', + 'md-star-border', + 'md-star', + 'md-people', + 'md-plus-one', + 'md-notifications', + 'md-pin-drop', + 'md-layers-clear', + 'md-layers', + 'md-edit-location', + 'md-tune', + 'md-transform', + 'md-timer-off', + 'md-timer', + 'md-file-upload', + 'md-file-download', + 'md-create-new-folder', + 'md-cloud-upload', + 'md-cloud-queue', + 'md-cloud-download', + 'md-cloud-done', + 'md-insert-chart', + 'md-functions', + 'md-format-quote', + 'md-attach-file', + 'md-storage', + 'md-save', + 'md-remove-circle-outline', + 'md-remove-circle', + 'md-remove', + 'md-low-priority', + 'md-link', + 'md-gesture', + 'md-forward', + 'md-flag', + 'md-drafts', + 'md-create', + 'md-content-paste', + 'md-content-cut', + 'md-content-copy', + 'md-clear', + 'md-block', + 'md-backspace', + 'md-add-box', + 'md-add', + 'md-add-circle-outline', + 'md-add-circle', + 'md-location-on', + 'md-mail-outline', + 'md-email', + 'md-not-interested', + 'md-library-books', + 'md-library-add', + 'md-equalizer', + 'md-add-alert', + 'md-visibility-off', + 'md-visibility', + 'md-verified-user', + 'md-update', + 'md-trending-up', + 'md-trending-flat', + 'md-trending-down', + 'md-translate', + 'md-toc', + 'md-timeline', + 'md-thumb-up', + 'md-thumb-down', + 'md-swap-vert', + 'md-swap-horiz', + 'md-supervisor-account', + 'md-subject', + 'md-settings', + 'md-search', + 'md-schedule', + 'md-restore', + 'md-query-builder', + 'md-power-settings-new', + 'md-opacity', + 'md-note-add', + 'md-lock-outline', + 'md-lock-open', + 'md-list', + 'md-lightbulb-outline', + 'md-launch', + 'md-label-outline', + 'md-label', + 'md-input', + 'md-info-outline', + 'md-info', + 'md-hourglass', + 'md-home', + 'md-history', + 'md-highlight-off', + 'md-help-outline', + 'md-help', + 'md-get-app', + 'md-translate', + 'md-fingerprint', + 'md-findIn-page', + 'md-favorite-border', + 'md-favorite', + 'md-extension', + 'md-explore', + 'md-exit-to-app', + 'md-event', + 'md-description', + 'md-delete-forever', + 'md-delete', + 'md-dashboard', + 'md-code', + 'md-build', + 'md-bug-report', + 'md-assignment', + 'md-assessment', + 'md-alarm-on', + 'md-alarm-off', + 'md-alarm-add', + 'md-alarm', + 'md-account-circle', + 'fc-vlc', + 'fc-view-details', + 'fc-upload', + 'fc-tree-structure', + 'fc-timeline', + 'fc-template', + 'fc-survey', + 'fc-signature', + 'fc-share', + 'fc-services', + 'fc-rules', + 'fc-questions', + 'fc-process', + 'fc-plus', + 'fc-overtime', + 'fc-organization', + 'fc-numerical-sorting21', + 'fc-numerical-sorting12', + 'fc-multiple-inputs', + 'fc-mind-map', + 'fc-menu', + 'fc-list', + 'fc-like', + 'fc-like-placeholder', + 'fc-info', + 'fc-import', + 'fc-image-file', + 'fc-idea', + 'fc-home', + 'fc-high-priority', + 'fc-low-priority', + 'fc-genealogy', + 'fc-full-trash', + 'fc-document-search', + 'fc-file', + 'fc-faq', + 'fc-export', + 'fc-empty-trash', + 'fc-download', + 'fc-document', + 'fc-deployment', + 'fc-delete-database', + 'fc-conference-call', + 'fc-database', + 'fc-data-protection', + 'fc-data-encryption', + 'fc-data-configuration', + 'fc-data-backup', + 'fc-checkmark', + 'fc-cancel', + 'fc-briefcase', + 'fc-binoculars', + 'fc-automatic', + 'fc-alphabetical-sorting-za', + 'fc-alphabetical-sorting-az', + 'fc-add-database', + 'fc-accept-database', + 'fc-about', + 'fc-radar-chart', + 'fc-scatter-chart', + 'fc-pie-chart', + 'fc-line-chart', + 'fc-flow-chart', + 'fc-doughnut-chart', + 'fc-bar-chart', + 'fc-area-chart', + 'fc-line-bar-chart', + 'fc-workflow', + 'fc-todo-list', + 'fc-synchronize', + 'fc-repair', + 'fc-statistics', + 'fc-settings', + 'fc-search', + 'fc-serial-tasks', + 'fc-safe', + 'fc-negative-dynamic', + 'fc-positive-dynamic', + 'fc-planner', + 'fc-parallel-tasks', + 'fc-org-unit', + 'fc-opened-folder', + 'fc-ok', + 'fc-inspection', + 'fc-globe', + 'fc-folder', + 'fc-electronics', + 'fc-data-sheet', + 'fc-command-line', + 'fc-calendar', + 'fc-calculator', + 'fc-bullish', + 'fc-bearish', + 'fc-bookmark', + 'fc-approval', + 'fc-advertising', + 'di-linux', + 'di-python', + 'di-chrome', + 'di-database', + 'di-firefox', + 'di-markdown', + 'di-postgresql', + 'di-terminal', + 'di-windows', + 'bi-table', + 'bi-analyse', + 'bi-layer', + 'bi-layer-minus', + 'bi-layer-plus', + 'bs-list-task', + 'bs-list-check', + 'bs-link', + 'bs-link-45-deg', + 'bs-envelope-open', + 'bs-envelope', + 'bs-alarm', + 'gi-mesh-network', + 'im-earth', + 'im-sphere', + ] diff --git a/dash-fastapi-frontend/config/exception.py b/dash-fastapi-frontend/config/exception.py new file mode 100644 index 0000000000000000000000000000000000000000..fac1304aa7773b5fd250426be96ecc5013830c17 --- /dev/null +++ b/dash-fastapi-frontend/config/exception.py @@ -0,0 +1,57 @@ +from dash import set_props +from utils.feedback_util import MessageManager, NotificationManager +from utils.log_util import logger + + +class AuthException(Exception): + """ + 自定义令牌异常AuthException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class RequestException(Exception): + """ + 自定义请求异常RequestException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceException(Exception): + """ + 自定义服务异常ServiceException + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +class ServiceWarning(Exception): + """ + 自定义服务警告ServiceWarning + """ + + def __init__(self, data: str = None, message: str = None): + self.data = data + self.message = message + + +def global_exception_handler(error): + if isinstance(error, AuthException): + set_props('token-invalid-modal', {'visible': True}) + elif isinstance(error, RequestException): + NotificationManager.error(description=error.message, message='请求异常') + elif isinstance(error, ServiceWarning): + MessageManager.warning(content=error.message) + elif isinstance(error, ServiceException): + MessageManager.error(content=error.message) + else: + logger.exception(f'[exception]{error}') + NotificationManager.error(description=str(error), message='服务异常') diff --git a/dash-fastapi-frontend/config/global_config.py b/dash-fastapi-frontend/config/global_config.py deleted file mode 100644 index 2485a4f30eb23513db3dc01c050ec42abdfcca0d..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/config/global_config.py +++ /dev/null @@ -1,493 +0,0 @@ -import os -from config.env import AppConfig - - -class PathConfig: - - # 项目绝对根目录 - ABS_ROOT_PATH = os.path.abspath(os.getcwd()) - - -class RouterConfig: - - # 合法pathname列表 - BASIC_VALID_PATHNAME = [ - '/', '/login', '/forget' - ] - - # 静态路由列表 - STATIC_VALID_PATHNAME = ['/', '/login', '/forget', '/user/profile'] - - -class ApiBaseUrlConfig: - - # api基本url - BaseUrl = AppConfig.app_base_url + AppConfig.app_proxy_path if AppConfig.app_is_proxy else AppConfig.app_base_url - - -class IconConfig: - - ICON_LIST = [ - 'antd-carry-out', - 'antd-car', - 'antd-bulb', - 'antd-build', - 'antd-bug', - 'antd-bar-code', - 'antd-branches', - 'antd-aim', - 'antd-issues-close', - 'antd-ellipsis', - 'antd-user', - 'antd-unlock', - 'antd-repair', - 'antd-team', - 'antd-sync', - 'antd-setting', - 'antd-send', - 'antd-schedule', - 'antd-save', - 'antd-rocket', - 'antd-reload', - 'antd-read', - 'antd-qrcode', - 'antd-power-off', - 'antd-number', - 'antd-notification', - 'antd-menu', - 'antd-mail', - 'antd-lock', - 'antd-loading', - 'antd-key', - 'antd-hourglass', - 'antd-global', - 'antd-function', - 'antd-import', - 'antd-export', - 'antd-dashboard', - 'antd-control', - 'antd-console-sql', - 'antd-compass', - 'antd-comment', - 'antd-code', - 'antd-cluster', - 'antd-clear', - 'antd-camera', - 'antd-book', - 'antd-catalog', - 'antd-api', - 'antd-alert', - 'antd-account-book', - 'antd-alipay', - 'antd-alipay-circle', - 'antd-weibo', - 'antd-github', - 'antd-fall', - 'antd-rise', - 'antd-stock', - 'antd-home', - 'antd-fund', - 'antd-area-chart', - 'antd-radar-chart', - 'antd-bar-chart', - 'antd-pie-chart', - 'antd-box-plot', - 'antd-dot-chart', - 'antd-line-chart', - 'antd-field-binary', - 'antd-field-number', - 'antd-field-string', - 'antd-field-time', - 'antd-file-add', - 'antd-file-done', - 'antd-file', - 'antd-file-image', - 'antd-file-markdown', - 'antd-file-pdf', - 'antd-file-protect', - 'antd-file-sync', - 'antd-file-text', - 'antd-file-word', - 'antd-file-zip', - 'antd-filter', - 'antd-fire', - 'antd-woman', - 'antd-arrow-up', - 'antd-arrow-down', - 'antd-arrow-left', - 'antd-arrow-right', - 'antd-flag', - 'antd-user-add', - 'antd-folder-add', - 'antd-man', - 'antd-tag', - 'antd-folder', - 'antd-user-delete', - 'antd-trophy', - 'antd-shopping-cart', - 'antd-folder-open', - 'antd-fork', - 'antd-select', - 'antd-tags', - 'antd-thunderbolt', - 'antd-sound', - 'antd-fund-projection-screen', - 'antd-funnel-plot', - 'antd-gift', - 'antd-robot', - 'antd-pushpin', - 'antd-printer', - 'antd-phone', - 'antd-picture', - 'antd-idcard', - 'antd-partition', - 'antd-monitor', - 'antd-more', - 'antd-apartment', - 'antd-money-collect', - 'antd-experiment', - 'antd-link', - 'antd-mobile', - 'antd-coffee', - 'antd-layout', - 'antd-eye', - 'antd-eye-invisible', - 'antd-exception', - 'antd-dollar', - 'antd-euro', - 'antd-download', - 'antd-environment', - 'antd-deployment-unit', - 'antd-crown', - 'antd-desktop', - 'antd-like', - 'antd-dislike', - 'antd-disconnect', - 'antd-app-store', - 'antd-app-store-add', - 'antd-bell', - 'antd-calculator', - 'antd-calendar', - 'antd-database', - 'antd-history', - 'antd-search', - 'antd-file-search', - 'antd-cloud', - 'antd-cloud-upload', - 'antd-cloud-download', - 'antd-cloud-server', - 'antd-cloud-sync', - 'antd-swap', - 'antd-rollback', - 'antd-login', - 'antd-logout', - 'antd-menu-fold', - 'antd-menu-unfold', - 'antd-full-screen', - 'antd-full-screen-exit', - 'antd-question-circle', - 'antd-plus-circle', - 'antd-minus-circle', - 'antd-info-circle', - 'antd-exclamation-circle', - 'antd-close-circle', - 'antd-check-circle', - 'antd-clock-circle', - 'antd-stop', - 'antd-edit', - 'antd-delete', - 'antd-highlight', - 'antd-redo', - 'antd-undo', - 'antd-zoom-in', - 'antd-zoom-out', - 'antd-sort-ascending', - 'antd-sort-descending', - 'antd-table', - 'antd-question', - 'antd-plus', - 'antd-minus', - 'antd-close', - 'antd-check', - 'antd-sketch', - 'antd-bank', - 'antd-block', - 'antd-insurance', - 'antd-smile', - 'antd-skin', - 'antd-star', - 'antd-right-circle-two-tone', - 'antd-left-circle-two-tone', - 'antd-up-circle-two-tone', - 'antd-down-circle-two-tone', - 'antd-up-square-two-tone', - 'antd-down-square-two-tone', - 'antd-left-square-two-tone', - 'antd-right-square-two-tone', - 'antd-question-circle-two-tone', - 'antd-plus-circle-two-tone', - 'antd-minus-circle-two-tone', - 'antd-plus-square-two-tone', - 'antd-minus-square-two-tone', - 'antd-info-circle-two-tone', - 'antd-exclamation-circle-two-tone', - 'antd-close-circle-two-tone', - 'antd-close-square-two-tone', - 'antd-check-circle-two-tone', - 'antd-check-square-two-tone', - 'antd-edit-two-tone', - 'antd-delete-two-tone', - 'antd-highlight-two-tone', - 'antd-pie-chart-two-tone', - 'antd-box-chart-two-tone', - 'antd-fund-two-tone', - 'antd-sliders-two-tone', - 'antd-api-two-tone', - 'antd-cloud-two-tone', - 'antd-hourglass-two-tone', - 'antd-notification-two-tone', - 'antd-tool-two-tone', - 'antd-down', - 'antd-up', - 'antd-left', - 'antd-right', - 'md-star-half', - 'md-star-border', - 'md-star', - 'md-people', - 'md-plus-one', - 'md-notifications', - 'md-pin-drop', - 'md-layers-clear', - 'md-layers', - 'md-edit-location', - 'md-tune', - 'md-transform', - 'md-timer-off', - 'md-timer', - 'md-file-upload', - 'md-file-download', - 'md-create-new-folder', - 'md-cloud-upload', - 'md-cloud-queue', - 'md-cloud-download', - 'md-cloud-done', - 'md-insert-chart', - 'md-functions', - 'md-format-quote', - 'md-attach-file', - 'md-storage', - 'md-save', - 'md-remove-circle-outline', - 'md-remove-circle', - 'md-remove', - 'md-low-priority', - 'md-link', - 'md-gesture', - 'md-forward', - 'md-flag', - 'md-drafts', - 'md-create', - 'md-content-paste', - 'md-content-cut', - 'md-content-copy', - 'md-clear', - 'md-block', - 'md-backspace', - 'md-add-box', - 'md-add', - 'md-add-circle-outline', - 'md-add-circle', - 'md-location-on', - 'md-mail-outline', - 'md-email', - 'md-not-interested', - 'md-library-books', - 'md-library-add', - 'md-equalizer', - 'md-add-alert', - 'md-visibility-off', - 'md-visibility', - 'md-verified-user', - 'md-update', - 'md-trending-up', - 'md-trending-flat', - 'md-trending-down', - 'md-translate', - 'md-toc', - 'md-timeline', - 'md-thumb-up', - 'md-thumb-down', - 'md-swap-vert', - 'md-swap-horiz', - 'md-supervisor-account', - 'md-subject', - 'md-settings', - 'md-search', - 'md-schedule', - 'md-restore', - 'md-query-builder', - 'md-power-settings-new', - 'md-opacity', - 'md-note-add', - 'md-lock-outline', - 'md-lock-open', - 'md-list', - 'md-lightbulb-outline', - 'md-launch', - 'md-label-outline', - 'md-label', - 'md-input', - 'md-info-outline', - 'md-info', - 'md-hourglass', - 'md-home', - 'md-history', - 'md-highlight-off', - 'md-help-outline', - 'md-help', - 'md-get-app', - 'md-translate', - 'md-fingerprint', - 'md-findIn-page', - 'md-favorite-border', - 'md-favorite', - 'md-extension', - 'md-explore', - 'md-exit-to-app', - 'md-event', - 'md-description', - 'md-delete-forever', - 'md-delete', - 'md-dashboard', - 'md-code', - 'md-build', - 'md-bug-report', - 'md-assignment', - 'md-assessment', - 'md-alarm-on', - 'md-alarm-off', - 'md-alarm-add', - 'md-alarm', - 'md-account-circle', - 'fc-vlc', - 'fc-view-details', - 'fc-upload', - 'fc-tree-structure', - 'fc-timeline', - 'fc-template', - 'fc-survey', - 'fc-signature', - 'fc-share', - 'fc-services', - 'fc-rules', - 'fc-questions', - 'fc-process', - 'fc-plus', - 'fc-overtime', - 'fc-organization', - 'fc-numerical-sorting21', - 'fc-numerical-sorting12', - 'fc-multiple-inputs', - 'fc-mind-map', - 'fc-menu', - 'fc-list', - 'fc-like', - 'fc-like-placeholder', - 'fc-info', - 'fc-import', - 'fc-image-file', - 'fc-idea', - 'fc-home', - 'fc-high-priority', - 'fc-low-priority', - 'fc-genealogy', - 'fc-full-trash', - 'fc-document-search', - 'fc-file', - 'fc-faq', - 'fc-export', - 'fc-empty-trash', - 'fc-download', - 'fc-document', - 'fc-deployment', - 'fc-delete-database', - 'fc-conference-call', - 'fc-database', - 'fc-data-protection', - 'fc-data-encryption', - 'fc-data-configuration', - 'fc-data-backup', - 'fc-checkmark', - 'fc-cancel', - 'fc-briefcase', - 'fc-binoculars', - 'fc-automatic', - 'fc-alphabetical-sorting-za', - 'fc-alphabetical-sorting-az', - 'fc-add-database', - 'fc-accept-database', - 'fc-about', - 'fc-radar-chart', - 'fc-scatter-chart', - 'fc-pie-chart', - 'fc-line-chart', - 'fc-flow-chart', - 'fc-doughnut-chart', - 'fc-bar-chart', - 'fc-area-chart', - 'fc-line-bar-chart', - 'fc-workflow', - 'fc-todo-list', - 'fc-synchronize', - 'fc-repair', - 'fc-statistics', - 'fc-settings', - 'fc-search', - 'fc-serial-tasks', - 'fc-safe', - 'fc-negative-dynamic', - 'fc-positive-dynamic', - 'fc-planner', - 'fc-parallel-tasks', - 'fc-org-unit', - 'fc-opened-folder', - 'fc-ok', - 'fc-inspection', - 'fc-globe', - 'fc-folder', - 'fc-electronics', - 'fc-data-sheet', - 'fc-command-line', - 'fc-calendar', - 'fc-calculator', - 'fc-bullish', - 'fc-bearish', - 'fc-bookmark', - 'fc-approval', - 'fc-advertising', - 'di-linux', - 'di-python', - 'di-chrome', - 'di-database', - 'di-firefox', - 'di-markdown', - 'di-postgresql', - 'di-terminal', - 'di-windows', - 'bi-table', - 'bi-analyse', - 'bi-layer', - 'bi-layer-minus', - 'bi-layer-plus', - 'bs-list-task', - 'bs-list-check', - 'bs-link', - 'bs-link-45-deg', - 'bs-envelope-open', - 'bs-envelope', - 'bs-alarm', - 'gi-mesh-network', - 'im-earth', - 'im-sphere' - ] diff --git a/dash-fastapi-frontend/config/router.py b/dash-fastapi-frontend/config/router.py new file mode 100644 index 0000000000000000000000000000000000000000..9a8b58b2502e57da1c09544aef0e7d241e549345 --- /dev/null +++ b/dash-fastapi-frontend/config/router.py @@ -0,0 +1,83 @@ +class RouterConfig: + """ + 路由配置 + + WHITE_ROUTES_LIST: 白名单路由列表 + CONSTANT_ROUTES: 公共路由列表 + """ + + WHITE_ROUTES_LIST = [ + { + 'path': '/login', + 'component': 'login', + }, + { + 'path': '/forget', + 'component': 'forget', + }, + { + 'path': '/register', + 'component': 'register', + }, + ] + + CONSTANT_ROUTES = [ + { + 'path': '/login', + 'component': 'login', + 'name': 'Login', + 'hidden': True, + }, + { + 'path': '/forget', + 'component': 'forget', + 'name': 'Forget', + 'hidden': True, + }, + { + 'path': '/register', + 'component': 'register', + 'name': 'Register', + 'hidden': True, + }, + { + 'path': '', + 'component': 'Layout', + 'name': '', + 'redirect': '/', + 'children': [ + { + 'path': '/', + 'component': 'dashboard', + 'name': 'Index', + 'meta': { + 'title': '首页', + 'icon': 'antd-dashboard', + 'affix': True, + }, + } + ], + }, + { + 'path': 'user', + 'component': 'Layout', + 'hidden': True, + 'name': 'UserProfile', + 'redirect': 'noredirect', + 'meta': { + 'title': '系统设置', + 'icon': 'antd-trophy', + }, + 'children': [ + { + 'path': 'profile', + 'component': 'system.user.profile', + 'name': 'Profile', + 'meta': { + 'title': '个人资料', + 'icon': 'antd-idcard', + }, + } + ], + }, + ] diff --git a/dash-fastapi-frontend/ruff.toml b/dash-fastapi-frontend/ruff.toml new file mode 100644 index 0000000000000000000000000000000000000000..ab99b0ad1933c93d2bfae6f78e9bf3c141a5169a --- /dev/null +++ b/dash-fastapi-frontend/ruff.toml @@ -0,0 +1,4 @@ +line-length = 80 + +[format] +quote-style = "single" \ No newline at end of file diff --git a/dash-fastapi-frontend/server.py b/dash-fastapi-frontend/server.py index 026ad3dcbabc3f92b2e458181a7485e65bfeba81..853fe10e6ce1d579cadf6b3d7e348de3bfd0bc8d 100644 --- a/dash-fastapi-frontend/server.py +++ b/dash-fastapi-frontend/server.py @@ -1,17 +1,17 @@ -import dash -import os -import time -from loguru import logger +from dash import Dash from flask import request, session from user_agents import parse from config.env import AppConfig -from config.global_config import PathConfig +from config.exception import global_exception_handler +from utils.log_util import logger -app = dash.Dash( + +app = Dash( __name__, compress=True, suppress_callback_exceptions=True, - update_title=None + update_title=None, + on_error=global_exception_handler, ) server = app.server @@ -23,33 +23,41 @@ app.server.secret_key = AppConfig.app_secret_key app.server.config['COMPRESS_ALGORITHM'] = AppConfig.app_compress_algorithm app.server.config['COMPRESS_BR_LEVEL'] = AppConfig.app_compress_br_level -log_time = time.strftime("%Y%m%d", time.localtime()) -# sys_log_file_path = os.path.join(PathConfig.ABS_ROOT_PATH, 'log', 'sys_log', f'sys_request_log_{log_time}.log') -api_log_file_path = os.path.join(PathConfig.ABS_ROOT_PATH, 'log', 'api_log', f'api_request_log_{log_time}.log') -# logger.add(sys_log_file_path, filter=lambda x: '[sys]' in x['message'], -# rotation="50MB", encoding="utf-8", enqueue=True, compression="zip") -logger.add(api_log_file_path, filter=lambda x: '[api]' in x['message'], - rotation="50MB", encoding="utf-8", enqueue=True, compression="zip") - # 获取用户浏览器信息 @server.before_request def get_user_agent_info(): - request_addr = request.headers.get("X-Forwarded-For") if AppConfig.app_env == 'prod' else request.remote_addr + request_addr = ( + request.headers.get('X-Forwarded-For') + if AppConfig.app_env == 'prod' + else request.remote_addr + ) user_string = str(request.user_agent) user_agent = parse(user_string) bw = user_agent.browser.family if user_agent.browser.version != (): bw_version = user_agent.browser.version[0] if bw == 'IE': - logger.warning("[sys]请求人:{}||请求IP:{}||请求方法:{}||请求Data:{}", - session.get('name'), request_addr, request.method, '用户使用IE内核') + logger.warning( + '[sys]请求人:{}||请求IP:{}||请求方法:{}||请求Data:{}', + session.get('name'), + request_addr, + request.method, + '用户使用IE内核', + ) return "

请不要使用IE浏览器或360浏览器兼容模式

" if bw_version < 71: - logger.warning("[sys]请求人:{}||请求IP:{}||请求方法:{}||请求Data:{}", - session.get('name'), request_addr, request.method, '用户Chrome内核版本太低') - return "

Chrome内核版本号太低,请升级浏览器

" \ - "

点击此处可下载最新版Chrome浏览器

" + logger.warning( + '[sys]请求人:{}||请求IP:{}||请求方法:{}||请求Data:{}', + session.get('name'), + request_addr, + request.method, + '用户Chrome内核版本太低', + ) + return ( + "

Chrome内核版本号太低,请升级浏览器

" + "

点击此处可下载最新版Chrome浏览器

" + ) # 配置系统日志 diff --git a/dash-fastapi-frontend/store/store.py b/dash-fastapi-frontend/store/store.py index 95f8e4d68bbcb6fd872d3e07be15dda9ade0b0fd..65c8d14a9e84dc19fbbf4f2e103c7af9b646d322 100644 --- a/dash-fastapi-frontend/store/store.py +++ b/dash-fastapi-frontend/store/store.py @@ -1,23 +1,19 @@ -from dash import html, dcc +from dash import dcc, html def render_store_container(): - return html.Div( [ # 应用主题颜色存储容器 dcc.Store(id='system-app-primary-color-container', data='#1890ff'), - dcc.Store(id='custom-app-primary-color-container', storage_type='session'), - # 接口校验返回存储容器 - dcc.Store(id='api-check-token'), - # 接口校验返回存储容器 - dcc.Store(id='api-check-result-container'), + dcc.Store( + id='custom-app-primary-color-container', storage_type='session' + ), # token存储容器 dcc.Store(id='token-container', storage_type='session'), - # 菜单信息存储容器 - dcc.Store(id='menu-info-store-container'), - dcc.Store(id='menu-list-store-container'), - # 菜单current_key存储容器 - dcc.Store(id='current-key-container'), + # 当前路由存储容器 + dcc.Store(id='current-pathname-container'), + # 路由列表存储容器 + dcc.Store(id='router-list-container'), ] ) diff --git a/dash-fastapi-frontend/utils/cache_util.py b/dash-fastapi-frontend/utils/cache_util.py new file mode 100644 index 0000000000000000000000000000000000000000..e64edbc5ca5c99c1b58712664b848c19018c937a --- /dev/null +++ b/dash-fastapi-frontend/utils/cache_util.py @@ -0,0 +1,113 @@ +from cachebox import LRUCache, TTLCache +from flask import session +from typing import Any, Dict +from config.env import CacheConfig + + +cache_manager = LRUCache( + maxsize=CacheConfig.lru_cache_maxsize, + iterable=None, + capacity=CacheConfig.lru_cache_capacity, +) +ttl_manager = TTLCache( + maxsize=CacheConfig.ttl_cache_maxsize, ttl=CacheConfig.ttl_cache_expire +) + + +class CacheManager: + """ + 缓存管理器 + """ + + @classmethod + def get(cls, target_key: str): + """ + 获取缓存值 + + :param target_key: 缓存key + :return: 缓存值 + """ + cache_value = ( + cache_manager.get(session.get('Authorization')).get(target_key) + if cache_manager.get(session.get('Authorization')) + else None + ) + return cache_value + + @classmethod + def set(cls, target_obj: Dict): + """ + 设置缓存值 + + :param target_obj: 缓存值 + :return: + """ + cache = cache_manager.get(session.get('Authorization')) + if cache: + cache.update(target_obj) + else: + cache = target_obj + cache_manager.insert(session.get('Authorization'), cache) + + @classmethod + def delete(cls, target_key: str): + """ + 删除缓存值 + + :param target_key: 缓存key + :return: + """ + cache = cache_manager.get(session.get('Authorization')) + cache.pop(target_key, None) + cache_manager.insert(session.get('Authorization'), cache) + + @classmethod + def clear(cls): + """ + 清空缓存值 + + :return: + """ + cache = cache_manager.get(session.get('Authorization')) + if cache: + del cache_manager[session.get('Authorization')] + + +class TTLCacheManager: + """ + TTL缓存管理器 + """ + + @classmethod + def get(cls, target_key: str): + """ + 获取缓存值 + + :param target_key: 缓存key + :return: 缓存值 + """ + return ttl_manager.get(target_key) + + @classmethod + def set(cls, target_key: str, target_value: Any): + """ + 设置缓存值 + + :param target_key: 缓存key + :param target_value: 缓存值 + :return: + """ + ttl_manager.insert(target_key, target_value) + + @classmethod + def delete(cls, target_keys: str): + """ + 删除缓存值 + + :param target_keys: 缓存keys + :return: + """ + target_key_list = target_keys.split(',') + for target_key in target_key_list: + if ttl_manager.get(target_key) is not None: + del ttl_manager[target_key] diff --git a/dash-fastapi-frontend/utils/common.py b/dash-fastapi-frontend/utils/common.py deleted file mode 100644 index dd4e84b360af5bd16747d70f3b34939b8a38a32d..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/utils/common.py +++ /dev/null @@ -1,7 +0,0 @@ -def validate_data_not_empty(input_data): - """ - 工具方法:根据输入数据校验数据是否不为None和'' - :param input_data: 输入数据 - :return: 校验结果 - """ - return input_data is not None and input_data != '' diff --git a/dash-fastapi-frontend/utils/common_util.py b/dash-fastapi-frontend/utils/common_util.py new file mode 100644 index 0000000000000000000000000000000000000000..c7c80db538eb4168073fa5bf897cac825d7850f3 --- /dev/null +++ b/dash-fastapi-frontend/utils/common_util.py @@ -0,0 +1,32 @@ +from typing import Dict, List, Union + + +class ValidateUtil: + """ + 校验工具类 + """ + + @classmethod + def is_empty(cls, input_data: Union[Dict, List, str]): + """ + 工具方法:根据输入数据校验数据是否为空 + + :param input_data: 输入数据 + :return: 校验结果 + """ + return ( + input_data is None + or input_data == {} + or input_data == [] + or input_data == '' + ) + + @classmethod + def not_empty(cls, input_data: Union[Dict, List, str]): + """ + 工具方法:根据输入数据校验数据是否不为空 + + :param input_data: 输入数据 + :return: 校验结果 + """ + return not cls.is_empty(input_data) diff --git a/dash-fastapi-frontend/utils/dict_util.py b/dash-fastapi-frontend/utils/dict_util.py new file mode 100644 index 0000000000000000000000000000000000000000..efe79eead127464d0d1be5759a843cecbf76a994 --- /dev/null +++ b/dash-fastapi-frontend/utils/dict_util.py @@ -0,0 +1,105 @@ +from typing import Any, Literal +from api.system.dict.data import DictDataApi +from utils.cache_util import TTLCacheManager + + +class DictManager: + """ + 字典管理器 + """ + + @classmethod + def get_dict_options(cls, dict_type: str): + """ + 获取字典数据 + + :param dict_type: 字典类型 + :return: 字典数据 + """ + cache_dict_value = TTLCacheManager.get(target_key=dict_type) + if cache_dict_value: + select_options, dict_options = cache_dict_value + else: + dict_data = DictDataApi.get_dicts(dict_type=dict_type).get('data') + select_options = [ + dict( + label=item.get('dict_label'), + value=item.get('dict_value'), + ) + for item in dict_data + ] + dict_options = [ + dict( + label=item.get('dict_label'), + value=item.get('dict_value'), + css_class=item.get('css_class'), + list_class=item.get('list_class'), + ) + for item in dict_data + ] + TTLCacheManager.set( + target_key=dict_type, + target_value=[select_options, dict_options], + ) + + return [select_options, dict_options] + + @classmethod + def get_dict_label(cls, dict_type: str, dict_value: Any): + """ + 根据字典类型和字典值获取字典标签 + + :param dict_type: 字典类型 + :param dict_value: 字典值 + :return: 字典标签 + """ + options = cls.get_dict_options(dict_type=dict_type)[1] + if dict_value is None: + return '' + + for option in options: + if option.get('value') == str(dict_value): + return option.get('label') + return str(dict_value) + + @classmethod + def get_tag_color( + cls, + tag_type: Literal[ + 'default', 'primary', 'success', 'info', 'warning', 'danger' + ] = 'default', + ): + """ + 根据标签类型获取标签颜色 + + :param tag_type: 标签类型 + :return: 标签颜色 + """ + if tag_type == 'primary': + return 'blue' + elif tag_type == 'success': + return 'green' + elif tag_type == 'info': + return 'cyan' + elif tag_type == 'warning': + return 'gold' + elif tag_type == 'danger': + return 'red' + return None + + @classmethod + def get_dict_tag(cls, dict_type: dict, dict_value: Any): + """ + 根据字典类型和字典值获取字典标签tag + + :param dict_type: 字典类型 + :param dict_value: 字典值 + :return: 字典标签tag + """ + options = cls.get_dict_options(dict_type=dict_type)[1] + for option in options: + if option.get('value') == str(dict_value): + return dict( + tag=option.get('label'), + color=cls.get_tag_color(option.get('list_class')), + ) diff --git a/dash-fastapi-frontend/utils/feedback_util.py b/dash-fastapi-frontend/utils/feedback_util.py new file mode 100644 index 0000000000000000000000000000000000000000..f68f3342263b5b7ecf5200fb449e3188e607f9d9 --- /dev/null +++ b/dash-fastapi-frontend/utils/feedback_util.py @@ -0,0 +1,491 @@ +from dash import set_props +from typing import Dict, Literal, Optional, Union +from uuid import uuid4 + + +class MessageManager: + """ + 全局提示管理器 + """ + + @classmethod + def default( + cls, + container_id: Optional[Union[str, Dict]] = 'global-message-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + className: Optional[str] = None, + content: Optional[str] = '请求成功', + duration: Optional[int] = 3, + icon: Optional[str] = None, + iconRenderer: Optional[Literal['AntdIcon', 'fontawesome']] = 'AntdIcon', + key: Optional[str] = None, + maxCount: Optional[int] = None, + style: Optional[Dict] = None, + top: Optional[int] = 8, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'className': className, + 'content': content, + 'duration': duration, + 'icon': icon, + 'iconRenderer': iconRenderer, + 'key': key, + 'maxCount': maxCount, + 'style': style, + 'top': top, + 'type': 'default', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdMessage', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def info( + cls, + container_id: Optional[Union[str, Dict]] = 'global-message-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + className: Optional[str] = None, + content: Optional[str] = '请求成功', + duration: Optional[int] = 3, + icon: Optional[str] = None, + iconRenderer: Optional[Literal['AntdIcon', 'fontawesome']] = 'AntdIcon', + key: Optional[str] = None, + maxCount: Optional[int] = None, + style: Optional[Dict] = None, + top: Optional[int] = 8, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'className': className, + 'content': content, + 'duration': duration, + 'icon': icon, + 'iconRenderer': iconRenderer, + 'key': key, + 'maxCount': maxCount, + 'style': style, + 'top': top, + 'type': 'info', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdMessage', + 'namespace': 'feffery_antd_components', + } + }, + namespace='feffery_antd_components', + ) + + @classmethod + def success( + cls, + container_id: Optional[Union[str, Dict]] = 'global-message-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + className: Optional[str] = None, + content: Optional[str] = '请求成功', + duration: Optional[int] = 3, + icon: Optional[str] = None, + iconRenderer: Optional[Literal['AntdIcon', 'fontawesome']] = 'AntdIcon', + key: Optional[str] = None, + maxCount: Optional[int] = None, + style: Optional[Dict] = None, + top: Optional[int] = 8, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'className': className, + 'content': content, + 'duration': duration, + 'icon': icon, + 'iconRenderer': iconRenderer, + 'key': key, + 'maxCount': maxCount, + 'style': style, + 'top': top, + 'type': 'success', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdMessage', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def warning( + cls, + container_id: Optional[Union[str, Dict]] = 'global-message-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + className: Optional[str] = None, + content: Optional[str] = '请求失败', + duration: Optional[int] = 3, + icon: Optional[str] = None, + iconRenderer: Optional[Literal['AntdIcon', 'fontawesome']] = 'AntdIcon', + key: Optional[str] = None, + maxCount: Optional[int] = None, + style: Optional[Dict] = None, + top: Optional[int] = 8, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'className': className, + 'content': content, + 'duration': duration, + 'icon': icon, + 'iconRenderer': iconRenderer, + 'key': key, + 'maxCount': maxCount, + 'style': style, + 'top': top, + 'type': 'warning', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdMessage', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def error( + cls, + container_id: Optional[Union[str, Dict]] = 'global-message-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + className: Optional[str] = None, + content: Optional[str] = '请求异常', + duration: Optional[int] = 3, + icon: Optional[str] = None, + iconRenderer: Optional[Literal['AntdIcon', 'fontawesome']] = 'AntdIcon', + key: Optional[str] = None, + maxCount: Optional[int] = None, + style: Optional[Dict] = None, + top: Optional[int] = 8, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'className': className, + 'content': content, + 'duration': duration, + 'icon': icon, + 'iconRenderer': iconRenderer, + 'key': key, + 'maxCount': maxCount, + 'style': style, + 'top': top, + 'type': 'error', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdMessage', + 'namespace': 'feffery_antd_components', + } + }, + ) + + +class NotificationManager: + """ + 通知提醒管理器 + """ + + @classmethod + def default( + cls, + container_id: Optional[ + Union[str, Dict] + ] = 'global-notification-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + bottom: Optional[int] = 24, + className: Optional[str] = None, + closeable: Optional[bool] = True, + closeButton: Optional[Dict] = None, + description: Optional[str] = None, + duration: Optional[Union[float, int]] = 4.5, + key: Optional[str] = None, + message: Optional[str] = '请求成功', + placement: Optional[ + Literal[ + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ] + ] = 'topRight', + style: Optional[Dict] = None, + top: Optional[int] = 24, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'bottom': bottom, + 'className': className, + 'closeable': closeable, + 'closeButton': closeButton, + 'description': description, + 'duration': duration, + 'key': key, + 'message': message, + 'placement': placement, + 'style': style, + 'top': top, + 'type': 'default', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdNotification', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def info( + cls, + container_id: Optional[ + Union[str, Dict] + ] = 'global-notification-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + bottom: Optional[int] = 24, + className: Optional[str] = None, + closeable: Optional[bool] = True, + closeButton: Optional[Dict] = None, + description: Optional[str] = None, + duration: Optional[Union[float, int]] = 4.5, + key: Optional[str] = None, + message: Optional[str] = '请求成功', + placement: Optional[ + Literal[ + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ] + ] = 'topRight', + style: Optional[Dict] = None, + top: Optional[int] = 24, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'bottom': bottom, + 'className': className, + 'closeable': closeable, + 'closeButton': closeButton, + 'description': description, + 'duration': duration, + 'key': key, + 'message': message, + 'placement': placement, + 'style': style, + 'top': top, + 'type': 'info', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdNotification', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def success( + cls, + container_id: Optional[ + Union[str, Dict] + ] = 'global-notification-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + bottom: Optional[int] = 24, + className: Optional[str] = None, + closeable: Optional[bool] = True, + closeButton: Optional[Dict] = None, + description: Optional[str] = None, + duration: Optional[Union[float, int]] = 4.5, + key: Optional[str] = None, + message: Optional[str] = '请求成功', + placement: Optional[ + Literal[ + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ] + ] = 'topRight', + style: Optional[Dict] = None, + top: Optional[int] = 24, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'bottom': bottom, + 'className': className, + 'closeable': closeable, + 'closeButton': closeButton, + 'description': description, + 'duration': duration, + 'key': key, + 'message': message, + 'placement': placement, + 'style': style, + 'top': top, + 'type': 'success', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdNotification', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def warning( + cls, + container_id: Optional[ + Union[str, Dict] + ] = 'global-notification-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + bottom: Optional[int] = 24, + className: Optional[str] = None, + closeable: Optional[bool] = True, + closeButton: Optional[Dict] = None, + description: Optional[str] = None, + duration: Optional[Union[float, int]] = 4.5, + key: Optional[str] = None, + message: Optional[str] = '请求失败', + placement: Optional[ + Literal[ + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ] + ] = 'topRight', + style: Optional[Dict] = None, + top: Optional[int] = 24, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'bottom': bottom, + 'className': className, + 'closeable': closeable, + 'closeButton': closeButton, + 'description': description, + 'duration': duration, + 'key': key, + 'message': message, + 'placement': placement, + 'style': style, + 'top': top, + 'type': 'warning', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdNotification', + 'namespace': 'feffery_antd_components', + } + }, + ) + + @classmethod + def error( + cls, + container_id: Optional[ + Union[str, Dict] + ] = 'global-notification-container', + id: Optional[Union[str, Dict]] = str(uuid4()), + bottom: Optional[int] = 24, + className: Optional[str] = None, + closeable: Optional[bool] = True, + closeButton: Optional[Dict] = None, + description: Optional[str] = None, + duration: Optional[Union[float, int]] = 4.5, + key: Optional[str] = None, + message: Optional[str] = '请求异常', + placement: Optional[ + Literal[ + 'top', + 'bottom', + 'topLeft', + 'topRight', + 'bottomLeft', + 'bottomRight', + ] + ] = 'topRight', + style: Optional[Dict] = None, + top: Optional[int] = 24, + underCompatibilityMode: Optional[bool] = False, + ): + set_props( + container_id, + { + 'children': { + 'props': { + 'id': id, + 'bottom': bottom, + 'className': className, + 'closeable': closeable, + 'closeButton': closeButton, + 'description': description, + 'duration': duration, + 'key': key, + 'message': message, + 'placement': placement, + 'style': style, + 'top': top, + 'type': 'error', + 'underCompatibilityMode': underCompatibilityMode, + }, + 'type': 'AntdNotification', + 'namespace': 'feffery_antd_components', + } + }, + ) diff --git a/dash-fastapi-frontend/utils/file.py b/dash-fastapi-frontend/utils/file.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/dash-fastapi-frontend/utils/log_util.py b/dash-fastapi-frontend/utils/log_util.py new file mode 100644 index 0000000000000000000000000000000000000000..66089dd262a00f3ab38dc8d72548b5d9d57707bd --- /dev/null +++ b/dash-fastapi-frontend/utils/log_util.py @@ -0,0 +1,38 @@ +import os +import time +from loguru import logger +from config.env import PathConfig + + +log_time = time.strftime('%Y%m%d', time.localtime()) +# sys_log_file_path = os.path.join(PathConfig.ABS_ROOT_PATH, 'logs', 'sys_log', f'sys_request_log_{log_time}.log') +api_log_file_path = os.path.join( + PathConfig.ABS_ROOT_PATH, + 'logs', + 'api_log', + f'api_request_log_{log_time}.log', +) +exception_log_file_path = os.path.join( + PathConfig.ABS_ROOT_PATH, + 'logs', + 'exception_log', + f'exception_log_{log_time}.log', +) +# logger.add(sys_log_file_path, filter=lambda x: '[sys]' in x['message'], +# rotation="50MB", encoding="utf-8", enqueue=True, compression="zip") +logger.add( + api_log_file_path, + filter=lambda x: '[api]' in x['message'], + rotation='50MB', + encoding='utf-8', + enqueue=True, + compression='zip', +) +logger.add( + exception_log_file_path, + filter=lambda x: '[exception]' in x['message'], + rotation='50MB', + encoding='utf-8', + enqueue=True, + compression='zip', +) diff --git a/dash-fastapi-frontend/utils/permission_util.py b/dash-fastapi-frontend/utils/permission_util.py new file mode 100644 index 0000000000000000000000000000000000000000..7a75f688479861b7123571d4156968092c5ae8ef --- /dev/null +++ b/dash-fastapi-frontend/utils/permission_util.py @@ -0,0 +1,73 @@ +from typing import List, Union +from utils.cache_util import CacheManager + + +class PermissionManager: + """ + 权限管理器 + """ + + @classmethod + def check_perms(cls, perm: Union[str, List], is_strict: bool = False): + """ + 校验当前用户是否具有相应的权限标识 + + :param perm: 权限标识 + :param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过 + :return: 校验结果 + """ + user_perm_list = ( + CacheManager.get('permissions').get('perms') + if CacheManager.get('permissions') + else [] + ) + if '*:*:*' in user_perm_list: + return True + if isinstance(perm, str): + if perm in user_perm_list: + return True + if isinstance(perm, list): + if is_strict: + if all([perm_str in user_perm_list for perm_str in perm]): + return True + else: + if any([perm_str in user_perm_list for perm_str in perm]): + return True + return False + + @classmethod + def check_roles(cls, role_key: Union[str, List], is_strict: bool = False): + """ + 根据角色校验当前用户是否具有相应的权限 + + :param role_key: 角色标识 + :param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过 + :return: 校验结果 + """ + user_role_list = ( + CacheManager.get('permissions').get('roles') + if CacheManager.get('permissions') + else [] + ) + user_role_key_list = [role.role_key for role in user_role_list] + if isinstance(role_key, str): + if role_key in user_role_key_list: + return True + if isinstance(role_key, list): + if is_strict: + if all( + [ + role_key_str in user_role_key_list + for role_key_str in role_key + ] + ): + return True + else: + if any( + [ + role_key_str in user_role_key_list + for role_key_str in role_key + ] + ): + return True + return False diff --git a/dash-fastapi-frontend/utils/request.py b/dash-fastapi-frontend/utils/request.py index 51b4fc26827a40db165202bc61aa42465561e7fc..4ad69d6c5f48eb83e748c51eadb14e0a899a353d 100644 --- a/dash-fastapi-frontend/utils/request.py +++ b/dash-fastapi-frontend/utils/request.py @@ -1,70 +1,182 @@ import requests -from typing import Optional from flask import session, request -from config.env import AppConfig -from config.global_config import ApiBaseUrlConfig -from server import logger +from typing import Dict, Optional +from config.constant import HttpStatusConstant +from config.enums import ApiMethod +from config.env import ApiConfig, AppConfig +from config.exception import ( + AuthException, + RequestException, + ServiceException, + ServiceWarning, +) +from utils.cache_util import CacheManager +from utils.log_util import logger -def api_request(method: str, url: str, is_headers: bool, params: Optional[dict] = None, data: Optional[dict] = None, - json: Optional[dict] = None, timeout: Optional[int] = None, stream: Optional[bool] = False): - api_url = ApiBaseUrlConfig.BaseUrl + url - method = method.lower().strip() +def api_request( + url: str, + method: ApiMethod, + headers: Optional[Dict] = {}, + params: Optional[Dict] = None, + data: Optional[Dict] = None, + json: Optional[Dict] = None, + files: Optional[Dict] = None, + timeout: Optional[int] = None, + stream: Optional[bool] = False, +): + """ + Api请求方法 + + :param url: 请求url + :param method: 请求方法 + :param headers: 请求头 + :param params: 查询参数 + :param data: 表单参数 + :param json: 请求体 + :param files: 请求文件 + :param timeout: 请求超时时间 + :param stream: 是否为流式请求 + :return: 请求结果 + """ + api_url = ApiConfig.BaseUrl + url + api_method = method.value user_agent = request.headers.get('User-Agent') - authorization = session.get('Authorization') if session.get('Authorization') else '' - remote_addr = request.headers.get("X-Forwarded-For") if AppConfig.app_env == 'prod' else request.remote_addr - if is_headers: - api_headers = {'Authorization': 'Bearer ' + authorization, 'remote_addr': remote_addr, - 'User-Agent': user_agent, 'is_browser': 'no'} - else: - api_headers = {'remote_addr': remote_addr, 'User-Agent': user_agent, 'is_browser': 'no'} - try: - if method == 'get': - response = requests.get(url=api_url, params=params, data=data, json=json, headers=api_headers, - timeout=timeout, stream=stream) - elif method == 'post': - response = requests.post(url=api_url, params=params, data=data, json=json, headers=api_headers, - timeout=timeout, stream=stream) - elif method == 'delete': - response = requests.delete(url=api_url, params=params, data=data, json=json, headers=api_headers, - timeout=timeout, stream=stream) - elif method == 'put': - response = requests.put(url=api_url, params=params, data=data, json=json, headers=api_headers, - timeout=timeout, stream=stream) - elif method == 'patch': - response = requests.patch(url=api_url, params=params, data=data, json=json, headers=api_headers, - timeout=timeout, stream=stream) - else: - raise ValueError(f'Unsupported HTTP method: {method}') + authorization = ( + session.get('Authorization') if session.get('Authorization') else '' + ) + remote_addr = ( + request.headers.get('X-Forwarded-For') + if AppConfig.app_env == 'prod' + else request.remote_addr + ) + merged_headers = {'is_token': True, **headers} + is_token = merged_headers.get('is_token') + api_headers = { + k: v for k, v in merged_headers.items() if isinstance(v, (str, bytes)) + } + api_headers.update( + { + 'remote_addr': remote_addr, + 'User-Agent': user_agent, + 'is_browser': 'no', + } + ) + if is_token: + api_headers.update({'Authorization': 'Bearer ' + authorization}) + if api_method == 'get': + response = requests.get( + url=api_url, + params=params, + data=data, + json=json, + files=files, + headers=api_headers, + timeout=timeout, + stream=stream, + ) + elif api_method == 'post': + response = requests.post( + url=api_url, + params=params, + data=data, + json=json, + files=files, + headers=api_headers, + timeout=timeout, + stream=stream, + ) + elif api_method == 'delete': + response = requests.delete( + url=api_url, + params=params, + data=data, + json=json, + files=files, + headers=api_headers, + timeout=timeout, + stream=stream, + ) + elif api_method == 'put': + response = requests.put( + url=api_url, + params=params, + data=data, + json=json, + files=files, + headers=api_headers, + timeout=timeout, + stream=stream, + ) + elif api_method == 'patch': + response = requests.patch( + url=api_url, + params=params, + data=data, + json=json, + files=files, + headers=api_headers, + timeout=timeout, + stream=stream, + ) - data_list = [params, data, json] + data_list = [params, data, json, files] + status_code = response.status_code + request_user = ( + CacheManager.get('user_info').get('user_name') + if CacheManager.get('user_info') + else None + ) + request_params = ','.join([str(x) for x in data_list if x]) + log_message = LogMessage( + request_user, + remote_addr, + api_method, + url, + request_params, + ) + if status_code == HttpStatusConstant.SUCCESS: if stream: - response_code = response.status_code - response_message = '获取成功' if response_code == 200 else '获取失败' + logger.info(log_message.generate('获取成功')) + return response else: response_code = response.json().get('code') - response_message = response.json().get('message') - session['code'] = response_code - session['message'] = response_message - if response_code == 200: - logger.info("[api]请求人:{}||请求IP:{}||请求方法:{}||请求Api:{}||请求参数:{}||请求结果:{}", - session.get('user_info').get('user_name') if session.get('user_info') else None, - remote_addr, method, url, - ','.join([str(x) for x in data_list if x]), - response_message) - else: - logger.warning("[api]请求人:{}||请求IP:{}||请求方法:{}||请求Api:{}||请求参数:{}||请求结果:{}", - session.get('user_info').get('user_name') if session.get('user_info') else None, - remote_addr, method, url, - ','.join([str(x) for x in data_list if x]), - response_message) + response_message = response.json().get('msg') + response_log = log_message.generate(response_message) + if response_code == HttpStatusConstant.SUCCESS: + logger.info(response_log) + return response.json() + elif response_code == HttpStatusConstant.UNAUTHORIZED: + logger.warning(response_log) + raise AuthException(message=response_message) + elif response_code == HttpStatusConstant.ERROR: + logger.error(response_log) + raise ServiceException(message=response_message) + elif response_code == HttpStatusConstant.WARN: + logger.warning(response_log) + raise ServiceWarning(message=response_message) + else: + logger.error(response_log) + raise RequestException(message=response_message) + else: + logger.error(log_message.generate('请求异常')) + raise RequestException(message='请求异常') + - return response if stream else response.json() - except Exception as e: - logger.error("[api]请求人:{}||请求IP:{}||请求方法:{}||请求Api:{}||请求结果:{}", - session.get('user_info').get('user_name') if session.get('user_info') else None, - remote_addr, method, url, str(e)) - session['code'] = 500 - session['message'] = str(e) +class LogMessage: + def __init__( + self, + request_user: str, + request_ip: str, + request_method: str, + request_url: str, + request_params: str, + ): + self.request_user = request_user + self.request_ip = request_ip + self.request_method = request_method + self.request_url = request_url + self.request_params = request_params - return dict(code=500, data='', message=str(e)) + def generate(self, message: str): + return f'[api]请求人:{self.request_user}||请求IP:{self.request_ip}||请求方法:{self.request_method}||请求Api:{self.request_url}||请求参数:{self.request_params}||请求结果:{message}' diff --git a/dash-fastapi-frontend/utils/router_util.py b/dash-fastapi-frontend/utils/router_util.py new file mode 100644 index 0000000000000000000000000000000000000000..08f2a9e8c77d3ce281f42fc42e2ffd21e48241f0 --- /dev/null +++ b/dash-fastapi-frontend/utils/router_util.py @@ -0,0 +1,198 @@ +import json +from copy import deepcopy +from typing import Dict, List +from config.constant import CommonConstant, MenuConstant + + +class RouterUtil: + """ + 路由工具类 + """ + + @classmethod + def generate_menu_tree(cls, router_list: List, path: str = ''): + """ + 生成菜单树 + + :param router_list: 路由列表 + :param path: 路由path + :return: 菜单树 + """ + menu_list = [] + for router in router_list: + copy_router = deepcopy(router) + if ( + copy_router.get('path') in ['', '/'] + and len(copy_router.get('children') or []) == 1 + ): + copy_router = copy_router['children'][0] + copy_router['path'] = ( + copy_router.get('path') + if copy_router.get('path') + and ( + copy_router.get('path').startswith('/') + or cls.is_http(copy_router.get('path')) + ) + else '/' + copy_router.get('path') + ) + meta = copy_router.get('meta') if copy_router.get('meta') else {} + copy_router['props'] = { + **meta, + 'key': copy_router.get('name') + path + copy_router.get('path'), + 'href': path + copy_router.get('path'), + } + if copy_router.get('component') in [ + MenuConstant.LAYOUT, + MenuConstant.PARENT_VIEW, + ]: + copy_router['component'] = MenuConstant.SUB_MENU + if cls.is_http(copy_router.get('path')): + copy_router['props']['target'] = '_blank' + elif copy_router.get('component') == MenuConstant.INNER_LINK: + if copy_router.get('path') == '/': + copy_router = copy_router['children'] + copy_router['props']['href'] = '/' + copy_router.get('path') + cls.__genrate_item_menu(copy_router, 'innerlink') + else: + if copy_router.get('children'): + copy_router['component'] = MenuConstant.SUB_MENU + else: + cls.__genrate_item_menu(copy_router, 'innerlink') + else: + cls.__genrate_item_menu( + copy_router, copy_router.get('component') + ) + + if copy_router.get('children'): + copy_router['children'] = cls.generate_menu_tree( + copy_router.get('children'), + copy_router.get('props').get('href'), + ) + else: + query = ( + '?' + + '&'.join( + [ + '{}={}'.format(k, v) + for k, v in json.loads( + copy_router.get('query') + ).items() + ] + ) + if copy_router.get('query') + else '' + ) + copy_router['props']['href'] = ( + copy_router.get('props').get('href') + query + ) + copy_router.pop('name', None) + copy_router.pop('meta', None) + menu_list.append(copy_router) + + return menu_list + + @classmethod + def get_visible_routers(cls, router_list: List): + """ + 获取可见路由 + + :param router_list: 路由列表 + :return: 可见路由列表 + """ + new_router_list = [] + for router in router_list: + copy_router = deepcopy(router) + if copy_router.get('hidden'): + continue + if copy_router.get('children'): + copy_router['children'] = cls.get_visible_routers( + copy_router.get('children') + ) + new_router_list.append(copy_router) + + return new_router_list + + @classmethod + def generate_search_panel_data( + cls, menu_list: List, section_path: List = [] + ): + """ + 生成搜索面板数据 + + :param menu_list: 菜单列表 + :param section_path: 分组路径 + :return: 搜索面板数据 + """ + search_panel_data = [] + for item in menu_list: + if item.get('children'): + section_path.append(item.get('props').get('title')) + search_panel_data.extend( + cls.generate_search_panel_data( + item.get('children'), section_path + ) + ) + section_path.pop() + else: + href = item.get('props').get('href') + target = ( + '_blank' + if cls.is_http(item.get('props').get('href')) + else '_self' + ) + item_dict = dict( + id=item.get('props').get('key'), + title=item.get('props').get('title'), + section='/'.join(section_path), + handler=f'() => window.open("{href}", "{target}")', + ) + search_panel_data.append(item_dict) + + return search_panel_data + + @classmethod + def generate_validate_pathname_list(cls, menu_list: List): + """ + 生成合法路由列表 + + :param menu_list: 菜单列表 + :return: 合法路由列表 + """ + validate_pathname_list = [] + for item in menu_list: + if item.get('children'): + validate_pathname_list.extend( + cls.generate_validate_pathname_list(item.get('children')) + ) + else: + href = item.get('props').get('href') + validate_pathname_list.append(href) + + return validate_pathname_list + + @classmethod + def __genrate_item_menu(cls, router: Dict, modules: str): + """ + 生成Item类型菜单 + + :param router: 路由信息 + :param modules: 组件路径 + + :return: Item类型菜单 + """ + router['props']['modules'] = modules + router['component'] = MenuConstant.ITEM + + return router + + @classmethod + def is_http(cls, link: str): + """ + 判断是否为http(s)://开头 + + :param link: 链接 + :return: 是否为http(s)://开头 + """ + return link.startswith(CommonConstant.HTTP) or link.startswith( + CommonConstant.HTTPS + ) diff --git a/dash-fastapi-frontend/utils/string_util.py b/dash-fastapi-frontend/utils/string_util.py new file mode 100644 index 0000000000000000000000000000000000000000..0fd7f9c88ae43cc780513eb310e1689befd109d3 --- /dev/null +++ b/dash-fastapi-frontend/utils/string_util.py @@ -0,0 +1,25 @@ +class StringUtil: + """ + 字符串工具类 + """ + + @classmethod + def insert_before_substring( + cls, original_str: str, start_substr: str, new_str: str + ): + """ + 在字符串中指定字符串位置插入新字符串 + + :param original_str: 原始字符串 + :param start_substr: 指定的字符串 + :param new_str: 插入的新字符串 + :return: 处理完成的新字符串 + """ + start_index = original_str.find(start_substr) + if start_index != -1: + before_start = original_str[:start_index] + after_start = original_str[start_index:] + new_str_with_insertion = before_start + new_str + after_start + return new_str_with_insertion + else: + return original_str diff --git a/dash-fastapi-frontend/utils/time_format_util.py b/dash-fastapi-frontend/utils/time_format_util.py new file mode 100644 index 0000000000000000000000000000000000000000..b57c58246d9ec6db0eda12f7a72bc7afd39461a1 --- /dev/null +++ b/dash-fastapi-frontend/utils/time_format_util.py @@ -0,0 +1,82 @@ +from copy import deepcopy +from datetime import datetime +from dateutil.parser import parse +from typing import Dict, List, Union + + +class TimeFormatUtil: + """ + 时间格式化工具类 + """ + + @classmethod + def format_time( + cls, time_info: Union[str, datetime], format: str = '%Y-%m-%d %H:%M:%S' + ): + """ + 格式化时间字符串或datetime对象为指定格式 + + :param time_info: 时间字符串或datetime对象 + :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :return: 格式化后的时间字符串 + """ + if isinstance(time_info, datetime): + format_date = time_info.strftime(format) + else: + try: + date = parse(time_info) + format_date = date.strftime(format) + except Exception: + format_date = time_info + + return format_date + + @classmethod + def format_time_dict( + cls, time_dict: Dict, format: str = '%Y-%m-%d %H:%M:%S' + ): + """ + 格式化时间字典 + + :param time_dict: 时间字典 + :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :return: 格式化后的时间字典 + """ + copy_time_dict = deepcopy(time_dict) + for k, v in copy_time_dict.items(): + if isinstance(v, (str, datetime)): + copy_time_dict[k] = cls.format_time(v, format) + elif isinstance(v, dict): + copy_time_dict[k] = cls.format_time_dict(v, format) + elif isinstance(v, list): + copy_time_dict[k] = cls.format_time_list(v, format) + else: + copy_time_dict[k] = v + + return copy_time_dict + + @classmethod + def format_time_list( + cls, time_list: List, format: str = '%Y-%m-%d %H:%M:%S' + ): + """ + 格式化时间列表 + + :param time_list: 时间列表 + :param format: 格式化格式,默认为'%Y-%m-%d %H:%M:%S' + :return: 格式化后的时间列表 + """ + format_time_list = [] + for item in time_list: + if isinstance(item, (str, datetime)): + format_item = cls.format_time(item, format) + elif isinstance(item, dict): + format_item = cls.format_time_dict(item, format) + elif isinstance(item, list): + format_item = cls.format_time_list(item, format) + else: + format_item = item + + format_time_list.append(format_item) + + return format_time_list diff --git a/dash-fastapi-frontend/utils/tree_tool.py b/dash-fastapi-frontend/utils/tree_tool.py deleted file mode 100644 index 7ebd634e1064770c40adc4615761d46c2d19887f..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/utils/tree_tool.py +++ /dev/null @@ -1,249 +0,0 @@ -def find_node_values(data, key): - """ - 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表。 - :param data: 待查找的树形list - :param key: 目标键 - :return: 包含目标键的字典中目标键对应的值组成的列表 - """ - result = [] - for item in data: - if isinstance(item, dict): - if key in item: - result.append(item[key]) - # 递归查找子节点 - result.extend(find_node_values(item.values(), key)) - elif isinstance(item, list): - # 递归查找子节点 - result.extend(find_node_values(item, key)) - return result - - -def find_key_by_href(data, href): - """ - 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表。 - :param data: 待查找的树形list - :param href: 目标pathname - :return: 目标值对应的key - """ - for item in data: - if 'children' in item: - result = find_key_by_href(item['children'], href) - if result is not None: - return result - elif 'href' in item['props'] and item['props']['href'] == href: - return item['props']['key'] - return None - - -def find_title_by_key(data, key): - """ - 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表。 - :param data: 待查找的树形list - :param key: 目标key - :return: 目标值对应的title - """ - for item in data: - if 'children' in item: - result = find_title_by_key(item['children'], key) - if result is not None: - return result - elif 'key' in item['props'] and item['props']['key'] == key: - return item['props']['title'] - return None - - -def find_href_by_key(data, key): - """ - 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表。 - :param data: 待查找的树形list - :param key: 目标key - :return: 目标值对应的href - """ - for item in data: - if 'children' in item: - result = find_href_by_key(item['children'], key) - if result is not None: - return result - elif 'key' in item['props'] and item['props']['key'] == key: - return item['props'].get('href') - return None - - -def find_modules_by_key(data, key): - """ - 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表。 - :param data: 待查找的树形list - :param key: 目标key - :return: 目标值对应的module - """ - for item in data: - if 'children' in item: - result = find_modules_by_key(item['children'], key) - if result is not None: - return result - elif 'key' in item['props'] and item['props']['key'] == key: - return item['props'].get('modules') - return None - - -def find_parents(tree, target_key): - """ - 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表。 - :param tree: 待查找的树形list - :param target_key: 目标target_key - :return: 目标值对应的所有根节点的title - """ - result = [] - - def search_parents(node, key): - if 'children' in node: - for child in node['children']: - temp_result = search_parents(child, key) - if len(temp_result) > 0: - result.append({'title': node['props']['title']}) - result.extend(temp_result) - return result - - if 'key' in node['props'] and node['props']['key'] == key: - result.append({'title': node['props']['title']}) - return result - - return [] - - for node in tree: - result = search_parents(node, target_key) - if len(result) > 0: - break - - return result[::-1] - - -def deal_user_menu_info(pid: int, permission_list: list): - """ - 工具方法:根据菜单信息生成树形嵌套数据 - :param pid: 菜单id - :param permission_list: 菜单列表信息 - :return: 菜单树形嵌套数据 - """ - menu_list = [] - for permission in permission_list: - if permission['parent_id'] == pid: - children = deal_user_menu_info(permission['menu_id'], permission_list) - antd_menu_list_data = {} - if children and permission['menu_type'] == 'M': - antd_menu_list_data['component'] = 'SubMenu' - antd_menu_list_data['props'] = { - 'key': str(permission['menu_id']), - 'title': permission['menu_name'], - 'icon': permission['icon'], - 'modules': permission['component'] - } - antd_menu_list_data['children'] = children - elif permission['menu_type'] == 'C': - antd_menu_list_data['component'] = 'Item' - antd_menu_list_data['props'] = { - 'key': str(permission['menu_id']), - 'title': permission['menu_name'], - 'icon': permission['icon'], - 'href': permission['path'], - 'modules': permission['component'] - } - antd_menu_list_data['button'] = children - elif permission['menu_type'] == 'F': - antd_menu_list_data['component'] = 'Button' - antd_menu_list_data['props'] = { - 'key': str(permission['menu_id']), - 'title': permission['menu_name'], - 'icon': permission['icon'] - } - elif permission['is_frame'] == 0: - antd_menu_list_data['component'] = 'Item' - antd_menu_list_data['props'] = { - 'key': str(permission['menu_id']), - 'title': permission['menu_name'], - 'icon': permission['icon'], - 'href': permission['path'], - 'target': '_blank', - 'modules': 'link' - } - else: - antd_menu_list_data['component'] = 'Item' - antd_menu_list_data['props'] = { - 'key': str(permission['menu_id']), - 'title': permission['menu_name'], - 'icon': permission['icon'], - 'href': permission['path'], - 'modules': permission['component'] - } - menu_list.append(antd_menu_list_data) - - return menu_list - - -def get_dept_tree(pid: int, permission_list: list): - """ - 工具方法:根据部门信息生成树形嵌套数据 - :param pid: 部门id - :param permission_list: 部门列表信息 - :return: 部门树形嵌套数据 - """ - dept_list = [] - for permission in permission_list: - if permission['parent_id'] == pid: - children = get_dept_tree(permission['dept_id'], permission_list) - dept_list_data = {} - if children: - dept_list_data['children'] = children - dept_list_data['key'] = str(permission['dept_id']) - dept_list_data['dept_id'] = permission['dept_id'] - dept_list_data['dept_name'] = permission['dept_name'] - dept_list_data['order_num'] = permission['order_num'] - dept_list_data['status'] = permission['status'] - dept_list_data['create_time'] = permission['create_time'] - dept_list_data['operation'] = permission['operation'] - dept_list.append(dept_list_data) - - return dept_list - - -def list_to_tree(permission_list: list, sub_id_str: str, parent_id_str: str) -> list: - """ - 工具方法:根据列表信息生成树形嵌套数据 - :param permission_list: 列表信息 - :param sub_id_str: 子id字符串 - :param parent_id_str: 父id字符串 - :return: 树形嵌套数据 - """ - # 转成id为key的字典 - mapping: dict = dict(zip([i[sub_id_str] for i in permission_list], permission_list)) - - # 树容器 - container: list = [] - - for d in permission_list: - # 如果找不到父级项,则是根节点 - parent: dict = mapping.get(d[parent_id_str]) - if parent is None: - container.append(d) - else: - children: list = parent.get('children') - if not children: - children = [] - children.append(d) - parent.update({'children': children}) - - return container - - -def get_search_panel_data(menu_list: list): - search_data = [] - for item in menu_list: - if item.get('menu_type') == 'C' or item.get('is_frame') == 0: - item_dict = dict( - id=str(item.get('menu_id')), - title=item.get('menu_name'), - handler='() => window.open("%s", "_self")' % item.get('path') - ) - search_data.append(item_dict) - - return search_data diff --git a/dash-fastapi-frontend/utils/tree_util.py b/dash-fastapi-frontend/utils/tree_util.py new file mode 100644 index 0000000000000000000000000000000000000000..72c840e5a40569e634c63621eddc05a3773efd16 --- /dev/null +++ b/dash-fastapi-frontend/utils/tree_util.py @@ -0,0 +1,344 @@ +class TreeUtil: + """ + 树形数据处理工具类 + """ + + @classmethod + def find_node_values(cls, data, key): + """ + 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表 + + :param data: 待查找的树形list + :param key: 目标键 + :return: 包含目标键的字典中目标键对应的值组成的列表 + """ + result = [] + for item in data: + if isinstance(item, dict): + if key in item: + result.append(item[key]) + # 递归查找子节点 + result.extend(cls.find_node_values(item.values(), key)) + elif isinstance(item, list): + # 递归查找子节点 + result.extend(cls.find_node_values(item, key)) + return result + + @classmethod + def find_key_by_href(cls, data, href): + """ + 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表 + + :param data: 待查找的树形list + :param href: 目标pathname + :return: 目标值对应的key + """ + for item in data: + if 'children' in item: + result = cls.find_key_by_href(item['children'], href) + if result is not None: + return result + elif 'href' in item['props'] and item['props']['href'] == href: + return item['props']['key'] + return None + + @classmethod + def find_title_by_key(cls, data, key): + """ + 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表 + + :param data: 待查找的树形list + :param key: 目标key + :return: 目标值对应的title + """ + for item in data: + if 'children' in item: + result = cls.find_title_by_key(item['children'], key) + if result is not None: + return result + elif 'key' in item['props'] and item['props']['key'] == key: + return item['props']['title'] + return None + + @classmethod + def find_href_by_key(cls, data, key): + """ + 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表 + + :param data: 待查找的树形list + :param key: 目标key + :return: 目标值对应的href + """ + for item in data: + if 'children' in item: + result = cls.find_href_by_key(item['children'], key) + if result is not None: + return result + elif 'key' in item['props'] and item['props']['key'] == key: + return item['props'].get('href') + return None + + @classmethod + def find_modules_by_key(cls, data, key): + """ + 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表 + + :param data: 待查找的树形list + :param key: 目标key + :return: 目标值对应的module + """ + for item in data: + if 'children' in item: + result = cls.find_modules_by_key(item['children'], key) + if result is not None: + return result + elif 'key' in item['props'] and item['props']['key'] == key: + return item['props'].get('modules') + return None + + @classmethod + def find_parents(cls, tree, target_key): + """ + 递归查找所有包含目标键的字典,并返回该键对应的值组成的列表 + + :param tree: 待查找的树形list + :param target_key: 目标target_key + :return: 目标值对应的所有根节点的title + """ + result = [] + + def search_parents(node, key): + if 'children' in node: + for child in node['children']: + temp_result = search_parents(child, key) + if len(temp_result) > 0: + result.append({'title': node['props']['title']}) + result.extend(temp_result) + return result + + if 'key' in node['props'] and node['props']['key'] == key: + result.append({'title': node['props']['title']}) + return result + + return [] + + for node in tree: + result = search_parents(node, target_key) + if len(result) > 0: + break + + return result[::-1] + + @classmethod + def deal_user_menu_info(cls, pid: int, permission_list: list): + """ + 工具方法:根据菜单信息生成树形嵌套数据 + + :param pid: 菜单id + :param permission_list: 菜单列表信息 + :return: 菜单树形嵌套数据 + """ + menu_list = [] + for permission in permission_list: + if permission['parent_id'] == pid: + children = cls.deal_user_menu_info( + permission['menu_id'], permission_list + ) + antd_menu_list_data = {} + if children and permission['menu_type'] == 'M': + antd_menu_list_data['component'] = 'SubMenu' + antd_menu_list_data['props'] = { + 'key': str(permission['menu_id']), + 'title': permission['menu_name'], + 'icon': permission['icon'], + 'modules': permission['component'], + } + antd_menu_list_data['children'] = children + elif permission['menu_type'] == 'C': + antd_menu_list_data['component'] = 'Item' + antd_menu_list_data['props'] = { + 'key': str(permission['menu_id']), + 'title': permission['menu_name'], + 'icon': permission['icon'], + 'href': permission['path'], + 'modules': permission['component'], + } + antd_menu_list_data['button'] = children + elif permission['menu_type'] == 'F': + antd_menu_list_data['component'] = 'Button' + antd_menu_list_data['props'] = { + 'key': str(permission['menu_id']), + 'title': permission['menu_name'], + 'icon': permission['icon'], + } + elif permission['is_frame'] == 0: + antd_menu_list_data['component'] = 'Item' + antd_menu_list_data['props'] = { + 'key': str(permission['menu_id']), + 'title': permission['menu_name'], + 'icon': permission['icon'], + 'href': permission['path'], + 'target': '_blank', + 'modules': 'link', + } + else: + antd_menu_list_data['component'] = 'Item' + antd_menu_list_data['props'] = { + 'key': str(permission['menu_id']), + 'title': permission['menu_name'], + 'icon': permission['icon'], + 'href': permission['path'], + 'modules': permission['component'], + } + menu_list.append(antd_menu_list_data) + + return menu_list + + @classmethod + def get_dept_tree(cls, pid: int, permission_list: list): + """ + 工具方法:根据部门信息生成树形嵌套数据 + + :param pid: 部门id + :param permission_list: 部门列表信息 + :return: 部门树形嵌套数据 + """ + dept_list = [] + for permission in permission_list: + if permission['parent_id'] == pid: + children = cls.get_dept_tree( + permission['dept_id'], permission_list + ) + dept_list_data = {} + if children: + dept_list_data['children'] = children + dept_list_data['key'] = str(permission['dept_id']) + dept_list_data['dept_id'] = permission['dept_id'] + dept_list_data['dept_name'] = permission['dept_name'] + dept_list_data['order_num'] = permission['order_num'] + dept_list_data['status'] = permission['status'] + dept_list_data['create_time'] = permission['create_time'] + dept_list_data['operation'] = permission['operation'] + dept_list.append(dept_list_data) + + return dept_list + + @classmethod + def list_to_tree( + cls, permission_list: list, sub_id_str: str, parent_id_str: str + ) -> list: + """ + 工具方法:根据列表信息生成树形嵌套数据 + + :param permission_list: 列表信息 + :param sub_id_str: 子id字符串 + :param parent_id_str: 父id字符串 + :return: 树形嵌套数据 + """ + # 转成id为key的字典 + mapping: dict = dict( + zip([i[sub_id_str] for i in permission_list], permission_list) + ) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d[parent_id_str]) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container + + @classmethod + def list_to_tree_select( + cls, + permission_list: list, + title_str: str, + key_str: str, + value_str: str, + parent_id_str: str, + ) -> list: + """ + 工具方法:根据列表信息生成树选择器嵌套数据 + + :param permission_list: 列表信息 + :param title_str: title字符串 + :param key_str: key字符串 + :param value_str: value字符串 + :param parent_id_str: parent_id字符串 + :return: 树形嵌套数据 + """ + permission_list = [ + dict( + title=item[title_str], + key=str(item[key_str]), + value=str(item[value_str]), + parent_id=str(item[parent_id_str]), + ) + for item in permission_list + ] + # 转成id为key的字典 + mapping: dict = dict( + zip([i['key'] for i in permission_list], permission_list) + ) + + # 树容器 + container: list = [] + + for d in permission_list: + # 如果找不到父级项,则是根节点 + parent: dict = mapping.get(d['parent_id']) + if parent is None: + container.append(d) + else: + children: list = parent.get('children') + if not children: + children = [] + children.append(d) + parent.update({'children': children}) + + return container + + @classmethod + def get_search_panel_data(cls, menu_list: list): + search_data = [] + for item in menu_list: + if item.get('menu_type') == 'C' or item.get('is_frame') == 0: + item_dict = dict( + id=str(item.get('menu_id')), + title=item.get('menu_name'), + handler='() => window.open("%s", "_self")' + % item.get('path'), + ) + search_data.append(item_dict) + + return search_data + + @classmethod + def find_tree_all_keys(cls, tree, keys_list): + """ + 递归函数,用于查找树形数据结构中所有键名为'name'的值,并将它们添加到列表中。 + + :param tree: 树形数据结构,假设为嵌套字典或列表 + :param keys_list: 用于存储找到的'key'值的列表 + :return: 包含所有找到的'key'值的列表 + """ + if isinstance(tree, dict): + for key, value in tree.items(): + if key == 'key': + keys_list.append(value) + elif isinstance(value, (dict, list)): + cls.find_tree_all_keys(value, keys_list) + elif isinstance(tree, list): + for item in tree: + cls.find_tree_all_keys(item, keys_list) + + return keys_list diff --git a/dash-fastapi-frontend/views/__init__.py b/dash-fastapi-frontend/views/__init__.py index bc382401e0e24f3e75582fe162ccc540b34e4d86..277167ddf9193d19a7dfff23643fa0af85ff0d1d 100644 --- a/dash-fastapi-frontend/views/__init__.py +++ b/dash-fastapi-frontend/views/__init__.py @@ -1,10 +1,12 @@ from . import ( - layout, - dashboard, - system, - monitor, - tool, - login, - page_404, - forget + dashboard, # noqa: F401 + forget, # noqa: F401 + innerlink, # noqa: F401 + layout, # noqa: F401 + login, # noqa: F401 + monitor, # noqa: F401 + page_404, # noqa: F401 + register, # noqa: F401 + system, # noqa: F401 + tool, # noqa: F401 ) diff --git a/dash-fastapi-frontend/views/dashboard/__init__.py b/dash-fastapi-frontend/views/dashboard/__init__.py index da12fc7b497318324bfd0da23de8a85f84fb8994..938daaa493ccde76a98cd389a89b838dc089885c 100644 --- a/dash-fastapi-frontend/views/dashboard/__init__.py +++ b/dash-fastapi-frontend/views/dashboard/__init__.py @@ -1,7 +1,7 @@ from .components import page_top, page_bottom -def render_dashboard(): +def render(): return [ page_top.render_page_top(), page_bottom.render_page_bottom() diff --git a/dash-fastapi-frontend/views/dashboard/components/page_bottom.py b/dash-fastapi-frontend/views/dashboard/components/page_bottom.py index c5a94c48a6b6a51f6b5fbad8b224032beff5c944..b9c8a6589f0d0e29e6c5ffcf2bd55fb93445a280 100644 --- a/dash-fastapi-frontend/views/dashboard/components/page_bottom.py +++ b/dash-fastapi-frontend/views/dashboard/components/page_bottom.py @@ -1,7 +1,7 @@ -from dash import html +import feffery_antd_charts as fact import feffery_antd_components as fac import feffery_utils_components as fuc -import feffery_antd_charts as fact +from dash import html def render_page_bottom(): @@ -13,7 +13,7 @@ def render_page_bottom(): 'koubei': 8, 'output': 4, 'contribute': 5, - 'hot': 7 + 'hot': 7, }, { 'name': '团队', @@ -21,7 +21,7 @@ def render_page_bottom(): 'koubei': 9, 'output': 6, 'contribute': 3, - 'hot': 1 + 'hot': 1, }, { 'name': '部门', @@ -29,8 +29,8 @@ def render_page_bottom(): 'koubei': 1, 'output': 6, 'contribute': 5, - 'hot': 7 - } + 'hot': 7, + }, ] radar_data = [] @@ -39,167 +39,139 @@ def render_page_bottom(): 'koubei': '口碑', 'output': '产量', 'contribute': '贡献', - 'hot': '热度' + 'hot': '热度', } for item in radar_origin_data: for key, value in item.items(): if key != 'name': - radar_data.append({ - 'name': item['name'], - 'label': radar_title_map[key], - 'value': value - }) + radar_data.append( + { + 'name': item['name'], + 'label': radar_title_map[key], + 'value': value, + } + ) project_list = [ { - "id": "xxx1", - "title": "Alipay", - "logo": "https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png", - "description": "那是一种内在的东西,他们到达不了,也无法触及的", - "updatedAt": "2023-09-15T01:08:36.135Z", - "member": "科学搬砖组", - "href": "", - "memberLink": "" + 'id': 'xxx1', + 'title': 'Alipay', + 'logo': 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', + 'description': '那是一种内在的东西,他们到达不了,也无法触及的', + 'updatedAt': '2023-09-15T01:08:36.135Z', + 'member': '科学搬砖组', + 'href': '', + 'memberLink': '', }, { - "id": "xxx2", - "title": "Angular", - "logo": "https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png", - "description": "希望是一个好东西,也许是最好的,好东西是不会消亡的", - "updatedAt": "2017-07-24T00:00:00.000Z", - "member": "全组都是吴彦祖", - "href": "", - "memberLink": "" + 'id': 'xxx2', + 'title': 'Angular', + 'logo': 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', + 'description': '希望是一个好东西,也许是最好的,好东西是不会消亡的', + 'updatedAt': '2017-07-24T00:00:00.000Z', + 'member': '全组都是吴彦祖', + 'href': '', + 'memberLink': '', }, { - "id": "xxx3", - "title": "Ant Design", - "logo": "https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png", - "description": "城镇中有那么多的酒馆,她却偏偏走进了我的酒馆", - "updatedAt": "2023-09-15T01:08:36.135Z", - "member": "中二少女团", - "href": "", - "memberLink": "" + 'id': 'xxx3', + 'title': 'Ant Design', + 'logo': 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', + 'description': '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', + 'updatedAt': '2023-09-15T01:08:36.135Z', + 'member': '中二少女团', + 'href': '', + 'memberLink': '', }, { - "id": "xxx4", - "title": "Ant Design Pro", - "logo": "https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png", - "description": "那时候我只会想自己想要什么,从不想自己拥有什么", - "updatedAt": "2017-07-23T00:00:00.000Z", - "member": "程序员日常", - "href": "", - "memberLink": "" + 'id': 'xxx4', + 'title': 'Ant Design Pro', + 'logo': 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', + 'description': '那时候我只会想自己想要什么,从不想自己拥有什么', + 'updatedAt': '2017-07-23T00:00:00.000Z', + 'member': '程序员日常', + 'href': '', + 'memberLink': '', }, { - "id": "xxx5", - "title": "Bootstrap", - "logo": "https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png", - "description": "凛冬将至", - "updatedAt": "2017-07-23T00:00:00.000Z", - "member": "高逼格设计天团", - "href": "", - "memberLink": "" + 'id': 'xxx5', + 'title': 'Bootstrap', + 'logo': 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', + 'description': '凛冬将至', + 'updatedAt': '2017-07-23T00:00:00.000Z', + 'member': '高逼格设计天团', + 'href': '', + 'memberLink': '', }, { - "id": "xxx6", - "title": "React", - "logo": "https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png", - "description": "生命就像一盒巧克力,结果往往出人意料", - "updatedAt": "2017-07-23T00:00:00.000Z", - "member": "骗你来学计算机", - "href": "", - "memberLink": "" - } + 'id': 'xxx6', + 'title': 'React', + 'logo': 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', + 'description': '生命就像一盒巧克力,结果往往出人意料', + 'updatedAt': '2017-07-23T00:00:00.000Z', + 'member': '骗你来学计算机', + 'href': '', + 'memberLink': '', + }, ] activity_list = [ { - "id": "trend-1", - "updatedAt": "2023-09-15 01:08:36", - "user": { - "name": "曲丽丽", - "avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png" - }, - "group": { - "name": "高逼格设计天团", - "link": "http://github.com/" - }, - "project": { - "name": "六月迭代", - "link": "http://github.com/" + 'id': 'trend-1', + 'updatedAt': '2023-09-15 01:08:36', + 'user': { + 'name': '曲丽丽', + 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', }, - "template": "新建项目" + 'group': {'name': '高逼格设计天团', 'link': 'http://github.com/'}, + 'project': {'name': '六月迭代', 'link': 'http://github.com/'}, + 'template': '新建项目', }, { - "id": "trend-2", - "updatedAt": "2023-09-15 01:08:36", - "user": { - "name": "付小小", - "avatar": "https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png" + 'id': 'trend-2', + 'updatedAt': '2023-09-15 01:08:36', + 'user': { + 'name': '付小小', + 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png', }, - "group": { - "name": "高逼格设计天团", - "link": "http://github.com/" - }, - "project": { - "name": "六月迭代", - "link": "http://github.com/" - }, - "template": "新建项目" + 'group': {'name': '高逼格设计天团', 'link': 'http://github.com/'}, + 'project': {'name': '六月迭代', 'link': 'http://github.com/'}, + 'template': '新建项目', }, { - "id": "trend-3", - "updatedAt": "2023-09-15 01:08:36", - "user": { - "name": "林东东", - "avatar": "https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png" - }, - "group": { - "name": "中二少女团", - "link": "http://github.com/" - }, - "project": { - "name": "六月迭代", - "link": "http://github.com/" + 'id': 'trend-3', + 'updatedAt': '2023-09-15 01:08:36', + 'user': { + 'name': '林东东', + 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png', }, - "template": "新建项目" + 'group': {'name': '中二少女团', 'link': 'http://github.com/'}, + 'project': {'name': '六月迭代', 'link': 'http://github.com/'}, + 'template': '新建项目', }, { - "id": "trend-4", - "updatedAt": "2023-09-15 01:08:36", - "user": { - "name": "周星星", - "avatar": "https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png" + 'id': 'trend-4', + 'updatedAt': '2023-09-15 01:08:36', + 'user': { + 'name': '周星星', + 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png', }, - "group": { - "name": "白鹭酱油开发组", - "link": "http://github.com/" - }, - "project": { - "name": "5 月日常迭代", - "link": "http://github.com/" - }, - "template": "发布了" + 'group': {'name': '白鹭酱油开发组', 'link': 'http://github.com/'}, + 'project': {'name': '5 月日常迭代', 'link': 'http://github.com/'}, + 'template': '发布了', }, { - "id": "trend-5", - "updatedAt": "2023-09-15 01:08:36", - "user": { - "name": "乐哥", - "avatar": "https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png" - }, - "group": { - "name": "程序员日常", - "link": "http://github.com/" - }, - "project": { - "name": "品牌迭代", - "link": "http://github.com/" + 'id': 'trend-5', + 'updatedAt': '2023-09-15 01:08:36', + 'user': { + 'name': '乐哥', + 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png', }, - "template": "新建项目" - } + 'group': {'name': '程序员日常', 'link': 'http://github.com/'}, + 'project': {'name': '品牌迭代', 'link': 'http://github.com/'}, + 'template': '新建项目', + }, ] return html.Div( @@ -217,28 +189,26 @@ def render_page_bottom(): fac.AntdAvatar( mode='image', src=item.get('logo'), - size='small' + size='small', ), - html.A( - item.get('title') - ) + html.A(item.get('title')), ], - className='card-title' + className='card-title', ), html.Div( item.get('description'), - className='card-description' + className='card-description', ), html.Div( [ html.A(item.get('member')), html.Span( '9小时前', - className='datetime' - ) + className='datetime', + ), ], - className='project-item' - ) + className='project-item', + ), ] ) for item in project_list @@ -246,16 +216,12 @@ def render_page_bottom(): className='project-list', title='进行中的项目', bordered=False, - extraLink={ - 'content': '全部项目' - }, - bodyStyle={ - 'padding': 0 - }, + extraLink={'content': '全部项目'}, + bodyStyle={'padding': 0}, style={ 'marginBottom': '24px', - 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px' - } + 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px', + }, ), fac.AntdCard( fac.AntdSpace( @@ -267,44 +233,74 @@ def render_page_bottom(): html.Div( fac.AntdAvatar( mode='image', - src=item.get('user').get('avatar'), + src=item.get( + 'user' + ).get('avatar'), size='small', ), style={ 'flex': '0 1', - 'marginRight': '16px' - } + 'marginRight': '16px', + }, ), html.Div( [ html.Div( [ - html.Span(f"{item.get('user').get('name')} 在 "), - html.A(item.get('group').get('name'), href=item.get('group').get('link')), - html.Span(f" {item.get('template')} "), - html.A(item.get('project').get('name'), href=item.get('project').get('link')) + html.Span( + f"{item.get('user').get('name')} 在 " + ), + html.A( + item.get( + 'group' + ).get( + 'name' + ), + href=item.get( + 'group' + ).get( + 'link' + ), + ), + html.Span( + f" {item.get('template')} " + ), + html.A( + item.get( + 'project' + ).get( + 'name' + ), + href=item.get( + 'project' + ).get( + 'link' + ), + ), ], - key=item.get('id') + key=item.get( + 'id' + ), ), html.Div( - item.get('updatedAt'), + item.get( + 'updatedAt' + ), style={ 'color': 'rgba(0,0,0,.45)', 'fontSize': '14px', - 'lineHeight': '22px' - } + 'lineHeight': '22px', + }, ), ], style={ 'flex': '1 1 auto' - } + }, ), ], - style={ - 'display': 'flex' - } + style={'display': 'flex'}, ), - fac.AntdDivider() + fac.AntdDivider(), ] ) for item in activity_list @@ -313,22 +309,22 @@ def render_page_bottom(): style={ 'width': '100%', 'maxHeight': '500px', - 'overflowY': 'auto' - } + 'overflowY': 'auto', + }, ), title='动态', bordered=False, style={ 'marginBottom': '24px', - 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px' - } + 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px', + }, ), ], xl=16, lg=24, md=24, sm=24, - xs=24 + xs=24, ), fac.AntdCol( [ @@ -342,22 +338,20 @@ def render_page_bottom(): html.A('操作五'), fac.AntdButton( '添加', - type="primary", + type='primary', size='small', icon=fac.AntdIcon(icon='antd-plus'), - style={ - 'marginLeft': '20px' - } - ) + style={'marginLeft': '20px'}, + ), ], - className='item-group' + className='item-group', ), title='快速开始 / 便捷导航', bordered=False, style={ 'marginBottom': '24px', - 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px' - } + 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px', + }, ), fac.AntdCard( html.Div( @@ -368,25 +362,21 @@ def render_page_bottom(): yField='value', seriesField='name', point={}, - legend={ - 'position': 'bottom' - }, + legend={'position': 'bottom'}, ), style={ 'minHeight': '400px', 'margin': '0 auto', - 'paddingTop': '30px' - } + 'paddingTop': '30px', + }, ), title='XX 指数', bordered=False, - bodyStyle={ - 'padding': 0 - }, + bodyStyle={'padding': 0}, style={ 'marginBottom': '24px', - 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px' - } + 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px', + }, ), fac.AntdCard( html.Div( @@ -397,28 +387,30 @@ def render_page_bottom(): [ fac.AntdAvatar( mode='image', - src=item.get('logo'), - size='small' + src=item.get( + 'logo' + ), + size='small', ), html.Span( item.get('member'), - className='member' - ) + className='member', + ), ] ), - span=12 + span=12, ) for item in project_list ] ), - className='members' + className='members', ), title='团队', bordered=False, style={ 'marginBottom': '24px', - 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px' - } + 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px', + }, ), ], xl=8, @@ -426,15 +418,13 @@ def render_page_bottom(): md=24, sm=24, xs=24, - style={ - 'padding': '0 12px' - } + style={'padding': '0 12px'}, ), ], - gutter=24 + gutter=24, ), fuc.FefferyStyle( - rawStyle=''' + rawStyle=""" .project-list .card-title { font-size: 0; } @@ -527,7 +517,7 @@ def render_page_bottom(): .members a .member:hover span { color: #1890ff; } - ''' - ) + """ + ), ] ) diff --git a/dash-fastapi-frontend/views/dashboard/components/page_top.py b/dash-fastapi-frontend/views/dashboard/components/page_top.py index e4073482106e6f561aaaf7665801e8c2f61d38a6..6adc3f88aeaefe84d5f08add03e5bae44a1191db 100644 --- a/dash-fastapi-frontend/views/dashboard/components/page_top.py +++ b/dash-fastapi-frontend/views/dashboard/components/page_top.py @@ -1,63 +1,59 @@ -from dash import html import feffery_antd_components as fac import feffery_utils_components as fuc - -from flask import session -from config.global_config import ApiBaseUrlConfig +from dash import html +from config.env import ApiConfig +from utils.cache_util import CacheManager def render_page_top(): - return html.Div( - [ - html.Div( - fac.AntdAvatar( - id='dashboard-avatar-info', - mode='image', - src=f"{ApiBaseUrlConfig.BaseUrl}{session.get('user_info').get('avatar')}&token={session.get('Authorization')}", - size='large' - ), - className='avatar', - ), - html.Div( - [ - html.Div( - fac.AntdText(f"早安,{session.get('user_info').get('nick_name')},祝你开心每一天!"), - className='content-title', - ), - html.Div('交互专家 |蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED') - ], - className='content', + [ + html.Div( + fac.AntdAvatar( + id='dashboard-avatar-info', + mode='image', + src=f"{ApiConfig.BaseUrl}{CacheManager.get('user_info').get('avatar')}" + if CacheManager.get('user_info').get('avatar') + else '/assets/imgs/profile.jpg', + size='large', ), - html.Div( - [ - html.Div( - fac.AntdStatistic( - title='项目数', - value=56 - ), - className='stat-item' + className='avatar', + ), + html.Div( + [ + html.Div( + fac.AntdText( + f"早安,{CacheManager.get('user_info').get('nick_name')},祝你开心每一天!" ), - html.Div( - fac.AntdStatistic( - title='团队内排名', - value=8, - suffix='/ 24' - ), - className='stat-item' - ), - html.Div( - fac.AntdStatistic( - title='项目访问', - value=2223 - ), - className='stat-item' + className='content-title', + ), + html.Div( + '交互专家 |蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED' + ), + ], + className='content', + ), + html.Div( + [ + html.Div( + fac.AntdStatistic(title='项目数', value=56), + className='stat-item', + ), + html.Div( + fac.AntdStatistic( + title='团队内排名', value=8, suffix='/ 24' ), - ], - className='extra-content' - ), - fuc.FefferyStyle( - rawStyle=''' + className='stat-item', + ), + html.Div( + fac.AntdStatistic(title='项目访问', value=2223), + className='stat-item', + ), + ], + className='extra-content', + ), + fuc.FefferyStyle( + rawStyle=""" .page-header-content { display: flex; } @@ -137,13 +133,13 @@ def render_page_top(): .extra-content .stat-item:last-child::after { display: none; } - ''' - ) - ], - className='page-header-content', - style={ - 'padding': '12px', - 'marginBottom': '24px', - 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px' - } - ) + """ + ), + ], + className='page-header-content', + style={ + 'padding': '12px', + 'marginBottom': '24px', + 'boxShadow': 'rgba(0, 0, 0, 0.1) 0px 4px 12px', + }, + ) diff --git a/dash-fastapi-frontend/views/forget.py b/dash-fastapi-frontend/views/forget.py index 3e480b425166630d45b70a9db02dc93b8e04fdbc..c6656a43841c675892c52cdb7bbd4c2b9f226fdb 100644 --- a/dash-fastapi-frontend/views/forget.py +++ b/dash-fastapi-frontend/views/forget.py @@ -1,106 +1,99 @@ -from dash import html, dcc import feffery_antd_components as fac import feffery_utils_components as fuc +from dash import dcc, html +from callbacks import forget_c # noqa: F401 -import callbacks.forget_c - -def render_forget_content(): +def render(): return html.Div( [ - fac.AntdCard( [ - dcc.Store(id='sms_code-session_id-container', storage_type='session'), + dcc.Store( + id='sms_code-session_id-container', + storage_type='session', + ), fac.AntdForm( [ - fac.AntdFormItem( - fac.AntdInput( - placeholder='请输入用户名', - id='forget-username', - size='large', - prefix=fac.AntdIcon( - icon='antd-user' - ), - ), - id='forget-username-form-item' - ), - fac.AntdFormItem( - fac.AntdInput( - placeholder='请输入新密码', - id='forget-password', - mode='password', - passwordUseMd5=True, - size='large', - prefix=fac.AntdIcon( - icon='antd-lock' + fac.AntdFlex( + [ + fac.AntdFormItem( + fac.AntdInput( + placeholder='请输入用户名', + id='forget-username', + size='large', + prefix=fac.AntdIcon( + icon='antd-user' + ), + ), + id='forget-username-form-item', ), - ), - id='forget-password-form-item' - ), - fac.AntdFormItem( - fac.AntdInput( - placeholder='请再次输入新密码', - id='forget-password-again', - mode='password', - passwordUseMd5=True, - size='large', - prefix=fac.AntdIcon( - icon='antd-lock' + fac.AntdFormItem( + fac.AntdInput( + placeholder='请输入新密码', + id='forget-password', + mode='password', + passwordUseMd5=True, + size='large', + prefix=fac.AntdIcon( + icon='antd-lock' + ), + ), + id='forget-password-form-item', ), - ), - id='forget-password-again-form-item' - ), - fac.AntdSpace( - [ fac.AntdFormItem( fac.AntdInput( - placeholder='请输入短信验证码', - id='forget-input-captcha', + placeholder='请再次输入新密码', + id='forget-password-again', + mode='password', + passwordUseMd5=True, size='large', prefix=fac.AntdIcon( - icon='antd-check-circle' + icon='antd-lock' ), - style={ - 'width': '270px' - } ), - id='forget-captcha-form-item' + id='forget-password-again-form-item', + ), + fac.AntdFormItem( + fac.AntdFlex( + [ + fac.AntdInput( + placeholder='请输入短信验证码', + id='forget-input-captcha', + size='large', + prefix=fac.AntdIcon( + icon='antd-check-circle' + ), + style={'flex': '1 1 0%'}, + ), + fac.AntdButton( + '获取验证码', + id='get-message-code', + size='large', + ), + ], + gap='small', + ), + id='forget-captcha-form-item', ), fac.AntdFormItem( fac.AntdButton( - '获取验证码', - id='get-message-code', + '保存', + id='forget-submit', type='primary', - size='large' - ) + block=True, + size='large', + ), + style={'marginTop': '20px'}, ), ], - align='end', - size=10 + vertical=True, ), - fac.AntdFormItem( - fac.AntdButton( - '保存', - id='forget-submit', - type='primary', - loadingChildren='保存中', - autoSpin=True, - block=True, - size='large', - ), - style={ - 'marginTop': '20px' - } - ) ], layout='vertical', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), - - fuc.FefferyCountDown(id='message-code-count-down') + fuc.FefferyCountDown(id='message-code-count-down'), ], id='forget-form-container', title='重置密码', @@ -109,24 +102,24 @@ def render_forget_content(): 'content': '返回登录', 'href': '/login', 'target': '_self', - 'style': { - 'font-size': '16px' - } + 'style': {'fontSize': '16px'}, }, headStyle={ - 'font-weight': 'bold', - 'text-align': 'center', - 'font-size': '30px' + 'fontWeight': 'bold', + 'textAlign': 'center', + 'fontSize': '30px', }, style={ 'position': 'fixed', 'top': '16%', 'left': '50%', - 'width': '500px', + 'width': '480px', + 'minWidth': '420px', + 'maxWidth': '75vw', 'padding': '0px 30px', - 'transform': 'translateX(-50%)' - } + 'transform': 'translateX(-50%)', + }, ), - ] + ], + id='forget-page', ) - diff --git a/dash-fastapi-frontend/views/innerlink/__init__.py b/dash-fastapi-frontend/views/innerlink/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a866be5fc1bf0c06914ac7f1678c44536ea4e78c --- /dev/null +++ b/dash-fastapi-frontend/views/innerlink/__init__.py @@ -0,0 +1,33 @@ +from dash import dcc, html +import feffery_utils_components as fuc +from callbacks import innnerlink_c # noqa: F401 + + +def render(*args, **kwargs): + return [ + html.Div( + [ + dcc.Interval( + id='init-iframe-interval', + n_intervals=0, + interval=500, + disabled=False, + ), + fuc.FefferyStyle( + rawStyle=""" + iframe { + border: none; + width: 100%; + height: 100%; + display: block + } + """ + ), + html.Iframe( + id='innerlink-iframe', + ), + ], + id='innerlink-container', + style={'position': 'relative', 'height': 'calc(100vh - 120px)'}, + ) + ] diff --git a/dash-fastapi-frontend/views/layout/__init__.py b/dash-fastapi-frontend/views/layout/__init__.py index 52b992ba21719b0b4fd802100718a846c9f453c1..cfc811cabd0beacb55a5be38639fd2dd10240030 100644 --- a/dash-fastapi-frontend/views/layout/__init__.py +++ b/dash-fastapi-frontend/views/layout/__init__.py @@ -1,42 +1,33 @@ -from dash import html -import feffery_utils_components as fuc import feffery_antd_components as fac - -from views.layout.components.head import render_head_content -from views.layout.components.content import render_main_content +import feffery_utils_components as fuc +from dash import html +from callbacks.layout_c import fold_side_menu # noqa: F401 +from callbacks.layout_c import index_c # noqa: F401 from views.layout.components.aside import render_aside_content -import callbacks.layout_c.fold_side_menu -import callbacks.layout_c.index_c - +from views.layout.components.content import render_main_content +from views.layout.components.head import render_head_content -def render_content(menu_info): +def render(menu_info): return fuc.FefferyTopProgress( html.Div( [ # 全局重载 fuc.FefferyReload(id='trigger-reload-output'), - - html.Div(id='idle-placeholder-container'), - + # 响应式监听组件 + fuc.FefferyResponsive(id='responsive-layout-container'), # 布局设置抽屉 fac.AntdDrawer( [ fac.AntdText( '主题颜色', - style={ - 'fontSize': 16, - 'fontWeight': 500 - } + style={'fontSize': 16, 'fontWeight': 500}, ), fuc.FefferyHexColorPicker( id='hex-color-picker', color='#1890ff', showAlpha=True, - style={ - 'width': '100%', - 'marginTop': '10px' - } + style={'width': '100%', 'marginTop': '10px'}, ), fac.AntdInput( id='selected-color-input', @@ -44,57 +35,54 @@ def render_content(menu_info): readOnly=True, style={ 'marginTop': '15px', - 'background': '#1890ff' - } + 'background': '#1890ff', + }, ), fac.AntdSpace( [ fac.AntdButton( [ - fac.AntdIcon( - icon='antd-save' - ), + fac.AntdIcon(icon='antd-save'), '保存配置', ], id='save-setting', - type='primary' + type='primary', ), fac.AntdButton( [ - fac.AntdIcon( - icon='antd-sync' - ), + fac.AntdIcon(icon='antd-sync'), '重置配置', ], id='reset-setting', ), ], - style={ - 'marginTop': '15px' - } - ) + style={'marginTop': '15px'}, + ), ], id='layout-setting-drawer', visible=False, title='布局设置', - width=320 + width=320, ), - # 退出登录对话框提示 fac.AntdModal( html.Div( [ - fac.AntdIcon(icon='fc-info', style={'font-size': '28px'}), - fac.AntdText('确定注销并退出系统吗?', style={'margin-left': '5px'}), + fac.AntdIcon( + icon='fc-info', style={'font-size': '28px'} + ), + fac.AntdText( + '确定注销并退出系统吗?', + style={'margin-left': '5px'}, + ), ] ), id='logout-modal', visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), - # 平台主页面 fac.AntdRow( [ @@ -106,18 +94,16 @@ def render_content(menu_info): id='side-menu', style={ 'height': '100vh', + 'overflowX': 'hidden', 'overflowY': 'auto', 'transition': 'width 1s', - 'background': '#001529' - } + 'background': '#001529', + }, ), ), id='left-side-menu-container', - # style={ - # 'flex': '1' - # } + flex='none', ), - # 右侧区域 fac.AntdCol( [ @@ -130,26 +116,19 @@ def render_content(menu_info): 'marginBottom': '10px', 'position': 'sticky', 'top': 0, - 'zIndex': 999 - } + 'zIndex': 999, + }, ), - fac.AntdRow( - render_main_content(), - wrap=False - ) + fac.AntdRow(render_main_content(), wrap=False), ], - style={ - 'flex': '6', - 'width': '300px' - } + flex='auto', + style={'width': 0}, ), ], - ) + ), ], id='index-main-content-container', ), listenPropsMode='include', - includeProps=[ - 'tabs-container.items' - ] + includeProps=['tabs-container.items'], ) diff --git a/dash-fastapi-frontend/views/layout/components/aside.py b/dash-fastapi-frontend/views/layout/components/aside.py index 5be584e63f8e2e0b46a80cbc41863eea93a39092..4b01c9f83ca4c48a1c72e9ee199b854a6b2766fd 100644 --- a/dash-fastapi-frontend/views/layout/components/aside.py +++ b/dash-fastapi-frontend/views/layout/components/aside.py @@ -1,12 +1,14 @@ import feffery_antd_components as fac -import dash - -import callbacks.layout_c.aside_c +from dash import dcc, get_asset_url +from callbacks.layout_c import aside_c # noqa: F401 +from config.env import AppConfig def render_aside_content(menu_info): - return [ + dcc.Store(id='current-key_path-store'), + dcc.Store(id='current-item-store'), + dcc.Store(id='current-item_path-store'), fac.AntdSider( [ fac.AntdRow( @@ -15,33 +17,33 @@ def render_aside_content(menu_info): fac.AntdImage( width=32, height=32, - src=dash.get_asset_url('imgs/logo.png'), + src=get_asset_url('imgs/logo.png'), preview=False, ), flex='1', style={ 'height': '100%', 'display': 'flex', - 'alignItems': 'center' - } + 'alignItems': 'center', + }, ), fac.AntdCol( fac.AntdText( - '后台管理系统', + AppConfig.app_name, id='logo-text', style={ 'fontSize': '22px', - # 'paddingLeft': '20px', - 'color': 'rgb(255, 255, 255)' - } + 'color': 'rgb(255, 255, 255)', + }, ), flex='5', style={ 'height': '100%', 'display': 'flex', 'alignItems': 'center', - } - ) + 'marginLeft': '25px', + }, + ), ], style={ 'height': '50px', @@ -49,36 +51,22 @@ def render_aside_content(menu_info): 'position': 'sticky', 'top': 0, 'zIndex': 999, - 'paddingLeft': '10px' - } + 'paddingLeft': '18px', + }, ), fac.AntdMenu( id='index-side-menu', - menuItems=[ - { - 'component': 'Item', - 'props': { - 'key': '首页', - 'title': '首页', - 'icon': 'antd-dashboard', - 'href': '/' - } - } - ] + menu_info, + menuItems=menu_info, mode='inline', theme='dark', defaultSelectedKey='首页', - defaultOpenKeys=['-1_1'], - style={ - 'width': '100%', - 'height': 'calc(100vh - 50px)' - } + style={'width': '100%', 'height': 'calc(100vh - 50px)'}, ), ], id='menu-collapse-sider-custom', collapsible=True, - collapsedWidth=60, + collapsedWidth=64, trigger=None, - width=210 + width=256, ), ] diff --git a/dash-fastapi-frontend/views/layout/components/content.py b/dash-fastapi-frontend/views/layout/components/content.py index c0f577dfcc53fdfb0e0b7e6317047eb436d9d299..a78b8dfc745c56de05758b8b6966d52b80b3a8aa 100644 --- a/dash-fastapi-frontend/views/layout/components/content.py +++ b/dash-fastapi-frontend/views/layout/components/content.py @@ -1,7 +1,6 @@ -from dash import html import feffery_antd_components as fac - -from views.dashboard import render_dashboard +from dash import html +from views.dashboard import render def render_main_content(): @@ -14,26 +13,26 @@ def render_main_content(): items=[ { 'label': '首页', - 'key': '首页', + 'key': 'Index/', 'closable': False, - 'children': render_dashboard(), + 'children': render(), 'contextMenu': [ { 'key': '刷新页面', 'label': '刷新页面', - 'icon': 'antd-reload' + 'icon': 'antd-reload', }, { 'key': '关闭其他', 'label': '关闭其他', - 'icon': 'antd-close-circle' + 'icon': 'antd-close-circle', }, { 'key': '全部关闭', 'label': '全部关闭', - 'icon': 'antd-close-circle' - } - ] + 'icon': 'antd-close-circle', + }, + ], } ], id='tabs-container', @@ -42,17 +41,17 @@ def render_main_content(): style={ 'width': '100%', 'paddingLeft': '15px', - 'paddingRight': '15px' - } + 'paddingRight': '15px', + }, ), # id='index-main-content-container', style={ 'width': '100%', 'height': '100%', 'backgroundColor': 'white', - } + }, ) ], - flex='auto' + flex='auto', ) ] diff --git a/dash-fastapi-frontend/views/layout/components/head.py b/dash-fastapi-frontend/views/layout/components/head.py index 5f1e7fde23c89728dcd369f5519c616df620defc..29613a6932739459c8d5c3a8c53d6063d2da6617 100644 --- a/dash-fastapi-frontend/views/layout/components/head.py +++ b/dash-fastapi-frontend/views/layout/components/head.py @@ -1,8 +1,8 @@ -from dash import html import feffery_antd_components as fac -from flask import session -from config.global_config import ApiBaseUrlConfig -import callbacks.layout_c.head_c +from dash import html +from callbacks.layout_c import head_c # noqa: F401 +from config.env import ApiConfig +from utils.cache_util import CacheManager def render_head_content(): @@ -12,81 +12,60 @@ def render_head_content(): html.Div( fac.AntdButton( fac.AntdIcon( - id='fold-side-menu-icon', - icon='antd-menu-fold' + id='fold-side-menu-icon', icon='antd-menu-fold' ), id='fold-side-menu', type='text', shape='circle', size='large', - style={ - 'marginLeft': '5px', - 'background': 'white' - } + style={'marginLeft': '5px', 'background': 'white'}, ), style={ 'height': '100%', 'display': 'flex', - 'alignItems': 'center' - } + 'alignItems': 'center', + }, ), - flex='1' + id='fold-side-menu-col', + flex='1', ), - # 页首面包屑区域 fac.AntdCol( fac.AntdBreadcrumb( items=[ - { - 'title': '首页', - 'icon': 'antd-dashboard', - 'href': '/#' - } + {'title': '首页', 'icon': 'antd-dashboard', 'href': '/#'} ], - id='header-breadcrumb' + id='header-breadcrumb', + style={ + 'height': '100%', + 'display': 'flex', + 'alignItems': 'center', + }, ), - style={ - 'height': '100%', - 'display': 'flex', - 'alignItems': 'center' - }, - flex='21' + id='header-breadcrumb-col', + flex='21', ), - # 页首中部搜索区域 fac.AntdCol( fac.AntdParagraph( [ fac.AntdText( - 'Ctrl', - keyboard=True, - style={ - 'color': '#8c8c8c' - } + 'Ctrl', keyboard=True, style={'color': '#8c8c8c'} ), fac.AntdText( - 'K', - keyboard=True, - style={ - 'color': '#8c8c8c' - } + 'K', keyboard=True, style={'color': '#8c8c8c'} ), - fac.AntdText( - '唤出搜索面板', - style={ - 'color': '#8c8c8c' - } - ) + fac.AntdText('唤出搜索面板', style={'color': '#8c8c8c'}), ], style={ 'height': '100%', 'display': 'flex', - 'alignItems': 'center' - } + 'alignItems': 'center', + }, ), - flex='6' + id='header-search-col', + flex='6', ), - # 页首开源项目地址 fac.AntdCol( html.A( @@ -94,16 +73,16 @@ def render_head_content(): src='https://gitee.com/insistence2022/dash-fastapi-admin/badge/star.svg?theme=dark' ), href='https://gitee.com/insistence2022/dash-fastapi-admin', - target='_blank' + target='_blank', + style={ + 'height': '100%', + 'display': 'flex', + 'alignItems': 'center', + }, ), - style={ - 'height': '100%', - 'display': 'flex', - 'alignItems': 'center' - }, - flex='3' + id='header-gitee-col', + flex='3', ), - # 页首右侧用户信息区域 fac.AntdCol( fac.AntdSpace( @@ -113,11 +92,13 @@ def render_head_content(): fac.AntdAvatar( id='avatar-info', mode='image', - src=f"{ApiBaseUrlConfig.BaseUrl}{session.get('user_info').get('avatar')}&token={session.get('Authorization')}", - size=36 + src=f"{ApiConfig.BaseUrl}{CacheManager.get('user_info').get('avatar')}" + if CacheManager.get('user_info').get('avatar') + else '/assets/imgs/profile.jpg', + size=36, ), count=6, - size='small' + size='small', ), content=fac.AntdTabs( items=[ @@ -135,18 +116,18 @@ def render_head_content(): 'padding': '5px 10px', 'height': 40, 'width': 300, - 'borderBottom': '1px solid #f1f3f5' - } + 'borderBottom': '1px solid #f1f3f5', + }, ) for i in range(1, 8) ], direction='vertical', style={ 'height': 280, - 'overflowY': 'auto' - } + 'overflowY': 'auto', + }, ) - ] + ], }, { 'key': '已读消息', @@ -162,63 +143,57 @@ def render_head_content(): 'padding': '5px 10px', 'height': 40, 'width': 300, - 'borderBottom': '1px solid #f1f3f5' - } + 'borderBottom': '1px solid #f1f3f5', + }, ) for i in range(8, 15) ], direction='vertical', style={ 'height': 280, - 'overflowY': 'auto' - } + 'overflowY': 'auto', + }, ) - ] + ], }, ], - centered=True + centered=True, ), - placement='bottomRight' + placement='bottomRight', ), - fac.AntdDropdown( id='index-header-dropdown', - title=session.get('user_info').get('user_name'), + title=CacheManager.get('user_info').get('user_name'), arrow=True, menuItems=[ { 'title': '个人资料', 'key': '个人资料', - 'icon': 'antd-idcard' + 'icon': 'antd-idcard', }, { 'title': '布局设置', 'key': '布局设置', - 'icon': 'antd-layout' - }, - { - 'isDivider': True + 'icon': 'antd-layout', }, + {'isDivider': True}, { 'title': '退出登录', 'key': '退出登录', - 'icon': 'antd-logout' + 'icon': 'antd-logout', }, ], placement='bottomRight', - overlayStyle={ - 'width': '100px' - } - ) + ), ], style={ 'height': '100%', 'float': 'right', 'display': 'flex', - 'alignItems': 'center' - } + 'alignItems': 'center', + }, ), - flex='3' + flex='3', ), fac.AntdCol( # 全局刷新按钮 @@ -226,8 +201,7 @@ def render_head_content(): fac.AntdTooltip( fac.AntdButton( fac.AntdIcon( - id='index-reload-icon', - icon='fc-synchronize' + id='index-reload-icon', icon='fc-synchronize' ), id='index-reload', type='text', @@ -235,18 +209,18 @@ def render_head_content(): size='large', style={ 'backgroundColor': 'rgb(255 255 255 / 0%)', - } + }, ), title='刷新', - placement='bottom' + placement='bottom', ) ), style={ 'height': '100%', 'paddingRight': '3px', 'display': 'flex', - 'alignItems': 'center' + 'alignItems': 'center', }, - flex='1' + flex='1', ), ] diff --git a/dash-fastapi-frontend/views/login.py b/dash-fastapi-frontend/views/login.py index 65f6b1d9392f473399ca462ac78e9db246b89181..d670c1557ee8e6b02207e8fd8cc54033984ad479 100644 --- a/dash-fastapi-frontend/views/login.py +++ b/dash-fastapi-frontend/views/login.py @@ -1,211 +1,284 @@ -from dash import html, dcc import feffery_antd_components as fac +import feffery_utils_components as fuc +import time +from dash import dcc, html +from callbacks import login_c # noqa: F401 +from config.env import AppConfig -import callbacks.login_c -from api.config import query_config_list_api - - -def render_content(): - captcha_enabled_info = query_config_list_api(config_key='sys.account.captchaEnabled') - forget_enabled_info = query_config_list_api(config_key='sys.account.forgetUser') - captcha_hidden = False - forget_show = True - if captcha_enabled_info.get('code') == 200: - captcha_hidden = False if captcha_enabled_info.get('data') == 'true' else True - if forget_enabled_info.get('code') == 200: - forget_show = False if forget_enabled_info.get('data') == 'false' else True +def render(): return html.Div( [ + dcc.Store(id='login-success-container'), dcc.Store(id='captcha_image-session_id-container'), + fuc.FefferyKeyPress( + id='keyboard-enter-submit', + keys='enter', + ), html.Div( [ html.Div( - [ - fac.AntdText('HELLO', style={'color': 'rgba(255,255,255,0.8)'}) - ], - style={ - 'fontSize': '60px', - 'fontWeight': '500' - } - ), - html.Div( - [ - fac.AntdText('WELCOME', style={'color': 'rgba(255,255,255,0.8)'}), - ], - style={ - 'fontSize': '60px', - 'fontWeight': '500' - } - ), - html.Div( - [ - fac.AntdText('欢迎使用通用后台管理系统', style={'color': 'rgba(255,255,255,0.8)'}), - ], - style={ - 'fontSize': '18px', - 'fontWeight': '600', - 'marginTop': '20px' - } - ), - ], - style={ - 'position': 'fixed', - 'top': '20%', - 'left': '26%', - 'width': '430px', - 'padding': '0px 30px', - 'transform': 'translateX(-50%)' - } - ), - fac.AntdCard( - [ - fac.AntdForm( - [ - fac.AntdFormItem( - fac.AntdInput( - placeholder='请输入用户名', - id='login-username', - size='large', - prefix=fac.AntdIcon( - icon='antd-user' - ), - ), - id='login-username-form-item' - ), - fac.AntdFormItem( - fac.AntdInput( - placeholder='请输入密码', - id='login-password', - mode='password', - passwordUseMd5=True, - size='large', - prefix=fac.AntdIcon( - icon='antd-lock' - ), - ), - id='login-password-form-item' - ), - html.Div( - [ - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdInput( - placeholder='请输入验证码', - id='login-captcha', - size='large', - prefix=fac.AntdIcon( - icon='antd-check-circle' + html.Div( + [ + html.Div( + [ + html.Div( + [ + html.Span( + html.Img( + alt='logo', + src='/assets/imgs/logo.png', ), - style={ - 'width': '210px' - } + className='ant-pro-form-login-logo', + ), + html.Span( + AppConfig.app_name, + className='ant-pro-form-login-title', ), - id='login-captcha-form-item' + ], + className='ant-pro-form-login-header', + ), + html.Div( + f'{AppConfig.app_name} 是 Dash 中最完备的中后台管理系统', + className='ant-pro-form-login-desc ', + ), + ], + className='ant-pro-form-login-top', + ), + html.Div( + [ + fac.AntdForm( + fac.AntdFlex( + [ + fac.AntdFormItem( + fac.AntdInput( + placeholder='请输入用户名', + id='login-username', + size='large', + prefix=fac.AntdIcon( + icon='antd-user' + ), + ), + id='login-username-form-item', + ), + fac.AntdFormItem( + fac.AntdInput( + placeholder='请输入密码', + id='login-password', + mode='password', + passwordUseMd5=True, + size='large', + prefix=fac.AntdIcon( + icon='antd-lock' + ), + ), + id='login-password-form-item', + ), + html.Div( + fac.AntdFormItem( + [ + fac.AntdFlex( + [ + fac.AntdInput( + placeholder='请输入验证码', + id='login-captcha', + size='large', + prefix=fac.AntdIcon( + icon='antd-check-circle' + ), + ), + html.Div( + [ + fac.AntdImage( + id='login-captcha-image', + src='', + height=39.6, + width=100, + preview=False, + style={ + 'border': '1px solid #d9d9d9', + 'borderRadius': '8px', + }, + ) + ], + id='login-captcha-image-container', + ), + ], + align='center', + gap='small', + ), + ], + id='login-captcha-form-item', + ), + id='captcha-row-container', + ), + fac.AntdFormItem( + fac.AntdFlex( + [ + fac.AntdCheckbox( + id='login-remember-me', + label='记住我', + ), + html.Div( + id='forget-password-link-container' + ), + ], + align='center', + justify='space-between', + ), + style={ + 'marginBottom': '10px' + }, + ), + fac.AntdFormItem( + fac.AntdButton( + '登录', + id='login-submit', + type='primary', + block=True, + size='large', + ), + style={ + 'marginBottom': '15px' + }, + ), + fac.AntdFormItem( + html.Div( + id='register-user-link-container', + ), + ), + ], + vertical=True, ), - fac.AntdFormItem( - html.Div( - [ - fac.AntdImage( - id='login-captcha-image', - src='', - height=37, - width=100, - preview=False - ) - ], - id='login-captcha-image-container', - n_clicks=1, - style={ - 'border': '1px solid #ccc' - } - ) - ) - ], - align='end', - size=10 - ), - ], - id='captcha-row-container', - hidden=captcha_hidden - ), - fac.AntdSpace( - [ - html.Div(id='test'), - fac.AntdButton( - '忘记密码', - id='forget-password-link', - type='link', - href='/forget', - target='_self' - ) - ], - align='center', - size=240 - ) if forget_show else [], - fac.AntdFormItem( - fac.AntdButton( - '登录', - id='login-submit', - type='primary', - loadingChildren='登录中', - autoSpin=True, - block=True, - size='large', + layout='vertical', + style={'width': '100%'}, + ), + ], + className='ant-pro-form-login-main', + style={ + 'width': '328px', + 'minWidth': '280px', + 'maxWidth': '75vw', + }, + ), + ], + className='ant-pro-form-login-container', + ), + style={'flex': '1', 'padding': '32px 0'}, + ), + fac.AntdFooter( + html.Div( + html.Div( + html.Span( + f'Copyright © 2023-{time.localtime().tm_year} insistence.tech All Rights Reserved.', + className='anticon anticon-copyright', + role='img', + **{'aria-label': 'copyright'}, ), - style={ - 'marginTop': '20px' - } - ) - ], - layout='vertical', + className='ant-pro-global-footer-copyright', + ), + className='ant-pro-global-footer', + ), style={ - 'width': '100%' - } + 'padding': '0px', + 'background': 'none', + }, ), ], - id='login-form-container', - title='登录', - hoverable=True, - style={ - 'position': 'fixed', - 'top': '16%', - 'left': '70%', - 'width': '430px', - 'padding': '0px 30px', - 'transform': 'translateX(-50%)' - } + id='login-page', + className='acss-trkbkn', ), - fac.AntdFooter( - html.Div( - fac.AntdText( - '版权所有©2023 Dash-FastAPI-Admin', - style={ - 'margin': '0' - } - ), - style={ - 'display': 'flex', - 'height': '100%', - 'justifyContent': 'center', - 'alignItems': 'center' + fuc.FefferyStyle( + rawStyle=""" + .acss-trkbkn { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: 100vh; + overflow: auto; + -webkit-background-size: 100% 100%; + background-size: 100% 100%; + } + .ant-pro-form-login-container { + display: flex; + flex: 1; + flex-direction: column; + height: 100%; + padding-inline: 32px; + padding-block: 24px; + overflow: auto; + background: inherit; + } + .ant-pro-form-login-top { + text-align: center; + } + .ant-pro-form-login-header { + display: flex; + align-items: center; + justify-content: center; + height: 44px; + line-height: 44px; + } + .ant-pro-form-login-logo { + width: 44px; + height: 44px; + margin-inline-end: 16px; + vertical-align: top; + } + .ant-pro-form-login-logo img { + width: 100%; + } + img { + vertical-align: middle; + border-style: none; + } + .ant-pro-form-login-title { + position: relative; + inset-block-start: 2px; + font-weight: 600; + font-size: 33px; + } + .ant-pro-form-login-desc { + margin-block-start: 12px; + margin-block-end: 40px; + color: rgba(0, 0, 0, 0.65); + font-size: 14px; + } + .ant-pro-form-login-main { + margin: 0 auto; + } + .ant-pro-global-footer { + margin-block: 0; + margin-block-start: 48px; + margin-block-end: 24px; + margin-inline: 0; + padding-block: 0; + padding-inline: 16px; + text-align: center; + } + .ant-pro-global-footer-copyright { + font-size: 14px; + color: rgba(0, 0, 0, 0.88); + } + .anticon { + display: inline-flex; + align-items: center; + color: inherit; + font-style: normal; + line-height: 0; + text-align: center; + text-transform: none; + vertical-align: -0.125em; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; } - ), - style={ - 'backgroundColor': 'rgb(255 255 255 / 0%)', - 'height': '40px', - 'position': 'fixed', - 'bottom': 0, - 'left': '50%', - 'width': '500px', - 'padding': '20px 50px', - 'transform': 'translateX(-50%)' - } + """ ), ], - id='container', - style={ - 'height': '100vh', - } ) diff --git a/dash-fastapi-frontend/views/monitor/__init__.py b/dash-fastapi-frontend/views/monitor/__init__.py index 749a188215e1ef79317a92a7efde8ddf220a903c..6e2cf3dea82d26b43dc6f5b49f5b4e12fdc5d5ec 100644 --- a/dash-fastapi-frontend/views/monitor/__init__.py +++ b/dash-fastapi-frontend/views/monitor/__init__.py @@ -1,9 +1,9 @@ from . import ( - online, - job, - druid, - server, - cache, - operlog, - logininfor + server, # noqa: F401 + cache, # noqa: F401 + druid, # noqa: F401 + logininfor, # noqa: F401 + job, # noqa: F401 + online, # noqa: F401 + operlog, # noqa: F401 ) diff --git a/dash-fastapi-frontend/views/monitor/cache/__init__.py b/dash-fastapi-frontend/views/monitor/cache/__init__.py index 6db4a4806f872a9676bea7308ee0ea8b84f1beaa..0bb9286b4f9826b20f95d7a0fab4a4bdcf34b7dc 100644 --- a/dash-fastapi-frontend/views/monitor/cache/__init__.py +++ b/dash-fastapi-frontend/views/monitor/cache/__init__.py @@ -1,4 +1,4 @@ from . import ( - control, - list + control, # noqa: F401 + list, # noqa: F401 ) diff --git a/dash-fastapi-frontend/views/monitor/cache/control/__init__.py b/dash-fastapi-frontend/views/monitor/cache/control/__init__.py index 08dd3fbf5956903b3b7569fc74dc2f52479124d0..ba88e2164ceb4b460f1046860a529be9640246d7 100644 --- a/dash-fastapi-frontend/views/monitor/cache/control/__init__.py +++ b/dash-fastapi-frontend/views/monitor/cache/control/__init__.py @@ -1,31 +1,30 @@ -from dash import html, dcc import feffery_antd_components as fac - -import callbacks.monitor_c.cache_c.control_c -from api.cache import get_cache_statistical_info_api +from dash import dcc, html +from api.monitor.cache import CacheApi +from callbacks.monitor_c.cache_c import control_c # noqa: F401 def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - command_stats = [] - db_size = '' - info = {} - cache_info_res = get_cache_statistical_info_api() - if cache_info_res.get('code') == 200: - cache_info = cache_info_res.get('data') - command_stats = cache_info.get('command_stats') - db_size = cache_info.get('db_size') - info = cache_info.get('info') + cache_info_res = CacheApi.get_cache() + cache_info = cache_info_res.get('data') + command_stats = cache_info.get('command_stats') + db_size = cache_info.get('db_size') + info = cache_info.get('info') return [ - dcc.Store(id='control-button-perms-container', data=button_perms), - dcc.Store(id='init-echarts-data-container', data=dict(command_stats=command_stats, used_memory_human=info.get('used_memory_human'))), + dcc.Store( + id='init-echarts-data-container', + data=dict( + command_stats=command_stats, + used_memory_human=info.get('used_memory_human'), + ), + ), dcc.Store(id='echarts-data-container'), dcc.Interval( id='init-echarts-interval', n_intervals=0, interval=500, - disabled=False + disabled=False, ), html.Div( [ @@ -38,82 +37,93 @@ def render(*args, **kwargs): [ fac.AntdDescriptionItem( info.get('redis_version'), - label='Redis版本' + label='Redis版本', ), fac.AntdDescriptionItem( - '单机'if info.get('redis_mode') == 'standalone' else '集群', - label='运行模式' + '单机' + if info.get('redis_mode') + == 'standalone' + else '集群', + label='运行模式', ), fac.AntdDescriptionItem( info.get('tcp_port'), - label='端口' + label='端口', ), fac.AntdDescriptionItem( info.get('connected_clients'), - label='客户端数' + label='客户端数', ), fac.AntdDescriptionItem( info.get('uptime_in_days'), - label='运行时间(天)' + label='运行时间(天)', ), fac.AntdDescriptionItem( info.get('used_memory_human'), - label='使用内存' + label='使用内存', ), fac.AntdDescriptionItem( - info.get('used_cpu_user_children'), - label='使用CPU' + info.get( + 'used_cpu_user_children' + ), + label='使用CPU', ), fac.AntdDescriptionItem( info.get('maxmemory_human'), - label='内存配置' + label='内存配置', ), fac.AntdDescriptionItem( - '否' if info.get('aof_enabled') == 0 else '是', - label='AOF是否开启' + '否' + if info.get('aof_enabled') == 0 + else '是', + label='AOF是否开启', ), fac.AntdDescriptionItem( - info.get('rdb_last_bgsave_status'), - label='RDB是否成功' + info.get( + 'rdb_last_bgsave_status' + ), + label='RDB是否成功', ), fac.AntdDescriptionItem( - db_size, - label='Key数量' + db_size, label='Key数量' ), fac.AntdDescriptionItem( f"{info.get('instantaneous_input_kbps')}kps/{info.get('instantaneous_output_kbps')}kps", - label='网络入口/出口' + label='网络入口/出口', ), ], bordered=True, column=4, labelStyle={ 'fontWeight': 600, - 'textAlign': 'center' + 'textAlign': 'center', }, style={ 'width': '100%', 'textAlign': 'center', 'marginLeft': '20px', - 'marginRight': '20px' - } + 'marginRight': '20px', + }, ) ], title=html.Div( [ fac.AntdIcon(icon='antd-desktop'), - fac.AntdText('基本信息', style={'marginLeft': '10px'}) + fac.AntdText( + '基本信息', + style={'marginLeft': '10px'}, + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=24 + span=24, ), ], - gutter=20 + gutter=20, ), fac.AntdRow( [ @@ -124,22 +134,25 @@ def render(*args, **kwargs): id='command-stats-charts-container', style={ 'height': '420px', - 'width': '100%' - } + 'width': '100%', + }, ), ], title=html.Div( [ fac.AntdIcon(icon='antd-pie-chart'), - fac.AntdText('命令统计', style={'marginLeft': '10px'}) + fac.AntdText( + '命令统计', + style={'marginLeft': '10px'}, + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdCard( @@ -148,30 +161,30 @@ def render(*args, **kwargs): id='memory-charts-container', style={ 'height': '420px', - 'width': '100%' - } + 'width': '100%', + }, ), ], title=html.Div( [ fac.AntdIcon(icon='antd-compass'), - fac.AntdText('内存信息', style={'marginLeft': '10px'}) + fac.AntdText( + '内存信息', + style={'marginLeft': '10px'}, + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=12 + span=12, ), ], gutter=20, - style={ - 'marginTop': '20px', - 'marginBottom': '20px' - } + style={'marginTop': '20px', 'marginBottom': '20px'}, ), ], - ) + ), ] diff --git a/dash-fastapi-frontend/views/monitor/cache/list/__init__.py b/dash-fastapi-frontend/views/monitor/cache/list/__init__.py index 015739c3b78c9a05f458faa6822586566876bdbb..d40728cb0809fe12f5f2c1443124d3423d202886 100644 --- a/dash-fastapi-frontend/views/monitor/cache/list/__init__.py +++ b/dash-fastapi-frontend/views/monitor/cache/list/__init__.py @@ -1,16 +1,21 @@ -from dash import html, dcc import feffery_antd_components as fac - -from api.cache import get_cache_name_list_api -import callbacks.monitor_c.cache_c.list_c +from dash import dcc, html +from api.monitor.cache import CacheApi +from callbacks.monitor_c.cache_c import list_c # noqa: F401 def render(*args, **kwargs): - cache_name_data = [] - cache_name_res = get_cache_name_list_api() - if cache_name_res.get('code') == 200: - cache_name_list = cache_name_res.get('data') - cache_name_data = [{'key': item.get('cache_name'), 'id': index + 1, 'operation': {'type': 'link', 'icon': 'antd-delete'}, **item} for index, item in enumerate(cache_name_list)] + cache_name_res = CacheApi.list_cache_name() + cache_name_list = cache_name_res.get('data') + cache_name_data = [ + { + 'key': item.get('cache_name'), + 'id': index + 1, + 'operation': {'type': 'link', 'icon': 'antd-delete'}, + **item, + } + for index, item in enumerate(cache_name_list) + ] return html.Div( [ @@ -53,36 +58,38 @@ def render(*args, **kwargs): 'renderOptions': { 'renderType': 'button' }, - } + }, + ], + enableCellClickListenColumns=[ + 'id', + 'cache_name', + 'remark', ], - enableCellClickListenColumns=['id', 'cache_name', 'remark'], bordered=False, pagination={ 'showSizeChanger': True, 'showQuickJumper': True, - 'hideOnSinglePage': True + 'hideOnSinglePage': True, }, ) ), title=fac.AntdSpace( [ fac.AntdIcon(icon='antd-book'), - fac.AntdText('缓存列表') + fac.AntdText('缓存列表'), ] ), extra=fac.AntdButton( id='refresh-cache_name', type='link', - icon=fac.AntdIcon( - icon='antd-reload' - ) + icon=fac.AntdIcon(icon='antd-reload'), ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=8 + span=8, ), fac.AntdCol( fac.AntdCard( @@ -111,36 +118,37 @@ def render(*args, **kwargs): 'renderOptions': { 'renderType': 'button' }, - } + }, + ], + enableCellClickListenColumns=[ + 'id', + 'cache_key', ], - enableCellClickListenColumns=['id', 'cache_key'], bordered=False, pagination={ 'showSizeChanger': True, 'showQuickJumper': True, 'hideOnSinglePage': True, - } + }, ) ), title=fac.AntdSpace( [ fac.AntdIcon(icon='antd-key'), - fac.AntdText('键名列表') + fac.AntdText('键名列表'), ] ), extra=fac.AntdButton( id='refresh-cache_key', type='link', - icon=fac.AntdIcon( - icon='antd-reload' - ) + icon=fac.AntdIcon(icon='antd-reload'), ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=8 + span=8, ), fac.AntdCol( fac.AntdCard( @@ -150,21 +158,17 @@ def render(*args, **kwargs): fac.AntdInput( id='cache_name-input', readOnly=True, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), - label='缓存名称' + label='缓存名称', ), fac.AntdFormItem( fac.AntdInput( id='cache_key-input', readOnly=True, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), - label='缓存键名' + label='缓存键名', ), fac.AntdFormItem( fac.AntdInput( @@ -173,43 +177,37 @@ def render(*args, **kwargs): readOnly=True, autoSize={ 'minRows': 5, - 'maxRows': 10 + 'maxRows': 10, }, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), - label='缓存内容' - ) + label='缓存内容', + ), ], layout='vertical', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), title=fac.AntdSpace( [ fac.AntdIcon(icon='antd-file-text'), - fac.AntdText('缓存内容') + fac.AntdText('缓存内容'), ] ), extra=fac.AntdButton( '清除全部', id='clear-all-cache', type='link', - icon=fac.AntdIcon( - icon='antd-clear' - ) + icon=fac.AntdIcon(icon='antd-clear'), ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=8 - ) + span=8, + ), ], - gutter=10 - ) + gutter=10, + ), ] ) diff --git a/dash-fastapi-frontend/views/monitor/druid/__init__.py b/dash-fastapi-frontend/views/monitor/druid/__init__.py index a79fa8cdcc7f919c0f08eccc65f870f4acee41a8..1cb1a1b33920d2aac33f782a132e19f42697ce30 100644 --- a/dash-fastapi-frontend/views/monitor/druid/__init__.py +++ b/dash-fastapi-frontend/views/monitor/druid/__init__.py @@ -1,6 +1,4 @@ from dash import html -import feffery_utils_components as fuc -import feffery_antd_components as fac def render(*args, **kwargs): diff --git a/dash-fastapi-frontend/views/monitor/job/__init__.py b/dash-fastapi-frontend/views/monitor/job/__init__.py index 03dec390b2b4c1c2d070fd0307e91014aa9fb4ab..ff8ac380bdc74855dee1867731a0c1d282e80bb2 100644 --- a/dash-fastapi-frontend/views/monitor/job/__init__.py +++ b/dash-fastapi-frontend/views/monitor/job/__init__.py @@ -1,83 +1,27 @@ -from dash import dcc, html import feffery_antd_components as fac -import json - -import callbacks.monitor_c.job_c.job_c +from dash import dcc, html +from callbacks.monitor_c.job_c import job_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager from . import job_log -from api.job import get_job_list_api -from api.dict import query_dict_data_list_api def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - option = [] - option_table = [] - info = query_dict_data_list_api(dict_type='sys_job_group') - if info.get('code') == 200: - data = info.get('data') - option = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in data] - option_table = [ - dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for item - in data] - option_dict = {item.get('value'): item for item in option_table} - - job_params = dict(page_num=1, page_size=10) - table_info = get_job_list_api(job_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(checked=True) - else: - item['status'] = dict(checked=False) - if str(item.get('job_group')) in option_dict.keys(): - item['job_group'] = dict( - tag=option_dict.get(str(item.get('job_group'))).get('label'), - color=json.loads(option_dict.get(str(item.get('job_group'))).get('css_class')).get('color') - ) - item['key'] = str(item['job_id']) - item['operation'] = [ - { - 'title': '修改', - 'icon': 'antd-edit' - } if 'monitor:job:edit' in button_perms else None, - { - 'title': '删除', - 'icon': 'antd-delete' - } if 'monitor:job:remove' in button_perms else None, - { - 'title': '执行一次', - 'icon': 'antd-rocket' - } if 'monitor:job:changeStatus' in button_perms else None, - { - 'title': '任务详细', - 'icon': 'antd-eye' - } if 'monitor:job:query' in button_perms else None, - { - 'title': '调度日志', - 'icon': 'antd-history' - } if 'monitor:job:query' in button_perms else None - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = job_c.generate_job_table(query_params) return [ - dcc.Store(id='job-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='job-export-complete-judge-container'), # 绑定的导出组件 dcc.Download(id='job-export-container'), # 定时任务模块操作类型存储容器 dcc.Store(id='job-operations-store'), - dcc.Store(id='job-operations-store-bk'), - # 定时任务模块修改操作行key存储容器 - dcc.Store(id='job-edit-id-store'), + # 定时任务模块弹窗类型存储容器 + dcc.Store(id='job-modal_type-store'), + # 定时任务模块表单数据存储容器 + dcc.Store(id='job-form-store'), # 定时任务模块删除操作行key存储容器 dcc.Store(id='job-delete-ids-store'), # 定时任务日志管理模块操作类型存储容器 @@ -105,40 +49,31 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 210 - } + }, ), - label='任务名称' + label='任务名称', ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_job_group', id='job-job_group-select', placeholder='请选择任务组名', - options=option, style={ 'width': 210 - } + }, ), - label='任务组名' + label='任务组名', ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_job_status', id='job-status-select', placeholder='请选择任务状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '暂停', - 'value': '1' - } - ], style={ 'width': 200 - } + }, ), - label='任务状态' + label='任务状态', ), fac.AntdFormItem( fac.AntdButton( @@ -147,7 +82,7 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ) ), fac.AntdFormItem( @@ -156,20 +91,20 @@ def render(*args, **kwargs): id='job-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ) - ) + ), ], style={ 'paddingBottom': '10px' - } + }, ), ], layout='inline', ) ], id='job-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -188,14 +123,18 @@ def render(*args, **kwargs): ], id={ 'type': 'job-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'monitor:job:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -205,15 +144,19 @@ def render(*args, **kwargs): ], id={ 'type': 'job-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'monitor:job:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -223,15 +166,19 @@ def render(*args, **kwargs): ], id={ 'type': 'job-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:job:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -243,9 +190,13 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'monitor:job:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:export' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -255,20 +206,22 @@ def render(*args, **kwargs): ], id={ 'type': 'job-operation-log', - 'index': 'log' + 'index': 'log', }, style={ 'color': '#909399', 'background': '#f4f4f5', - 'border-color': '#d3d4d6' - } - ) if 'monitor:job:query' in button_perms else [], + 'border-color': '#d3d4d6', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:query' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -282,10 +235,10 @@ def render(*args, **kwargs): ), ], id='job-hidden', - shape='circle' + shape='circle', ), id='job-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -297,24 +250,22 @@ def render(*args, **kwargs): ), ], id='job-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -339,7 +290,7 @@ def render(*args, **kwargs): }, }, { - 'dataIndex': 'job_group', + 'dataIndex': 'job_group_tag', 'title': '任务组名', 'renderOptions': { 'renderType': 'tags' @@ -360,7 +311,7 @@ def render(*args, **kwargs): }, }, { - 'dataIndex': 'status', + 'dataIndex': 'status_checked', 'title': '状态', 'renderOptions': { 'renderType': 'switch' @@ -369,43 +320,36 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 120, 'renderOptions': { 'renderType': 'dropdown', 'dropdownProps': { 'title': '更多' - } + }, }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑定时任务表单modal fac.AntdModal( [ @@ -416,331 +360,268 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'job-form-value', - 'index': 'job_name' - }, + name='job_name', placeholder='请输入任务名称', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id={ 'type': 'job-form-label', 'index': 'job_name', - 'required': True + 'required': True, }, required=True, label='任务名称', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 3}, + wrapperCol={'span': 21}, ), - span=12 + span=24, ), + ], + gutter=5, + ), + fac.AntdRow( + [ fac.AntdCol( fac.AntdFormItem( - fac.AntdSelect( - id={ - 'type': 'job-form-value', - 'index': 'job_group' - }, + ApiSelect( + dict_type='sys_job_group', + name='job_group', placeholder='请选择任务分组', - options=option, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id={ 'type': 'job-form-label', 'index': 'job_group', - 'required': False + 'required': False, }, label='任务分组', - labelCol={ - 'span': 6 + labelCol={'span': 6}, + wrapperCol={'span': 18}, + ), + span=12, + ), + fac.AntdCol( + fac.AntdFormItem( + ApiSelect( + dict_type='sys_job_executor', + name='job_executor', + placeholder='请选择任务执行器', + style={'width': '100%'}, + ), + id={ + 'type': 'job-form-label', + 'index': 'job_executor', + 'required': False, }, - wrapperCol={ - 'span': 18 - } + label='任务执行器', + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), - span=12 - ) + span=12, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'job-form-value', - 'index': 'invoke_target' - }, + name='invoke_target', placeholder='请输入调用目标字符串', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id={ 'type': 'job-form-label', 'index': 'invoke_target', - 'required': True + 'required': True, }, required=True, label='调用方法', - labelCol={ - 'span': 3 - }, - wrapperCol={ - 'span': 21 - } + labelCol={'span': 3}, + wrapperCol={'span': 21}, ), - span=24 + span=24, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'job-form-value', - 'index': 'job_args' - }, + name='job_args', placeholder='请输入位置参数', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id={ 'type': 'job-form-label', 'index': 'job_args', - 'required': False + 'required': False, }, label='位置参数', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'job-form-value', - 'index': 'job_kwargs' - }, + name='job_kwargs', placeholder='请输入关键字参数', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id={ 'type': 'job-form-label', 'index': 'job_kwargs', - 'required': False + 'required': False, }, label='关键字参数', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), - span=12 - ) + span=12, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'job-form-value', - 'index': 'cron_expression' - }, + name='cron_expression', placeholder='请输入cron执行表达式', addonAfter=html.Div( [ fac.AntdSpace( [ - fac.AntdText('生成表达式'), - fac.AntdIcon(icon='antd-field-time') + fac.AntdText( + '生成表达式' + ), + fac.AntdIcon( + icon='antd-field-time' + ), ] ) ], - id='generate-expression' + id='generate-expression', ), - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id={ 'type': 'job-form-label', 'index': 'cron_expression', - 'required': True + 'required': True, }, required=True, label='cron表达式', - labelCol={ - 'span': 3 - }, - wrapperCol={ - 'span': 21 - } + labelCol={'span': 3}, + wrapperCol={'span': 21}, ), - span=24 + span=24, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdRadioGroup( - id={ - 'type': 'job-form-value', - 'index': 'misfire_policy' - }, + name='misfire_policy', options=[ { 'label': '立即执行', - 'value': '1' + 'value': '1', }, { 'label': '执行一次', - 'value': '2' + 'value': '2', }, { 'label': '放弃执行', - 'value': '3' - } + 'value': '3', + }, ], defaultValue='1', optionType='button', - buttonStyle='solid' + buttonStyle='solid', ), id={ 'type': 'job-form-label', 'index': 'misfire_policy', - 'required': False + 'required': False, }, label='执行策略', - labelCol={ - 'span': 3 - }, - wrapperCol={ - 'span': 21 - } + labelCol={'span': 3}, + wrapperCol={'span': 21}, ), - span=24 + span=24, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdRadioGroup( - id={ - 'type': 'job-form-value', - 'index': 'concurrent' - }, + name='concurrent', options=[ - { - 'label': '允许', - 'value': '0' - }, - { - 'label': '禁止', - 'value': '1' - } + {'label': '允许', 'value': '0'}, + {'label': '禁止', 'value': '1'}, ], defaultValue='1', optionType='button', - buttonStyle='solid' + buttonStyle='solid', ), id={ 'type': 'job-form-label', 'index': 'concurrent', - 'required': False + 'required': False, }, label='是否并发', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'job-form-value', - 'index': 'status' - }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '暂停', - 'value': '1' - }, - ], + ApiRadioGroup( + dict_type='sys_job_status', + name='status', defaultValue='0', ), id={ 'type': 'job-form-label', 'index': 'status', - 'required': False + 'required': False, }, label='状态', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), - span=12 + span=12, ), ], - gutter=5 - ) + gutter=5, + ), ], - style={ - 'marginRight': '30px' - } + id='job-form', + enableBatchControl=True, + style={'marginRight': '30px'}, ) ], id='job-modal', mask=False, width=850, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除定时任务二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='job-delete-text'), @@ -748,20 +629,18 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), - # 任务调度日志modal fac.AntdModal( - job_log.render(button_perms), + job_log.render(), id='job_to_job_log-modal', mask=False, maskClosable=False, width=1050, renderFooter=False, - okClickClose=False + okClickClose=False, ), - # 任务明细modal fac.AntdModal( [ @@ -774,100 +653,85 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'job_name' + 'index': 'job_name', } ), label='任务名称', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'job_name' - }, - labelCol={ - 'span': 8 + 'index': 'job_name', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=12 + span=24, ), + ], + gutter=10, + ), + fac.AntdRow( + [ fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'job_group' + 'index': 'job_group', } ), label='任务分组', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'job_group' - }, - labelCol={ - 'span': 8 + 'index': 'job_group', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), - ], - gutter=5 - ), - fac.AntdRow( - [ fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'job_executor' + 'index': 'job_executor', } ), label='任务执行器', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'job_executor' + 'index': 'job_executor', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), + ], + gutter=10, + ), + fac.AntdRow( + [ fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'invoke_target' + 'index': 'invoke_target', } ), label='调用目标函数', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'invoke_target' - }, - labelCol={ - 'span': 8 + 'index': 'invoke_target', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=12 + span=24, ), ], - gutter=5 + gutter=10, ), fac.AntdRow( [ @@ -876,49 +740,37 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'job_args' + 'index': 'job_args', } ), label='位置参数', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'job_args' - }, - labelCol={ - 'span': 8 + 'index': 'job_args', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'job_kwargs' + 'index': 'job_kwargs', } ), label='关键字参数', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'job_kwargs' - }, - labelCol={ - 'span': 8 + 'index': 'job_kwargs', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), ], - gutter=5 + gutter=10, ), fac.AntdRow( [ @@ -927,25 +779,22 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'cron_expression' + 'index': 'cron_expression', } ), label='cron表达式', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'cron_expression' - }, - labelCol={ - 'span': 4 + 'index': 'cron_expression', }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], + gutter=10, ), fac.AntdRow( [ @@ -954,49 +803,37 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'misfire_policy' + 'index': 'misfire_policy', } ), label='执行策略', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'misfire_policy' - }, - labelCol={ - 'span': 8 + 'index': 'misfire_policy', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'concurrent' + 'index': 'concurrent', } ), label='是否并发', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'concurrent' - }, - labelCol={ - 'span': 8 + 'index': 'concurrent', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), ], - gutter=5 + gutter=10, ), fac.AntdRow( [ @@ -1005,59 +842,42 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'status' + 'index': 'status', } ), label='任务状态', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'status' - }, - labelCol={ - 'span': 8 + 'index': 'status', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_detail-form-value', - 'index': 'create_time' + 'index': 'create_time', } ), label='创建时间', required=True, id={ 'type': 'job_detail-form-label', - 'index': 'create_time' - }, - labelCol={ - 'span': 8 + 'index': 'create_time', }, - wrapperCol={ - 'span': 16 - } ), - span=12 + span=12, ), ], + gutter=10, ), ], - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - }, - style={ - 'marginRight': '15px' - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, + style={'marginRight': '15px'}, ) ], id='job_detail-modal', diff --git a/dash-fastapi-frontend/views/monitor/job/job_log.py b/dash-fastapi-frontend/views/monitor/job/job_log.py index ba5f97d2086da75bc82e64c85e55d90880e0691d..0d20408a62134a442c8849b7e6a72e66b964269e 100644 --- a/dash-fastapi-frontend/views/monitor/job/job_log.py +++ b/dash-fastapi-frontend/views/monitor/job/job_log.py @@ -1,12 +1,12 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.monitor_c.job_c.job_log_c +from dash import dcc, html +from callbacks.monitor_c.job_c import job_log_c # noqa: F401 +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager -def render(button_perms): +def render(): return [ - dcc.Store(id='job_log-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='job_log-export-complete-judge-container'), # 绑定的导出组件 @@ -30,22 +30,26 @@ def render(button_perms): allowClear=True, style={ 'width': 240 - } + }, ), label='任务名称', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_job_group', id='job_log-job_group-select', placeholder='请选择任务组名', - options=[], style={ 'width': 240 - } + }, ), label='任务组名', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdSelect( @@ -54,29 +58,33 @@ def render(button_perms): options=[ { 'label': '成功', - 'value': '0' + 'value': '0', }, { 'label': '失败', - 'value': '1' - } + 'value': '1', + }, ], style={ 'width': 240 - } + }, ), label='执行状态', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='job_log-create_time-range', style={ 'width': 240 - } + }, ), label='执行时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -85,9 +93,11 @@ def render(button_perms): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -95,16 +105,18 @@ def render(button_perms): id='job_log-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='job_log-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -123,15 +135,19 @@ def render(button_perms): ], id={ 'type': 'job_log-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:job:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -141,14 +157,18 @@ def render(button_perms): ], id={ 'type': 'job_log-operation-button', - 'index': 'clear' + 'index': 'clear', }, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:job:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -160,15 +180,17 @@ def render(button_perms): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'monitor:job:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'monitor:job:export' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -182,10 +204,10 @@ def render(button_perms): ), ], id='job_log-hidden', - shape='circle' + shape='circle', ), id='job_log-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -197,24 +219,22 @@ def render(button_perms): ), ], id='job_log-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -239,7 +259,7 @@ def render(button_perms): }, }, { - 'dataIndex': 'job_group', + 'dataIndex': 'job_group_tag', 'title': '任务组名', 'renderOptions': { 'renderType': 'tags' @@ -260,7 +280,7 @@ def render(button_perms): }, }, { - 'dataIndex': 'status', + 'dataIndex': 'status_tag', 'title': '执行状态', 'renderOptions': { 'renderType': 'tags' @@ -276,10 +296,11 @@ def render(button_perms): { 'title': '操作', 'dataIndex': 'operation', + 'width': 120, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, @@ -288,28 +309,32 @@ def render(button_perms): 'pageSize': 10, 'current': 1, 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], + 'pageSizeOptions': [ + 10, + 30, + 50, + 100, + ], 'showQuickJumper': True, - 'total': 0 + 'total': 0, }, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 任务调度日志明细modal,表单项id使用字典类型,index与后端数据库字段一一对应 fac.AntdModal( [ @@ -322,49 +347,41 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_name' + 'index': 'job_name', } ), label='任务名称', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_name' - }, - labelCol={ - 'span': 8 + 'index': 'job_name', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_group' + 'index': 'job_group', } ), label='任务分组', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_group' + 'index': 'job_group', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -373,49 +390,41 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_executor' + 'index': 'job_executor', } ), label='任务执行器', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_executor' - }, - labelCol={ - 'span': 8 + 'index': 'job_executor', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'invoke_target' + 'index': 'invoke_target', } ), label='调用目标字符串', required=True, id={ 'type': 'job_log-form-label', - 'index': 'invoke_target' + 'index': 'invoke_target', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -424,49 +433,41 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_args' + 'index': 'job_args', } ), label='位置参数', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_args' - }, - labelCol={ - 'span': 8 + 'index': 'job_args', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_kwargs' + 'index': 'job_kwargs', } ), label='关键字参数', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_kwargs' - }, - labelCol={ - 'span': 8 + 'index': 'job_kwargs', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -475,23 +476,19 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_trigger' + 'index': 'job_trigger', } ), label='任务触发器', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_trigger' + 'index': 'job_trigger', }, - labelCol={ - 'span': 4 - }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], ), @@ -502,23 +499,19 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'job_message' + 'index': 'job_message', } ), label='日志信息', required=True, id={ 'type': 'job_log-form-label', - 'index': 'job_message' - }, - labelCol={ - 'span': 4 + 'index': 'job_message', }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], ), @@ -529,49 +522,41 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'status' + 'index': 'status', } ), label='执行状态', required=True, id={ 'type': 'job_log-form-label', - 'index': 'status' + 'index': 'status', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'create_time' + 'index': 'create_time', } ), label='执行时间', required=True, id={ 'type': 'job_log-form-label', - 'index': 'create_time' - }, - labelCol={ - 'span': 8 + 'index': 'create_time', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -580,36 +565,26 @@ def render(button_perms): fac.AntdText( id={ 'type': 'job_log-form-value', - 'index': 'exception_info' + 'index': 'exception_info', } ), label='异常信息', required=True, id={ 'type': 'job_log-form-label', - 'index': 'exception_info' + 'index': 'exception_info', }, - labelCol={ - 'span': 4 - }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], ), ], - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - }, - style={ - 'marginRight': '15px' - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, + style={'marginRight': '15px'}, ) ], id='job_log-modal', @@ -617,7 +592,6 @@ def render(button_perms): width=850, renderFooter=False, ), - # 删除任务调度日志二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='job_log-delete-text'), @@ -625,6 +599,6 @@ def render(button_perms): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/monitor/logininfor/__init__.py b/dash-fastapi-frontend/views/monitor/logininfor/__init__.py index ebe85e5c4f9fb051eb1c606e7fec8b5072479558..f47512212cb4c3459e487ff0ff007cc8e4cb5113 100644 --- a/dash-fastapi-frontend/views/monitor/logininfor/__init__.py +++ b/dash-fastapi-frontend/views/monitor/logininfor/__init__.py @@ -1,33 +1,17 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.monitor_c.logininfor_c -from api.log import get_login_log_list_api +from dash import dcc, html +from callbacks.monitor_c import logininfor_c +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - login_log_params = dict(page_num=1, page_size=10) - table_info = get_login_log_list_api(login_log_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='成功', color='blue') - else: - item['status'] = dict(tag='失败', color='volcano') - item['key'] = str(item['info_id']) + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = logininfor_c.generate_logininfor_table( + query_params + ) return [ - dcc.Store(id='login_log-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='login_log-export-complete-judge-container'), # 绑定的导出组件 @@ -55,10 +39,12 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='登录地址', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -68,41 +54,38 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='用户名称', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_common_status', id='login_log-status-select', placeholder='登录状态', - options=[ - { - 'label': '成功', - 'value': 0 - }, - { - 'label': '失败', - 'value': 1 - } - ], style={ 'width': 240 - } + }, ), label='状态', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='login_log-login_time-range', style={ 'width': 240 - } + }, ), label='登录时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -111,9 +94,11 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -121,16 +106,18 @@ def render(*args, **kwargs): id='login_log-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='login_log-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -149,15 +136,19 @@ def render(*args, **kwargs): ], id={ 'type': 'login_log-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:logininfor:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:logininfor:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -167,14 +158,18 @@ def render(*args, **kwargs): ], id={ 'type': 'login_log-operation-button', - 'index': 'clear' + 'index': 'clear', }, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:logininfor:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:logininfor:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -187,9 +182,13 @@ def render(*args, **kwargs): style={ 'color': '#74bcff', 'background': '#e8f4ff', - 'border-color': '#d1e9ff' - } - ) if 'monitor:logininfor:unlock' in button_perms else [], + 'border-color': '#d1e9ff', + }, + ) + if PermissionManager.check_perms( + 'monitor:logininfor:unlock' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -201,15 +200,17 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'monitor:logininfor:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'monitor:logininfor:export' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -223,10 +224,10 @@ def render(*args, **kwargs): ), ], id='login_log-hidden', - shape='circle' + shape='circle', ), id='login_log-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -238,24 +239,22 @@ def render(*args, **kwargs): ), ], id='login_log-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -308,7 +307,7 @@ def render(*args, **kwargs): }, }, { - 'dataIndex': 'status', + 'dataIndex': 'status_tag', 'title': '登录状态', 'renderOptions': { 'renderType': 'tags' @@ -333,35 +332,30 @@ def render(*args, **kwargs): rowSelectionWidth=50, bordered=True, sortOptions={ - 'sortDataIndexes': ['user_name', 'login_time'], - 'multiple': False - }, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total + 'sortDataIndexes': [ + 'user_name', + 'login_time', + ], + 'multiple': False, }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 删除操作日志二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='login_log-delete-text'), @@ -369,6 +363,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/monitor/online/__init__.py b/dash-fastapi-frontend/views/monitor/online/__init__.py index db80ae5fefd74c9e98a1c0b583bdfdedde77991b..4c924875c93c886513f9ec9766eb90d87d058321 100644 --- a/dash-fastapi-frontend/views/monitor/online/__init__.py +++ b/dash-fastapi-frontend/views/monitor/online/__init__.py @@ -1,36 +1,14 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.monitor_c.online_c -from api.online import get_online_list_api +from dash import dcc, html +from callbacks.monitor_c import online_c +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - online_params = dict(page_num=1, page_size=10) - table_info = get_online_list_api(online_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - item['key'] = str(item['session_id']) - item['operation'] = [ - { - 'content': '强退', - 'type': 'link', - 'icon': 'antd-delete' - } if 'monitor:online:forceLogout' in button_perms else {}, - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = online_c.generate_online_table(query_params) return [ - dcc.Store(id='online-button-perms-container', data=button_perms), # 在线用户模块操作类型存储容器 dcc.Store(id='online-operations-store'), dcc.Store(id='online-operations-store-bk'), @@ -57,9 +35,9 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 210 - } + }, ), - label='登录地址' + label='登录地址', ), fac.AntdFormItem( fac.AntdInput( @@ -69,9 +47,9 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 210 - } + }, ), - label='用户名称' + label='用户名称', ), fac.AntdFormItem( fac.AntdButton( @@ -80,7 +58,7 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ) ), fac.AntdFormItem( @@ -89,20 +67,20 @@ def render(*args, **kwargs): id='online-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ) - ) + ), ], style={ 'paddingBottom': '10px' - } + }, ), ], layout='inline', ) ], id='online-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -121,21 +99,23 @@ def render(*args, **kwargs): ], id={ 'type': 'online-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:online:batchLogout' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:online:batchLogout' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -149,10 +129,10 @@ def render(*args, **kwargs): ), ], id='online-hidden', - shape='circle' + shape='circle', ), id='online-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -164,24 +144,22 @@ def render(*args, **kwargs): ), ], id='online-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -192,7 +170,7 @@ def render(*args, **kwargs): data=table_data, columns=[ { - 'dataIndex': 'session_id', + 'dataIndex': 'token_id', 'title': '会话编号', 'renderOptions': { 'renderType': 'ellipsis' @@ -250,40 +228,33 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 120, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 强退会话二次确认modal fac.AntdModal( fac.AntdText('是否确认强退?', id='online-delete-text'), @@ -291,6 +262,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/monitor/operlog/__init__.py b/dash-fastapi-frontend/views/monitor/operlog/__init__.py index 6e7715784fba06d736ed8f1b0441b811c2b52da2..098fa082e49a5cb319be7d09e211981a3cfe5acb 100644 --- a/dash-fastapi-frontend/views/monitor/operlog/__init__.py +++ b/dash-fastapi-frontend/views/monitor/operlog/__init__.py @@ -1,59 +1,17 @@ -from dash import dcc, html import feffery_antd_components as fac -import json - -import callbacks.monitor_c.operlog_c -from api.log import get_operation_log_list_api -from api.dict import query_dict_data_list_api +from dash import dcc, html +from callbacks.monitor_c import operlog_c +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - option = [] - option_table = [] - info = query_dict_data_list_api(dict_type='sys_oper_type') - if info.get('code') == 200: - data = info.get('data') - option = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in data] - option_table = [ - dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for item - in data] - option_dict = {item.get('value'): item for item in option_table} - - operation_log_params = dict(page_num=1, page_size=10) - table_info = get_operation_log_list_api(operation_log_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == 0: - item['status'] = dict(tag='成功', color='blue') - else: - item['status'] = dict(tag='失败', color='volcano') - if str(item.get('business_type')) in option_dict.keys(): - item['business_type'] = dict( - tag=option_dict.get(str(item.get('business_type'))).get('label'), - color=json.loads(option_dict.get(str(item.get('business_type'))).get('css_class')).get('color') - ) - item['key'] = str(item['oper_id']) - item['cost_time'] = f"{item['cost_time']}毫秒" - item['operation'] = [ - { - 'content': '详情', - 'type': 'link', - 'icon': 'antd-eye' - } if 'monitor:operlog:query' in button_perms else {}, - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = operlog_c.generate_operlog_table( + query_params + ) return [ - dcc.Store(id='operation_log-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='operation_log-export-complete-judge-container'), # 绑定的导出组件 @@ -81,10 +39,12 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='系统模块', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -94,53 +54,52 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='操作人员', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_oper_type', id='operation_log-business_type-select', placeholder='操作类型', - options=option, style={ 'width': 240 - } + }, ), label='类型', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_common_status', id='operation_log-status-select', placeholder='操作状态', - options=[ - { - 'label': '成功', - 'value': 0 - }, - { - 'label': '失败', - 'value': 1 - } - ], style={ 'width': 240 - } + }, ), label='状态', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='operation_log-oper_time-range', style={ 'width': 240 - } + }, ), label='操作时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -149,9 +108,11 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -159,16 +120,18 @@ def render(*args, **kwargs): id='operation_log-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='operation_log-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -187,15 +150,19 @@ def render(*args, **kwargs): ], id={ 'type': 'operation_log-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:operlog:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:operlog:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -205,14 +172,18 @@ def render(*args, **kwargs): ], id={ 'type': 'operation_log-operation-button', - 'index': 'clear' + 'index': 'clear', }, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'monitor:operlog:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'monitor:operlog:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -224,15 +195,17 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'monitor:operlog:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'monitor:operlog:export' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -246,10 +219,10 @@ def render(*args, **kwargs): ), ], id='operation_log-hidden', - shape='circle' + shape='circle', ), id='operation_log-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -261,24 +234,22 @@ def render(*args, **kwargs): ), ], id='operation_log-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -303,7 +274,7 @@ def render(*args, **kwargs): }, }, { - 'dataIndex': 'business_type', + 'dataIndex': 'business_type_tag', 'title': '操作类型', 'renderOptions': { 'renderType': 'tags' @@ -331,7 +302,7 @@ def render(*args, **kwargs): }, }, { - 'dataIndex': 'status', + 'dataIndex': 'status_tag', 'title': '操作状态', 'renderOptions': { 'renderType': 'tags' @@ -354,44 +325,40 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 120, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, sortOptions={ - 'sortDataIndexes': ['oper_name', 'oper_time'], - 'multiple': False - }, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total + 'sortDataIndexes': [ + 'oper_name', + 'oper_time', + ], + 'multiple': False, }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 操作日志明细modal fac.AntdModal( [ @@ -404,49 +371,41 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'title' + 'index': 'title', } ), label='操作模块', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'title' + 'index': 'title', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'oper_url' + 'index': 'oper_url', } ), label='请求地址', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'oper_url' - }, - labelCol={ - 'span': 8 + 'index': 'oper_url', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -455,49 +414,41 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'login_info' + 'index': 'login_info', } ), label='登录信息', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'login_info' + 'index': 'login_info', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'request_method' + 'index': 'request_method', } ), label='请求方式', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'request_method' - }, - labelCol={ - 'span': 8 + 'index': 'request_method', }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -506,23 +457,19 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'method' + 'index': 'method', } ), label='操作方法', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'method' - }, - labelCol={ - 'span': 4 + 'index': 'method', }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], ), @@ -533,23 +480,19 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'oper_param' + 'index': 'oper_param', } ), label='请求参数', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'oper_param' + 'index': 'oper_param', }, - labelCol={ - 'span': 4 - }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], ), @@ -560,23 +503,19 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'json_result' + 'index': 'json_result', } ), label='返回参数', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'json_result' - }, - labelCol={ - 'span': 4 + 'index': 'json_result', }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - span=24 + span=24, ), ], ), @@ -587,83 +526,65 @@ def render(*args, **kwargs): fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'status' + 'index': 'status', } ), label='操作状态', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'status' + 'index': 'status', }, - labelCol={ - 'span': 12 - }, - wrapperCol={ - 'span': 12 - } + labelCol={'span': 12}, + wrapperCol={'span': 12}, ), - span=8 + span=8, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'cost_time' + 'index': 'cost_time', } ), label='消耗时间', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'cost_time' - }, - labelCol={ - 'span': 12 + 'index': 'cost_time', }, - wrapperCol={ - 'span': 12 - } + labelCol={'span': 12}, + wrapperCol={'span': 12}, ), - span=6 + span=6, ), fac.AntdCol( fac.AntdFormItem( fac.AntdText( id={ 'type': 'operation_log-form-value', - 'index': 'oper_time' + 'index': 'oper_time', } ), label='操作时间', required=True, id={ 'type': 'operation_log-form-label', - 'index': 'oper_time' + 'index': 'oper_time', }, - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, ), - span=10 + span=10, ), ], - gutter=5 + gutter=5, ), ], - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - }, - style={ - 'marginRight': '15px' - } + labelCol={'span': 8}, + wrapperCol={'span': 16}, + style={'marginRight': '15px'}, ) ], id='operation_log-modal', @@ -671,7 +592,6 @@ def render(*args, **kwargs): width=850, renderFooter=False, ), - # 删除操作日志二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='operation_log-delete-text'), @@ -679,6 +599,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/monitor/server/__init__.py b/dash-fastapi-frontend/views/monitor/server/__init__.py index b88944e5861c9b34d6fa75aa075a69c3e502d0e8..a5fc74e345183f5f33dd0fd715e5a930d146e114 100644 --- a/dash-fastapi-frontend/views/monitor/server/__init__.py +++ b/dash-fastapi-frontend/views/monitor/server/__init__.py @@ -1,45 +1,46 @@ -from dash import html, dcc import feffery_antd_components as fac - -from api.server import get_server_statistical_info_api +from dash import html +from api.monitor.server import ServerApi def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - cpu = {} - mem = {} - sys = {} - py = {} - sys_files = [] - server_info_res = get_server_statistical_info_api() - if server_info_res.get('code') == 200: - server_info = server_info_res.get('data') - cpu = [dict(name=key, value=value) for key, value in server_info.get('cpu').items()] - for item in cpu: - if item.get('name') == 'cpu_num': - item['name'] = '核心数' - if item.get('name') == 'used': - item['name'] = '用户使用率' - if item.get('name') == 'sys': - item['name'] = '系统使用率' - if item.get('name') == 'free': - item['name'] = '当前空闲率' - mem = [dict(name=key, value=value) for key, value in server_info.get('mem').items()] - for item in mem: - if item.get('name') == 'total': - item['name'] = '总内存' - if item.get('name') == 'used': - item['name'] = '已用内存' - if item.get('name') == 'free': - item['name'] = '剩余内存' - if item.get('name') == 'usage': - item['name'] = '使用率' - sys = server_info.get('sys') - py = server_info.get('py') - sys_files = server_info.get('sys_files') + server_info_res = ServerApi.get_server() + server_info = server_info_res.get('data') + cpu = [ + dict(name=key, value=value) + for key, value in server_info.get('cpu').items() + ] + for item in cpu: + if item.get('name') == 'cpu_num': + item['name'] = '核心数' + if item.get('name') == 'used': + item['name'] = '用户使用率' + item['value'] = f"{item['value']}%" + if item.get('name') == 'sys': + item['name'] = '系统使用率' + item['value'] = f"{item['value']}%" + if item.get('name') == 'free': + item['name'] = '当前空闲率' + item['value'] = f"{item['value']}%" + mem = [ + dict(name=key, value=value) + for key, value in server_info.get('mem').items() + ] + for item in mem: + if item.get('name') == 'total': + item['name'] = '总内存' + if item.get('name') == 'used': + item['name'] = '已用内存' + if item.get('name') == 'free': + item['name'] = '剩余内存' + if item.get('name') == 'usage': + item['name'] = '使用率' + item['value'] = f"{item['value']}%" + sys = server_info.get('sys') + py = server_info.get('py') + sys_files = server_info.get('sys_files') return [ - dcc.Store(id='server-button-perms-container', data=button_perms), html.Div( [ fac.AntdRow( @@ -66,23 +67,23 @@ def render(*args, **kwargs): }, ], bordered=False, - pagination={ - 'hideOnSinglePage': True - } + pagination={'hideOnSinglePage': True}, ) ], title=html.Div( [ fac.AntdIcon(icon='antd-box-plot'), - fac.AntdText('CPU', style={'marginLeft': '10px'}) + fac.AntdText( + 'CPU', style={'marginLeft': '10px'} + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdCard( @@ -106,26 +107,26 @@ def render(*args, **kwargs): }, ], bordered=False, - pagination={ - 'hideOnSinglePage': True - } + pagination={'hideOnSinglePage': True}, ) ], title=html.Div( [ fac.AntdIcon(icon='antd-file-text'), - fac.AntdText('内存', style={'marginLeft': '10px'}) + fac.AntdText( + '内存', style={'marginLeft': '10px'} + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=12 + span=12, ), ], - gutter=20 + gutter=20, ), fac.AntdRow( [ @@ -136,53 +137,51 @@ def render(*args, **kwargs): [ fac.AntdDescriptionItem( sys.get('computer_name'), - label='服务器名称' + label='服务器名称', ), fac.AntdDescriptionItem( sys.get('os_name'), - label='操作系统' + label='操作系统', ), fac.AntdDescriptionItem( sys.get('computer_ip'), - label='服务器IP' + label='服务器IP', ), fac.AntdDescriptionItem( sys.get('os_arch'), - label='系统架构' + label='系统架构', ), ], bordered=True, column=2, - labelStyle={ - 'textAlign': 'center' - }, + labelStyle={'textAlign': 'center'}, style={ 'width': '100%', 'textAlign': 'center', 'marginLeft': '20px', - 'marginRight': '20px' - } + 'marginRight': '20px', + }, ) ], title=html.Div( [ fac.AntdIcon(icon='antd-desktop'), - fac.AntdText('服务器信息', style={'marginLeft': '10px'}) + fac.AntdText( + '服务器信息', + style={'marginLeft': '10px'}, + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=24 + span=24, ), ], gutter=20, - style={ - 'marginTop': '20px', - 'marginBottom': '20px' - } + style={'marginTop': '20px', 'marginBottom': '20px'}, ), fac.AntdRow( [ @@ -193,61 +192,58 @@ def render(*args, **kwargs): [ fac.AntdDescriptionItem( py.get('name'), - label='Python名称' + label='Python名称', ), fac.AntdDescriptionItem( py.get('version'), - label='Python版本' + label='Python版本', ), fac.AntdDescriptionItem( py.get('start_time'), - label='启动时间' + label='启动时间', ), fac.AntdDescriptionItem( py.get('run_time'), - label='运行时长' + label='运行时长', ), fac.AntdDescriptionItem( - py.get('home'), - label='安装路径' + py.get('home'), label='安装路径' ), fac.AntdDescriptionItem( - py.get('project_dir'), - label='项目路径' + sys.get('user_dir'), + label='项目路径', ), ], bordered=True, column=2, - labelStyle={ - 'textAlign': 'center' - }, + labelStyle={'textAlign': 'center'}, style={ 'width': '100%', 'textAlign': 'center', 'marginLeft': '20px', - 'marginRight': '20px' - } + 'marginRight': '20px', + }, ) ], title=html.Div( [ fac.AntdIcon(icon='antd-filter'), - fac.AntdText('Python解释器信息', style={'marginLeft': '10px'}) + fac.AntdText( + 'Python解释器信息', + style={'marginLeft': '10px'}, + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=24 + span=24, ), ], gutter=20, - style={ - 'marginTop': '20px', - 'marginBottom': '20px' - } + style={'marginTop': '20px', 'marginBottom': '20px'}, ), fac.AntdRow( [ @@ -272,8 +268,8 @@ def render(*args, **kwargs): }, }, { - 'dataIndex': 'disk_name', - 'title': '盘符类型', + 'dataIndex': 'type_name', + 'title': '盘符名称', 'renderOptions': { 'renderType': 'ellipsis' }, @@ -308,31 +304,29 @@ def render(*args, **kwargs): }, ], bordered=False, - pagination={ - 'hideOnSinglePage': True - } + pagination={'hideOnSinglePage': True}, ) ], title=html.Div( [ fac.AntdIcon(icon='antd-file-sync'), - fac.AntdText('磁盘状态', style={'marginLeft': '10px'}) + fac.AntdText( + '磁盘状态', + style={'marginLeft': '10px'}, + ), ] ), size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=24 + span=24, ), ], gutter=20, - style={ - 'marginTop': '20px', - 'marginBottom': '20px' - } + style={'marginTop': '20px', 'marginBottom': '20px'}, ), ], - ) + ), ] diff --git a/dash-fastapi-frontend/views/page_404.py b/dash-fastapi-frontend/views/page_404.py index 654bf70113f16dfbcf5d1bbd97e271e2f82a97c7..ef0a10d407e0748b1eec5e839b7e99711b8c13d5 100644 --- a/dash-fastapi-frontend/views/page_404.py +++ b/dash-fastapi-frontend/views/page_404.py @@ -1,9 +1,8 @@ -from dash import html import feffery_antd_components as fac +from dash import html -def render_content(): - +def render(): return html.Div( [ html.Div( @@ -12,27 +11,19 @@ def render_content(): status='404', title='页面不存在', subTitle='检查您的网址输入是否正确', - style={ - 'paddingBottom': 0, - 'paddingTop': 0 - } + style={'paddingBottom': 0, 'paddingTop': 0}, ), fac.AntdButton( - '回到首页', - type='link', - href='/', - target='_self' - ) + '回到首页', type='link', href='/', target='_self' + ), ], - style={ - 'textAlign': 'center' - } + style={'textAlign': 'center'}, ) ], style={ 'height': '100vh', 'display': 'flex', 'alignItems': 'center', - 'justifyContent': 'center' - } + 'justifyContent': 'center', + }, ) diff --git a/dash-fastapi-frontend/views/register.py b/dash-fastapi-frontend/views/register.py new file mode 100644 index 0000000000000000000000000000000000000000..eac9fdcd56546b3b1a94ef2e9573b1462f6d1ed4 --- /dev/null +++ b/dash-fastapi-frontend/views/register.py @@ -0,0 +1,132 @@ +import feffery_antd_components as fac +import feffery_utils_components as fuc +from dash import dcc, html +from callbacks import register_c # noqa: F401 + + +def render(): + return html.Div( + [ + fac.AntdCard( + [ + dcc.Store(id='register-success-container'), + dcc.Store(id='register-captcha_image-session_id-container'), + fuc.FefferyKeyPress( + id='register-keyboard-enter-submit', + keys='enter', + ), + fac.AntdForm( + fac.AntdFlex( + [ + fac.AntdFormItem( + fac.AntdInput( + placeholder='请输入用户名', + id='register-username', + size='large', + prefix=fac.AntdIcon(icon='antd-user'), + ), + id='register-username-form-item', + ), + fac.AntdFormItem( + fac.AntdInput( + placeholder='请输入密码', + id='register-password', + mode='password', + passwordUseMd5=True, + size='large', + prefix=fac.AntdIcon(icon='antd-lock'), + ), + id='register-password-form-item', + ), + fac.AntdFormItem( + fac.AntdInput( + placeholder='请再次输入密码', + id='register-confirm_password', + mode='password', + passwordUseMd5=True, + size='large', + prefix=fac.AntdIcon(icon='antd-lock'), + ), + id='register-confirm_password-form-item', + ), + html.Div( + fac.AntdFormItem( + fac.AntdFlex( + [ + fac.AntdInput( + placeholder='请输入验证码', + id='register-captcha', + size='large', + prefix=fac.AntdIcon( + icon='antd-check-circle' + ), + ), + html.Div( + [ + fac.AntdImage( + id='register-captcha-image', + src='', + height=39.6, + width=100, + preview=False, + style={ + 'border': '1px solid #d9d9d9', + 'borderRadius': '8px', + }, + ) + ], + id='register-captcha-image-container', + ), + ], + align='center', + gap='small', + ), + id='register-captcha-form-item', + ), + id='register-captcha-row-container', + ), + fac.AntdFormItem( + fac.AntdButton( + '注册', + id='register-submit', + type='primary', + block=True, + size='large', + ), + style={'marginTop': '20px'}, + ), + ], + vertical=True, + ), + layout='vertical', + style={'width': '100%'}, + ), + ], + id='register-form-container', + title='用户注册', + hoverable=True, + extraLink={ + 'content': '返回登录', + 'href': '/login', + 'target': '_self', + 'style': {'fontSize': '16px'}, + }, + headStyle={ + 'fontWeight': 'bold', + 'textAlign': 'center', + 'fontSize': '30px', + }, + style={ + 'position': 'fixed', + 'top': '16%', + 'left': '50%', + 'width': '480px', + 'minWidth': '420px', + 'maxWidth': '75vw', + 'padding': '0px 30px', + 'transform': 'translateX(-50%)', + }, + ), + ], + id='register-page', + ) diff --git a/dash-fastapi-frontend/views/system/__init__.py b/dash-fastapi-frontend/views/system/__init__.py index 750ffd1090776c35b113cb49ff27e8dcd988c4df..40c107c717cd0ba27c1962cdfe53c8ed70ae968f 100644 --- a/dash-fastapi-frontend/views/system/__init__.py +++ b/dash-fastapi-frontend/views/system/__init__.py @@ -1,10 +1,10 @@ from . import ( - user, - role, - menu, - dept, - post, - dict, - config, - notice + config, # noqa: F401 + dept, # noqa: F401 + dict, # noqa: F401 + menu, # noqa: F401 + notice, # noqa: F401 + post, # noqa: F401 + role, # noqa: F401 + user, # noqa: F401 ) diff --git a/dash-fastapi-frontend/views/system/config/__init__.py b/dash-fastapi-frontend/views/system/config/__init__.py index 2b5093c2041234c6980550d65d3442898b08af1f..484b9f367a800c34a95035ed17436b0c690b5657 100644 --- a/dash-fastapi-frontend/views/system/config/__init__.py +++ b/dash-fastapi-frontend/views/system/config/__init__.py @@ -1,54 +1,26 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.system_c.config_c -from api.config import get_config_list_api +from dash import dcc, html +from callbacks.system_c import config_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - config_params = dict(page_num=1, page_size=10) - table_info = get_config_list_api(config_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['config_type'] == 'Y': - item['config_type'] = dict(tag='是', color='blue') - else: - item['config_type'] = dict(tag='否', color='volcano') - item['key'] = str(item['config_id']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:config:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:config:remove' in button_perms else {}, - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = config_c.generate_config_table(query_params) return [ - dcc.Store(id='config-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='config-export-complete-judge-container'), # 绑定的导出组件 dcc.Download(id='config-export-container'), # 参数管理模块操作类型存储容器 dcc.Store(id='config-operations-store'), - dcc.Store(id='config-operations-store-bk'), - # 参数管理模块修改操作行key存储容器 - dcc.Store(id='config-edit-id-store'), + # 参数管理模块弹窗类型存储容器 + dcc.Store(id='config-modal_type-store'), + # 参数管理模块表单数据存储容器 + dcc.Store(id='config-form-store'), # 参数管理模块删除操作行key存储容器 dcc.Store(id='config-delete-ids-store'), fac.AntdRow( @@ -70,10 +42,12 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 235 - } + }, ), label='参数名称', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -83,41 +57,38 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 235 - } + }, ), label='参数键名', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_yes_no', id='config-config_type-select', placeholder='系统内置', - options=[ - { - 'label': '是', - 'value': 'Y' - }, - { - 'label': '否', - 'value': 'N' - } - ], style={ 'width': 235 - } + }, ), label='系统内置', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='config-create_time-range', style={ 'width': 235 - } + }, ), label='创建时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -126,9 +97,11 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -136,16 +109,18 @@ def render(*args, **kwargs): id='config-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='config-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -164,14 +139,18 @@ def render(*args, **kwargs): ], id={ 'type': 'config-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:config:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:config:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -181,15 +160,19 @@ def render(*args, **kwargs): ], id={ 'type': 'config-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'system:config:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'system:config:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -199,15 +182,19 @@ def render(*args, **kwargs): ], id={ 'type': 'config-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:config:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:config:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -219,9 +206,13 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'system:config:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'system:config:export' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -234,15 +225,17 @@ def render(*args, **kwargs): 'color': '#ff9292', 'background': '#ffeded', 'border-color': '#ffdbdb', - 'marginRight': '10px' - } - ) if 'system:config:edit' in button_perms else [], + 'marginRight': '10px', + }, + ) + if PermissionManager.check_perms( + 'system:config:remove' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -256,10 +249,10 @@ def render(*args, **kwargs): ), ], id='config-hidden', - shape='circle' + shape='circle', ), id='config-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -271,24 +264,22 @@ def render(*args, **kwargs): ), ], id='config-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -350,40 +341,33 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 170, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑参数配置表单modal fac.AntdModal( [ @@ -394,25 +378,20 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'config-form-value', - 'index': 'config_name' - }, + name='config_name', placeholder='请输入参数名称', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='参数名称', required=True, id={ 'type': 'config-form-label', 'index': 'config_name', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -421,25 +400,20 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'config-form-value', - 'index': 'config_key' - }, + name='config_key', placeholder='请输入参数键名', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='参数键名', required=True, id={ 'type': 'config-form-label', 'index': 'config_key', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -448,25 +422,20 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'config-form-value', - 'index': 'config_value' - }, + name='config_value', placeholder='请输入参数键值', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='参数键值', required=True, id={ 'type': 'config-form-label', 'index': 'config_value', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -474,34 +443,20 @@ def render(*args, **kwargs): [ fac.AntdCol( fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'config-form-value', - 'index': 'config_type' - }, - options=[ - { - 'label': '是', - 'value': 'Y' - }, - { - 'label': '否', - 'value': 'N' - }, - ], - defaultValue='0', - style={ - 'width': 350 - } + ApiRadioGroup( + dict_type='sys_yes_no', + name='config_type', + defaultValue='Y', + style={'width': 350}, ), label='系统内置', id={ 'type': 'config-form-label', 'index': 'config_type', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), @@ -510,44 +465,36 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'config-form-value', - 'index': 'remark' - }, + name='remark', placeholder='请输入内容', allowClear=True, mode='text-area', - style={ - 'width': 350 - } + style={'width': 350}, ), label='备注', id={ 'type': 'config-form-label', 'index': 'remark', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), ], - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id='config-form', + enableBatchControl=True, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ) ], id='config-modal', mask=False, width=580, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除参数配置二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='config-delete-text'), @@ -555,6 +502,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True - ) + centered=True, + ), ] diff --git a/dash-fastapi-frontend/views/system/dept/__init__.py b/dash-fastapi-frontend/views/system/dept/__init__.py index 5983d0342477b890cbd8e49ed050858c025a4c5b..f499d2cf966f2d4ccf2b99a15bd6bfa184989590 100644 --- a/dash-fastapi-frontend/views/system/dept/__init__.py +++ b/dash-fastapi-frontend/views/system/dept/__init__.py @@ -1,78 +1,24 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.system_c.dept_c -from api.dept import get_dept_list_api -from utils.tree_tool import list_to_tree +from dash import dcc, html +from callbacks.system_c import dept_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - table_data_new = [] - default_expanded_row_keys = [] - table_info = get_dept_list_api({}) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - for item in table_data: - default_expanded_row_keys.append(str(item['dept_id'])) - item['key'] = str(item['dept_id']) - if item['parent_id'] == 0: - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dept:edit' in button_perms else {}, - { - 'content': '新增', - 'type': 'link', - 'icon': 'antd-plus' - } if 'system:dept:add' in button_perms else {}, - ] - elif item['status'] == '1': - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dept:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dept:remove' in button_perms else {}, - ] - else: - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dept:edit' in button_perms else {}, - { - 'content': '新增', - 'type': 'link', - 'icon': 'antd-plus' - } if 'system:dept:add' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dept:remove' in button_perms else {}, - ] - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - table_data_new = list_to_tree(table_data, 'dept_id', 'parent_id') + query_params = {} + table_data, default_expanded_row_keys = dept_c.generate_dept_table( + query_params + ) return [ - dcc.Store(id='dept-button-perms-container', data=button_perms), # 部门管理模块操作类型存储容器 dcc.Store(id='dept-operations-store'), - dcc.Store(id='dept-operations-store-bk'), - # 部门管理模块修改操作行key存储容器 - dcc.Store(id='dept-edit-id-store'), + # 部门管理模块弹窗类型存储容器 + dcc.Store(id='dept-modal_type-store'), + # 部门管理模块表单数据存储容器 + dcc.Store(id='dept-form-store'), # 部门管理模块删除操作行key存储容器 dcc.Store(id='dept-delete-ids-store'), fac.AntdRow( @@ -96,29 +42,20 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), - label='部门名称' + label='部门名称', ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='dept-status-select', placeholder='部门状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 240 - } + }, ), - label='部门状态' + label='部门状态', ), fac.AntdFormItem( fac.AntdButton( @@ -127,7 +64,7 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ) ), fac.AntdFormItem( @@ -136,13 +73,13 @@ def render(*args, **kwargs): id='dept-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ) - ) + ), ], style={ 'paddingBottom': '10px' - } + }, ), ], layout='inline', @@ -168,14 +105,18 @@ def render(*args, **kwargs): ], id={ 'type': 'dept-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:dept:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:dept:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -187,15 +128,13 @@ def render(*args, **kwargs): style={ 'color': '#909399', 'background': '#f4f4f5', - 'border-color': '#d3d4d6' - } + 'border-color': '#d3d4d6', + }, ), ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -209,10 +148,10 @@ def render(*args, **kwargs): ), ], id='dept-hidden', - shape='circle' + shape='circle', ), id='dept-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -224,24 +163,22 @@ def render(*args, **kwargs): ), ], id='dept-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -249,7 +186,7 @@ def render(*args, **kwargs): fac.AntdSpin( fac.AntdTable( id='dept-list-table', - data=table_data_new, + data=table_data, columns=[ { 'dataIndex': 'dept_id', @@ -257,7 +194,7 @@ def render(*args, **kwargs): 'renderOptions': { 'renderType': 'ellipsis' }, - 'hidden': True + 'hidden': True, }, { 'dataIndex': 'dept_name', @@ -290,10 +227,11 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 240, 'renderOptions': { 'renderType': 'button' }, - } + }, ], bordered=True, pagination={ @@ -303,21 +241,20 @@ def render(*args, **kwargs): style={ 'width': '100%', 'padding-right': '10px', - 'padding-bottom': '20px' - } + 'padding-bottom': '20px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑部门表单modal fac.AntdModal( [ @@ -330,36 +267,28 @@ def render(*args, **kwargs): [ fac.AntdFormItem( fac.AntdTreeSelect( - id={ - 'type': 'dept-form-value', - 'index': 'parent_id' - }, + id='dept-tree-select', + name='parent_id', placeholder='请选择上级部门', treeData=[], treeNodeFilterProp='title', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='上级部门', required=True, id={ 'type': 'dept-form-label', 'index': 'parent_id', - 'required': True + 'required': True, }, - labelCol={ - 'span': 4 - }, - wrapperCol={ - 'span': 20 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), ], id='dept-parent_id-div', - hidden=False + hidden=False, ), - span=24 + span=24, ), ] ), @@ -368,177 +297,133 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dept-form-value', - 'index': 'dept_name' - }, + name='dept_name', placeholder='请输入部门名称', allowClear=True, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='部门名称', required=True, id={ 'type': 'dept-form-label', 'index': 'dept_name', - 'required': True - } + 'required': True, + }, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdInputNumber( - id={ - 'type': 'dept-form-value', - 'index': 'order_num' - }, + name='order_num', min=0, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='显示顺序', required=True, id={ 'type': 'dept-form-label', 'index': 'order_num', - 'required': True - } + 'required': True, + }, ), - span=12 - ) + span=12, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dept-form-value', - 'index': 'leader' - }, + name='leader', placeholder='请输入负责人', allowClear=True, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='负责人', id={ 'type': 'dept-form-label', 'index': 'leader', - 'required': False - } + 'required': False, + }, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dept-form-value', - 'index': 'phone' - }, + name='phone', placeholder='请输入联系电话', allowClear=True, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='联系电话', id={ 'type': 'dept-form-label', 'index': 'phone', - 'required': False - } + 'required': False, + }, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dept-form-value', - 'index': 'email' - }, + name='email', placeholder='请输入邮箱', allowClear=True, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='邮箱', id={ 'type': 'dept-form-label', 'index': 'email', - 'required': False - } + 'required': False, + }, ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'dept-form-value', - 'index': 'status' - }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], + ApiRadioGroup( + dict_type='sys_normal_disable', + name='status', defaultValue='0', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), label='部门状态', id={ 'type': 'dept-form-label', 'index': 'status', - 'required': False - } + 'required': False, + }, ), - span=12 + span=12, ), ], - gutter=5 + gutter=5, ), ], - labelCol={ - 'span': 8 - }, - wrapperCol={ - 'span': 16 - }, - style={ - 'marginRight': '15px' - } + id='dept-form', + enableBatchControl=True, + labelCol={'span': 8}, + wrapperCol={'span': 16}, + style={'marginRight': '15px'}, ) ], id='dept-modal', mask=False, width=650, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除部门二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='dept-delete-text'), @@ -546,6 +431,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/system/dict/__init__.py b/dash-fastapi-frontend/views/system/dict/__init__.py index ac8824a0b815a4232925fddc946a20ddb4f54285..6ba8f60392292ec9809311c2d17e6138a8e20554 100644 --- a/dash-fastapi-frontend/views/system/dict/__init__.py +++ b/dash-fastapi-frontend/views/system/dict/__init__.py @@ -1,62 +1,30 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.system_c.dict_c.dict_c +from dash import dcc, html +from callbacks.system_c.dict_c import dict_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager from . import dict_data -from api.dict import get_dict_type_list_api def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - dict_type_params = dict(page_num=1, page_size=10) - table_info = get_dict_type_list_api(dict_type_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['dict_id']) - item['dict_type'] = { - 'content': item['dict_type'], - 'type': 'link', - } - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:dict:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:dict:remove' in button_perms else {}, - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = dict_c.generate_dict_type_table(query_params) return [ - dcc.Store(id='dict_type-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='dict_type-export-complete-judge-container'), # 绑定的导出组件 dcc.Download(id='dict_type-export-container'), # 字典管理模块操作类型存储容器 dcc.Store(id='dict_type-operations-store'), - dcc.Store(id='dict_type-operations-store-bk'), dcc.Store(id='dict_data-operations-store'), - dcc.Store(id='dict_data-operations-store-bk'), - # 字典管理模块修改操作行key存储容器 - dcc.Store(id='dict_type-edit-id-store'), - dcc.Store(id='dict_data-edit-id-store'), + # 字典管理模块弹窗类型存储容器 + dcc.Store(id='dict_type-modal_type-store'), + dcc.Store(id='dict_data-modal_type-store'), + # 字典管理模块表单数据存储容器 + dcc.Store(id='dict_type-form-store'), + dcc.Store(id='dict_data-form-store'), # 字典管理模块删除操作行key存储容器 dcc.Store(id='dict_type-delete-ids-store'), dcc.Store(id='dict_data-delete-ids-store'), @@ -79,10 +47,12 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='字典名称', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -92,41 +62,38 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='字典类型', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='dict_type-status-select', placeholder='字典状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 240 - } + }, ), label='状态', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='dict_type-create_time-range', style={ 'width': 240 - } + }, ), label='创建时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -135,9 +102,11 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -145,16 +114,18 @@ def render(*args, **kwargs): id='dict_type-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='dict_type-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -173,14 +144,18 @@ def render(*args, **kwargs): ], id={ 'type': 'dict_type-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:dict:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:dict:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -190,15 +165,19 @@ def render(*args, **kwargs): ], id={ 'type': 'dict_type-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'system:dict:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'system:dict:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -208,15 +187,19 @@ def render(*args, **kwargs): ], id={ 'type': 'dict_type-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:dict:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:dict:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -228,9 +211,13 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'system:dict:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'system:dict:export' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -242,15 +229,17 @@ def render(*args, **kwargs): style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:dict:edit' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:dict:remove' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -264,10 +253,10 @@ def render(*args, **kwargs): ), ], id='dict_type-hidden', - shape='circle' + shape='circle', ), id='dict_type-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -279,24 +268,22 @@ def render(*args, **kwargs): ), ], id='dict_type-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -351,40 +338,33 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 170, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑字典类型表单modal fac.AntdModal( [ @@ -395,25 +375,20 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_type-form-value', - 'index': 'dict_name' - }, + name='dict_name', placeholder='请输入字典名称', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='字典名称', required=True, id={ 'type': 'dict_type-form-label', 'index': 'dict_name', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -422,25 +397,20 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_type-form-value', - 'index': 'dict_type' - }, + name='dict_type', placeholder='请输入字典类型', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='字典类型', required=True, id={ 'type': 'dict_type-form-label', 'index': 'dict_type', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -448,34 +418,20 @@ def render(*args, **kwargs): [ fac.AntdCol( fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'dict_type-form-value', - 'index': 'status' - }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], + ApiRadioGroup( + dict_type='sys_normal_disable', + name='status', defaultValue='0', - style={ - 'width': 350 - } + style={'width': 350}, ), label='状态', id={ 'type': 'dict_type-form-label', 'index': 'status', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), @@ -484,44 +440,36 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_type-form-value', - 'index': 'remark' - }, + name='remark', placeholder='请输入内容', allowClear=True, mode='text-area', - style={ - 'width': 350 - } + style={'width': 350}, ), label='备注', id={ 'type': 'dict_type-form-label', 'index': 'remark', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), ], - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id='dict_type-form', + enableBatchControl=True, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ) ], id='dict_type-modal', mask=False, width=580, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除字典类型二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='dict_type-delete-text'), @@ -529,17 +477,16 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), - # 字典数据modal fac.AntdModal( - dict_data.render(button_perms), + dict_data.render(), id='dict_type_to_dict_data-modal', mask=False, maskClosable=False, width=1000, renderFooter=False, - okClickClose=False - ) + okClickClose=False, + ), ] diff --git a/dash-fastapi-frontend/views/system/dict/dict_data.py b/dash-fastapi-frontend/views/system/dict/dict_data.py index 6352390e0b91c00ab6ec492111fd8a8d6d285110..abf6a927243c939345a4cbeff393b8b85ae6b282 100644 --- a/dash-fastapi-frontend/views/system/dict/dict_data.py +++ b/dash-fastapi-frontend/views/system/dict/dict_data.py @@ -1,13 +1,13 @@ -from dash import dcc, html import feffery_antd_components as fac +from dash import dcc, html +from callbacks.system_c.dict_c import dict_data_c # noqa: F401 +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager -import callbacks.system_c.dict_c.dict_data_c - - -def render(button_perms): +def render(): return [ - dcc.Store(id='dict_data-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='dict_data-export-complete-judge-container'), # 绑定的导出组件 @@ -31,10 +31,12 @@ def render(button_perms): allowClear=False, style={ 'width': 240 - } + }, ), label='字典名称', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -44,31 +46,26 @@ def render(button_perms): allowClear=True, style={ 'width': 240 - } + }, ), label='字典标签', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='dict_data-status-select', placeholder='数据状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 240 - } + }, ), label='状态', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -77,9 +74,11 @@ def render(button_perms): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -87,16 +86,18 @@ def render(button_perms): id='dict_data-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='dict_data-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -115,14 +116,18 @@ def render(button_perms): ], id={ 'type': 'dict_data-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:dict:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:dict:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -132,15 +137,19 @@ def render(button_perms): ], id={ 'type': 'dict_data-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'system:dict:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'system:dict:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -150,15 +159,19 @@ def render(button_perms): ], id={ 'type': 'dict_data-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:dict:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:dict:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -170,15 +183,17 @@ def render(button_perms): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'system:dict:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'system:dict:export' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -192,10 +207,10 @@ def render(button_perms): ), ], id='dict_data-hidden', - shape='circle' + shape='circle', ), id='dict_data-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -207,24 +222,22 @@ def render(button_perms): ), ], id='dict_data-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -287,11 +300,11 @@ def render(button_perms): 'title': '操作', 'dataIndex': 'operation', 'fixed': 'right', - 'width': 150, + 'width': 170, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, @@ -301,28 +314,32 @@ def render(button_perms): 'pageSize': 10, 'current': 1, 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], + 'pageSizeOptions': [ + 10, + 30, + 50, + 100, + ], 'showQuickJumper': True, - 'total': 0 + 'total': 0, }, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑字典数据表单modal fac.AntdModal( [ @@ -333,24 +350,19 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_data-form-value', - 'index': 'dict_type' - }, + name='dict_type', placeholder='请输入字典类型', disabled=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='字典类型', id={ 'type': 'dict_data-form-label', 'index': 'dict_type', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), @@ -359,25 +371,20 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_data-form-value', - 'index': 'dict_label' - }, + name='dict_label', placeholder='请输入数据标签', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='数据标签', required=True, id={ 'type': 'dict_data-form-label', 'index': 'dict_label', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -386,25 +393,20 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_data-form-value', - 'index': 'dict_value' - }, + name='dict_value', placeholder='请输入数据键值', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='数据键值', required=True, id={ 'type': 'dict_data-form-label', 'index': 'dict_value', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -413,24 +415,19 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_data-form-value', - 'index': 'css_class' - }, + name='css_class', placeholder='请输入样式属性', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='样式属性', id={ 'type': 'dict_data-form-label', 'index': 'css_class', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), @@ -439,25 +436,20 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdInputNumber( - id={ - 'type': 'dict_data-form-value', - 'index': 'dict_sort' - }, + name='dict_sort', defaultValue=0, min=0, - style={ - 'width': 350 - } + style={'width': 350}, ), label='显示排序', required=True, id={ 'type': 'dict_data-form-label', 'index': 'dict_sort', - 'required': True - } + 'required': True, + }, ), - span=24 + span=24, ), ] ), @@ -466,49 +458,44 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdSelect( - id={ - 'type': 'dict_data-form-value', - 'index': 'list_class' - }, + name='list_class', placeholder='回显样式', options=[ { 'label': '默认', - 'value': 'default' + 'value': 'default', }, { 'label': '主要', - 'value': 'primary' + 'value': 'primary', }, { 'label': '成功', - 'value': 'success' + 'value': 'success', }, { 'label': '信息', - 'value': 'info' + 'value': 'info', }, { 'label': '警告', - 'value': 'warning' + 'value': 'warning', }, { 'label': '危险', - 'value': 'danger' - } + 'value': 'danger', + }, ], - style={ - 'width': 350 - } + style={'width': 350}, ), label='回显样式', id={ 'type': 'dict_data-form-label', 'index': 'list_class', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), @@ -516,34 +503,20 @@ def render(button_perms): [ fac.AntdCol( fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'dict_data-form-value', - 'index': 'status' - }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], + ApiRadioGroup( + dict_type='sys_normal_disable', + name='status', defaultValue='0', - style={ - 'width': 350 - } + style={'width': 350}, ), label='状态', id={ 'type': 'dict_data-form-label', 'index': 'status', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), @@ -552,35 +525,28 @@ def render(button_perms): fac.AntdCol( fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'dict_data-form-value', - 'index': 'remark' - }, + name='remark', placeholder='请输入内容', allowClear=True, mode='text-area', - style={ - 'width': 350 - } + style={'width': 350}, ), label='备注', id={ 'type': 'dict_data-form-label', 'index': 'remark', - 'required': False - } + 'required': False, + }, ), - span=24 + span=24, ), ] ), ], - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id='dict_data-form', + enableBatchControl=True, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ) ], id='dict_data-modal', @@ -588,15 +554,14 @@ def render(button_perms): maskClosable=False, width=580, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除字典数据二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='dict_data-delete-text'), id='dict_data-delete-confirm-modal', visible=False, title='提示', - renderFooter=True + renderFooter=True, ), ] diff --git a/dash-fastapi-frontend/views/system/menu/__init__.py b/dash-fastapi-frontend/views/system/menu/__init__.py index 0e8988d07bf6f613feccb8a6430ea629d643f6a4..ef6a7d63508761fd60a53e0b918a12d4355b4f2c 100644 --- a/dash-fastapi-frontend/views/system/menu/__init__.py +++ b/dash-fastapi-frontend/views/system/menu/__init__.py @@ -1,72 +1,21 @@ -from dash import dcc, html import feffery_antd_components as fac - -from api.menu import get_menu_list_api -from utils.tree_tool import list_to_tree +from dash import dcc, html +from callbacks.system_c.menu_c import menu_c +from components.ApiSelect import ApiSelect +from config.constant import MenuConstant +from utils.permission_util import PermissionManager from views.system.menu.components.icon_category import render_icon -import callbacks.system_c.menu_c.menu_c def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - table_data_new = [] - table_info = get_menu_list_api({}) - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - for item in table_data: - item['key'] = str(item['menu_id']) - item['icon'] = [ - { - 'type': 'link', - 'icon': item['icon'], - 'disabled': True, - 'style': { - 'color': 'rgba(0, 0, 0, 0.8)' - } - }, - ] - if item['status'] == '1': - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:menu:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:menu:remove' in button_perms else {}, - ] - else: - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:menu:edit' in button_perms else {}, - { - 'content': '新增', - 'type': 'link', - 'icon': 'antd-plus' - } if 'system:menu:add' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:menu:remove' in button_perms else {}, - ] - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - table_data_new = list_to_tree(table_data, 'menu_id', 'parent_id') + query_params = {} + table_data = menu_c.generate_menu_table(query_params)[0] return [ - dcc.Store(id='menu-button-perms-container', data=button_perms), # 菜单管理模块操作类型存储容器 dcc.Store(id='menu-operations-store'), - dcc.Store(id='menu-operations-store-bk'), + # 菜单管理模块弹窗类型存储容器 + dcc.Store(id='menu-modal_type-store'), # modal菜单类型存储容器 dcc.Store(id='menu-modal-menu-type-store'), # 不同菜单类型的触发器 @@ -98,29 +47,20 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), - label='菜单名称' + label='菜单名称', ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='menu-status-select', placeholder='菜单状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 240 - } + }, ), - label='菜单状态' + label='菜单状态', ), fac.AntdFormItem( fac.AntdButton( @@ -129,7 +69,7 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ) ), fac.AntdFormItem( @@ -138,20 +78,20 @@ def render(*args, **kwargs): id='menu-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ) - ) + ), ], style={ 'paddingBottom': '10px' - } + }, ), ], layout='inline', ) ], id='menu-search-form-container', - hidden=False + hidden=False, ) ) ] @@ -170,14 +110,18 @@ def render(*args, **kwargs): ], id={ 'type': 'menu-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:menu:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:menu:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -189,15 +133,13 @@ def render(*args, **kwargs): style={ 'color': '#909399', 'background': '#f4f4f5', - 'border-color': '#d3d4d6' - } + 'border-color': '#d3d4d6', + }, ), ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -211,10 +153,10 @@ def render(*args, **kwargs): ), ], id='menu-hidden', - shape='circle' + shape='circle', ), id='menu-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -226,24 +168,22 @@ def render(*args, **kwargs): ), ], id='menu-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -251,7 +191,7 @@ def render(*args, **kwargs): fac.AntdSpin( fac.AntdTable( id='menu-list-table', - data=table_data_new, + data=table_data, columns=[ { 'dataIndex': 'menu_id', @@ -259,7 +199,7 @@ def render(*args, **kwargs): 'renderOptions': { 'renderType': 'ellipsis' }, - 'hidden': True + 'hidden': True, }, { 'dataIndex': 'menu_name', @@ -317,10 +257,11 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 240, 'renderOptions': { 'renderType': 'button' }, - } + }, ], bordered=True, pagination={ @@ -329,170 +270,152 @@ def render(*args, **kwargs): style={ 'width': '100%', 'padding-right': '10px', - 'padding-bottom': '20px' - } + 'padding-bottom': '20px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑菜单表单modal fac.AntdModal( [ fac.AntdForm( [ - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdTreeSelect( - id='menu-parent_id', - placeholder='请选择上级菜单', - treeData=[], - defaultValue='0', - treeNodeFilterProp='title', - style={ - 'width': 495 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdTreeSelect( + id='menu-parent_id', + placeholder='请选择上级菜单', + treeData=[], + defaultValue='0', + treeNodeFilterProp='title', + style={'width': '100%'}, + ), + label='上级菜单', + required=True, + id='menu-parent_id-form-item', + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - label='上级菜单', - required=True, - id='menu-parent_id-form-item', - labelCol={ - 'span': 4, - }, - wrapperCol={ - 'span': 20 - } + span=24, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdRadioGroup( - id='menu-menu_type', - options=[ - { - 'label': '目录', - 'value': 'M' - }, - { - 'label': '菜单', - 'value': 'C' - }, - { - 'label': '按钮', - 'value': 'F' - }, - ], - defaultValue='M', - style={ - 'width': 495 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdRadioGroup( + id='menu-menu_type', + options=[ + { + 'label': '目录', + 'value': MenuConstant.TYPE_DIR, + }, + { + 'label': '菜单', + 'value': MenuConstant.TYPE_MENU, + }, + { + 'label': '按钮', + 'value': MenuConstant.TYPE_BUTTON, + }, + ], + defaultValue='M', + style={'width': '100%'}, + ), + label='菜单类型', + required=True, + id='menu-menu_type-form-item', + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - label='菜单类型', - required=True, - id='menu-menu_type-form-item', - labelCol={ - 'span': 4, - }, - wrapperCol={ - 'span': 20 - } - ) + span=24, + ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdPopover( - fac.AntdInput( - id='menu-icon', - placeholder='点击此处选择图标', - readOnly=True, - style={ - 'width': 495 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdPopover( + fac.AntdInput( + id='menu-icon', + placeholder='点击此处选择图标', + readOnly=True, + style={'width': '100%'}, + ), + content=render_icon(), + trigger='click', + placement='bottom', ), - content=render_icon(), - trigger='click', - placement='bottom' + label='菜单图标', + id='menu-icon-form-item', + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - label='菜单图标', - id='menu-icon-form-item', - labelCol={ - 'span': 4, - }, - wrapperCol={ - 'span': 20 - } + span=24, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( - id='menu-menu_name', - placeholder='请输入菜单名称', - allowClear=True, - style={ - 'width': 200 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='menu-menu_name', + placeholder='请输入菜单名称', + allowClear=True, + style={'width': '100%'}, + ), + label='菜单名称', + required=True, + id='menu-menu_name-form-item', ), - label='菜单名称', - required=True, - id='menu-menu_name-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + span=12, ), - fac.AntdFormItem( - fac.AntdInputNumber( - id='menu-order_num', - min=0, - style={ - 'width': 200 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInputNumber( + id='menu-order_num', + min=0, + style={'width': '100%'}, + ), + label='显示排序', + required=True, + id='menu-order_num-form-item', ), - label='显示排序', - required=True, - id='menu-order_num-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + span=12, ), ], - size="middle" + gutter=10, ), html.Div(id='content-by-menu-type'), - ] + ], + labelCol={'span': 8}, + wrapperCol={'span': 16}, + style={'marginRight': '15px'}, ) ], id='menu-modal', mask=False, width=680, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除菜单二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='menu-delete-text'), @@ -500,6 +423,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/system/menu/components/__init__.py b/dash-fastapi-frontend/views/system/menu/components/__init__.py index 6593c9df7fae641268ddeef114a3f13ed0027298..14c5787a0bf702a9d467a13b78ce49c5e9097ba6 100644 --- a/dash-fastapi-frontend/views/system/menu/components/__init__.py +++ b/dash-fastapi-frontend/views/system/menu/components/__init__.py @@ -1,5 +1,5 @@ from . import ( - content_type, - menu_type, - button_type + button_type, # noqa: F401 + content_type, # noqa: F401 + menu_type, # noqa: F401 ) diff --git a/dash-fastapi-frontend/views/system/menu/components/button_type.py b/dash-fastapi-frontend/views/system/menu/components/button_type.py index e84ece65d33c7e99abd9c2944948bd667ca68934..028064d54bf9fbced47126a85799142be7e306b0 100644 --- a/dash-fastapi-frontend/views/system/menu/components/button_type.py +++ b/dash-fastapi-frontend/views/system/menu/components/button_type.py @@ -1,42 +1,28 @@ -from dash import html import feffery_antd_components as fac - -import callbacks.system_c.menu_c.components_c.button_type_c +from callbacks.system_c.menu_c.components_c import button_type_c # noqa: F401 def render(): return [ - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( - id='button-menu-perms', - placeholder='请输入权限字符', - allowClear=True, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='控制器中定义的权限字符,如:system:user:list' - ), - fac.AntdText('权限字符') - ] + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='button-menu-perms', + placeholder='请输入权限字符', + allowClear=True, + style={'width': '100%'}, + ), + label='权限字符', + tooltip='控制器中定义的权限字符,如:system:user:list', + id='button-menu-perms-form-item', + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - id='button-menu-perms-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } - ), + span=24, + ) ], - size="middle" - ), + gutter=10, + ) ] diff --git a/dash-fastapi-frontend/views/system/menu/components/content_type.py b/dash-fastapi-frontend/views/system/menu/components/content_type.py index 5d663af2d4c08b1075b47be24abb453312ba4bf7..59f071e624a15afbe820bf3de73bfd55145c4371 100644 --- a/dash-fastapi-frontend/views/system/menu/components/content_type.py +++ b/dash-fastapi-frontend/views/system/menu/components/content_type.py @@ -1,159 +1,86 @@ -from dash import html import feffery_antd_components as fac - -import callbacks.system_c.menu_c.components_c.content_type_c +from callbacks.system_c.menu_c.components_c import content_type_c # noqa: F401 +from config.constant import ( + MenuConstant, + SysNormalDisableConstant, + SysShowHideConstant, +) +from components.ApiRadioGroup import ApiRadioGroup def render(): return [ - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdRadioGroup( - id='content-menu-is_frame', - options=[ - { - 'label': '是', - 'value': 0 - }, - { - 'label': '否', - 'value': 1 - }, - ], - defaultValue=1, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择是外链则路由地址需要以`http(s)://`开头' - ), - fac.AntdText('是否外链') - ] + fac.AntdCol( + fac.AntdFormItem( + fac.AntdRadioGroup( + id='content-menu-is_frame', + options=[ + { + 'label': '是', + 'value': MenuConstant.YES_FRAME, + }, + {'label': '否', 'value': MenuConstant.NO_FRAME}, + ], + defaultValue=MenuConstant.NO_FRAME, + style={'width': '100%'}, + ), + label='是否外链', + tooltip='选择是外链则路由地址需要以`http(s)://`开头', + id='content-menu-is_frame-form-item', ), - id='content-menu-is_frame-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + span=12, ), - fac.AntdFormItem( - fac.AntdInput( - id='content-menu-path', - placeholder='请输入路由地址', - allowClear=True, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头' - ), - fac.AntdText('路由地址') - ] + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='content-menu-path', + placeholder='请输入路由地址', + allowClear=True, + style={'width': '100%'}, + ), + label='路由地址', + tooltip='访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头', + required=True, + id='content-menu-path-form-item', ), - required=True, - id='content-menu-path-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdRadioGroup( - id='content-menu-visible', - options=[ - { - 'label': '显示', - 'value': '0' - }, - { - 'label': '隐藏', - 'value': '1' - }, - ], - defaultValue='0', - style={ - 'width': 200 - } + fac.AntdCol( + fac.AntdFormItem( + ApiRadioGroup( + dict_type='sys_show_hide', + id='content-menu-visible', + defaultValue=SysShowHideConstant.SHOW, + style={'width': '100%'}, + ), + label='显示状态', + tooltip='选择隐藏则路由将不会出现在侧边栏,但仍然可以访问', + id='content-menu-visible-form-item', ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择隐藏则路由将不会出现在侧边栏,但仍然可以访问' - ), - fac.AntdText('显示状态') - ] - ), - id='content-menu-visible-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + span=12, ), - fac.AntdFormItem( - fac.AntdRadioGroup( - id='content-menu-status', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], - defaultValue='0', - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择停用则路由将不会出现在侧边栏,也不能被访问' - ), - fac.AntdText('菜单状态') - ] + fac.AntdCol( + fac.AntdFormItem( + ApiRadioGroup( + dict_type='sys_normal_disable', + id='content-menu-status', + defaultValue=SysNormalDisableConstant.NORMAL, + style={'width': '100%'}, + ), + label='菜单状态', + tooltip='选择停用则路由将不会出现在侧边栏,也不能被访问', + id='content-menu-status-form-item', ), - id='content-menu-status-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + span=12, ), ], - size="middle" - ) + gutter=10, + ), ] diff --git a/dash-fastapi-frontend/views/system/menu/components/icon_category.py b/dash-fastapi-frontend/views/system/menu/components/icon_category.py index f6a44e15e875fcfdea8c8fddb6c06758a024d2d9..b83bd3c32faaeefe50e914cb198234a6753d7136 100644 --- a/dash-fastapi-frontend/views/system/menu/components/icon_category.py +++ b/dash-fastapi-frontend/views/system/menu/components/icon_category.py @@ -1,10 +1,9 @@ -from dash import html import feffery_antd_components as fac -from config.global_config import IconConfig +from dash import html +from config.env import IconConfig def render_icon(): - return html.Div( [ fac.AntdRadioGroup( @@ -14,18 +13,12 @@ def render_icon(): 'label': fac.AntdIcon( icon=icon, ), - 'value': icon + 'value': icon, } for icon in IconConfig.ICON_LIST ], - style={ - 'width': 450, - 'paddingLeft': '10px' - } + style={'width': 450, 'paddingLeft': '10px'}, ), ], - style={ - 'maxHeight': '135px', - 'overflow': 'auto' - } + style={'maxHeight': '135px', 'overflow': 'auto'}, ) diff --git a/dash-fastapi-frontend/views/system/menu/components/menu_type.py b/dash-fastapi-frontend/views/system/menu/components/menu_type.py index 6d975925878ca8bef16db0ba6a9dd60746f0c8a5..52a04f8e1033f295e5ebc4e643896f7f882bf459 100644 --- a/dash-fastapi-frontend/views/system/menu/components/menu_type.py +++ b/dash-fastapi-frontend/views/system/menu/components/menu_type.py @@ -1,290 +1,179 @@ -from dash import html import feffery_antd_components as fac - -import callbacks.system_c.menu_c.components_c.menu_type_c +from callbacks.system_c.menu_c.components_c import menu_type_c # noqa: F401 +from config.constant import ( + MenuConstant, + SysNormalDisableConstant, + SysShowHideConstant, +) +from components.ApiRadioGroup import ApiRadioGroup def render(): return [ - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdRadioGroup( - id='menu-menu-is_frame', - options=[ - { - 'label': '是', - 'value': 0 - }, - { - 'label': '否', - 'value': 1 - }, - ], - defaultValue=1, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择是外链则路由地址需要以`http(s)://`开头' - ), - fac.AntdText('是否外链') - ] - ), - id='menu-menu-is_frame-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdRadioGroup( + id='menu-menu-is_frame', + options=[ + { + 'label': '是', + 'value': MenuConstant.YES_FRAME, + }, + {'label': '否', 'value': MenuConstant.NO_FRAME}, + ], + defaultValue=MenuConstant.NO_FRAME, + style={'width': '100%'}, + ), + label='是否外链', + tooltip='选择是外链则路由地址需要以`http(s)://`开头', + id='menu-menu-is_frame-form-item', + ), + span=12, ), - fac.AntdFormItem( - fac.AntdInput( - id='menu-menu-path', - placeholder='请输入路由地址', - allowClear=True, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头' - ), - fac.AntdText('路由地址') - ] - ), - required=True, - id='menu-menu-path-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='menu-menu-path', + placeholder='请输入路由地址', + allowClear=True, + style={'width': '100%'}, + ), + label='路由地址', + tooltip='访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头', + required=True, + id='menu-menu-path-form-item', + ), + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( - id='menu-menu-component', - placeholder='请输入组件路径', - allowClear=True, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='访问的组件路径,如:`system.user.index`,默认在`views`目录下' - ), - fac.AntdText('组件路径') - ] - ), - id='menu-menu-component-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='menu-menu-route_name', + placeholder='请输入路由名称', + allowClear=True, + style={'width': '100%'}, + ), + label='路由名称', + tooltip='默认不填则和路由地址相同:如地址为:`user`,则名称为`User`(注意:因为router会删除名称相同路由,为避免名字的冲突,特殊情况下请自定义,保证唯一性)', + id='menu-menu-route_name-form-item', + ), + span=12, ), - fac.AntdFormItem( - fac.AntdInput( - id='menu-menu-perms', - placeholder='请输入权限字符', - allowClear=True, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='控制器中定义的权限字符,如:system:user:list' - ), - fac.AntdText('权限字符') - ] - ), - id='menu-menu-perms-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='menu-menu-component', + placeholder='请输入组件路径', + allowClear=True, + style={'width': '100%'}, + ), + label='组件路径', + tooltip='访问的组件路径,如:`system.user.index`,默认在`views`目录下', + id='menu-menu-component-form-item', + ), + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( - id='menu-menu-query', - placeholder='请输入路由参数', - allowClear=True, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' - ), - fac.AntdText('路由参数') - ] - ), - id='menu-menu-query-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='menu-menu-perms', + placeholder='请输入权限字符', + allowClear=True, + style={'width': '100%'}, + ), + label='权限字符', + tooltip='控制器中定义的权限字符,如:system:user:list', + id='menu-menu-perms-form-item', + ), + span=12, ), - fac.AntdFormItem( - fac.AntdRadioGroup( - id='menu-menu-is_cache', - options=[ - { - 'label': '缓存', - 'value': 0 - }, - { - 'label': '不缓存', - 'value': 1 - }, - ], - defaultValue=0, - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致' - ), - fac.AntdText('是否缓存') - ] - ), - id='menu-menu-is_cache-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='menu-menu-query', + placeholder='请输入路由参数', + allowClear=True, + style={'width': '100%'}, + ), + label='路由参数', + tooltip='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`', + id='menu-menu-query-form-item', + ), + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdRadioGroup( - id='menu-menu-visible', - options=[ - { - 'label': '显示', - 'value': '0' - }, - { - 'label': '隐藏', - 'value': '1' - }, - ], - defaultValue='0', - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择隐藏则路由将不会出现在侧边栏,但仍然可以访问' - ), - fac.AntdText('显示状态') - ] - ), - id='menu-menu-visible-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + fac.AntdRadioGroup( + id='menu-menu-is_cache', + options=[ + { + 'label': '缓存', + 'value': MenuConstant.YES_CACHE, + }, + { + 'label': '不缓存', + 'value': MenuConstant.NO_CACHE, + }, + ], + defaultValue=MenuConstant.YES_CACHE, + style={'width': '100%'}, + ), + label='是否缓存', + id='menu-menu-is_cache-form-item', + ), + span=12, ), - fac.AntdFormItem( - fac.AntdRadioGroup( - id='menu-menu-status', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], - defaultValue='0', - style={ - 'width': 200 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='选择停用则路由将不会出现在侧边栏,也不能被访问' - ), - fac.AntdText('菜单状态') - ] - ), - id='menu-menu-status-form-item', - labelCol={ - 'span': 8, - }, - wrapperCol={ - 'span': 16 - } + fac.AntdCol( + fac.AntdFormItem( + ApiRadioGroup( + dict_type='sys_show_hide', + id='menu-menu-visible', + defaultValue=SysShowHideConstant.SHOW, + style={'width': '100%'}, + ), + label='显示状态', + tooltip='选择隐藏则路由将不会出现在侧边栏,但仍然可以访问', + id='menu-menu-visible-form-item', + ), + span=12, ), ], - size="middle" - ) + gutter=10, + ), + fac.AntdRow( + [ + fac.AntdCol( + fac.AntdFormItem( + ApiRadioGroup( + dict_type='sys_normal_disable', + id='menu-menu-status', + defaultValue=SysNormalDisableConstant.NORMAL, + style={'width': '100%'}, + ), + label='菜单状态', + tooltip='选择停用则路由将不会出现在侧边栏,也不能被访问', + id='menu-menu-status-form-item', + ), + span=12, + ), + ], + gutter=10, + ), ] diff --git a/dash-fastapi-frontend/views/system/notice/__init__.py b/dash-fastapi-frontend/views/system/notice/__init__.py index dea00474097007f8a70267c9c7def49a9ec76192..e2a3a2fd18de2267aad9ae768b436a5aa8eae4a6 100644 --- a/dash-fastapi-frontend/views/system/notice/__init__.py +++ b/dash-fastapi-frontend/views/system/notice/__init__.py @@ -1,70 +1,23 @@ -from dash import dcc, html import feffery_antd_components as fac import feffery_utils_components as fuc +from dash import dcc, html from flask import session -import json -import uuid - -import callbacks.system_c.notice_c -from config.global_config import ApiBaseUrlConfig -from api.notice import get_notice_list_api -from api.dict import query_dict_data_list_api +from callbacks.system_c import notice_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from config.env import ApiConfig +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - option = [] - option_table = [] - info = query_dict_data_list_api(dict_type='sys_notice_type') - if info.get('code') == 200: - data = info.get('data') - option = [dict(label=item.get('dict_label'), value=item.get('dict_value')) for item in data] - option_table = [ - dict(label=item.get('dict_label'), value=item.get('dict_value'), css_class=item.get('css_class')) for item - in data] - option_dict = {item.get('value'): item for item in option_table} - - notice_params = dict(page_num=1, page_size=10) - table_info = get_notice_list_api(notice_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='关闭', color='volcano') - if str(item.get('notice_type')) in option_dict.keys(): - item['notice_type'] = dict( - tag=option_dict.get(str(item.get('notice_type'))).get('label'), - color=json.loads(option_dict.get(str(item.get('notice_type'))).get('css_class')).get('color') - ) - item['key'] = str(item['notice_id']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:notice:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:notice:remove' in button_perms else {}, - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = notice_c.generate_notice_table(query_params) return [ - dcc.Store(id='notice-button-perms-container', data=button_perms), # 通知公告管理模块操作类型存储容器 dcc.Store(id='notice-operations-store'), - dcc.Store(id='notice-operations-store-bk'), + # 通知公告管理模块弹窗类型存储容器 + dcc.Store(id='notice-modal_type-store'), # 通知公告管理模块修改操作行key存储容器 dcc.Store(id='notice-edit-id-store'), # 通知公告管理模块删除操作行key存储容器 @@ -88,10 +41,12 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='公告标题', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -101,32 +56,38 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), label='操作人员', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_notice_type', id='notice-notice_type-select', placeholder='公告类型', - options=option, style={ 'width': 240 - } + }, ), label='类型', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='notice-create_time-range', style={ 'width': 240 - } + }, ), label='创建时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -135,9 +96,11 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -145,16 +108,18 @@ def render(*args, **kwargs): id='notice-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='notice-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -173,14 +138,18 @@ def render(*args, **kwargs): ], id={ 'type': 'notice-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:notice:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:notice:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -190,15 +159,19 @@ def render(*args, **kwargs): ], id={ 'type': 'notice-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'system:notice:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'system:notice:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -208,21 +181,23 @@ def render(*args, **kwargs): ], id={ 'type': 'notice-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:notice:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:notice:remove' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -236,10 +211,10 @@ def render(*args, **kwargs): ), ], id='notice-hidden', - shape='circle' + shape='circle', ), id='notice-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -251,24 +226,22 @@ def render(*args, **kwargs): ), ], id='notice-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -323,40 +296,33 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 170, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑通知公告modal fac.AntdModal( [ @@ -368,79 +334,48 @@ def render(*args, **kwargs): fac.AntdFormItem( fac.AntdInput( id='notice-notice_title', - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id='notice-notice_title-form-item', required=True, label='公告标题', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } ), - span=12 + span=12, ), fac.AntdCol( fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_notice_type', id='notice-notice_type', - options=option, - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id='notice-notice_type-form-item', required=True, label='公告类型', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } ), - span=12 - ) + span=12, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ fac.AntdCol( fac.AntdFormItem( - fac.AntdRadioGroup( + ApiRadioGroup( + dict_type='sys_notice_status', id='notice-status', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '关闭', - 'value': '1' - } - ], - style={ - 'width': '100%' - } + style={'width': '100%'}, ), id='notice-status-form-item', label='状态', - labelCol={ - 'span': 3 - }, - wrapperCol={ - 'span': 21 - } + labelCol={'span': 3}, + wrapperCol={'span': 21}, ), - span=24 + span=24, ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -452,75 +387,72 @@ def render(*args, **kwargs): 'placeholder': '请输入...' }, uploadImage={ - 'server': f'{ApiBaseUrlConfig.BaseUrl}/common/uploadForEditor', + 'server': f'{ApiConfig.BaseUrl}/common/uploadForEditor', 'fieldName': 'file', 'maxFileSize': 10 * 1024 * 1024, 'maxNumberOfFiles': 10, 'meta': { - 'baseUrl': ApiBaseUrlConfig.BaseUrl, - 'uploadId': str(uuid.uuid4()), - 'taskPath': 'notice' + 'base_url': ApiConfig.BaseUrl, }, 'metaWithUrl': True, 'headers': { - 'Authorization': 'Bearer ' + session.get('Authorization') + 'Authorization': 'Bearer ' + + session.get( + 'Authorization' + ) }, 'withCredentials': True, 'timeout': 5 * 1000, - 'base64LimitSize': 500 * 1024 + 'base64LimitSize': 500 * 1024, }, uploadVideo={ - 'server': f'{ApiBaseUrlConfig.BaseUrl}/common/uploadForEditor', + 'server': f'{ApiConfig.BaseUrl}/common/uploadForEditor', 'fieldName': 'file', - 'maxFileSize': 100 * 1024 * 1024, + 'maxFileSize': 100 + * 1024 + * 1024, 'maxNumberOfFiles': 3, 'meta': { - 'baseUrl': ApiBaseUrlConfig.BaseUrl, - 'uploadId': str(uuid.uuid4()), - 'taskPath': 'notice' + 'base_url': ApiConfig.BaseUrl, }, 'metaWithUrl': True, 'headers': { - 'Authorization': 'Bearer ' + session.get('Authorization') + 'Authorization': 'Bearer ' + + session.get( + 'Authorization' + ) }, 'withCredentials': True, - 'timeout': 15 * 1000 + 'timeout': 15 * 1000, }, editorStyle={ 'height': 300, - 'width': '100%' + 'width': '100%', }, - style={ - 'marginBottom': 15 - } + style={'marginBottom': 15}, ), id='notice-notice_content-form-item', label='内容', - labelCol={ - 'span': 3 - }, - wrapperCol={ - 'span': 21 - } + labelCol={'span': 3}, + wrapperCol={'span': 21}, ), - span=24 + span=24, ), ], - gutter=5 - ) + gutter=5, + ), ], - style={ - 'marginRight': '30px' - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, + style={'marginRight': '30px'}, ) ], id='notice-modal', mask=False, width=900, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除通知公告二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='notice-delete-text'), @@ -528,6 +460,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/system/post/__init__.py b/dash-fastapi-frontend/views/system/post/__init__.py index b19207b6db7573654d0bf202bd33d3013c190934..bbd8deb550c9eda15e2173e6081bd20861e187ba 100644 --- a/dash-fastapi-frontend/views/system/post/__init__.py +++ b/dash-fastapi-frontend/views/system/post/__init__.py @@ -1,54 +1,26 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.system_c.post_c -from api.post import get_post_list_api +from dash import dcc, html +from callbacks.system_c import post_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - post_params = dict(page_num=1, page_size=10) - table_info = get_post_list_api(post_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(tag='正常', color='blue') - else: - item['status'] = dict(tag='停用', color='volcano') - item['key'] = str(item['post_id']) - item['operation'] = [ - { - 'content': '修改', - 'type': 'link', - 'icon': 'antd-edit' - } if 'system:post:edit' in button_perms else {}, - { - 'content': '删除', - 'type': 'link', - 'icon': 'antd-delete' - } if 'system:post:remove' in button_perms else {}, - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = post_c.generate_post_table(query_params) return [ - dcc.Store(id='post-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='post-export-complete-judge-container'), # 绑定的导出组件 dcc.Download(id='post-export-container'), # 岗位管理模块操作类型存储容器 dcc.Store(id='post-operations-store'), - dcc.Store(id='post-operations-store-bk'), - # 岗位管理模块修改操作行key存储容器 - dcc.Store(id='post-edit-id-store'), + # 岗位管理模块弹窗类型存储容器 + dcc.Store(id='post-modal_type-store'), + # 岗位管理模块表单数据存储容器 + dcc.Store(id='post-form-store'), # 岗位管理模块删除操作行key存储容器 dcc.Store(id='post-delete-ids-store'), fac.AntdRow( @@ -72,9 +44,9 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 210 - } + }, ), - label='岗位编码' + label='岗位编码', ), fac.AntdFormItem( fac.AntdInput( @@ -84,29 +56,20 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 210 - } + }, ), - label='岗位名称' + label='岗位名称', ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='post-status-select', placeholder='岗位状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 200 - } + }, ), - label='岗位状态' + label='岗位状态', ), fac.AntdFormItem( fac.AntdButton( @@ -115,7 +78,7 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ) ), fac.AntdFormItem( @@ -124,20 +87,20 @@ def render(*args, **kwargs): id='post-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ) - ) + ), ], style={ 'paddingBottom': '10px' - } + }, ), ], layout='inline', ) ], id='post-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -156,14 +119,18 @@ def render(*args, **kwargs): ], id={ 'type': 'post-operation-button', - 'index': 'add' + 'index': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:post:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:post:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -173,15 +140,19 @@ def render(*args, **kwargs): ], id={ 'type': 'post-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'system:post:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'system:post:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -191,15 +162,19 @@ def render(*args, **kwargs): ], id={ 'type': 'post-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:post:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:post:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -211,15 +186,19 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'system:post:export' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'system:post:export' + ) + else [], ], style={ 'paddingBottom': '10px', - } + }, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -233,10 +212,10 @@ def render(*args, **kwargs): ), ], id='post-hidden', - shape='circle' + shape='circle', ), id='post-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -248,24 +227,22 @@ def render(*args, **kwargs): ), ], id='post-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -320,40 +297,33 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 170, 'renderOptions': { 'renderType': 'button' }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑岗位表单modal fac.AntdModal( [ @@ -361,128 +331,96 @@ def render(*args, **kwargs): [ fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'post-form-value', - 'index': 'post_name' - }, + name='post_name', placeholder='请输入岗位名称', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='岗位名称', required=True, id={ 'type': 'post-form-label', 'index': 'post_name', - 'required': True - } + 'required': True, + }, + hasFeedback=True, ), fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'post-form-value', - 'index': 'post_code' - }, + name='post_code', placeholder='请输入岗位编码', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='岗位编码', required=True, id={ 'type': 'post-form-label', 'index': 'post_code', - 'required': True - } + 'required': True, + }, + hasFeedback=True, ), fac.AntdFormItem( fac.AntdInputNumber( - id={ - 'type': 'post-form-value', - 'index': 'post_sort' - }, + name='post_sort', defaultValue=0, min=0, - style={ - 'width': 350 - } + style={'width': 350}, ), label='岗位顺序', required=True, id={ 'type': 'post-form-label', 'index': 'post_sort', - 'required': True - } + 'required': True, + }, + hasFeedback=True, ), fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'post-form-value', - 'index': 'status' - }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], + ApiRadioGroup( + dict_type='sys_normal_disable', + name='status', defaultValue='0', - style={ - 'width': 350 - } + style={'width': 350}, ), label='岗位状态', id={ 'type': 'post-form-label', 'index': 'status', - 'required': False - } + 'required': False, + }, + hasFeedback=True, ), fac.AntdFormItem( fac.AntdInput( - id={ - 'type': 'post-form-value', - 'index': 'remark' - }, + name='remark', placeholder='请输入内容', allowClear=True, mode='text-area', - style={ - 'width': 350 - } + style={'width': 350}, ), label='备注', id={ 'type': 'post-form-label', 'index': 'remark', - 'required': False - } + 'required': False, + }, + hasFeedback=True, ), ], - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id='post-form', + enableBatchControl=True, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ) ], id='post-modal', mask=False, width=580, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除岗位二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='post-delete-text'), @@ -490,6 +428,6 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), ] diff --git a/dash-fastapi-frontend/views/system/role/__init__.py b/dash-fastapi-frontend/views/system/role/__init__.py index 715045ed39fe9144d975054539475d8265c893c5..c944ad1364fb1d7b7f370892c424918435f51a73 100644 --- a/dash-fastapi-frontend/views/system/role/__init__.py +++ b/dash-fastapi-frontend/views/system/role/__init__.py @@ -1,128 +1,25 @@ -from dash import dcc, html import feffery_antd_components as fac - -import callbacks.system_c.role_c.role_c +from dash import dcc, html +from callbacks.system_c.role_c import role_c +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager from . import data_scope, allocate_user -from api.role import get_role_list_api def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - - role_params = dict(page_num=1, page_size=10) - table_info = get_role_list_api(role_params) - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(checked=True, disabled=item['role_id'] == 1) - else: - item['status'] = dict(checked=False, disabled=item['role_id'] == 1) - item['key'] = str(item['role_id']) - if item['role_id'] == 1: - item['operation'] = [] - else: - item['operation'] = fac.AntdSpace( - [ - fac.AntdButton( - '修改', - id={ - 'type': 'role-operation-table', - 'operation': 'edit', - 'index': str(item['role_id']) - }, - type='link', - icon=fac.AntdIcon( - icon='antd-edit' - ), - style={ - 'padding': 0 - } - ) if 'system:role:edit' in button_perms else [], - fac.AntdButton( - '删除', - id={ - 'type': 'role-operation-table', - 'operation': 'delete', - 'index': str(item['role_id']) - }, - type='link', - icon=fac.AntdIcon( - icon='antd-delete' - ), - style={ - 'padding': 0 - } - ) if 'system:role:remove' in button_perms else [], - fac.AntdPopover( - fac.AntdButton( - '更多', - type='link', - icon=fac.AntdIcon( - icon='antd-more' - ), - style={ - 'padding': 0 - } - ), - content=fac.AntdSpace( - [ - fac.AntdButton( - '数据权限', - id={ - 'type': 'role-operation-table', - 'operation': 'datascope', - 'index': str(item['role_id']) - }, - type='text', - block=True, - icon=fac.AntdIcon( - icon='antd-check-circle' - ), - style={ - 'padding': 0 - } - ), - fac.AntdButton( - '分配用户', - id={ - 'type': 'role-operation-table', - 'operation': 'allocation', - 'index': str(item['role_id']) - }, - type='text', - block=True, - icon=fac.AntdIcon( - icon='antd-user' - ), - style={ - 'padding': 0 - } - ), - ], - direction='vertical' - ), - placement='bottomRight' - ) if 'system:role:edit' in button_perms else [] - ] - ) + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = role_c.generate_role_table(query_params) return [ - dcc.Store(id='role-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='role-export-complete-judge-container'), # 绑定的导出组件 dcc.Download(id='role-export-container'), # 角色管理模块操作类型存储容器 dcc.Store(id='role-operations-store'), - dcc.Store(id='role-operations-store-bk'), + # 角色管理模块弹窗类型存储容器 + dcc.Store(id='role-modal_type-store'), # 角色管理模块修改操作行key存储容器 dcc.Store(id='role-edit-id-store'), # 角色管理模块删除操作行key存储容器 @@ -152,10 +49,12 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 220 - } + }, ), label='角色名称', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdInput( @@ -165,41 +64,38 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 220 - } + }, ), label='权限字符', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='role-status-select', placeholder='角色状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 220 - } + }, ), label='状态', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdDateRangePicker( id='role-create_time-range', style={ 'width': 240 - } + }, ), label='创建时间', - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -208,9 +104,11 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ), - style={'paddingBottom': '10px'}, + style={ + 'paddingBottom': '10px' + }, ), fac.AntdFormItem( fac.AntdButton( @@ -218,16 +116,18 @@ def render(*args, **kwargs): id='role-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ), - style={'paddingBottom': '10px'}, - ) + style={ + 'paddingBottom': '10px' + }, + ), ], layout='inline', ) ], id='role-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -246,15 +146,19 @@ def render(*args, **kwargs): ], id={ 'type': 'role-operation-button', - 'operation': 'add' + 'operation': 'add', }, style={ 'color': '#1890ff', 'background': '#e8f4ff', 'border-color': '#a3d3ff', - 'marginRight': '10px' - } - ) if 'system:role:add' in button_perms else [], + 'marginRight': '10px', + }, + ) + if PermissionManager.check_perms( + 'system:role:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -264,16 +168,20 @@ def render(*args, **kwargs): ], id={ 'type': 'role-operation-button', - 'operation': 'edit' + 'operation': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', 'border-color': '#d0f5e0', - 'marginRight': '10px' - } - ) if 'system:role:edit' in button_perms else [], + 'marginRight': '10px', + }, + ) + if PermissionManager.check_perms( + 'system:role:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -283,16 +191,20 @@ def render(*args, **kwargs): ], id={ 'type': 'role-operation-button', - 'operation': 'delete' + 'operation': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', 'border-color': '#ffdbdb', - 'marginRight': '10px' - } - ) if 'system:role:remove' in button_perms else [], + 'marginRight': '10px', + }, + ) + if PermissionManager.check_perms( + 'system:role:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -305,15 +217,17 @@ def render(*args, **kwargs): 'color': '#ffba00', 'background': '#fff8e6', 'border-color': '#ffe399', - 'marginRight': '10px' - } - ) if 'system:role:export' in button_perms else [], + 'marginRight': '10px', + }, + ) + if PermissionManager.check_perms( + 'system:role:export' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -327,10 +241,10 @@ def render(*args, **kwargs): ), ], id='role-hidden', - shape='circle' + shape='circle', ), id='role-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -342,24 +256,22 @@ def render(*args, **kwargs): ), ], id='role-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -400,6 +312,7 @@ def render(*args, **kwargs): { 'dataIndex': 'status', 'title': '状态', + 'width': 100, 'renderOptions': { 'renderType': 'switch' }, @@ -413,39 +326,31 @@ def render(*args, **kwargs): }, { 'title': '操作', - 'width': 180, + 'width': 200, 'dataIndex': 'operation', - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'padding-right': '10px' - } + 'padding-right': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=24 + span=24, ) ], - gutter=5 + gutter=5, ), - # 新增和编辑角色表单modal fac.AntdModal( [ @@ -456,126 +361,84 @@ def render(*args, **kwargs): id={ 'type': 'role-form-value', 'index': 'role_name', - 'required': True + 'required': True, }, placeholder='请输入角色名称', allowClear=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='角色名称', required=True, id={ 'type': 'role-form-label', 'index': 'role_name', - 'required': True - }, - labelCol={ - 'span': 6 + 'required': True, }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( fac.AntdInput( id={ 'type': 'role-form-value', 'index': 'role_key', - 'required': True + 'required': True, }, placeholder='请输入权限字符', allowClear=True, - style={ - 'width': 350 - } - ), - label=html.Div( - [ - fac.AntdTooltip( - fac.AntdIcon( - icon='antd-question-circle' - ), - title='控制器中定义的权限字符,如:common' - ), - fac.AntdText('权限字符') - ] + style={'width': 350}, ), + label='权限字符', + tooltip='控制器中定义的权限字符,如:common', required=True, id={ 'type': 'role-form-label', 'index': 'role_key', - 'required': True - }, - labelCol={ - 'span': 6 + 'required': True, }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( fac.AntdInputNumber( id={ 'type': 'role-form-value', 'index': 'role_sort', - 'required': True + 'required': True, }, placeholder='请输入角色顺序', defaultValue=0, min=0, - style={ - 'width': 350 - } + style={'width': 350}, ), label='角色顺序', required=True, id={ 'type': 'role-form-label', 'index': 'role_sort', - 'required': True + 'required': True, }, - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( - fac.AntdRadioGroup( + ApiRadioGroup( + dict_type='sys_normal_disable', id={ 'type': 'role-form-value', 'index': 'status', - 'required': False + 'required': False, }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], - style={ - 'width': 350 - } + style={'width': 350}, ), label='状态', id={ 'type': 'role-form-label', 'index': 'status', - 'required': False - }, - labelCol={ - 'span': 6 + 'required': False, }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( [ @@ -584,14 +447,14 @@ def render(*args, **kwargs): fac.AntdCol( fac.AntdCheckbox( id='role-menu-perms-radio-fold-unfold', - label='展开/折叠' + label='展开/折叠', ), span=7, ), fac.AntdCol( fac.AntdCheckbox( id='role-menu-perms-radio-all-none', - label='全选/全不选' + label='全选/全不选', ), span=8, ), @@ -599,14 +462,12 @@ def render(*args, **kwargs): fac.AntdCheckbox( id='role-menu-perms-radio-parent-children', label='父子联动', - checked=True + checked=True, ), span=6, ), ], - style={ - 'paddingTop': '6px' - } + style={'paddingTop': '6px'}, ), fac.AntdRow( fac.AntdCol( @@ -618,56 +479,44 @@ def render(*args, **kwargs): multiple=True, checkable=True, showLine=False, - selectable=False + selectable=False, ) ], style={ 'border': 'solid 1px rgba(0, 0, 0, 0.2)', 'border-radius': '5px', - 'width': 350 - } + 'width': 350, + }, ) ), - style={ - 'paddingTop': '6px' - } + style={'paddingTop': '6px'}, ), ], label='菜单权限', id='role-menu-perms-form-item', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( fac.AntdInput( id={ 'type': 'role-form-value', 'index': 'remark', - 'required': False + 'required': False, }, placeholder='请输入内容', allowClear=True, mode='text-area', - style={ - 'width': 350 - } + style={'width': 350}, ), label='备注', id={ 'type': 'role-form-label', 'index': 'remark', - 'required': False + 'required': False, }, - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), ] ) @@ -676,9 +525,8 @@ def render(*args, **kwargs): mask=False, width=600, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除角色二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='role-delete-text'), @@ -686,9 +534,8 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), - # 数据权限modal fac.AntdModal( data_scope.render(), @@ -697,18 +544,17 @@ def render(*args, **kwargs): mask=False, width=600, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 分配用户modal fac.AntdModal( - allocate_user.render(button_perms), + allocate_user.render(), id='role_to_allocated_user-modal', title='分配用户', mask=False, maskClosable=False, width=1000, renderFooter=False, - okClickClose=False - ) + okClickClose=False, + ), ] diff --git a/dash-fastapi-frontend/views/system/role/allocate_user.py b/dash-fastapi-frontend/views/system/role/allocate_user.py index 2deaa70c30981ae146013362aed03e1d130c936a..80f88e981317e4a14e76569a1b683a96d274951b 100644 --- a/dash-fastapi-frontend/views/system/role/allocate_user.py +++ b/dash-fastapi-frontend/views/system/role/allocate_user.py @@ -1,32 +1,38 @@ -from dash import dcc, html import feffery_antd_components as fac - +from dash import dcc +from callbacks.system_c.role_c import allocate_user_c # noqa: F401 from .component import query_form_table -import callbacks.system_c.role_c.allocate_user_c - -def render(button_perms): +def render(): return [ - dcc.Store(id='allocate_user-button-perms-container', data=button_perms), dcc.Store(id='allocate_user-role_id-container'), # 分配用户模块操作类型存储容器 - dcc.Store(id={ - 'type': 'allocate_user-operations-container', - 'index': 'allocated' - }), - dcc.Store(id={ - 'type': 'allocate_user-operations-container', - 'index': 'unallocated' - }), + dcc.Store( + id={ + 'type': 'allocate_user-operations-container', + 'index': 'allocated', + } + ), + dcc.Store( + id={ + 'type': 'allocate_user-operations-container', + 'index': 'unallocated', + } + ), # 分配用户模块删除操作行key存储容器 dcc.Store(id='allocate_user-delete-ids-store'), - query_form_table.render(button_perms=button_perms, allocate_index='allocated', is_operation=True), - + query_form_table.render( + allocate_index='allocated', + is_operation=True, + ), # 添加用户表单modal fac.AntdModal( [ - query_form_table.render(button_perms=button_perms, allocate_index='unallocated', is_operation=False), + query_form_table.render( + allocate_index='unallocated', + is_operation=False, + ), ], id='allocate_user-modal', title='选择用户', @@ -34,15 +40,14 @@ def render(button_perms): maskClosable=False, width=900, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 取消授权二次确认modal fac.AntdModal( fac.AntdText('是否确认取消授权?', id='allocate_user-delete-text'), id='allocate_user-delete-confirm-modal', visible=False, title='提示', - renderFooter=True + renderFooter=True, ), ] diff --git a/dash-fastapi-frontend/views/system/role/component/query_form_table.py b/dash-fastapi-frontend/views/system/role/component/query_form_table.py index 893dd0b26d4d0374e31e06e73a5191a1699a2426..468b4e61554771fd0342cd007eb5579bf440afc6 100644 --- a/dash-fastapi-frontend/views/system/role/component/query_form_table.py +++ b/dash-fastapi-frontend/views/system/role/component/query_form_table.py @@ -1,8 +1,9 @@ -from dash import html import feffery_antd_components as fac +from dash import html +from utils.permission_util import PermissionManager -def render(button_perms, allocate_index, is_operation): +def render(allocate_index, is_operation): table_column = [ { 'dataIndex': 'user_id', @@ -12,44 +13,32 @@ def render(button_perms, allocate_index, is_operation): { 'dataIndex': 'user_name', 'title': '用户名称', - 'renderOptions': { - 'renderType': 'ellipsis' - }, + 'renderOptions': {'renderType': 'ellipsis'}, }, { 'dataIndex': 'nick_name', 'title': '用户昵称', - 'renderOptions': { - 'renderType': 'ellipsis' - }, + 'renderOptions': {'renderType': 'ellipsis'}, }, { 'dataIndex': 'email', 'title': '邮箱', - 'renderOptions': { - 'renderType': 'ellipsis' - }, + 'renderOptions': {'renderType': 'ellipsis'}, }, { 'dataIndex': 'phonenumber', 'title': '手机', - 'renderOptions': { - 'renderType': 'ellipsis' - }, + 'renderOptions': {'renderType': 'ellipsis'}, }, { 'dataIndex': 'status', 'title': '状态', - 'renderOptions': { - 'renderType': 'tags' - }, + 'renderOptions': {'renderType': 'tags'}, }, { 'dataIndex': 'create_time', 'title': '创建时间', - 'renderOptions': { - 'renderType': 'ellipsis' - }, + 'renderOptions': {'renderType': 'ellipsis'}, }, ] @@ -60,231 +49,242 @@ def render(button_perms, allocate_index, is_operation): 'dataIndex': 'operation', 'fixed': 'right', 'width': 150, - 'renderOptions': { - 'renderType': 'button' - }, + 'renderOptions': {'renderType': 'button'}, } ) return fac.AntdRow( - [ - fac.AntdCol( - [ - fac.AntdRow( - [ - fac.AntdCol( - html.Div( - [ - fac.AntdForm( - [ - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'allocate_user-user_name-input', - 'index': allocate_index - }, - placeholder='请输入用户名称', - autoComplete='off', - allowClear=True, - style={ - 'width': 240 - } - ), - label='用户名称', - style={'paddingBottom': '10px'}, - ), - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'allocate_user-phonenumber-input', - 'index': allocate_index - }, - placeholder='请输入手机号码', - autoComplete='off', - allowClear=True, - style={ - 'width': 240 - } - ), - label='手机号码', - style={'paddingBottom': '10px'}, - ), - fac.AntdFormItem( - fac.AntdButton( - '搜索', - id={ - 'type': 'allocate_user-search', - 'index': allocate_index - }, - type='primary', - icon=fac.AntdIcon( - icon='antd-search' - ) - ), - style={'paddingBottom': '10px'}, - ), - fac.AntdFormItem( - fac.AntdButton( - '重置', - id={ - 'type': 'allocate_user-reset', - 'index': allocate_index - }, - icon=fac.AntdIcon( - icon='antd-sync' - ) - ), - style={'paddingBottom': '10px'}, - ) - ], - layout='inline', - ) - ], - id={ - 'type': 'allocate_user-search-form-container', - 'index': allocate_index - }, - hidden=False - ), - ) - ] - ), - fac.AntdRow( - [ - fac.AntdCol( - fac.AntdSpace( - [ - fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-plus' + [ + fac.AntdCol( + [ + fac.AntdRow( + [ + fac.AntdCol( + html.Div( + [ + fac.AntdForm( + [ + fac.AntdFormItem( + fac.AntdInput( + id={ + 'type': 'allocate_user-user_name-input', + 'index': allocate_index, + }, + placeholder='请输入用户名称', + autoComplete='off', + allowClear=True, + style={'width': 240}, ), - '添加用户', - ], - id='allocate_user-add', - style={ - 'color': '#1890ff', - 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:role:edit' in button_perms else [], - fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-close-circle' + label='用户名称', + style={ + 'paddingBottom': '10px' + }, + ), + fac.AntdFormItem( + fac.AntdInput( + id={ + 'type': 'allocate_user-phonenumber-input', + 'index': allocate_index, + }, + placeholder='请输入手机号码', + autoComplete='off', + allowClear=True, + style={'width': 240}, ), - '批量取消授权', - ], - id={ - 'type': 'allocate_user-operation-button', - 'index': 'delete' - }, - disabled=True, - style={ - 'color': '#ff9292', - 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:role:edit' in button_perms else [], - ], - style={ - 'paddingBottom': '10px' - } - ), - span=16 - ) if is_operation else [], - fac.AntdCol( - fac.AntdSpace( - [ - html.Div( - fac.AntdTooltip( + label='手机号码', + style={ + 'paddingBottom': '10px' + }, + ), + fac.AntdFormItem( fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-search' - ), - ], + '搜索', id={ - 'type': 'allocate_user-hidden', - 'index': allocate_index + 'type': 'allocate_user-search', + 'index': allocate_index, }, - shape='circle' + type='primary', + icon=fac.AntdIcon( + icon='antd-search' + ), ), - id={ - 'type': 'allocate_user-hidden-tooltip', - 'index': allocate_index + style={ + 'paddingBottom': '10px' }, - title='隐藏搜索' - ) - ), - html.Div( - fac.AntdTooltip( + ), + fac.AntdFormItem( fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-sync' - ), - ], + '重置', id={ - 'type': 'allocate_user-refresh', - 'index': allocate_index + 'type': 'allocate_user-reset', + 'index': allocate_index, }, - shape='circle' + icon=fac.AntdIcon( + icon='antd-sync' + ), ), - title='刷新' - ) - ), - ], - style={ - 'float': 'right', - 'paddingBottom': '10px' - } - ), - span=8 if is_operation else 24, - style={ - 'paddingRight': '10px' - } - ) - ], - gutter=5 - ), - fac.AntdRow( - [ - fac.AntdCol( - fac.AntdSpin( - fac.AntdTable( - id={ - 'type': 'allocate_user-list-table', - 'index': allocate_index + style={ + 'paddingBottom': '10px' + }, + ), + ], + layout='inline', + ) + ], + id={ + 'type': 'allocate_user-search-form-container', + 'index': allocate_index, + }, + hidden=False, + ), + ) + ] + ), + fac.AntdRow( + [ + fac.AntdCol( + fac.AntdSpace( + [ + fac.AntdButton( + [ + fac.AntdIcon(icon='antd-plus'), + '添加用户', + ], + id='allocate_user-add', + style={ + 'color': '#1890ff', + 'background': '#e8f4ff', + 'border-color': '#a3d3ff', }, - data=[], - columns=table_column, - rowSelectionType='checkbox', - rowSelectionWidth=50, - bordered=True, - maxWidth=1000, - pagination={ - 'pageSize': 10, - 'current': 1, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': 0 + ) + if PermissionManager.check_perms( + 'system:role:add' + ) + else [], + fac.AntdButton( + [ + fac.AntdIcon( + icon='antd-close-circle' + ), + '批量取消授权', + ], + id={ + 'type': 'allocate_user-operation-button', + 'index': 'delete', }, - mode='server-side', + disabled=True, style={ - 'width': '100%', - 'padding-right': '10px' - } + 'color': '#ff9292', + 'background': '#ffeded', + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:role:remove' + ) + else [], + ], + style={'paddingBottom': '10px'}, + ), + span=16, + ) + if is_operation + else [], + fac.AntdCol( + fac.AntdSpace( + [ + html.Div( + fac.AntdTooltip( + fac.AntdButton( + [ + fac.AntdIcon( + icon='antd-search' + ), + ], + id={ + 'type': 'allocate_user-hidden', + 'index': allocate_index, + }, + shape='circle', + ), + id={ + 'type': 'allocate_user-hidden-tooltip', + 'index': allocate_index, + }, + title='隐藏搜索', + ) ), - text='数据加载中' + html.Div( + fac.AntdTooltip( + fac.AntdButton( + [ + fac.AntdIcon( + icon='antd-sync' + ), + ], + id={ + 'type': 'allocate_user-refresh', + 'index': allocate_index, + }, + shape='circle', + ), + title='刷新', + ) + ), + ], + style={ + 'float': 'right', + 'paddingBottom': '10px', + }, + ), + span=8 if is_operation else 24, + style={'paddingRight': '10px'}, + ), + ], + gutter=5, + ), + fac.AntdRow( + [ + fac.AntdCol( + fac.AntdSpin( + fac.AntdTable( + id={ + 'type': 'allocate_user-list-table', + 'index': allocate_index, + }, + data=[], + columns=table_column, + rowSelectionType='checkbox', + rowSelectionWidth=50, + bordered=True, + maxWidth=1000, + pagination={ + 'pageSize': 10, + 'current': 1, + 'showSizeChanger': True, + 'pageSizeOptions': [ + 10, + 30, + 50, + 100, + ], + 'showQuickJumper': True, + 'total': 0, + }, + mode='server-side', + style={ + 'width': '100%', + 'padding-right': '10px', + }, ), - ) - ] - ), - ], - span=24 - ) - ], - gutter=5 - ) + text='数据加载中', + ), + ) + ] + ), + ], + span=24, + ) + ], + gutter=5, + ) diff --git a/dash-fastapi-frontend/views/system/role/data_scope.py b/dash-fastapi-frontend/views/system/role/data_scope.py index 7322e79f4e4973ec19a302e59d25ea3fc487f3df..6f1798a7902826aa6301a04fda2703d136794356 100644 --- a/dash-fastapi-frontend/views/system/role/data_scope.py +++ b/dash-fastapi-frontend/views/system/role/data_scope.py @@ -1,7 +1,6 @@ -from dash import html import feffery_antd_components as fac - -import callbacks.system_c.role_c.data_scope_c +from dash import html +import callbacks.system_c.role_c.data_scope_c # noqa: F401 def render(): @@ -12,95 +11,54 @@ def render(): fac.AntdInput( id={ 'type': 'datascope-form-value', - 'index': 'role_name' + 'index': 'role_name', }, placeholder='请输入角色名称', allowClear=True, disabled=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='角色名称', - id={ - 'type': 'datascope-form-label', - 'index': 'role_name' - }, - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id={'type': 'datascope-form-label', 'index': 'role_name'}, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( fac.AntdInput( id={ 'type': 'datascope-form-value', - 'index': 'role_key' + 'index': 'role_key', }, placeholder='请输入权限字符', allowClear=True, disabled=True, - style={ - 'width': 350 - } + style={'width': 350}, ), label='权限字符', - id={ - 'type': 'datascope-form-label', - 'index': 'role_key' - }, - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id={'type': 'datascope-form-label', 'index': 'role_key'}, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), fac.AntdFormItem( fac.AntdSelect( id={ 'type': 'datascope-form-value', - 'index': 'data_scope' + 'index': 'data_scope', }, options=[ - { - 'label': '全部数据权限', - 'value': '1' - }, - { - 'label': '自定义数据权限', - 'value': '2' - }, - { - 'label': '本部门数据权限', - 'value': '3' - },{ - 'label': '本部门及以下数据权限', - 'value': '4' - }, - { - 'label': '仅本人数据权限', - 'value': '5' - } + {'label': '全部数据权限', 'value': '1'}, + {'label': '自定义数据权限', 'value': '2'}, + {'label': '本部门数据权限', 'value': '3'}, + {'label': '本部门及以下数据权限', 'value': '4'}, + {'label': '仅本人数据权限', 'value': '5'}, ], placeholder='请选择权限范围', - style={ - 'width': 350 - } + style={'width': 350}, ), label='权限范围', - id={ - 'type': 'datascope-form-label', - 'index': 'data_scope' - }, - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + id={'type': 'datascope-form-label', 'index': 'data_scope'}, + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), html.Div( fac.AntdFormItem( @@ -110,14 +68,14 @@ def render(): fac.AntdCol( fac.AntdCheckbox( id='role-dept-perms-radio-fold-unfold', - label='展开/折叠' + label='展开/折叠', ), span=7, ), fac.AntdCol( fac.AntdCheckbox( id='role-dept-perms-radio-all-none', - label='全选/全不选' + label='全选/全不选', ), span=8, ), @@ -125,14 +83,12 @@ def render(): fac.AntdCheckbox( id='role-dept-perms-radio-parent-children', label='父子联动', - checked=True + checked=True, ), span=6, ), ], - style={ - 'paddingTop': '6px' - } + style={'paddingTop': '6px'}, ), fac.AntdRow( fac.AntdCol( @@ -144,32 +100,26 @@ def render(): multiple=True, checkable=True, showLine=False, - selectable=False + selectable=False, ) ], style={ 'border': 'solid 1px rgba(0, 0, 0, 0.2)', 'border-radius': '5px', - 'width': 350 - } + 'width': 350, + }, ) ), - style={ - 'paddingTop': '6px' - } + style={'paddingTop': '6px'}, ), ], label='数据权限', id='role-dept-perms-form-item', - labelCol={ - 'span': 6 - }, - wrapperCol={ - 'span': 18 - } + labelCol={'span': 6}, + wrapperCol={'span': 18}, ), - id='role-dept-perms-div' - ) + id='role-dept-perms-div', + ), ] ) ] diff --git a/dash-fastapi-frontend/views/system/user/__init__.py b/dash-fastapi-frontend/views/system/user/__init__.py index 934d9b6e70c4f3542b4702971b47ec33cb73926b..928026261c244d49b4acd0c35256737e85445b4e 100644 --- a/dash-fastapi-frontend/views/system/user/__init__.py +++ b/dash-fastapi-frontend/views/system/user/__init__.py @@ -1,71 +1,31 @@ -from dash import dcc, html import feffery_antd_components as fac -from flask import session - -from . import profile, allocate_role -from api.user import get_user_list_api -from api.dept import get_dept_tree_api -from config.global_config import ApiBaseUrlConfig - -import callbacks.system_c.user_c.user_c +from dash import dcc, html +from api.system.user import UserApi +from callbacks.system_c.user_c import user_c +from components import ManuallyUpload +from components.ApiRadioGroup import ApiRadioGroup +from components.ApiSelect import ApiSelect +from utils.permission_util import PermissionManager +from . import allocate_role, profile # noqa: F401 def render(*args, **kwargs): - button_perms = kwargs.get('button_perms') - dept_params = dict(dept_name='') - user_params = dict(page_num=1, page_size=10) - tree_info = get_dept_tree_api(dept_params) - table_info = get_user_list_api(user_params) - tree_data = [] - table_data = [] - page_num = 1 - page_size = 10 - total = 0 - if tree_info['code'] == 200: - tree_data = tree_info['data'] - if table_info['code'] == 200: - table_data = table_info['data']['rows'] - page_num = table_info['data']['page_num'] - page_size = table_info['data']['page_size'] - total = table_info['data']['total'] - for item in table_data: - if item['status'] == '0': - item['status'] = dict(checked=True, disabled=item['user_id'] == 1) - else: - item['status'] = dict(checked=False, disabled=item['user_id'] == 1) - item['key'] = str(item['user_id']) - if item['user_id'] == 1: - item['operation'] = [] - else: - item['operation'] = [ - { - 'title': '修改', - 'icon': 'antd-edit' - } if 'system:user:edit' in button_perms else None, - { - 'title': '删除', - 'icon': 'antd-delete' - } if 'system:user:remove' in button_perms else None, - { - 'title': '重置密码', - 'icon': 'antd-key' - } if 'system:user:resetPwd' in button_perms else None, - { - 'title': '分配角色', - 'icon': 'antd-check-circle' - } if 'system:user:edit' in button_perms else None - ] + query_params = dict(page_num=1, page_size=10) + table_data, table_pagination = user_c.generate_user_table(query_params) + tree_info = UserApi.dept_tree_select() + tree_data = tree_info['data'] return [ - dcc.Store(id='user-button-perms-container', data=button_perms), # 用于导出成功后重置dcc.Download的状态,防止多次下载文件 dcc.Store(id='user-export-complete-judge-container'), # 绑定的导出组件 dcc.Download(id='user-export-container'), # 用户管理模块操作类型存储容器 dcc.Store(id='user-operations-store'), - # 用户管理模块修改操作行key存储容器 - dcc.Store(id='user-edit-id-store'), + # 用户管理模块弹窗类型存储容器 + dcc.Store(id='user-modal_type-store'), + # 用户管理模块表单数据存储容器 + dcc.Store(id='user-form-store'), # 用户管理模块删除操作行key存储容器 dcc.Store(id='user-delete-ids-store'), fac.AntdRow( @@ -77,24 +37,18 @@ def render(*args, **kwargs): placeholder='请输入部门名称', autoComplete='off', allowClear=True, - prefix=fac.AntdIcon( - icon='antd-search' - ), - style={ - 'width': '85%' - } + prefix=fac.AntdIcon(icon='antd-search'), + style={'width': '85%'}, ), fac.AntdTree( id='dept-tree', treeData=tree_data, defaultExpandAll=True, showLine=False, - style={ - 'margin-top': '10px' - } - ) + style={'margin-top': '10px'}, + ), ], - span=4 + span=4, ), fac.AntdCol( [ @@ -115,9 +69,9 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), - label='用户名称' + label='用户名称', ), fac.AntdFormItem( fac.AntdInput( @@ -127,34 +81,25 @@ def render(*args, **kwargs): allowClear=True, style={ 'width': 240 - } + }, ), - label='手机号码' + label='手机号码', ), fac.AntdFormItem( - fac.AntdSelect( + ApiSelect( + dict_type='sys_normal_disable', id='user-status-select', placeholder='用户状态', - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - } - ], style={ 'width': 240 - } + }, ), - label='用户状态' + label='用户状态', ), ], style={ 'paddingBottom': '10px' - } + }, ), fac.AntdSpace( [ @@ -163,9 +108,9 @@ def render(*args, **kwargs): id='user-create_time-range', style={ 'width': 240 - } + }, ), - label='创建时间' + label='创建时间', ), fac.AntdFormItem( fac.AntdButton( @@ -174,7 +119,7 @@ def render(*args, **kwargs): type='primary', icon=fac.AntdIcon( icon='antd-search' - ) + ), ) ), fac.AntdFormItem( @@ -183,20 +128,20 @@ def render(*args, **kwargs): id='user-reset', icon=fac.AntdIcon( icon='antd-sync' - ) + ), ) - ) + ), ], style={ 'paddingBottom': '10px' - } + }, ), ], layout='inline', ) ], id='user-search-form-container', - hidden=False + hidden=False, ), ) ] @@ -213,13 +158,20 @@ def render(*args, **kwargs): ), '新增', ], - id='user-add', + id={ + 'type': 'user-operation-button', + 'index': 'add', + }, style={ 'color': '#1890ff', 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:user:add' in button_perms else [], + 'border-color': '#a3d3ff', + }, + ) + if PermissionManager.check_perms( + 'system:user:add' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -229,15 +181,19 @@ def render(*args, **kwargs): ], id={ 'type': 'user-operation-button', - 'index': 'edit' + 'index': 'edit', }, disabled=True, style={ 'color': '#71e2a3', 'background': '#e7faf0', - 'border-color': '#d0f5e0' - } - ) if 'system:user:edit' in button_perms else [], + 'border-color': '#d0f5e0', + }, + ) + if PermissionManager.check_perms( + 'system:user:edit' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -247,15 +203,19 @@ def render(*args, **kwargs): ], id={ 'type': 'user-operation-button', - 'index': 'delete' + 'index': 'delete', }, disabled=True, style={ 'color': '#ff9292', 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:user:remove' in button_perms else [], + 'border-color': '#ffdbdb', + }, + ) + if PermissionManager.check_perms( + 'system:user:remove' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -267,9 +227,13 @@ def render(*args, **kwargs): style={ 'color': '#909399', 'background': '#f4f4f5', - 'border-color': '#d3d4d6' - } - ) if 'system:user:export' in button_perms else [], + 'border-color': '#d3d4d6', + }, + ) + if PermissionManager.check_perms( + 'system:user:import' + ) + else [], fac.AntdButton( [ fac.AntdIcon( @@ -281,15 +245,17 @@ def render(*args, **kwargs): style={ 'color': '#ffba00', 'background': '#fff8e6', - 'border-color': '#ffe399' - } - ) if 'system:user:import' in button_perms else [], + 'border-color': '#ffe399', + }, + ) + if PermissionManager.check_perms( + 'system:user:export' + ) + else [], ], - style={ - 'paddingBottom': '10px' - } + style={'paddingBottom': '10px'}, ), - span=16 + span=16, ), fac.AntdCol( fac.AntdSpace( @@ -303,10 +269,10 @@ def render(*args, **kwargs): ), ], id='user-hidden', - shape='circle' + shape='circle', ), id='user-hidden-tooltip', - title='隐藏搜索' + title='隐藏搜索', ) ), html.Div( @@ -318,24 +284,22 @@ def render(*args, **kwargs): ), ], id='user-refresh', - shape='circle' + shape='circle', ), - title='刷新' + title='刷新', ) ), ], style={ 'float': 'right', - 'paddingBottom': '10px' - } + 'paddingBottom': '10px', + }, ), span=8, - style={ - 'paddingRight': '10px' - } - ) + style={'paddingRight': '10px'}, + ), ], - gutter=5 + gutter=5, ), fac.AntdRow( [ @@ -348,7 +312,6 @@ def render(*args, **kwargs): { 'dataIndex': 'user_id', 'title': '用户编号', - 'width': 100, 'renderOptions': { 'renderType': 'ellipsis' }, @@ -356,7 +319,6 @@ def render(*args, **kwargs): { 'dataIndex': 'user_name', 'title': '用户名称', - 'width': 120, 'renderOptions': { 'renderType': 'ellipsis' }, @@ -364,7 +326,6 @@ def render(*args, **kwargs): { 'dataIndex': 'nick_name', 'title': '用户昵称', - 'width': 120, 'renderOptions': { 'renderType': 'ellipsis' }, @@ -372,7 +333,6 @@ def render(*args, **kwargs): { 'dataIndex': 'dept_name', 'title': '部门', - 'width': 130, 'renderOptions': { 'renderType': 'ellipsis' }, @@ -380,7 +340,6 @@ def render(*args, **kwargs): { 'dataIndex': 'phonenumber', 'title': '手机号码', - 'width': 130, 'renderOptions': { 'renderType': 'ellipsis' }, @@ -388,7 +347,7 @@ def render(*args, **kwargs): { 'dataIndex': 'status', 'title': '状态', - 'width': 110, + 'width': 100, 'renderOptions': { 'renderType': 'switch' }, @@ -396,7 +355,6 @@ def render(*args, **kwargs): { 'dataIndex': 'create_time', 'title': '创建时间', - 'width': 160, 'renderOptions': { 'renderType': 'ellipsis' }, @@ -404,585 +362,292 @@ def render(*args, **kwargs): { 'title': '操作', 'dataIndex': 'operation', + 'width': 120, 'renderOptions': { 'renderType': 'dropdown', 'dropdownProps': { 'title': '更多' - } + }, }, - } + }, ], rowSelectionType='checkbox', rowSelectionWidth=50, bordered=True, - pagination={ - 'pageSize': page_size, - 'current': page_num, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': total - }, + pagination=table_pagination, mode='server-side', style={ 'width': '100%', - 'paddingRight': '10px' - } + 'paddingRight': '10px', + }, ), - text='数据加载中' + text='数据加载中', ), ) ] ), ], - span=20 - ) + span=20, + ), ], - gutter=5 + gutter=5, ), - - # 新增用户表单modal + # 新增和编辑用户表单modal fac.AntdModal( [ fac.AntdForm( [ - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'user_add-form-value', - 'index': 'nick_name' - }, - placeholder='请输入用户昵称', - allowClear=True, - style={ - 'width': 200 - } - ), - label='用户昵称', - required=True, - id={ - 'type': 'user_add-form-label', - 'index': 'nick_name', - 'required': True - } - ), - fac.AntdFormItem( - fac.AntdTreeSelect( - id={ - 'type': 'user_add-form-value', - 'index': 'dept_id' - }, - placeholder='请选择归属部门', - treeData=[], - treeNodeFilterProp='title', - style={ - 'width': 200 - } - ), - label='归属部门', - id={ - 'type': 'user_add-form-label', - 'index': 'dept_id', - 'required': False - }, - labelCol={ - 'offset': 1 - }, - ), - ], - size="middle" - ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + name='nick_name', + placeholder='请输入用户昵称', + allowClear=True, + style={'width': '100%'}, + ), + label='用户昵称', + required=True, id={ - 'type': 'user_add-form-value', - 'index': 'phonenumber' + 'type': 'user-form-label', + 'index': 'nick_name', + 'required': True, }, - placeholder='请输入手机号码', - allowClear=True, - style={ - 'width': 200 - } ), - label='手机号码', - id={ - 'type': 'user_add-form-label', - 'index': 'phonenumber', - 'required': False - }, - labelCol={ - 'offset': 1 - }, + span=12, ), - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdTreeSelect( + id='user-dpet-tree', + name='dept_id', + placeholder='请选择归属部门', + treeData=[], + treeNodeFilterProp='title', + style={'width': '100%'}, + ), + label='归属部门', id={ - 'type': 'user_add-form-value', - 'index': 'email' + 'type': 'user-form-label', + 'index': 'dept_id', + 'required': False, }, - placeholder='请输入邮箱', - allowClear=True, - style={ - 'width': 200 - } ), - label='邮箱', - id={ - 'type': 'user_add-form-label', - 'index': 'email', - 'required': False - }, - labelCol={ - 'offset': 5 - }, + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + name='phonenumber', + placeholder='请输入手机号码', + allowClear=True, + style={'width': '100%'}, + ), + label='手机号码', id={ - 'type': 'user_add-form-value', - 'index': 'user_name' + 'type': 'user-form-label', + 'index': 'phonenumber', + 'required': False, }, - placeholder='请输入用户名称', - allowClear=True, - style={ - 'width': 200 - } ), - label='用户名称', - required=True, - id={ - 'type': 'user_add-form-label', - 'index': 'user_name', - 'required': True - } + span=12, ), - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + name='email', + placeholder='请输入邮箱', + allowClear=True, + style={'width': '100%'}, + ), + label='邮箱', id={ - 'type': 'user_add-form-value', - 'index': 'password' + 'type': 'user-form-label', + 'index': 'email', + 'required': False, }, - placeholder='请输入密码', - mode='password', - passwordUseMd5=True, - style={ - 'width': 200 - } ), - label='用户密码', - required=True, - id={ - 'type': 'user_add-form-label', - 'index': 'password', - 'required': True - } + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdSelect( - id={ - 'type': 'user_add-form-value', - 'index': 'sex' - }, - placeholder='请选择性别', - options=[ - { - 'label': '男', - 'value': '0' - }, - { - 'label': '女', - 'value': '1' - }, - { - 'label': '未知', - 'value': '2' + html.Div( + fac.AntdRow( + [ + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='user-form-user_name', + name='user_name', + placeholder='请输入用户名称', + allowClear=True, + style={'width': '100%'}, + ), + label='用户名称', + required=True, + id={ + 'type': 'user-form-label', + 'index': 'user_name', + 'required': True, }, - ], - style={ - 'width': 200 - } + ), + span=12, ), - label='用户性别', - id={ - 'type': 'user_add-form-label', - 'index': 'sex', - 'required': False - }, - labelCol={ - 'offset': 1 - }, - ), - fac.AntdFormItem( - fac.AntdRadioGroup( - id={ - 'type': 'user_add-form-value', - 'index': 'status' - }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='user-form-password', + name='password', + placeholder='请输入密码', + mode='password', + passwordUseMd5=True, + style={'width': '100%'}, + ), + label='用户密码', + required=True, + id={ + 'type': 'user-form-label', + 'index': 'password', + 'required': True, }, - ], - defaultValue='0', - style={ - 'width': 200 - } - ), - label='用户状态', - id={ - 'type': 'user_add-form-label', - 'index': 'status', - 'required': False - }, - labelCol={ - 'offset': 2 - }, - ) - ], - size="middle" - ), - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdSelect( - id='user-add-post', - placeholder='请选择岗位', - options=[], - mode='multiple', - optionFilterProp='label', - style={ - 'width': 200 - } - ), - label='岗位', - id='user-add-post-form-item', - labelCol={ - 'offset': 4 - }, - ), - fac.AntdFormItem( - fac.AntdSelect( - id='user-add-role', - placeholder='请选择角色', - options=[], - mode='multiple', - optionFilterProp='label', - style={ - 'width': 200 - } + ), + span=12, ), - label='角色', - id='user-add-role-form-item', - labelCol={ - 'offset': 8 - }, - ), - ], - size="middle" + ], + gutter=10, + ), + id='user-user_name-password-container', ), - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'user_add-form-value', - 'index': 'remark' - }, - placeholder='请输入内容', - allowClear=True, - mode='text-area', - style={ - 'width': 490 - } - ), - label='备注', - id={ - 'type': 'user_add-form-label', - 'index': 'remark', - 'required': False - }, - labelCol={ - 'offset': 2 - }, - ), - ] - ) - ] - ) - ], - id='user-add-modal', - title='新增用户', - mask=False, - width=650, - renderFooter=True, - okClickClose=False - ), - - # 编辑用户表单modal - fac.AntdModal( - [ - fac.AntdForm( - [ - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + ApiSelect( + dict_type='sys_user_sex', + name='sex', + placeholder='请选择性别', + style={'width': '100%'}, + ), + label='用户性别', id={ - 'type': 'user_edit-form-value', - 'index': 'nick_name' + 'type': 'user-form-label', + 'index': 'sex', + 'required': False, }, - placeholder='请输入用户昵称', - allowClear=True, - style={ - 'width': 200 - } ), - label='用户昵称', - required=True, - id={ - 'type': 'user_edit-form-label', - 'index': 'nick_name', - 'required': True - } + span=12, ), - fac.AntdFormItem( - fac.AntdTreeSelect( + fac.AntdCol( + fac.AntdFormItem( + ApiRadioGroup( + dict_type='sys_normal_disable', + name='status', + defaultValue='0', + style={'width': '100%'}, + ), + label='用户状态', id={ - 'type': 'user_edit-form-value', - 'index': 'dept_id' + 'type': 'user-form-label', + 'index': 'status', + 'required': False, }, - placeholder='请选择归属部门', - treeData=[], - treeNodeFilterProp='title', - style={ - 'width': 200 - } ), - label='归属部门', - id={ - 'type': 'user_edit-form-label', - 'index': 'dept_id', - 'required': False - } + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdSelect( + id='user-post', + name='post_ids', + placeholder='请选择岗位', + options=[], + mode='multiple', + optionFilterProp='label', + style={'width': '100%'}, + ), + label='岗位', id={ - 'type': 'user_edit-form-value', - 'index': 'phonenumber' + 'type': 'user-form-label', + 'index': 'post_ids', + 'required': False, }, - placeholder='请输入手机号码', - allowClear=True, - style={ - 'width': 200 - } ), - label='手机号码', - id={ - 'type': 'user_edit-form-label', - 'index': 'phonenumber', - 'required': False - }, - labelCol={ - 'offset': 1 - }, + span=12, ), - fac.AntdFormItem( - fac.AntdInput( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdSelect( + id='user-role', + name='role_ids', + placeholder='请选择角色', + options=[], + mode='multiple', + optionFilterProp='label', + style={'width': '100%'}, + ), + label='角色', id={ - 'type': 'user_edit-form-value', - 'index': 'email' + 'type': 'user-form-label', + 'index': 'role_ids', + 'required': False, }, - placeholder='请输入邮箱', - allowClear=True, - style={ - 'width': 200 - } ), - label='邮箱', - id={ - 'type': 'user_edit-form-label', - 'index': 'email', - 'required': False - }, - labelCol={ - 'offset': 4 - }, + span=12, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( + fac.AntdRow( [ - fac.AntdFormItem( - fac.AntdSelect( - id={ - 'type': 'user_edit-form-value', - 'index': 'sex' - }, - placeholder='请选择性别', - options=[ - { - 'label': '男', - 'value': '0' - }, - { - 'label': '女', - 'value': '1' - }, - { - 'label': '未知', - 'value': '2' - }, - ], - style={ - 'width': 200 - } - ), - label='用户性别', - id={ - 'type': 'user_edit-form-label', - 'index': 'sex', - 'required': False - }, - labelCol={ - 'offset': 1 - }, - ), - fac.AntdFormItem( - fac.AntdRadioGroup( + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + name='remark', + placeholder='请输入内容', + allowClear=True, + mode='text-area', + style={'width': '100%'}, + ), + label='备注', id={ - 'type': 'user_edit-form-value', - 'index': 'status' + 'type': 'user-form-label', + 'index': 'remark', + 'required': False, }, - options=[ - { - 'label': '正常', - 'value': '0' - }, - { - 'label': '停用', - 'value': '1' - }, - ], - style={ - 'width': 200 - } + labelCol={'span': 4}, + wrapperCol={'span': 20}, ), - label='用户状态', - id={ - 'type': 'user_edit-form-label', - 'index': 'status', - 'required': False - }, - labelCol={ - 'offset': 1 - }, - ) - ], - size="middle" - ), - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdSelect( - id='user-edit-post', - placeholder='请选择岗位', - options=[], - mode='multiple', - optionFilterProp='label', - style={ - 'width': 200 - } - ), - label='岗位', - id='user-edit-post-form-item', - labelCol={ - 'offset': 4 - }, - ), - fac.AntdFormItem( - fac.AntdSelect( - id='user-edit-role', - placeholder='请选择角色', - options=[], - mode='multiple', - optionFilterProp='label', - style={ - 'width': 200 - } - ), - label='角色', - id='user-edit-role-form-item', - labelCol={ - 'offset': 7 - }, + span=24, ), ], - size="middle" + gutter=10, ), - fac.AntdSpace( - [ - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'user_edit-form-value', - 'index': 'remark' - }, - placeholder='请输入内容', - allowClear=True, - mode='text-area', - style={ - 'width': 485 - } - ), - label='备注', - id={ - 'type': 'user_edit-form-label', - 'index': 'remark', - 'required': False - }, - labelCol={ - 'offset': 2 - }, - ), - ] - ) - ] + ], + id='user-form', + enableBatchControl=True, + labelCol={'span': 8}, + wrapperCol={'span': 16}, + style={'marginRight': '15px'}, ) ], - id='user-edit-modal', - title='编辑用户', + id='user-modal', mask=False, width=650, renderFooter=True, - okClickClose=False + okClickClose=False, ), - # 删除用户二次确认modal fac.AntdModal( fac.AntdText('是否确认删除?', id='user-delete-text'), @@ -990,46 +655,30 @@ def render(*args, **kwargs): visible=False, title='提示', renderFooter=True, - centered=True + centered=True, ), - # 用户导入modal fac.AntdModal( [ html.Div( - fac.AntdDraggerUpload( - id='user-upload-choose', - apiUrl=f'{ApiBaseUrlConfig.BaseUrl}/common/upload', - apiUrlExtraParams={'taskPath': 'userUpload'}, - downloadUrl=f'{ApiBaseUrlConfig.BaseUrl}/common/caches', - downloadUrlExtraParams={'taskPath': 'userUpload', 'token': session.get('Authorization')}, - headers={'Authorization': 'Bearer ' + session.get('Authorization')}, - fileTypes=['xls', 'xlsx'], - fileListMaxLength=1, - text='用户导入', - hint='点击或拖拽文件至此处进行上传' - ), - style={ - 'marginTop': '10px' - } + [ + ManuallyUpload( + id='user-upload-choose', accept='.xls,.xlsx' + ), + ], + style={'marginTop': '10px'}, ), html.Div( [ fac.AntdCheckbox( - id='user-import-update-check', - checked=False + id='user-import-update-check', checked=False ), fac.AntdText( '是否更新已经存在的用户数据', - style={ - 'marginLeft': '5px' - } - ) + style={'marginLeft': '5px'}, + ), ], - style={ - 'textAlign': 'center', - 'marginTop': '10px' - } + style={'textAlign': 'center', 'marginTop': '10px'}, ), html.Div( [ @@ -1037,14 +686,11 @@ def render(*args, **kwargs): fac.AntdButton( '下载模板', id='download-user-import-template', - type='link' - ) + type='link', + ), ], - style={ - 'textAlign': 'center', - 'marginTop': '10px' - } - ) + style={'textAlign': 'center', 'marginTop': '10px'}, + ), ], id='user-import-confirm-modal', visible=False, @@ -1053,25 +699,19 @@ def render(*args, **kwargs): renderFooter=True, centered=True, okText='导入', - confirmAutoSpin=True, - loadingOkText='导入中', - okClickClose=False + okClickClose=False, ), - fac.AntdModal( fac.AntdText( id='batch-result-content', - className={ - 'whiteSpace': 'break-spaces' - } + className={'whiteSpace': 'break-spaces'}, ), id='batch-result-modal', visible=False, title='用户导入结果', renderFooter=False, - centered=True + centered=True, ), - # 重置密码modal fac.AntdModal( [ @@ -1079,32 +719,31 @@ def render(*args, **kwargs): [ fac.AntdFormItem( fac.AntdInput( - id='reset-password-input', - mode='password' + id='reset-password-input', mode='password' ), - label='请输入新密码' + label='请输入新密码', ), ], - layout='vertical' + layout='vertical', ), - dcc.Store(id='reset-password-row-key-store') + dcc.Store(id='reset-password-row-key-store'), ], id='user-reset-password-confirm-modal', visible=False, title='重置密码', renderFooter=True, - centered=True + centered=True, + okClickClose=False, ), - # 分配角色modal fac.AntdModal( - allocate_role.render(button_perms), + allocate_role.render(), id='user_to_allocated_role-modal', title='分配角色', mask=False, maskClosable=False, width=1000, renderFooter=False, - okClickClose=False - ) + okClickClose=False, + ), ] diff --git a/dash-fastapi-frontend/views/system/user/allocate_role.py b/dash-fastapi-frontend/views/system/user/allocate_role.py index 0b3f60f18d1ffec1287d61167e02be93a72c4491..91d17debedae21f890d6dabf8546137ad65d5ff9 100644 --- a/dash-fastapi-frontend/views/system/user/allocate_role.py +++ b/dash-fastapi-frontend/views/system/user/allocate_role.py @@ -1,48 +1,127 @@ -from dash import dcc, html import feffery_antd_components as fac +from dash import dcc +from callbacks.system_c.user_c import allocate_role_c # noqa: F401 -from .component import query_form_table -import callbacks.system_c.user_c.allocate_role_c - - -def render(button_perms): +def render(): return [ - dcc.Store(id='allocate_role-button-perms-container', data=button_perms), dcc.Store(id='allocate_role-user_id-container'), - # 分配角色模块操作类型存储容器 - dcc.Store(id={ - 'type': 'allocate_role-operations-container', - 'index': 'allocated' - }), - dcc.Store(id={ - 'type': 'allocate_role-operations-container', - 'index': 'unallocated' - }), - # 分配角色模块删除操作行key存储容器 - dcc.Store(id='allocate_role-delete-ids-store'), - query_form_table.render(button_perms=button_perms, allocate_index='allocated', is_operation=True), - - # 添加用户表单modal - fac.AntdModal( - [ - query_form_table.render(button_perms=button_perms, allocate_index='unallocated', is_operation=False), - ], - id='allocate_role-modal', - title='选择角色', - mask=False, - maskClosable=False, - width=900, - renderFooter=True, - okClickClose=False + fac.AntdTitle( + '基本信息', + level=4, + style={ + 'fontSize': '15px', + 'color': '#6379bb', + 'borderBottom': '1px solid #ddd', + 'margin': '8px 10px 25px 10px', + 'paddingBottom': '5px', + }, ), - - # 取消授权二次确认modal - fac.AntdModal( - fac.AntdText('是否确认取消授权?', id='allocate_role-delete-text'), - id='allocate_role-delete-confirm-modal', - visible=False, - title='提示', - renderFooter=True + fac.AntdForm( + fac.AntdRow( + [ + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='allocate_role-nick_name-input', + placeholder='请输入用户昵称', + autoComplete='off', + disabled=True, + ), + label='用户昵称', + style={'paddingBottom': '10px'}, + ), + span=8, + offset=2, + ), + fac.AntdCol( + fac.AntdFormItem( + fac.AntdInput( + id='allocate_role-user_name-input', + placeholder='请输入登录账号', + autoComplete='off', + disabled=True, + ), + label='登录账号', + style={'paddingBottom': '10px'}, + ), + span=8, + offset=2, + ), + ] + ), + ), + fac.AntdTitle( + '角色信息', + level=4, + style={ + 'fontSize': '15px', + 'color': '#6379bb', + 'borderBottom': '1px solid #ddd', + 'margin': '8px 10px 25px 10px', + 'paddingBottom': '5px', + }, + ), + fac.AntdSpin( + fac.AntdTable( + id='allocate_role-list-table', + data=[], + columns=[ + { + 'dataIndex': 'role_id', + 'title': '角色id', + 'hidden': True, + }, + { + 'dataIndex': 'role_name', + 'title': '角色名称', + 'renderOptions': {'renderType': 'ellipsis'}, + }, + { + 'dataIndex': 'role_key', + 'title': '权限字符', + 'renderOptions': {'renderType': 'ellipsis'}, + }, + { + 'dataIndex': 'create_time', + 'title': '创建时间', + 'renderOptions': {'renderType': 'ellipsis'}, + }, + ], + rowSelectionType='checkbox', + rowSelectionWidth=50, + bordered=True, + pagination={ + 'pageSize': 10, + 'current': 1, + 'showSizeChanger': True, + 'pageSizeOptions': [ + 10, + 30, + 50, + 100, + ], + 'showQuickJumper': True, + 'total': 0, + }, + style={ + 'width': '100%', + 'padding-right': '10px', + }, + ), + text='数据加载中', + ), + fac.AntdCenter( + fac.AntdSpace( + [ + fac.AntdButton( + '提交', id='allocate_role-submit-button', type='primary' + ), + fac.AntdButton( + '返回', + id='allocate_role-back-button', + ), + ] + ) ), ] diff --git a/dash-fastapi-frontend/views/system/user/component/__init__.py b/dash-fastapi-frontend/views/system/user/component/__init__.py deleted file mode 100644 index 5ec896d0e14a33cf2b1e6c4aebdd107de7b587c0..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/views/system/user/component/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from . import ( - query_form_table -) diff --git a/dash-fastapi-frontend/views/system/user/component/query_form_table.py b/dash-fastapi-frontend/views/system/user/component/query_form_table.py deleted file mode 100644 index 7a74b13a99dc1953cb653226ea7260f9897b4e42..0000000000000000000000000000000000000000 --- a/dash-fastapi-frontend/views/system/user/component/query_form_table.py +++ /dev/null @@ -1,283 +0,0 @@ -from dash import html -import feffery_antd_components as fac - - -def render(button_perms, allocate_index, is_operation): - table_column = [ - { - 'dataIndex': 'role_id', - 'title': '角色id', - 'hidden': True, - }, - { - 'dataIndex': 'role_name', - 'title': '角色名称', - 'renderOptions': { - 'renderType': 'ellipsis' - }, - }, - { - 'dataIndex': 'role_key', - 'title': '权限字符', - 'renderOptions': { - 'renderType': 'ellipsis' - }, - }, - { - 'dataIndex': 'role_sort', - 'title': '显示顺序', - 'renderOptions': { - 'renderType': 'ellipsis' - }, - }, - { - 'dataIndex': 'status', - 'title': '状态', - 'renderOptions': { - 'renderType': 'tags' - }, - }, - { - 'dataIndex': 'create_time', - 'title': '创建时间', - 'renderOptions': { - 'renderType': 'ellipsis' - }, - }, - ] - - if is_operation: - table_column.append( - { - 'title': '操作', - 'dataIndex': 'operation', - 'fixed': 'right', - 'width': 150, - 'renderOptions': { - 'renderType': 'button' - }, - } - ) - - return fac.AntdRow( - [ - fac.AntdCol( - [ - fac.AntdRow( - [ - fac.AntdCol( - html.Div( - [ - fac.AntdForm( - [ - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'allocate_role-role_name-input', - 'index': allocate_index - }, - placeholder='请输入角色名称', - autoComplete='off', - allowClear=True, - style={ - 'width': 240 - } - ), - label='角色名称', - style={'paddingBottom': '10px'}, - ), - fac.AntdFormItem( - fac.AntdInput( - id={ - 'type': 'allocate_role-role_key-input', - 'index': allocate_index - }, - placeholder='请输入权限字符', - autoComplete='off', - allowClear=True, - style={ - 'width': 240 - } - ), - label='权限字符', - style={'paddingBottom': '10px'}, - ), - fac.AntdFormItem( - fac.AntdButton( - '搜索', - id={ - 'type': 'allocate_role-search', - 'index': allocate_index - }, - type='primary', - icon=fac.AntdIcon( - icon='antd-search' - ) - ), - style={'paddingBottom': '10px'}, - ), - fac.AntdFormItem( - fac.AntdButton( - '重置', - id={ - 'type': 'allocate_role-reset', - 'index': allocate_index - }, - icon=fac.AntdIcon( - icon='antd-sync' - ) - ), - style={'paddingBottom': '10px'}, - ) - ], - layout='inline', - ) - ], - id={ - 'type': 'allocate_role-search-form-container', - 'index': allocate_index - }, - hidden=False - ), - ) - ] - ), - fac.AntdRow( - [ - fac.AntdCol( - fac.AntdSpace( - [ - fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-plus' - ), - '添加角色', - ], - id='allocate_role-add', - style={ - 'color': '#1890ff', - 'background': '#e8f4ff', - 'border-color': '#a3d3ff' - } - ) if 'system:user:edit' in button_perms else [], - fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-close-circle' - ), - '批量取消授权', - ], - id={ - 'type': 'allocate_role-operation-button', - 'index': 'delete' - }, - disabled=True, - style={ - 'color': '#ff9292', - 'background': '#ffeded', - 'border-color': '#ffdbdb' - } - ) if 'system:user:edit' in button_perms else [], - ], - style={ - 'paddingBottom': '10px' - } - ), - span=16 - ) if is_operation else [], - fac.AntdCol( - fac.AntdSpace( - [ - html.Div( - fac.AntdTooltip( - fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-search' - ), - ], - id={ - 'type': 'allocate_role-hidden', - 'index': allocate_index - }, - shape='circle' - ), - id={ - 'type': 'allocate_role-hidden-tooltip', - 'index': allocate_index - }, - title='隐藏搜索' - ) - ), - html.Div( - fac.AntdTooltip( - fac.AntdButton( - [ - fac.AntdIcon( - icon='antd-sync' - ), - ], - id={ - 'type': 'allocate_role-refresh', - 'index': allocate_index - }, - shape='circle' - ), - title='刷新' - ) - ), - ], - style={ - 'float': 'right', - 'paddingBottom': '10px' - } - ), - span=8 if is_operation else 24, - style={ - 'paddingRight': '10px' - } - ) - ], - gutter=5 - ), - fac.AntdRow( - [ - fac.AntdCol( - fac.AntdSpin( - fac.AntdTable( - id={ - 'type': 'allocate_role-list-table', - 'index': allocate_index - }, - data=[], - columns=table_column, - rowSelectionType='checkbox', - rowSelectionWidth=50, - bordered=True, - maxWidth=1000, - pagination={ - 'pageSize': 10, - 'current': 1, - 'showSizeChanger': True, - 'pageSizeOptions': [10, 30, 50, 100], - 'showQuickJumper': True, - 'total': 0 - }, - mode='server-side', - style={ - 'width': '100%', - 'padding-right': '10px' - } - ), - text='数据加载中' - ), - ) - ] - ), - ], - span=24 - ) - ], - gutter=5 - ) diff --git a/dash-fastapi-frontend/views/system/user/profile/__init__.py b/dash-fastapi-frontend/views/system/user/profile/__init__.py index 4d73f63c7e75ee9c2daa4f6ff72bd872bd17eca8..6e49077000b8e35fa78e0d75d9fb3e05e940ac11 100644 --- a/dash-fastapi-frontend/views/system/user/profile/__init__.py +++ b/dash-fastapi-frontend/views/system/user/profile/__init__.py @@ -1,11 +1,13 @@ -from dash import html -import feffery_utils_components as fuc import feffery_antd_components as fac -from flask import session -from . import user_avatar, user_info, reset_pwd +import feffery_utils_components as fuc +from dash import html +from api.system.user import UserApi +from utils.time_format_util import TimeFormatUtil +from . import reset_pwd, user_avatar, user_info def render(*args, **kwargs): + user_profile = UserApi.get_user_profile() return [ fac.AntdRow( @@ -16,96 +18,129 @@ def render(*args, **kwargs): html.Div( [ html.Div( - user_avatar.render(), + user_avatar.render( + user_profile.get('data').get( + 'avatar' + ) + ), style={ 'textAlign': 'center', - 'marginBottom': '10px' - } + 'marginBottom': '10px', + }, ), html.Ul( [ html.Li( [ - fac.AntdIcon(icon='antd-user'), + fac.AntdIcon( + icon='antd-user' + ), fac.AntdText('用户名称'), html.Div( - session.get('user_info').get('user_name'), + user_profile.get( + 'data' + ).get('user_name'), id='profile_c-username', - className='pull-right' - ) + className='pull-right', + ), ], - className='list-group-item' + className='list-group-item', ), html.Li( [ - fac.AntdIcon(icon='antd-mobile'), + fac.AntdIcon( + icon='antd-mobile' + ), fac.AntdText('手机号码'), html.Div( - session.get('user_info').get('phonenumber'), + user_profile.get( + 'data' + ).get('phonenumber'), id='profile_c-phonenumber', - className='pull-right' - ) + className='pull-right', + ), ], - className='list-group-item' + className='list-group-item', ), html.Li( [ - fac.AntdIcon(icon='antd-mail'), + fac.AntdIcon( + icon='antd-mail' + ), fac.AntdText('用户邮箱'), html.Div( - session.get('user_info').get('email'), + user_profile.get( + 'data' + ).get('email'), id='profile_c-email', - className='pull-right' - ) + className='pull-right', + ), ], - className='list-group-item' + className='list-group-item', ), html.Li( [ - fac.AntdIcon(icon='antd-cluster'), + fac.AntdIcon( + icon='antd-cluster' + ), fac.AntdText('所属部门'), html.Div( - session.get('dept_info').get('dept_name') if session.get( - 'dept_info') else "" + "/" + ','.join( - [item.get('post_name') for item in - session.get('post_info')]), + user_profile.get('data') + .get('dept') + .get('dept_name') + if user_profile.get( + 'data' + ).get('dept') + else '' + + ' / ' + + user_profile.get( + 'post_group' + ), id='profile_c-dept', - className='pull-right' - ) + className='pull-right', + ), ], - className='list-group-item' + className='list-group-item', ), html.Li( [ - fac.AntdIcon(icon='antd-team'), + fac.AntdIcon( + icon='antd-team' + ), fac.AntdText('所属角色'), html.Div( - ','.join([item.get('role_name') for item in - session.get('role_info')]), + user_profile.get( + 'role_group' + ), id='profile_c-role', - className='pull-right' - ) + className='pull-right', + ), ], - className='list-group-item' + className='list-group-item', ), html.Li( [ - fac.AntdIcon(icon='antd-schedule'), + fac.AntdIcon( + icon='antd-schedule' + ), fac.AntdText('创建日期'), html.Div( - session.get('user_info').get('create_time'), + TimeFormatUtil.format_time( + user_profile.get( + 'data' + ).get('create_time') + ), id='profile_c-create_time', - className='pull-right' - ) + className='pull-right', + ), ], - className='list-group-item' + className='list-group-item', ), ], - className='list-group list-group-striped' + className='list-group list-group-striped', ), fuc.FefferyStyle( - rawStyle= - ''' + rawStyle=""" .list-group-striped > .list-group-item { border-left: 0; border-right: 0; @@ -130,21 +165,19 @@ def render(*args, **kwargs): .pull-right { float: right !important; } - ''' - ) + """ + ), ], - style={ - 'width': '100%' - } + style={'width': '100%'}, ), ], title='个人信息', size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=10 + span=10, ), fac.AntdCol( fac.AntdCard( @@ -154,17 +187,28 @@ def render(*args, **kwargs): { 'key': '基本资料', 'label': '基本资料', - 'children': user_info.render() + 'children': user_info.render( + nick_name=user_profile.get( + 'data' + ).get('nick_name'), + phonenumber=user_profile.get( + 'data' + ).get('phonenumber'), + email=user_profile.get('data').get( + 'email' + ), + sex=user_profile.get('data').get( + 'sex' + ), + ), }, { 'key': '修改密码', 'label': '修改密码', - 'children': reset_pwd.render() - } + 'children': reset_pwd.render(), + }, ], - style={ - 'width': '100%' - } + style={'width': '100%'}, ) ], 'size="small"', @@ -172,11 +216,11 @@ def render(*args, **kwargs): size='small', style={ 'boxShadow': 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px' - } + }, ), - span=14 + span=14, ), ], - gutter=10 + gutter=10, ), ] diff --git a/dash-fastapi-frontend/views/system/user/profile/reset_pwd.py b/dash-fastapi-frontend/views/system/user/profile/reset_pwd.py index b7b15b2d276bfe4e3a9cf25cf08063bb792ea0bf..44ddfc9565e6a97f9c68217340d6ec72e680163b 100644 --- a/dash-fastapi-frontend/views/system/user/profile/reset_pwd.py +++ b/dash-fastapi-frontend/views/system/user/profile/reset_pwd.py @@ -1,66 +1,48 @@ import feffery_antd_components as fac - -import callbacks.system_c.user_c.profile_c.reset_pwd_c +from callbacks.system_c.user_c.profile_c import reset_pwd_c # noqa: F401 def render(): return fac.AntdForm( [ fac.AntdFormItem( - fac.AntdInput( - id='reset-old-password', - mode='password' - ), + fac.AntdInput(id='reset-old-password', mode='password'), id='reset-old-password-form-item', label='旧密码', - required=True + required=True, ), fac.AntdFormItem( - fac.AntdInput( - id='reset-new-password', - mode='password' - ), + fac.AntdInput(id='reset-new-password', mode='password'), id='reset-new-password-form-item', label='新密码', - required=True + required=True, ), fac.AntdFormItem( - fac.AntdInput( - id='reset-confirm-password', - mode='password' - ), + fac.AntdInput(id='reset-confirm-password', mode='password'), id='reset-confirm-password-form-item', label='确认密码', - required=True + required=True, ), fac.AntdFormItem( fac.AntdSpace( [ fac.AntdButton( - '保存', - id='reset-password-submit', - type='primary' + '保存', id='reset-password-submit', type='primary' ), fac.AntdButton( '关闭', id='reset-password-close', type='primary', - danger=True + danger=True, ), ], ), - wrapperCol={ - 'offset': 4 - } - ) + wrapperCol={'offset': 4}, + ), ], - labelCol={ - 'span': 4 - }, - wrapperCol={ - 'span': 20 - }, + labelCol={'span': 4}, + wrapperCol={'span': 20}, style={ 'margin': '0 auto' # 以快捷实现居中布局效果 - } + }, ) diff --git a/dash-fastapi-frontend/views/system/user/profile/user_avatar.py b/dash-fastapi-frontend/views/system/user/profile/user_avatar.py index 2c0a513da9fe295f6de6c4ce447505dc6cb10ccc..6eaea271a137fa770e63386ac1e85cae161ef855 100644 --- a/dash-fastapi-frontend/views/system/user/profile/user_avatar.py +++ b/dash-fastapi-frontend/views/system/user/profile/user_avatar.py @@ -1,32 +1,30 @@ -from dash import html, dcc -import feffery_utils_components as fuc import feffery_antd_components as fac -from flask import session - -from config.global_config import ApiBaseUrlConfig -import callbacks.system_c.user_c.profile_c.avatar_c +import feffery_utils_components as fuc +from dash import html, dcc +from callbacks.system_c.user_c.profile_c import avatar_c # noqa: F401 +from config.env import ApiConfig -def render(): +def render(avatar_path): return [ html.Div( [ fac.AntdImage( id='user-avatar-image-info', - src=f"{ApiBaseUrlConfig.BaseUrl}{session.get('user_info').get('avatar')}&token={session.get('Authorization')}", + src=f'{ApiConfig.BaseUrl}{avatar_path}' + if avatar_path + else '/assets/imgs/profile.jpg', preview=False, height='120px', width='120px', - style={ - 'borderRadius': '50%' - } + style={'borderRadius': '50%'}, ) ], id='avatar-edit-click', - className='user-info-head' + className='user-info-head', ), fuc.FefferyStyle( - rawStyle=''' + rawStyle=""" .user-info-head { position: relative; display: inline-block; @@ -50,7 +48,7 @@ def render(): line-height: 110px; border-radius: 50%; } - ''' + """ ), fac.AntdModal( [ @@ -60,7 +58,6 @@ def render(): [ html.Div( [ - fuc.FefferyImageCropper( id='avatar-cropper', alt='avatar', @@ -72,24 +69,21 @@ def render(): preview='#user-avatar-image-preview', style={ 'width': '100%', - 'height': '100%' - } + 'height': '100%', + }, ) ], id='avatar-cropper-container', - style={ - 'height': '350px', - 'width': '100%' - } + style={'height': '350px', 'width': '100%'}, ), ], - span=12 + span=12, ), fac.AntdCol( [ html.Div( id='user-avatar-image-preview', - className='avatar-upload-preview' + className='avatar-upload-preview', ), fuc.FefferyStyle( rawStyle=""" @@ -102,81 +96,72 @@ def render(): overflow: hidden; } """ - ) + ), ], - span=12 - ) + span=12, + ), ] ), html.Br(), fac.AntdRow( [ fac.AntdCol( - fac.AntdUpload( + dcc.Upload( + fac.AntdButton( + '选择', + icon=fac.AntdIcon(icon='antd-cloud-upload'), + ), id='avatar-upload-choose', - apiUrl=f'{ApiBaseUrlConfig.BaseUrl}/common/upload', - apiUrlExtraParams={'taskPath': 'avatarUpload'}, - downloadUrl=f"{ApiBaseUrlConfig.BaseUrl}/common/caches", - downloadUrlExtraParams={'taskPath': 'avatarUpload', 'token': session.get('Authorization')}, - headers={'Authorization': 'Bearer ' + session.get('Authorization')}, - fileMaxSize=10, - showUploadList=False, - fileTypes=['jpeg', 'jpg', 'png'], - buttonContent='选择' + accept='.jpeg,.jpg,.png', + max_size=10 * 1024 * 1024, ), - span=4 + span=4, ), fac.AntdCol( fac.AntdButton( id='zoom-out', - icon=fac.AntdIcon( - icon='antd-plus' - ) + icon=fac.AntdIcon(icon='antd-plus'), ), - span=2 + span=2, ), fac.AntdCol( fac.AntdButton( id='zoom-in', - icon=fac.AntdIcon( - icon='antd-minus' - ) + icon=fac.AntdIcon(icon='antd-minus'), ), - span=2 + span=2, ), fac.AntdCol( fac.AntdButton( icon=fac.AntdIcon( - id='rotate-left', - icon='antd-undo' + id='rotate-left', icon='antd-undo' ) ), - span=2 + span=2, ), fac.AntdCol( fac.AntdButton( icon=fac.AntdIcon( - id='rotate-right', - icon='antd-redo' + id='rotate-right', icon='antd-redo' ) ), - span=7 + span=7, ), fac.AntdCol( fac.AntdButton( '提交', id='change-avatar-submit', - type='primary' + type='primary', ), - span=7 + span=7, ), ], - gutter=10 - ) + gutter=10, + ), ], id='avatar-cropper-modal', title='修改头像', width=850, - mask=False - ) + mask=False, + ), ] diff --git a/dash-fastapi-frontend/views/system/user/profile/user_info.py b/dash-fastapi-frontend/views/system/user/profile/user_info.py index 75ca5ab38b8c222ac085093f77b225db08e97fd7..f5defa8be827b87917c6706af908bb5686032af8 100644 --- a/dash-fastapi-frontend/views/system/user/profile/user_info.py +++ b/dash-fastapi-frontend/views/system/user/profile/user_info.py @@ -1,84 +1,71 @@ import feffery_antd_components as fac +from callbacks.system_c.user_c.profile_c import user_info_c # noqa: F401 -import callbacks.system_c.user_c.profile_c.user_info_c - -def render(): +def render(nick_name, phonenumber, email, sex): return fac.AntdForm( [ fac.AntdFormItem( fac.AntdInput( id='reset-user-nick_name', - placeholder='请输入用户昵称' + placeholder='请输入用户昵称', + value=nick_name, ), id='reset-user-nick_name-form-item', label='用户昵称', - required=True + required=True, ), fac.AntdFormItem( fac.AntdInput( id='reset-user-phonenumber', - placeholder='请输入手机号码' + placeholder='请输入手机号码', + value=phonenumber, ), id='reset-user-phonenumber-form-item', label='手机号码', - required=True + required=True, ), fac.AntdFormItem( fac.AntdInput( - id='reset-user-email', - placeholder='请输入邮箱' + id='reset-user-email', placeholder='请输入邮箱', value=email ), id='reset-user-email-form-item', label='邮箱', - required=True + required=True, ), fac.AntdFormItem( fac.AntdRadioGroup( id='reset-user-sex', options=[ - { - 'label': '男', - 'value': '0' - }, - { - 'label': '女', - 'value': '1' - } + {'label': '男', 'value': '0'}, + {'label': '女', 'value': '1'}, ], - defaultValue='1' + defaultValue='1', + value=sex, ), id='reset-user-sex-form-item', - label='性别' + label='性别', ), fac.AntdFormItem( fac.AntdSpace( [ fac.AntdButton( - '保存', - id='reset-submit', - type='primary' + '保存', id='reset-submit', type='primary' ), fac.AntdButton( '关闭', id='reset-close', type='primary', - danger=True + danger=True, ), ], ), - wrapperCol={ - 'offset': 4 - } - ) + wrapperCol={'offset': 4}, + ), ], - labelCol={ - 'span': 4 - }, - wrapperCol={ - 'span': 20 - }, + labelCol={'span': 4}, + wrapperCol={'span': 20}, style={ 'margin': '0 auto' # 以快捷实现居中布局效果 - } + }, ) diff --git a/dash-fastapi-frontend/views/tool/__init__.py b/dash-fastapi-frontend/views/tool/__init__.py index 1648b98a30b1eb91a2c75c0aa8e7091526bdf557..2d8837a8192e3a28a1ce5339f4f658af3ab8c039 100644 --- a/dash-fastapi-frontend/views/tool/__init__.py +++ b/dash-fastapi-frontend/views/tool/__init__.py @@ -1,5 +1,5 @@ from . import ( - build, - gen, - swagger, + build, # noqa: F401 + gen, # noqa: F401 + swagger, # noqa: F401 ) diff --git a/dash-fastapi-frontend/views/tool/build/__init__.py b/dash-fastapi-frontend/views/tool/build/__init__.py index eed12cc1c9f6c4fa62b73f8c7597d9e200815a5f..58b4fc644522d28e44a45d5dcd4d844c80f07b89 100644 --- a/dash-fastapi-frontend/views/tool/build/__init__.py +++ b/dash-fastapi-frontend/views/tool/build/__init__.py @@ -1,8 +1,5 @@ from dash import html -import feffery_utils_components as fuc -import feffery_antd_components as fac def render(*args, **kwargs): - return html.Div('我是表单构建') diff --git a/dash-fastapi-frontend/views/tool/gen/__init__.py b/dash-fastapi-frontend/views/tool/gen/__init__.py index 3b7564a3acea4e8b0e498999800d3748e8d2de38..3c396c6a5d4d53e0c9f7cff0ab617b9a96870291 100644 --- a/dash-fastapi-frontend/views/tool/gen/__init__.py +++ b/dash-fastapi-frontend/views/tool/gen/__init__.py @@ -1,8 +1,5 @@ from dash import html -import feffery_utils_components as fuc -import feffery_antd_components as fac def render(*args, **kwargs): - return html.Div('我是代码生成') diff --git a/dash-fastapi-frontend/views/tool/swagger/__init__.py b/dash-fastapi-frontend/views/tool/swagger/__init__.py index aeafa30830e217af78693e83dba5dee5a96333d8..41b062004994f701a64b65f2b3ddc0c2decaefc1 100644 --- a/dash-fastapi-frontend/views/tool/swagger/__init__.py +++ b/dash-fastapi-frontend/views/tool/swagger/__init__.py @@ -1,10 +1,9 @@ -from dash import html import feffery_utils_components as fuc -from config.global_config import ApiBaseUrlConfig +from dash import html +from config.env import ApiConfig def render(*args, **kwargs): - return [ html.Div( [ @@ -18,14 +17,9 @@ def render(*args, **kwargs): } """ ), - html.Iframe( - src=f'{ApiBaseUrlConfig.BaseUrl}/docs' - ) + html.Iframe(src=f'{ApiConfig.BaseUrl}/docs'), ], id='swagger-docs-container', - style={ - 'position': 'relative', - 'height': 'calc(100vh - 120px)' - } + style={'position': 'relative', 'height': 'calc(100vh - 120px)'}, ) ] diff --git a/demo-pictures/zanzhu.jpg b/demo-pictures/zanzhu.jpg deleted file mode 100644 index fb6e349cfad942ed3ab5b07b186bb06a7b38d4ee..0000000000000000000000000000000000000000 Binary files a/demo-pictures/zanzhu.jpg and /dev/null differ diff --git a/demo-pictures/zanzhu_wx.jpg b/demo-pictures/zanzhu_wx.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa27920e851aa7b72706f0f905306cb911f43fa6 Binary files /dev/null and b/demo-pictures/zanzhu_wx.jpg differ diff --git a/demo-pictures/zanzhu_zfb.jpg b/demo-pictures/zanzhu_zfb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b02743c23d804d687ae23ccdc41d97fce9344f82 Binary files /dev/null and b/demo-pictures/zanzhu_zfb.jpg differ diff --git "a/demo-pictures/\344\270\252\344\272\272\350\265\204\346\226\231.png" "b/demo-pictures/\344\270\252\344\272\272\350\265\204\346\226\231.png" index 505fec4b68b19abd3c1ca2fb9f61655262b0dfbd..832197a42f7aa0467dbbf4135b08dd7446c2b5a2 100644 Binary files "a/demo-pictures/\344\270\252\344\272\272\350\265\204\346\226\231.png" and "b/demo-pictures/\344\270\252\344\272\272\350\265\204\346\226\231.png" differ diff --git "a/demo-pictures/\345\217\202\346\225\260\350\256\276\347\275\256.png" "b/demo-pictures/\345\217\202\346\225\260\350\256\276\347\275\256.png" index 3ec1463e332cfb117475f08f46899e595e4079ef..e2f9cdc096c6ffac548b0427af60d4f013a398f7 100644 Binary files "a/demo-pictures/\345\217\202\346\225\260\350\256\276\347\275\256.png" and "b/demo-pictures/\345\217\202\346\225\260\350\256\276\347\275\256.png" differ diff --git "a/demo-pictures/\345\234\250\347\272\277\347\224\250\346\210\267.png" "b/demo-pictures/\345\234\250\347\272\277\347\224\250\346\210\267.png" index f1a3a23b1af90cbe61e48f5afc731a2a6d454363..9625118654f7a7c9cf1ace00030f636f3bf4084b 100644 Binary files "a/demo-pictures/\345\234\250\347\272\277\347\224\250\346\210\267.png" and "b/demo-pictures/\345\234\250\347\272\277\347\224\250\346\210\267.png" differ diff --git "a/demo-pictures/\345\255\227\345\205\270\347\256\241\347\220\206.png" "b/demo-pictures/\345\255\227\345\205\270\347\256\241\347\220\206.png" index a6fbe3a64c7635c70b460cf8de3dd3b289103d61..2b71edcb8b2f01c8a20858f4a89a7fcefc585997 100644 Binary files "a/demo-pictures/\345\255\227\345\205\270\347\256\241\347\220\206.png" and "b/demo-pictures/\345\255\227\345\205\270\347\256\241\347\220\206.png" differ diff --git "a/demo-pictures/\345\256\232\346\227\266\344\273\273\345\212\241.png" "b/demo-pictures/\345\256\232\346\227\266\344\273\273\345\212\241.png" index 82673ad37889aa502faa52359895e80571ec4d1d..c997a6d9cb0622fd64e9a26e1365660f794334a9 100644 Binary files "a/demo-pictures/\345\256\232\346\227\266\344\273\273\345\212\241.png" and "b/demo-pictures/\345\256\232\346\227\266\344\273\273\345\212\241.png" differ diff --git "a/demo-pictures/\345\262\227\344\275\215\347\256\241\347\220\206.png" "b/demo-pictures/\345\262\227\344\275\215\347\256\241\347\220\206.png" index 97d65f137b0b9dea15a41b3b01d870801c5d36ff..8de23b035f2c4dac11a0f390f7c2b81fdf32a465 100644 Binary files "a/demo-pictures/\345\262\227\344\275\215\347\256\241\347\220\206.png" and "b/demo-pictures/\345\262\227\344\275\215\347\256\241\347\220\206.png" differ diff --git "a/demo-pictures/\345\277\230\350\256\260\345\257\206\347\240\201.png" "b/demo-pictures/\345\277\230\350\256\260\345\257\206\347\240\201.png" index 1e8ffd9476db936116a292513e9e7283aded7212..c8fa68a3d483f7a198f83df51b291ab3f83143cd 100644 Binary files "a/demo-pictures/\345\277\230\350\256\260\345\257\206\347\240\201.png" and "b/demo-pictures/\345\277\230\350\256\260\345\257\206\347\240\201.png" differ diff --git "a/demo-pictures/\346\223\215\344\275\234\346\227\245\345\277\227.png" "b/demo-pictures/\346\223\215\344\275\234\346\227\245\345\277\227.png" index 9e3cbb6b28c2f07aa4a1b28d414902996d8ca738..6241ac0cb050f48c4ec7a23e671b5a68e285d9ab 100644 Binary files "a/demo-pictures/\346\223\215\344\275\234\346\227\245\345\277\227.png" and "b/demo-pictures/\346\223\215\344\275\234\346\227\245\345\277\227.png" differ diff --git "a/demo-pictures/\346\234\215\345\212\241\347\233\221\346\216\247.png" "b/demo-pictures/\346\234\215\345\212\241\347\233\221\346\216\247.png" index 245c394e99b03911b208f804db9a76765d26807a..c595808369b5d60b78b6ccef8fe0e4349fc5afdb 100644 Binary files "a/demo-pictures/\346\234\215\345\212\241\347\233\221\346\216\247.png" and "b/demo-pictures/\346\234\215\345\212\241\347\233\221\346\216\247.png" differ diff --git "a/demo-pictures/\347\224\250\346\210\267\347\256\241\347\220\206.png" "b/demo-pictures/\347\224\250\346\210\267\347\256\241\347\220\206.png" index 08ae64af5221404139ed39001747dae571e78abe..8de3f06c275204f59fa608c89247b91904f9c72a 100644 Binary files "a/demo-pictures/\347\224\250\346\210\267\347\256\241\347\220\206.png" and "b/demo-pictures/\347\224\250\346\210\267\347\256\241\347\220\206.png" differ diff --git "a/demo-pictures/\347\231\273\345\275\225.png" "b/demo-pictures/\347\231\273\345\275\225.png" index ea954e4cb0d816a4b5ae7a14b056d880215b06a0..75231c597d2887773aa2e4508a6f6ff01c370051 100644 Binary files "a/demo-pictures/\347\231\273\345\275\225.png" and "b/demo-pictures/\347\231\273\345\275\225.png" differ diff --git "a/demo-pictures/\347\231\273\345\275\225\346\227\245\345\277\227.png" "b/demo-pictures/\347\231\273\345\275\225\346\227\245\345\277\227.png" index 5db63a5d1db829be9546dcb5457851be3c55b4a5..b6538e3058678b7a793d41b3f317c2cd7f3321fb 100644 Binary files "a/demo-pictures/\347\231\273\345\275\225\346\227\245\345\277\227.png" and "b/demo-pictures/\347\231\273\345\275\225\346\227\245\345\277\227.png" differ diff --git "a/demo-pictures/\347\263\273\347\273\237\346\216\245\345\217\243.png" "b/demo-pictures/\347\263\273\347\273\237\346\216\245\345\217\243.png" index 93392ae1266197646ec48fe5fda357f8027bed81..c43f8770dca196f36f7fe87f8261743606fdd736 100644 Binary files "a/demo-pictures/\347\263\273\347\273\237\346\216\245\345\217\243.png" and "b/demo-pictures/\347\263\273\347\273\237\346\216\245\345\217\243.png" differ diff --git "a/demo-pictures/\347\274\223\345\255\230\345\210\227\350\241\250.png" "b/demo-pictures/\347\274\223\345\255\230\345\210\227\350\241\250.png" index f1112e7527b17a118a28a28f90ba664fa886ab96..3b42d32858170920c22f29c8a0cff76006261781 100644 Binary files "a/demo-pictures/\347\274\223\345\255\230\345\210\227\350\241\250.png" and "b/demo-pictures/\347\274\223\345\255\230\345\210\227\350\241\250.png" differ diff --git "a/demo-pictures/\347\274\223\345\255\230\347\233\221\346\216\247.png" "b/demo-pictures/\347\274\223\345\255\230\347\233\221\346\216\247.png" index a45b627cc74beec2d8a964bf34f1b59a22130884..138afca09820c17ccfdfba656580be36cbefc40f 100644 Binary files "a/demo-pictures/\347\274\223\345\255\230\347\233\221\346\216\247.png" and "b/demo-pictures/\347\274\223\345\255\230\347\233\221\346\216\247.png" differ diff --git "a/demo-pictures/\350\217\234\345\215\225\347\256\241\347\220\206.png" "b/demo-pictures/\350\217\234\345\215\225\347\256\241\347\220\206.png" index 4c6bdaedae23393a0d5ac5ff1d162f53178d0cbc..556f3df5f4b342f5a221f7cea2ed570c0337c6cf 100644 Binary files "a/demo-pictures/\350\217\234\345\215\225\347\256\241\347\220\206.png" and "b/demo-pictures/\350\217\234\345\215\225\347\256\241\347\220\206.png" differ diff --git "a/demo-pictures/\350\247\222\350\211\262\347\256\241\347\220\206.png" "b/demo-pictures/\350\247\222\350\211\262\347\256\241\347\220\206.png" index c0c224c1e62ef2c933fbb484165cb2cecc3a2b67..633a22f87179d7fc2ba89e22cfeb3ea61e198916 100644 Binary files "a/demo-pictures/\350\247\222\350\211\262\347\256\241\347\220\206.png" and "b/demo-pictures/\350\247\222\350\211\262\347\256\241\347\220\206.png" differ diff --git "a/demo-pictures/\351\200\232\347\237\245\345\205\254\345\221\212.png" "b/demo-pictures/\351\200\232\347\237\245\345\205\254\345\221\212.png" index 0f6da668f6c5e8fbbeca9d7adfb869cd8e550300..8b3b45d4cfc6b9b27b0e18ce78af41f8a7426c08 100644 Binary files "a/demo-pictures/\351\200\232\347\237\245\345\205\254\345\221\212.png" and "b/demo-pictures/\351\200\232\347\237\245\345\205\254\345\221\212.png" differ diff --git "a/demo-pictures/\351\203\250\351\227\250\347\256\241\347\220\206.png" "b/demo-pictures/\351\203\250\351\227\250\347\256\241\347\220\206.png" index 20111d5029cd43618c3f1014f37d4b7ff8285548..389724179e128a474fd315d0fff064704c41cc2b 100644 Binary files "a/demo-pictures/\351\203\250\351\227\250\347\256\241\347\220\206.png" and "b/demo-pictures/\351\203\250\351\227\250\347\256\241\347\220\206.png" differ diff --git "a/demo-pictures/\351\246\226\351\241\265.png" "b/demo-pictures/\351\246\226\351\241\265.png" index b4c240c753114a250737591ec76593bfd3e0fbce..54b4ca4811b268fcc21e51d29b42ec50412762a5 100644 Binary files "a/demo-pictures/\351\246\226\351\241\265.png" and "b/demo-pictures/\351\246\226\351\241\265.png" differ diff --git a/requirements.txt b/requirements.txt index dfbeb623ec2bcda7d640dff8bc6da4d0dcc3d5f1..790b650f853e6db7df6501505c153ba4e610cef8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,23 +1,26 @@ APScheduler==3.10.4 -dash==2.10.2 -DateTime==5.1 -fastapi[all]==0.95.1 -feffery-antd-charts==0.0.1rc17 -feffery-antd-components==0.2.11 +asyncmy==0.2.9 +cachebox==4.1.2 +dash==2.18.1 +DateTime==5.5 +fastapi[all]==0.115.0 +feffery-antd-charts==0.1.0rc5 +feffery-antd-components==0.3.8 feffery-markdown-components==0.2.10 -feffery-utils-components==0.2.0b12 -Flask-Compress==1.13 +feffery-utils-components==0.2.0rc24 +Flask-Compress==1.15 jsonpath-ng==1.5.3 -loguru==0.7.0 -openpyxl==3.1.2 -pandas==1.5.3 +loguru==0.7.2 +openpyxl==3.1.5 +pandas==2.2.2 passlib[bcrypt]==1.7.4 -Pillow==10.2.0 -psutil==5.9.5 -PyMySQL==1.0.3 -python-jose[cryptography]==3.3.0 -redis==5.0.1 -requests==2.31.0 -SQLAlchemy==1.4.48 +Pillow==10.4.0 +psutil==6.0.0 +pydantic-validation-decorator==0.1.2 +PyJWT[crypto]==2.8.0 +PyMySQL==1.1.1 +redis==5.0.7 +requests==2.32.3 +SQLAlchemy[asyncio]==2.0.31 user-agents==2.2.0 -waitress==2.1.2 +waitress==3.0.0