# +====================================================================== # | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ] # +---------------------------------------------------------------------- # | Copyright (c) 2021-2026 一米阳光团队 版权所有 # +---------------------------------------------------------------------- # | 开源协议: Apache-2.0 # | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。 # +---------------------------------------------------------------------- # | 官方网站: https://www.djangoadmin.cn # +---------------------------------------------------------------------- # | 研发团队: 一米阳光团队 荣誉出品 # +====================================================================== # | 版权和免责声明: # | 一、知识产权声明 # | 本团队对该软件框架产品(以下简称"本产品")拥有完整知识产权,包括但不限于商标权、专利权、 # | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权, # | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。 # | 二、使用限制与禁止条款 # | 您理解并同意,禁止将本产品用于以下任何行为: # | 1. 任何违反中华人民共和国法律法规的用途; # | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为; # | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。 # | 三、责任豁免 # | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失 # | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三 # | 方提起索赔或行政机关进行调查,您应独立承担全部责任。 # | 四、违约与法律责任 # | 如发现任何违反本声明的行为,本团队有权: # | 1. 无条件配合公安机关及其他有权机关的调查取证工作; # | 2. 依法追究相关方的民事责任,并要求赔偿全部损失; # | 3. 保留进一步采取刑事控告等法律措施的权利。 # | 五、许可范围说明 # | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包 # | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通 # | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、 # | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造 # | 成的一切经济损失及商誉损害。 # | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件 # | 许可协议》附件为准。 # +====================================================================== import asyncio import json import os import random import time from datetime import datetime, timezone from fastapi.logger import logger from openpyxl.reader.excel import load_workbook from openpyxl.workbook import Workbook from shared.constants import PAGE_SIZE from modules.system.level.schemas import LevelForm, LevelStatusForm from modules.system.level.models import Level from modules.system.level.repository import level_repo from modules.upload import service as upload from config import FASTAPI_TEMP_PATH, FASTAPI_UPLOAD_DIR, FASTAPI_IMAGE_URL from core.database import get_db_session from core import R from utils.file import mkdir from core.security import get_user_id from utils.request import like_contains # 查询职级分页数据 def get_level_page(request): # 页码 page = int(request.query_params.get("pageNo", 1)) # 每页数 limit = int(request.query_params.get("pageSize", PAGE_SIZE)) # 查询数据 query = level_repo.filter(Level.is_delete == 0) # 职级名称 name = request.query_params.get('name') if name: query = query.filter(Level.name.like(like_contains(name), escape='\\')) # 职级状态 status = request.query_params.get('status') if status: query = query.filter(Level.status == status) # 排序 query = query.order_by(Level.sort.asc()) # 记录总数 count = query.count() # 分页查询 level_list = query.limit(limit).offset((page - 1) * limit).all() # 若当前页超出末页(数据被并发删除),自动回退到第一页 if page > 1 and not level_list: page = 1 count = query.count() level_list = query.limit(limit).offset(0).all() # 遍历数据源 result = [] if len(level_list) > 0: for item in level_list: # 对象转字典 data = item.to_dict() # 加入列表 result.append(data) # 返回结果 return R.ok(data=result, count=count) # 根据ID查询职级详情 def get_level_detail(level_id): # 根据ID查询职级 level = level_repo.get_by_id(level_id) # 查询结果为空判断 if not level: return None # 对象转字典 data = level.to_dict() # 返回结果 return data # 添加职级 def add_level(request, data: LevelForm): # 唯一性校验 if level_repo.filter(Level.name == data.name, Level.is_delete == 0).first(): return R.failed("职级名称不能重复") # 表单数据赋值给对象并插入 level_repo.create(**data.model_dump(exclude={'id'}), create_user=get_user_id(request)) # 返回结果 return R.ok(msg="添加成功") # 更新职级 def update_level(request, data: LevelForm): # 记录ID判空 if not data.id or data.id <= 0: return R.failed("记录ID不能为空") # 唯一性校验(排除自身) if level_repo.filter(Level.name == data.name, Level.id != data.id, Level.is_delete == 0).first(): return R.failed("职级名称不能重复") # 根据ID查询记录 if not level_repo.get_by_id(data.id): return R.failed("记录不存在") # 构造更新数据(排除 id) updData = data.model_dump(exclude={'id'}) updData['update_user'] = get_user_id(request) updData['update_time'] = datetime.now(tz=timezone.utc) result = level_repo.update(data.id, updData) # 提交数据 if not result: return R.failed("更新失败") # 返回结果 return R.ok(msg="更新成功") # 删除职级 def delete_level(level_id): return Level.batch_delete_r(level_id) # 设置状态 def update_level_status(request, data: LevelStatusForm): # 根据ID查询记录 if not level_repo.get_by_id(data.id): return R.failed("记录不存在") # 更新记录 result = level_repo.update(data.id, { "status": data.status, "update_user": get_user_id(request), "update_time": datetime.now(tz=timezone.utc) }) # 提交数据 if not result: return R.failed("设置失败") # 返回结果 return R.ok(msg="设置成功") # 导入Excel async def import_level(request, file): # 读取上传的文件(upload_file 返回 JSONResponse,需解析 body 判断业务结果) result = await upload.upload_file(file, ".xls|.xlsx") body = json.loads(result.body) if result.body else {} if body.get('code') != 0: return result # 本地文件地址 relative_path = body['data']['filePath'] path = FASTAPI_UPLOAD_DIR + relative_path def _import(): try: # 打开工作簿 wb = load_workbook(path) # 获取表单 ws = wb.worksheets[0] # 读取最大行 max_row = ws.max_row # 计数器 count = 0 # 遍历读取数据源 for item in range(2, max_row + 1): # 职级名称 name = ws.cell(item, 2).value # 职级排序 sort = ws.cell(item, 3).value # 职级状态 status = ws.cell(item, 4).value # 创建数据 level_repo.create( name=name, status=1 if status == "正常" else 2, sort=sort, create_user=get_user_id(request) ) count += 1 # 关闭工作簿 wb.close() return count finally: # 清理临时文件 if os.path.exists(path): os.remove(path) # Excel解析与同步DB写入放入线程池,避免阻塞事件循环 count = await asyncio.to_thread(_import) # 返回结果 return R.ok(msg="本次共导入【{}】条数据".format(count)) # 导出Excel def export_level(request): db = get_db_session() # 查询数据 query = level_repo.filter(Level.is_delete == 0) # 职级名称 name = request.query_params.get('name') if name: query = query.filter(Level.name.like(like_contains(name), escape='\\')) # 职级状态 status = request.query_params.get('status') if status: query = query.filter(Level.status == status) # 排序 query = query.order_by(Level.sort.asc()) # 导出数量限制,防止数据量过大导致内存溢出 EXPORT_MAX_ROWS = 10000 BATCH_SIZE = 500 # 实例化工作薄 wb = Workbook() # 指定第一个sheet ws = wb.worksheets[0] # 添加表头 ws.append(['职级ID', '职级名称', '职级排序', '职级状态', '创建时间', '更新时间']) # yield_per 分批加载,边查边写,避免双倍内存占用 for item in query.limit(EXPORT_MAX_ROWS).yield_per(BATCH_SIZE): ws.append([ item.id, item.name, item.sort, "正常" if item.status == 1 else "禁用", item.create_time.strftime('%Y-%m-%d %H:%M:%S') if item.create_time else None, item.update_time.strftime('%Y-%m-%d %H:%M:%S') if item.update_time else None, ]) db.expunge(item) # 保存文件 save_path = FASTAPI_TEMP_PATH + "/" + time.strftime('%Y%m%d') # 创建存放目录 mkdir(save_path) # 定义文件名,年月日时分秒随机数 file_name = time.strftime('%Y%m%d%H%M%S') + '%05d' % random.randint(0, 99999) # 重写合成文件名 file_name = os.path.join(file_name + ".xlsx") # 拼接文件路径 path = save_path + "/" + file_name # 写入文件 wb.save(r'' + path + '') # 文件访问地址 file_url = FASTAPI_IMAGE_URL + path.replace(FASTAPI_UPLOAD_DIR, "") # 返回结果 return R.ok(data=file_url) # 获取职级数据列表 def get_level_list(): # 查询职级数据列表 level_rows = level_repo.filter(Level.is_delete == 0, Level.status == 1).order_by(Level.sort.asc()).all() # 实例化列表 level_list = [] # 遍历数据源 if level_rows: for v in level_rows: # 对象转字典 item = v.to_dict() # 加入数组 level_list.append(item) # 返回结果 return level_list