Browse Source

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	vue-fastapi-backend/module_admin/controller/datastd_controller.py
#	vue-fastapi-backend/module_admin/service/datastd_service.py
master
xueyinfei 1 month ago
parent
commit
673ef69411
  1. 31
      vue-fastapi-backend/module_admin/controller/datastd_controller.py
  2. 13
      vue-fastapi-backend/module_admin/dao/datastd_dao.py
  3. 3
      vue-fastapi-backend/module_admin/entity/vo/datastd_vo.py
  4. 243
      vue-fastapi-backend/module_admin/service/datastd_service.py
  5. 83
      vue-fastapi-frontend/src/views/datastd/main/index.vue

31
vue-fastapi-backend/module_admin/controller/datastd_controller.py

@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends, Request,UploadFile,File
from sqlalchemy.ext.asyncio import AsyncSession
from config.enums import BusinessType
from config.get_db import get_db
@ -19,6 +19,7 @@ from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel
DataCatalogResponseWithChildren, DataAssetCatalogTreeResponse, DataCatalogMovedRequest, DataCatalogMergeRequest, \
DataCatalogChild, DataCatalogMoverelRequest
from pydantic_validation_decorator import ValidateFields
from utils.common_util import bytes2file_response
datastdController = APIRouter(prefix='/datastd', dependencies=[Depends(LoginService.get_current_user)])
@ -803,3 +804,31 @@ async def get_code_map_list2(
code_page_query_result = await DataStdService.get_code_map_list2(query_db, id)
logger.info('获取列配置列表成功')
return ResponseUtil.success(data=code_page_query_result)
# -----------------------------------------------------导入----------------------------------------------------------------------
@datastdController.post('/stdMain/importTemplate', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
async def export_std_main_template(request: Request, query_db: AsyncSession = Depends(get_db)):
main_import_template_result = await DataStdService.get_main_import_template_services()
logger.info('获取成功')
return ResponseUtil.streaming(data=bytes2file_response(main_import_template_result))
@datastdController.post('/stdMain/importData', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
@Log(title='用户管理', business_type=BusinessType.IMPORT)
async def batch_import_system_user(
request: Request,
file: UploadFile = File(...),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
batch_import_result = await DataStdService.batch_import_std_services(
request, query_db, file, current_user
)
logger.info(batch_import_result.message)
return ResponseUtil.success(msg=batch_import_result.message)
@datastdController.post('/stdDict/importTemplate', dependencies=[Depends(CheckUserInterfaceAuth('system:user:import'))])
async def export_std_dict_template(request: Request, query_db: AsyncSession = Depends(get_db)):
dict_import_template_result = await DataStdService.get_dict_import_template_services()
logger.info('获取成功')
return ResponseUtil.streaming(data=bytes2file_response(dict_import_template_result))

13
vue-fastapi-backend/module_admin/dao/datastd_dao.py

@ -143,7 +143,18 @@ class DataStdDao:
.order_by(DataStdCodeAppr.upd_time.desc())
.limit(1)
)
return result.scalar_one_or_none()
return result.scalar_one_or_none()
@classmethod
async def get_std_code_list_all(cls, query_db: AsyncSession):
"""
获取所有标准代码数据列表仅有效的
:param session: 异步数据库会话
:return: List[DataStdCode]
"""
stmt = select(DataStdCode).where(DataStdCode.class_id == 'code') # 只查有效的
result = await query_db.execute(stmt)
return result.scalars().all()
@classmethod
async def get_std_code_map_list(cls, db: AsyncSession, query_object: DataStdCodeModel, is_page: bool = False):
# 构建查询条件

3
vue-fastapi-backend/module_admin/entity/vo/datastd_vo.py

@ -117,7 +117,8 @@ class DataStdMainModel(BaseModel):
data_std_cn_name: Optional[str] = Field(default=None, description='标准中文名')
data_std_type: Optional[str] = Field(default=None, description='标准类型(0:基础数据 1:指标数据)')
data_sec_lvl: Optional[str] = Field(default=None, description='安全等级')
src_sys: Optional[int] = Field(default=None, description='归属系统')
data_std_vest: Optional[str] = Field(default=None, description='标准归属(sys:系统级 company:公司级')
src_sys: Optional[int] = Field(default=None, description='系统ID')
data_std_busi_defn: Optional[str] = Field(default=None, description='标准业务定义')
cd_id: Optional[str] = Field(default=None, description='代码id')
std_status: Optional[str] = Field(default=None, description='标准状态(1:有效 0:无效)')

243
vue-fastapi-backend/module_admin/service/datastd_service.py

@ -1,4 +1,4 @@
from fastapi import Request
from fastapi import Request,UploadFile,HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from exceptions.exception import ServiceException
from module_admin.dao.datastd_dao import DataStdDao
@ -6,15 +6,22 @@ from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.datastd_vo import DataStdCodeModel, DeleteDataStdModel, DataStdDictModel, DataStdMainModel,\
DataStdMainApprModel, DataStdDictApprModel, DataStdCodeApprModel, DataStdCodePageQueryModel, StdDictNoPageParam
from utils.common_util import CamelCaseUtil
from module_admin.entity.vo.datastd_vo import DataStdCodeModel,DeleteDataStdModel,DataStdDictModel,DataStdMainModel,DataStdMainApprModel,DataStdDictApprModel,DataStdCodeApprModel,DataStdCodePageQueryModel
import uuid
from module_admin.entity.vo.approval_vo import ApplyModel
from module_admin.service.approval_service import ApprovalService
from module_admin.service.metatask_service import MetataskService
from collections import defaultdict
from datetime import datetime
from config.constant import CommonConstant
from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel,DataCatalogResponseWithChildren,DataCatalogMovedRequest,DataCatalogMergeRequest,DataCatalogChild,DataCatalogMoverelRequest
from module_admin.entity.vo.user_vo import CurrentUserModel
from utils.common_util import CamelCaseUtil, export_list2excel, get_excel_template
import io
from module_admin.service.config_service import ConfigService
import requests
import pandas as pd
from config.env import AppConfig
class DataStdService:
"""
数据源标准服务层
@ -1254,26 +1261,6 @@ class DataStdService:
await ApprovalService.apply_services(query_db, applyModel, 'dataStdMain')
return CrudResponseModel(is_success=True, message='新增标准成功')
@classmethod
async def add_std_dict_appr(cls, query_db: AsyncSession, model: DataStdDictModel):
if not await cls.check_dict_unique_services(query_db, model):
raise ServiceException(message=f"字典编号 {model.data_dict_no} 已存在")
model.onum=str(uuid.uuid4())
model.data_dict_stat="1"
# 将 DataStdMainModel 转换为 DataStdMainApprModel,保留字段原始名
apprModel = DataStdDictApprModel(**model.model_dump(exclude_unset=True, by_alias=True))
apprModel.changeType="add"
apprModel.compareId=model.onum
apprModel.oldInstId=model.onum
apprModel.approStatus="waiting"
apprModel.flowId=str(uuid.uuid4())
await DataStdDao.add_std_dict_appr(query_db, apprModel)
applyModel = ApplyModel()
applyModel.businessType = "dataStdDict"
applyModel.businessId = apprModel.flowId
applyModel.applicant = apprModel.create_by
await ApprovalService.apply_services(query_db, applyModel, 'dataStdDict')
return CrudResponseModel(is_success=True, message='新增标准成功')
@classmethod
async def edit_std_main_appr(cls, query_db: AsyncSession, model: DataStdMainModel):
if not await cls.check_std_num_unique(query_db, model):
raise ServiceException(message=f"标准编号 {model.data_std_no} 已存在")
@ -1302,6 +1289,27 @@ class DataStdService:
await ApprovalService.apply_services(query_db, applyModel, 'dataStdMain')
return CrudResponseModel(is_success=True, message='修改标准成功')
@classmethod
async def add_std_dict_appr(cls, query_db: AsyncSession, model: DataStdDictModel):
if not await cls.check_dict_unique_services(query_db, model):
raise ServiceException(message=f"字典编号 {model.data_dict_no} 已存在")
model.onum=str(uuid.uuid4())
model.data_dict_stat="1"
# 将 DataStdMainModel 转换为 DataStdMainApprModel,保留字段原始名
apprModel = DataStdDictApprModel(**model.model_dump(exclude_unset=True, by_alias=True))
apprModel.changeType="add"
apprModel.compareId=model.onum
apprModel.oldInstId=model.onum
apprModel.approStatus="waiting"
apprModel.flowId=str(uuid.uuid4())
await DataStdDao.add_std_dict_appr(query_db, apprModel)
applyModel = ApplyModel()
applyModel.businessType = "dataStdDict"
applyModel.businessId = apprModel.flowId
applyModel.applicant = apprModel.create_by
await ApprovalService.apply_services(query_db, applyModel, 'dataStdDict')
return CrudResponseModel(is_success=True, message='新增标准成功')
@classmethod
async def edit_std_dict_appr(cls, query_db: AsyncSession, model: DataStdDictModel):
if not await cls.check_dict_unique_services(query_db, model):
raise ServiceException(message=f"字典编号 {model.c} 已存在")
@ -1327,7 +1335,7 @@ class DataStdService:
applyModel.businessId = apprModel.flowId
applyModel.applicant = apprModel.create_by
await ApprovalService.apply_services(query_db, applyModel, 'dataStdDict')
return CrudResponseModel(is_success=True, message='修改标准成功')
return CrudResponseModel(is_success=True, message='修改数据字典成功')
@classmethod
async def delete_std_main_Appr(cls, query_db: AsyncSession, ids: str):
if ids:
@ -1577,4 +1585,191 @@ class DataStdService:
return {
"tableData": table_data,
"children": children
}
}
@staticmethod
async def get_main_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
)
return binary_data
@classmethod
async def batch_import_std_services(
cls,
request: Request,
query_db: AsyncSession,
file: UploadFile,
current_user: CurrentUserModel
):
# Step 1: 读取 Excel
content = await file.read()
try:
df = pd.read_excel(io.BytesIO(content))
except Exception:
raise HTTPException(status_code=400, detail="Excel 文件解析失败")
if df.empty:
raise HTTPException(status_code=400, detail="导入文件内容为空")
std_type_mapping = {
'基础数据': '0',
'指标数据': '1'
}
std_vest_mapping = {
'公司级': 'company',
'系统级': 'sys'
}
# 获取全量标准代码
std_code_list = await DataStdDao.get_std_code_list_all(query_db)
# Step 2: 表头映射(中文转英文字段名)
header_dict = {
'标准归属': 'data_std_vest',
'来源系统': 'std_source_system',
'标准编号': 'data_std_no',
'标准中文名': 'std_name_zh',
'标准英文名': 'std_name_en',
'标准业务定义': 'std_definition',
'标准类型': 'std_type',
'标准来源': 'std_source',
'数据类别': 'data_category',
'安全等级': 'security_level',
'代码编号': 'code_no',
'业务认则部门': 'biz_dept',
'业务认则人员': 'biz_person',
'技术认则部门': 'tech_dept',
'技术认则人员': 'tech_person',
}
df.rename(columns=header_dict, inplace=True)
# ds系统列表
data_tree_result = await MetataskService.get_data_source_tree( request,current_user)
# Step 3: 生成统一 flowId
batch_flow_id = str(uuid.uuid4())
try:
for idx, row in df.iterrows():
# VO 数据校验
try:
input_code_no = row.get('code_no')
matched_code = next((item for item in std_code_list if item.cd_no == input_code_no), None)
if input_code_no and not matched_code:
raise HTTPException(status_code=400, detail=f"{idx + 2} 行导入失败,代码编号 [{input_code_no}] 在系统中不存在")
# 获取来源系统字段
input_src_sys = row.get("std_source_system")
matched_source = next((ds for ds in data_tree_result if ds.name == input_src_sys or str(ds.id) == str(input_src_sys)), None)
# 如果能匹配到系统,则使用 id;否则判断是否为空
if matched_source:
src_sys_id = matched_source.id
elif not input_src_sys or str(input_src_sys).strip() == "":
src_sys_id = 10000
else:
raise HTTPException(status_code=400, detail=f"{idx + 2} 行导入失败,来源系统 [{input_src_sys}] 不存在")
# 如果存在,使用对应 onum 作为 cdId
cd_id = matched_code.onum if matched_code else None
vo = DataStdMainModel(
dataStdVest=std_vest_mapping.get(row.get('data_std_vest'), 'company'), # 默认转为 'company',
srcSys=src_sys_id,
dataStdNo=row.get('data_std_no'),
dataStdCnName=row.get('std_name_zh'),
dataStdEngName=row.get('std_name_en'),
dataStdBusiDefn=row.get('std_definition'),
dataStdType=std_type_mapping.get(row.get('std_type'), '1'), # 默认转为 '1',
dataStdSrc=row.get('std_source'),
dataClas=row.get('data_category'),
dataSecLvl=str(row.get('security_level')),
cdId=cd_id,
dataStdBusiOwnershipDept=row.get('biz_dept'),
dataStdBusiOwnershipPrsn=row.get('biz_person'),
dataStdItOwnershipDept=row.get('tech_dept'),
dataStdItOwnershipPrsn=row.get('tech_person'),
beltDataStdContent=2, # 固定值
)
except Exception as ve:
raise HTTPException(status_code=400, detail=f"{idx + 2} 行数据校验失败: {ve}")
# 构造查询对象并检查是否存在
query_obj = DataStdMainModel(dataStdNo=vo.data_std_no)
exist_model = await DataStdDao.get_data_main_by_info(query_db, query_obj)
# 构造审批模型(无论新增/修改)
model = DataStdMainModel(**vo.model_dump(exclude_unset=True, by_alias=True))
model.create_by = current_user.user.user_name
model.std_status = "1"
model.belt_data_std_content = 2
model.create_time = datetime.now()
if exist_model:
# === 修改审批逻辑 ===
# 标准正在审批中则抛错
wating_list = await DataStdDao.check_std_main_waiting(exist_model.onum, query_db)
if wating_list:
raise ServiceException(message=f"{idx + 2} 行数据标准正在审批中,请等待审批完成")
last_appr = await DataStdDao.get_last_std_main_appr_by_id(query_db, exist_model.onum)
model.onum = exist_model.onum
appr_model = DataStdMainApprModel(**model.model_dump(exclude_unset=True, by_alias=True))
appr_model.changeType = "edit"
appr_model.onum = str(uuid.uuid4())
appr_model.oldInstId = model.onum
appr_model.compareId = last_appr.onum if last_appr else model.onum
appr_model.approStatus = "waiting"
appr_model.flowId = batch_flow_id
else:
# === 新增审批逻辑 ===
model.onum = str(uuid.uuid4())
appr_model = DataStdMainApprModel(**model.model_dump(exclude_unset=True, by_alias=True))
appr_model.changeType = "add"
appr_model.compareId = model.onum
appr_model.oldInstId = model.onum
appr_model.oldInstId = model.onum
appr_model.approStatus = "waiting"
appr_model.flowId = batch_flow_id
# 保存审批数据
await DataStdDao.add_std_main_appr(query_db, appr_model)
# 全部处理完成后统一发起审批流程
apply_model = ApplyModel()
apply_model.businessType = "dataStdMain"
apply_model.businessId = batch_flow_id
apply_model.applicant = current_user.user.user_name
await ApprovalService.apply_services(query_db, apply_model, 'dataStdMain')
await query_db.commit()
return CrudResponseModel(is_success=True, message="批量导入标准成功")
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"导入失败:{str(e)}")
@staticmethod
async def get_dict_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
)
return binary_data

83
vue-fastapi-frontend/src/views/datastd/main/index.vue

@ -208,6 +208,14 @@
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Upload"
@click="handleImport"
>导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
@ -377,6 +385,38 @@
<code-item-common ref="showCodeDialog" v-if="codeVisible" :codeId="codeId"/>
</el-dialog>
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<!-- <div class="el-upload__tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
</div> -->
<span>仅允许导入xlsxlsx格式文件</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog width="600px" append-to-body title="标准分类" v-model="treeVisible">
<el-form label-width="100px" ref="formRef" >
@ -445,6 +485,7 @@ import {
import useUserStore from '@/store/modules/user'
import { nextTick } from 'vue'
import codeItemCommon from '../stdcode/codeItemCommon.vue'
import { getToken } from "@/utils/auth";
import { datasourcetree } from "@/api/meta/metatask";
const { proxy } = getCurrentInstance()
@ -463,7 +504,21 @@ const handleTargetCatalogNodeClick = (data) => {
}
const codeMapId = ref(null);
const mapVisible = ref(false);
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true;
};
const handleFileSuccess = (response, file, fileList) => {
upload.open = false;
upload.isUploading = false;
proxy.$refs["uploadRef"].handleRemove(file);
proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
getList();
};
function importTemplate() {
proxy.download("datastd/stdMain/importTemplate", {
}, `数据标准_template_${new Date().getTime()}.xlsx`);
};
const directoryTableData = ref([])
const queryParams = ref({
dataStdEngName: '',
@ -515,7 +570,9 @@ const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
function submitFileForm() {
proxy.$refs["uploadRef"].submit();
};
const submitTree = async () => {
const response = await changeStdMainOum({onum:ids.value.toString(),beltDataStdContent:chooseOnumNum.value});
if (response.success){
@ -605,7 +662,21 @@ const handleAdd = () => {
//
dialogVisible.value = true;
};
/*** 用户导入参数 */
const upload = reactive({
//
open: false,
//
title: "",
//
isUploading: false,
//
updateSupport: 0,
//
headers: { Authorization: "Bearer " + getToken() },
//
url: import.meta.env.VITE_APP_BASE_API + "/datastd/stdMain/importData"
});
const handleEdit = (row) => {
const id = row.onum ? row.onum : ids.value.toString();
getStdMain(id).then((response) => {
@ -666,7 +737,11 @@ const isCollection = (data) => {
const isCollected = (data) => {
return data.bookmarkFlag === 1
}
/** 导入按钮操作 */
function handleImport() {
upload.title = "用户导入";
upload.open = true;
};
//
const isDirectory = (data) => {

Loading…
Cancel
Save