Browse Source

标签分类

master
si@aidatagov.com 3 weeks ago
parent
commit
4b85c1a8d0
  1. 148
      vue-fastapi-backend/module_admin/controller/metadata_config_controller.py
  2. 391
      vue-fastapi-backend/module_admin/dao/metadata_config_dao.py
  3. 7
      vue-fastapi-backend/module_admin/entity/do/meta_do.py
  4. 38
      vue-fastapi-backend/module_admin/entity/do/metadata_config_do.py
  5. 5
      vue-fastapi-backend/module_admin/entity/vo/metadata_config_vo.py
  6. 294
      vue-fastapi-backend/module_admin/service/metadata_config_service.py
  7. 92
      vue-fastapi-frontend/src/api/metadataConfig/directory.js
  8. 171
      vue-fastapi-frontend/src/views/metadataConfig/clas/components/AssetMoveDialog.vue
  9. 193
      vue-fastapi-frontend/src/views/metadataConfig/clas/components/FormDialog.vue
  10. 175
      vue-fastapi-frontend/src/views/metadataConfig/clas/components/MergerDialog.vue
  11. 173
      vue-fastapi-frontend/src/views/metadataConfig/clas/components/MoveDialog.vue
  12. 452
      vue-fastapi-frontend/src/views/metadataConfig/clas/index.vue

148
vue-fastapi-backend/module_admin/controller/metadata_config_controller.py

@ -2,6 +2,12 @@ from datetime import datetime
from fastapi import APIRouter, Depends, Request, Form
from pydantic_validation_decorator import ValidateFields
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel, \
DataCatalogResponseWithChildren, DataAssetCatalogTreeResponse, DataCatalogMovedRequest, DataCatalogMergeRequest, \
DataCatalogChild, DataCatalogMoverelRequest
from utils.log_util import logger
from config.enums import BusinessType
from module_admin.annotation.log_annotation import Log
from module_admin.entity.vo.metadata_config_vo import (
MetadataClasModel,
MetadataClasPageQueryModel,
@ -745,3 +751,145 @@ async def task_biz_DS_meta_metatask_delete(
edit_config_result = await MetadataConfigService.ds_metatask_delete(request, query_db, process, current_user)
return ResponseUtil.success(msg=edit_config_result)
# --------------------------------------------------------------------------------标签树---------------------------------------------------
@metadataConfigController.get(
'/cata/list', response_model=PageResponseModel)
async def get_data_catalog_list(
request: Request,
catalog_page_query: DataCatalogPageQueryModel = Depends(DataCatalogPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 设置字段
user_id = current_user.user.user_id
# 获取分页数据
catalog_page_query_result = await MetadataConfigService.get_catalog_list_services(query_db, catalog_page_query, user_id,
is_page=True)
logger.info('获取成功')
return ResponseUtil.success(model_content=catalog_page_query_result)
@metadataConfigController.delete('/cata/{content_onums}',
)
@Log(title='标准分类管理', business_type=BusinessType.DELETE)
async def delete_data_catalog(request: Request, content_onums: str, query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
delete_catalog = DeleteDataCatalogModel(content_onums=content_onums)
delete_catalog_result = await MetadataConfigService.delete_catalog_services(query_db, delete_catalog,
user_id=current_user.user.user_id)
logger.info(delete_catalog_result.message)
return ResponseUtil.success(msg=delete_catalog_result.message)
@metadataConfigController.put('/cata/removerel')
@ValidateFields(validate_model='removerel_data_ast_catalog')
@Log(title='标准分类管理', business_type=BusinessType.UPDATE)
async def removerel_data_ast_catalog(
request: Request,
removerel_catalog: DataCatalogChild,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 调用服务层方法
removerel_result = await MetadataConfigService.removerel_data_ast_catalog_services(query_db, removerel_catalog)
logger.info(removerel_result.message)
# 返回标准化响应
return ResponseUtil.success()
@metadataConfigController.put('/cata/moved')
@ValidateFields(validate_model='moved_data_catalog')
@Log(title='标准分类管理', business_type=BusinessType.UPDATE)
async def moved_data_catalog(
request: Request,
moved_catalog: DataCatalogMovedRequest,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 调用服务层方法
moved_result = await MetadataConfigService.moved_catalog_instr_services(query_db, moved_catalog)
logger.info(moved_result.message)
# 返回标准化响应
return ResponseUtil.success(
msg=moved_result.message
)
@metadataConfigController.put('/cata/merge')
@ValidateFields(validate_model='merge_data_catalog')
@Log(title='标准分类管理', business_type=BusinessType.UPDATE)
async def moved_data_catalog(
request: Request,
merge_catalog: DataCatalogMergeRequest,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 调用服务层方法
merge_result = await MetadataConfigService.merge_catalog_instr_services(query_db, merge_catalog)
logger.info(merge_result.message)
# 返回标准化响应
return ResponseUtil.success(
msg=merge_result.message
)
@metadataConfigController.put('/cata/moverel')
@ValidateFields(validate_model='moverel_data_ast_catalog')
@Log(title='标准分类管理', business_type=BusinessType.UPDATE)
async def moverel_data_ast_catalog(
request: Request,
moverel_catalog: DataCatalogMoverelRequest,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 调用服务层方法
moverel_result = await MetadataConfigService.moverel_data_ast_catalog_services(query_db, moverel_catalog)
logger.info(moverel_result.message)
# 返回标准化响应
return ResponseUtil.success()
@metadataConfigController.put('/cata/edit')
@ValidateFields(validate_model='edit_data_catalog')
@Log(title='标准分类管理', business_type=BusinessType.UPDATE)
async def edit_data_catalog(
request: Request,
edit_catalog: DataCatalogResponseWithChildren,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 设置审计字段
edit_catalog.upd_prsn = current_user.user.user_name
# 调用服务层方法
edit_result = await MetadataConfigService.edit_catalog_child_services(query_db, edit_catalog)
logger.info(edit_result.message)
# 返回标准化响应
return ResponseUtil.success(
msg=edit_result.message
)
@metadataConfigController.post('/cata')
@ValidateFields(validate_model='add_data_catalog')
@Log(title='标准分类管理', business_type=BusinessType.INSERT)
async def add_data_catalog(
request: Request,
add_catalog: DataCatalogResponseWithChildren,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
# 设置字段
add_catalog.upd_prsn = current_user.user.user_name
# 调用服务层方法
add_result = await MetadataConfigService.add_catalog_services(query_db, add_catalog)
logger.info(add_result.message)
# 新增成功后,更新新增数据目录的父亲节点的叶子标志为0
if add_result.is_success:
if add_catalog.supr_content_onum is not None:
supr_content_onum = add_catalog.supr_content_onum
await MetadataConfigService.edit_catalog_leaf_services(query_db, supr_content_onum, 0)
else:
logger.error(add_result.message)
# 返回标准化响应
return ResponseUtil.success(
msg=add_result.message
)

391
vue-fastapi-backend/module_admin/dao/metadata_config_dao.py

@ -1,5 +1,4 @@
from datetime import datetime, time
from sqlalchemy import delete, select, update, and_
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.entity.do.meta_do import MetadataClas, MetadataExtractInfo # ORM 类
from module_admin.entity.do.metadata_config_do import MetadataSec, SecuBizConfig, SecuBizPermiConfig, SecuBizConfigRela, \
@ -7,11 +6,14 @@ from module_admin.entity.do.metadata_config_do import MetadataSec, SecuBizConfig
TaskBizConfigRela, TaskBizConfig
# ORM 类
from typing import List
from module_admin.entity.do.metadata_config_do import DataAstContent, DataAstContentRela
from sqlalchemy.orm import aliased
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import true
from utils.page_util import PageUtil
from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel, \
DataCatalogChild
from sqlalchemy import delete, select, update, desc, or_, not_, and_
class MetadataConfigDao:
"""
@ -56,10 +58,7 @@ class MetadataConfigDao:
获取标签信息列表支持分页
"""
query = select(MetadataClas).where(
MetadataClas.clas_pri_clas.like(f"%{query_object.clas_pri_clas}%") if query_object.clas_pri_clas else True,
MetadataClas.clas_scd_clas.like(f"%{query_object.clas_scd_clas}%") if query_object.clas_scd_clas else True,
MetadataClas.clas_thre_clas.like(
f"%{query_object.clas_thre_clas}%") if query_object.clas_thre_clas else True,
MetadataClas.belt_batch_content==query_object.belt_batch_content if query_object.belt_batch_content else True,
MetadataClas.clas_name.like(f"%{query_object.clas_name}%") if query_object.clas_name else True,
MetadataClas.clas_eff_flag == query_object.clas_eff_flag if query_object.clas_eff_flag else True,
MetadataClas.upd_time.between(
@ -422,3 +421,381 @@ class MetadataConfigDao:
.where(TaskBizConfigRela.biz_onum == biz_onum)
)
return result.scalars().all()
# 数据标准目录
@classmethod
async def get_catalog_by_id(cls, db: AsyncSession, content_onum: int):
"""
根据目录ID获取目录详细信息
:param db: orm对象
:param content_onum: 目录ID
:return: 目录信息对象
"""
catalog_info = (
(await db.execute(select(DataAstContent).where(DataAstContent.content_onum == content_onum,
DataAstContent.content_stat == 1)))
.scalars()
.first()
)
return catalog_info
@classmethod
async def get_catalog_detail_by_info(cls, db: AsyncSession, catalog: DataCatalogPageQueryModel):
"""
根据目录参数获取目录信息
:param db: orm对象
:param catalog: 目录参数对象
:return: 目录信息对象
"""
catalog_info = (
(
await db.execute(
select(DataAstContent).where(
DataAstContent.content_name == catalog.content_name if catalog.content_name else True,
DataAstContent.content_stat == catalog.content_stat if catalog.content_stat else True,
DataAstContent.content_pic == catalog.content_pic if catalog.content_pic else True,
DataAstContent.content_stat == 1,
)
)
)
.scalars()
.first()
)
return catalog_info
@classmethod
async def update_leaf_node_flag(cls, db: AsyncSession):
"""
更新leaf_node_flag字段
"""
# 创建别名对象
t2 = aliased(DataAstContent, name='t2') # 正确使用aliased创建别名
subquery = (
select(DataAstContent.content_onum)
.where(
DataAstContent.content_stat == '1',
DataAstContent.leaf_node_flag == 0,
not_(
select(1)
.select_from(t2) # 使用别名后的表
.where(
t2.supr_content_onum == DataAstContent.content_onum,
t2.content_stat == '1'
)
.exists() # 添加exists()方法
)
)
).alias('temp')
stmt = (
update(DataAstContent)
.where(DataAstContent.content_onum.in_(subquery))
.values(leaf_node_flag=1, upd_time=datetime.now())
)
await db.execute(stmt)
@classmethod
async def add_catalog_dao(cls, db: AsyncSession, catalog1: dict, catalog2: dict):
"""
新增目录数据库操作
:param db: orm对象
:param catalog: 目录对象
:return:
"""
db_catalog = DataAstContent(**catalog1)
db.add(db_catalog)
await db.flush()
# 处理子关系(统一转换为 ORM 模型)
for child in catalog2.get('children', []):
# 如果是 Pydantic 模型实例,先转换为字典
if isinstance(child, DataCatalogChild):
child_dict = child.model_dump()
elif isinstance(child, dict):
child_dict = child
else:
raise TypeError("不支持的子关系数据类型")
# 创建 ORM 模型实例
processed_child = dict(child_dict)
processed_child['content_onum'] = db_catalog.content_onum
db_child = DataAstContentRela(**processed_child)
db.add(db_child)
await db.flush()
return db_catalog
@classmethod
async def edit_catalog_leaf_dao(cls, db: AsyncSession, catalog: dict):
"""
编辑叶子节点目录数据库操作
:param db: orm对象
:param catalog: 需要更新的目录字典
:return:
"""
content_onum = catalog['content_onum']
stmt = (
update(DataAstContent)
.where(DataAstContent.content_onum == content_onum)
.values(
leaf_node_flag=catalog['leaf_node_flag']
)
)
await db.execute(stmt)
@classmethod
async def edit_catalog_child_dao(cls, db: AsyncSession, catalog: dict):
"""
编辑目录数据库操作
:param db: orm对象
:param catalog: 需要更新的目录字典
:return:
"""
content_onum = catalog['content_onum']
stmt = (
update(DataAstContent)
.where(DataAstContent.content_onum == content_onum)
.values(
content_name=catalog['content_name'],
content_stat=catalog['content_stat'],
content_intr=catalog['content_intr'],
content_pic=catalog['content_pic'],
supr_content_onum=catalog['supr_content_onum'],
leaf_node_flag=catalog['leaf_node_flag'],
upd_prsn=catalog['upd_prsn'],
upd_time=datetime.now()
))
await db.execute(stmt)
# 处理子关系
for child in catalog.get('children', []):
rela_onum = child.get('rela_onum')
if rela_onum:
st = (
update(DataAstContentRela)
.where(DataAstContentRela.rela_onum == rela_onum)
.values(
content_onum=child.get('content_onum'),
ast_onum=child.get('ast_onum'),
rela_type=child.get('rela_type'),
rela_eff_begn_date=child.get('rela_eff_begn_date'),
rela_eff_end_date=child.get('rela_eff_end_date'),
upd_prsn=child.get('upd_prsn'))
)
await db.execute(st)
await cls.update_leaf_node_flag(db)
else:
child['content_onum'] = content_onum
db_child = DataAstContentRela(**child)
db.add(db_child)
await db.flush()
await cls.update_leaf_node_flag(db)
@classmethod
async def delete_catalog_dao(cls, db: AsyncSession, catalog: DeleteDataCatalogModel):
"""
删除目录数据库操作
:param db: orm对象
:param catalog: 目录对象
:content_stat=0 作废
:return:
"""
content_onums = catalog.content_onums.split(',')
await db.execute(
update(DataAstContentRela)
.where(DataAstContentRela.content_onum.in_(content_onums))
.values(
rela_status=0,
rela_eff_end_date=datetime.now()
)
)
await db.execute(
update(DataAstContent)
.where(DataAstContent.content_onum.in_(content_onums))
.values(
content_stat=0,
upd_time=datetime.now()
)
)
await cls.update_leaf_node_flag(db)
@classmethod
async def moved_catalog_instr_dao(cls, db: AsyncSession, moved_catalog_data: dict):
"""
编辑目录数据库操作
:param db: orm对象
:param catalog: 需要更新的目录字典
:return:
"""
# content_onum = moved_catalog_data['content_onum']
stmt = (
update(DataAstContent)
.where(DataAstContent.content_onum == moved_catalog_data['content_onum'],
DataAstContent.supr_content_onum == moved_catalog_data['supr_content_onum'])
.values(
supr_content_onum=moved_catalog_data['supr_content_onum_after'],
upd_time=datetime.now()
))
await db.execute(stmt)
await cls.update_leaf_node_flag(db)
@classmethod
async def merge_catalog_instr_dao(cls, db: AsyncSession, merge_catalog_data: dict):
"""
编辑目录数据库操作
:param db: orm对象
:param catalog: 需要更新的目录字典
:return:
"""
# stmt = (
# update(DataAstContent)
# .where(DataAstContent.content_onum == merge_catalog_data['content_onum'] , DataAstContent.supr_content_onum == merge_catalog_data['supr_content_onum'])
# .values(
# content_onum=merge_catalog_data['content_onum_after'],
# supr_content_onum=merge_catalog_data['supr_content_onum_after'],
# upd_time=datetime.now()
# ) )
# await db.execute(stmt)
stmt1 = (
update(DataAstContentRela)
.where(DataAstContentRela.content_onum == merge_catalog_data[
'content_onum'] and DataAstContentRela.rela_status == 1)
.values(
content_onum=merge_catalog_data['content_onum_after'],
rela_eff_begn_date=datetime.now()
)
)
await db.execute(stmt1)
stmt2 = (
update(DataAstContent)
.where(DataAstContent.content_onum == merge_catalog_data['content_onum'],
DataAstContent.supr_content_onum == merge_catalog_data['supr_content_onum'])
.values(content_stat='0')
)
await db.execute(stmt2)
await cls.update_leaf_node_flag(db)
@classmethod
async def removerel_data_ast_catalog_dao(cls, db: AsyncSession, removerel_catalog_data: dict):
"""
编辑资产关系数据库操作
:param db: orm对象
:param catalog: 需要更新的目录字典
:return:
"""
stmt = (
update(DataAstContentRela)
.where(DataAstContentRela.rela_onum == removerel_catalog_data['rela_onum'],
DataAstContentRela.content_onum == removerel_catalog_data['content_onum'])
.values(
rela_status=removerel_catalog_data['rela_status']
))
await db.execute(stmt)
await cls.update_leaf_node_flag(db)
@classmethod
async def moverel_data_ast_catalog_dao(cls, db: AsyncSession, moverel_catalog_data: dict):
"""
编辑资产关系数据库操作
:param db: orm对象
:param catalog: 需要更新的目录字典
:return:
"""
stmt = (
update(DataAstContentRela)
.where(DataAstContentRela.rela_onum == moverel_catalog_data['rela_onum'],
DataAstContentRela.content_onum == moverel_catalog_data['content_onum'])
.values(
content_onum=moverel_catalog_data['content_onum_after'],
rela_eff_end_date=datetime.now()
))
await db.execute(stmt)
await cls.update_leaf_node_flag(db)
@classmethod
async def get_catalog_list(cls, db: AsyncSession, query_object: DataCatalogPageQueryModel, user_id: int,
is_page: bool = False):
"""
根据查询参数获取数据资产目录列表
:param db: 异步会话对象
:param query_object: 分页查询参数对象
:param is_page: 是否分页
:return: 数据资产目录分页列表
"""
# 修改子查询部分
subquery_t1 = (
select(DataAstContentRela)
.where(DataAstContentRela.upd_prsn == query_object.upd_prsn,
DataAstContentRela.content_onum == '2' and DataAstContentRela.rela_status == '1')
.union_all(
select(DataAstContentRela)
.where(DataAstContentRela.content_onum != '2' and DataAstContentRela.rela_status == '1')
)
).alias('subquery_t1') # 为子查询分配唯一别名
query = (
select(
DataAstContent.content_onum,
DataAstContent.content_name,
DataAstContent.content_stat,
DataAstContent.content_intr,
DataAstContent.content_pic,
DataAstContent.supr_content_onum,
DataAstContent.leaf_node_flag,
DataAstContent.upd_prsn,
DataAstContent.upd_time,
subquery_t1.c.rela_onum, # 明确指定子查询的字段
subquery_t1.c.ast_onum,
subquery_t1.c.rela_type,
subquery_t1.c.rela_eff_begn_date,
subquery_t1.c.rela_eff_end_date,
subquery_t1.c.upd_prsn,
)
.distinct()
.select_from(DataAstContent)
.outerjoin(subquery_t1, DataAstContent.content_onum == subquery_t1.c.content_onum) # 明确使用子查询别名
.where(DataAstContent.content_stat == 1)
.order_by(DataAstContent.content_onum)
)
# 使用分页工具进行查询
data_ast_list = await PageUtil.paginate(
db,
query,
page_num=query_object.page_num,
page_size=query_object.page_size,
is_page=is_page
)
return data_ast_list

7
vue-fastapi-backend/module_admin/entity/do/meta_do.py

@ -78,14 +78,15 @@ class MetadataClas(Base):
__tablename__ = 't_metadata_clas'
clas_onum = Column(Integer, primary_key=True, comment='分类编号')
clas_pri_clas = Column(String(50, collation='utf8_general_ci'), comment='一级分类')
clas_scd_clas = Column(String(50, collation='utf8_general_ci'), comment='二级分类')
# clas_pri_clas = Column(String(50, collation='utf8_general_ci'), comment='一级分类')
# clas_scd_clas = Column(String(50, collation='utf8_general_ci'), comment='二级分类')
clas_tmpl = Column(String(200, collation='utf8_general_ci'), comment='标签模板')
clas_thre_clas = Column(String(50, collation='utf8_general_ci'), comment='三级分类')
# clas_thre_clas = Column(String(50, collation='utf8_general_ci'), comment='三级分类')
clas_name = Column(String(255, collation='utf8_general_ci'), comment='分类名称')
clas_eff_flag = Column(String(1, collation='utf8_general_ci'), default='0', comment='生效标志(0有效 1无效)')
rec_subm_prsn = Column(String(255, collation='utf8_general_ci'), comment='记录提交人')
upd_time = Column(DateTime, comment='更新时间')
belt_batch_content = Column(Integer, default=None, comment='分类')
class MetadataFldTabExtractInfo(Base):

38
vue-fastapi-backend/module_admin/entity/do/metadata_config_do.py

@ -1,5 +1,5 @@
from datetime import datetime
from sqlalchemy import Column, String, Integer, DateTime, TIMESTAMP,Boolean, func,DECIMAL
from sqlalchemy import Column,Text,Date, String, Integer, DateTime, TIMESTAMP,Boolean, func,DECIMAL
from config.database import Base
@ -10,11 +10,12 @@ from config.database import Base
# __tablename__ = 't_metadata_clas'
# clas_onum = Column(Integer, primary_key=True, default=0, comment='标签序号')
# clas_pri_clas = Column(String(50), default=None, comment='标签一级分类')
# clas_scd_clas = Column(String(50), default=None, comment='标签二级分类')
# clas_thre_clas = Column(String(50), default=None, comment='标签三级分类')
# # clas_pri_clas = Column(String(50), default=None, comment='标签一级分类')
# # clas_scd_clas = Column(String(50), default=None, comment='标签二级分类')
# # clas_thre_clas = Column(String(50), default=None, comment='标签三级分类')
# clas_name = Column(String(200), default=None, comment='标签名称')
# clas_tmpl = Column(String(200), default=None, comment='标签模版')
# belt_batch_content = Column(Integer, default=None, comment='分类')
# clas_eff_flag = Column(String(1), default=None, comment='标签有效标志')
# rec_subm_prsn = Column(String(64), default=None, comment='记录提交人')
# upd_time = Column(TIMESTAMP, default=func.now(), onupdate=func.now(), nullable=True, comment='更新时间')
@ -157,4 +158,31 @@ class DatasecConfig(Base):
status = Column(String(10), nullable=True, default='OFFLINE', comment='状态')
ds_time = Column(DateTime, nullable=True, comment="调度时间")
ds_ids = Column(String(50), nullable=True, comment="任务ID")
schId = Column(String(50), nullable=True, comment="调度id")
schId = Column(String(50), nullable=True, comment="调度id")
class DataAstContent(Base):
__tablename__ = "t_batch_content"
content_onum = Column(Integer, primary_key=True, autoincrement=True, comment='目录序号')
content_name = Column(String(255), nullable=False, comment='目录名称')
content_stat = Column(String(10), nullable=False, comment='目录状态(有效/废弃/停用)')
content_intr = Column(Text, comment='目录简介')
content_pic = Column(String(255), comment='目录负责人')
supr_content_onum = Column(Integer, comment='上级目录序号')
leaf_node_flag = Column(Integer, default=1, comment='叶子节点标志')
upd_prsn = Column(String(255), nullable=False, comment='更新人员')
upd_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
class DataAstContentRela(Base):
__tablename__ = "t_batch_content_rela"
rela_onum = Column(Integer, primary_key=True, autoincrement=True, comment='关系序号')
content_onum = Column(Integer, nullable=False, comment='目录序号')
ast_onum = Column(Integer, nullable=False, comment='资产序号')
rela_type = Column(String(50), default='归属关系', comment='关系类型')
rela_eff_begn_date = Column(Date, nullable=True, comment='关系生效开始日期')
rela_eff_end_date = Column(Date, nullable=True, comment='关系生效结束日期')
upd_prsn = Column(String(255), nullable=False, comment='更新人员')
rela_status = Column(String(18), nullable=True, comment='关系状态')

5
vue-fastapi-backend/module_admin/entity/vo/metadata_config_vo.py

@ -14,9 +14,8 @@ class MetadataClasModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
clas_onum: Optional[int] = Field(default=None, description='标签序号')
clas_pri_clas: Optional[str] = Field(default=None, description='标签一级分类')
clas_scd_clas: Optional[str] = Field(default=None, description='标签二级分类')
clas_thre_clas: Optional[str] = Field(default=None, description='标签三级分类')
belt_batch_content: Optional[int] = Field(default=None, description='分类')
clas_name: Optional[str] = Field(default=None, description='标签名称')
clas_tmpl: Optional[str] = Field(default=None, description='标签模版')
clas_eff_flag: Optional[Literal['0', '1']] = Field(default=None, description='标签有效标志')

294
vue-fastapi-backend/module_admin/service/metadata_config_service.py

@ -9,6 +9,8 @@ from utils.common_util import CamelCaseUtil
from module_admin.entity.do.metadata_config_do import SecuBizConfigRela,TaskBizConfigRela # ORM 类
from exceptions.exception import ServiceException,ServiceWarning
import uuid
from collections import defaultdict
from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel,DataCatalogResponseWithChildren,DataCatalogMovedRequest,DataCatalogMergeRequest,DataCatalogChild,DataCatalogMoverelRequest
from module_admin.entity.vo.dataSource_vo import ProcessDefinition,ParmScheduleVo,ProcessInstancePage,ParmSchedule
from typing import List
from datetime import datetime
@ -50,7 +52,10 @@ class MetadataConfigService:
existing = await MetadataConfigDao.get_clas_by_onum(query_db, page_object.clas_onum)
if existing:
raise ServiceException(message=f"主键重复,clas_onum={page_object.clas_onum} 已存在")
# 默认临时
if not page_object.belt_batch_content:
page_object.belt_batch_content=2
await MetadataConfigDao.add_metadata_clas_dao(query_db, page_object)
await query_db.commit()
return CrudResponseModel(is_success=True, message="新增成功")
@ -69,7 +74,7 @@ class MetadataConfigService:
edit_data = page_object.model_dump(exclude_unset=True)
# 当前记录旧主键(假设表单传入)
original_onum = page_object.original_clas_onum # 你需要在 model 中加上这个字段
original_onum = page_object.clas_onum # 你需要在 model 中加上这个字段
# 查询当前记录是否存在
existing = await cls.get_metadata_clas_detail_services(query_db, original_onum)
@ -789,3 +794,288 @@ class MetadataConfigService:
async def get_task_biz_config_rela_list_services(cls, result_db: AsyncSession, biz_onum: int):
ai_session_list = await MetadataConfigDao.get_task_rela_by_biz_id(result_db, biz_onum) # 查询最新的20条
return CamelCaseUtil.transform_result(ai_session_list)
# -------------------------------标签分类分级----------------------------
@classmethod
async def get_catalog_list_services(
cls, query_db: AsyncSession, query_object: DataCatalogPageQueryModel, user_id: int, is_page: bool = False
):
"""
获取数据目录列表信息service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 数据目录列表信息对象
"""
catalog_list_result = await MetadataConfigDao.get_catalog_list(query_db, query_object, user_id, is_page)
# 按contentOnum分组
grouped = defaultdict(list)
for item in catalog_list_result.rows:
grouped[item['contentOnum']].append(item)
nodes = {} # 存储所有处理后的节点
# 处理每个组,生成节点
for belt_data_std_content, items in grouped.items():
first_item = items[0]
is_leaf = first_item['leafNodeFlag'] == 1
rela_onum = first_item['relaOnum'] is not None
# 公共字段提取
common_fields = {
'contentOnum': first_item['contentOnum'],
'contentName': first_item['contentName'],
'contentStat': first_item['contentStat'],
'contentIntr': first_item['contentIntr'],
'contentPic': first_item['contentPic'],
'suprContentOnum': first_item['suprContentOnum'],
'leafNodeFlag': first_item['leafNodeFlag'],
'updPrsn': first_item['updPrsn'],
'updTime': first_item['updTime'],
'children': []
}
nodes[belt_data_std_content] = common_fields
# 构建父子关系
root = None
for belt_data_std_content, node in nodes.items():
supr = node['suprContentOnum']
if supr is None:
root = node
else:
parent = nodes.get(supr)
if parent:
parent['children'].append(node)
# 对每个父节点的children进行排序,将'临时的节点'放到最后
for belt_data_std_content, node in nodes.items():
if 'children' in node:
# 排序时,'临时的节点'会被放到最后
node['children'] = sorted(
node['children'],
key=lambda x: x['contentName'] == '临时',
reverse=False # True会将'临时的节点'排在最前面,False是排在最后
)
print('获取数据清单内容:',root)
catalog_list_result.rows = [root]
return catalog_list_result
@classmethod
async def delete_catalog_services(cls, query_db: AsyncSession, request: DeleteDataCatalogModel,user_id):
"""
删除数据目录信息service
:param query_db: orm对象
:param request: 删除数据目录请求对象
:return: 删除目录操作结果
"""
if request.content_onums:
content_onum_list = request.content_onums.split(',')
try:
for belt_data_std_content in content_onum_list:
catalog = await cls.get_catalog_detail_services(query_db, int(belt_data_std_content))
if not catalog:
raise ServiceException(message=f'目录ID {belt_data_std_content} 不存在')
await MetadataConfigDao.delete_catalog_dao(query_db, DeleteDataCatalogModel(content_onums=belt_data_std_content))
# await MetadataConfigDao.delete_ast_book_mark_rela_by_content_onum(query_db, int(belt_data_std_content), user_id)
await query_db.commit()
return CrudResponseModel(is_success=True, message='删除成功')
except Exception as e:
await query_db.rollback()
raise e
else:
raise ServiceException(message='传入目录id为空')
@classmethod
async def removerel_data_ast_catalog_services(cls, query_db: AsyncSession, request: DataCatalogChild):
"""
移除数据资产目录service
"""
removerel_catalog_data = {
'rela_onum': request.rela_onum,
'content_onum': request.content_onum,
'ast_onum': request.ast_onum,
'rela_type': request.rela_type,
'rela_eff_begn_date': request.rela_eff_begn_date,
'rela_eff_end_date': request.rela_eff_end_date,
'upd_prsn': request.upd_prsn,
'rela_status': request.rela_status
}
try:
await MetadataConfigDao.removerel_data_ast_catalog_dao(query_db, removerel_catalog_data)
await query_db.commit()
return CrudResponseModel(is_success=True, message='资产移除成功')
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"移除资产时发生错误: {str(e)}")
@classmethod
async def get_catalog_detail_services(cls, query_db: AsyncSession, belt_data_std_content: int):
"""
获取数据目录详细信息service
:param query_db: orm对象
:param belt_data_std_content: 数据目录ID
:return: 数据目录详细信息对象
"""
catalog_detail_result = await MetadataConfigDao.get_catalog_by_id(query_db, belt_data_std_content)
return catalog_detail_result
@classmethod
async def moved_catalog_instr_services(cls, query_db: AsyncSession, request: DataCatalogMovedRequest):
"""
移动数据目录service
"""
moved_catalog_data = {
'content_onum': request.content_onum,
'supr_content_onum': request.supr_content_onum,
'supr_content_onum_after': request.supr_content_onum_after
}
try:
await MetadataConfigDao.moved_catalog_instr_dao(query_db, moved_catalog_data)
await query_db.commit()
return CrudResponseModel(is_success=True, message='目录移动成功')
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"移动目录时发生错误: {str(e)}")
@classmethod
async def merge_catalog_instr_services(cls, query_db: AsyncSession, request: DataCatalogMergeRequest):
"""
移动数据目录service
"""
merge_catalog_data = {
'content_onum': request.content_onum,
'supr_content_onum': request.supr_content_onum,
'content_onum_after': request.content_onum_after,
'supr_content_onum_after': request.supr_content_onum_after
}
try:
await MetadataConfigDao.merge_catalog_instr_dao(query_db, merge_catalog_data)
await query_db.commit()
return CrudResponseModel(is_success=True, message='目录合并成功')
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"目录合并时发生错误: {str(e)}")
@classmethod
async def edit_catalog_child_services(cls, query_db: AsyncSession, request: DataCatalogResponseWithChildren):
"""
编辑数据目录信息service
:param query_db: orm对象
:param request: 编辑数据目录请求对象
:return: 编辑目录操作结果
"""
catalog_data = {
'content_onum': request.content_onum,
'content_name': request.content_name,
'content_stat': request.content_stat,
'content_intr': request.content_intr,
'content_pic': request.content_pic,
'supr_content_onum': request.supr_content_onum,
'leaf_node_flag': request.leaf_node_flag,
'upd_prsn': request.upd_prsn,
'children': [child.model_dump() for child in request.children] # 将 children 转换为字典列表
}
try:
for child in catalog_data["children"]:
# 设置 rela_eff_begn_date
if child.get("rela_eff_begn_date"):
child["rela_eff_begn_date"] = child["rela_eff_begn_date"].strftime("%Y-%m-%d %H:%M:%S")
else:
child["rela_eff_begn_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 设置 rela_eff_end_date
if child.get("rela_eff_end_date"):
child["rela_eff_end_date"] = child["rela_eff_end_date"].strftime("%Y-%m-%d %H:%M:%S")
else:
child["rela_eff_end_date"] = datetime(year=2999, month=12, day=31, hour=0, minute=0, second=0).strftime("%Y-%m-%d %H:%M:%S")
child["upd_prsn"] = request.upd_prsn
child["rela_status"] = "1"
await MetadataConfigDao.edit_catalog_child_dao(query_db, catalog_data)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"更新目录时发生错误: {str(e)}")
@classmethod
async def add_catalog_services(cls, query_db: AsyncSession, request: DataCatalogResponseWithChildren):
"""
新增数据目录信息service
:param query_db: orm对象
:param request: 新增数据目录请求对象
:return: 新增目录操作结果
"""
catalog_data1 = {
'content_name': request.content_name,
'content_stat': request.content_stat,
'content_intr': request.content_intr,
'content_pic': request.content_pic,
'supr_content_onum': request.supr_content_onum,
'leaf_node_flag': request.leaf_node_flag,
'upd_prsn': request.upd_prsn
}
catalog_data2 = {
'content_name': request.content_name,
'content_stat': request.content_stat,
'content_intr': request.content_intr,
'content_pic': request.content_pic,
'supr_content_onum': request.supr_content_onum,
'leaf_node_flag': request.leaf_node_flag,
'upd_prsn': request.upd_prsn,
'children': [child.model_dump() for child in request.children] # 将 children 转换为字典列表
}
try:
for child in catalog_data2["children"]:
child["rela_eff_begn_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 设置默认值,当前时间
child["rela_eff_end_date"] = datetime(year=2999, month=12, day=31, hour=0, minute=0, second=0).strftime("%Y-%m-%d %H:%M:%S"), # 设置默认值,2999-12-31
child["upd_prsn"] = request.upd_prsn,
child["rela_status"] = "1"
new_catalog = await MetadataConfigDao.add_catalog_dao(query_db, catalog_data1, catalog_data2)
await query_db.commit()
return CrudResponseModel(is_success=True, message='新增成功', data=new_catalog)
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"创建目录时发生错误: {str(e)}")
@classmethod
async def edit_catalog_leaf_services(cls, query_db: AsyncSession,content_onum : int, leaf_node_flag : int):
"""
编辑数据目录信息service
:param query_db: orm对象
:param request: 编辑数据目录请求对象
:return: 编辑目录操作结果
"""
catalog_data1 = {
'content_onum': content_onum,
'leaf_node_flag': leaf_node_flag
}
try:
await MetadataConfigDao.edit_catalog_leaf_dao(query_db, catalog_data1)
await query_db.commit()
return CrudResponseModel(is_success=True, message='更新成功')
except Exception as e:
await query_db.rollback()
raise ServiceException(message=f"更新目录时发生错误: {str(e)}")

92
vue-fastapi-frontend/src/api/metadataConfig/directory.js

@ -0,0 +1,92 @@
import request from '@/utils/request'
export function getDirectoryTree(params) {
return request({
url: '/default-api/metadataConfig/cata/list',
method: 'get',
params,
})
}
export function delDirectory(id) {
return request({
url: `/default-api/metadataConfig/cata/${id}`,
method: 'delete',
})
}
export function addDirectoryCollection(data) {
return request({
url: '/default-api/metadataConfig/cata/bookmark ',
method: 'post',
data,
})
}
export function cancelDirectoryCollection(id) {
return request({
url: `/default-api/metadataConfig/cata/bookmark/${id}`,
method: 'delete',
})
}
export function delDirectoryAsset(data) {
return request({
url: '/default-api/metadataConfig/cata/removerel',
method: 'put',
data,
})
}
export function moveDirectory(data) {
return request({
url: '/default-api/metadataConfig/cata/moved',
method: 'put',
data,
})
}
export function mergeDirectory(data) {
return request({
url: '/default-api/metadataConfig/cata/merge',
method: 'put',
data,
})
}
export function moveDirectoryAsset(data) {
return request({
url: '/default-api/metadataConfig/cata/moverel',
method: 'put',
data,
})
}
export function addDirectory(data) {
return request({
url: '/default-api/metadataConfig/cata',
method: 'post',
data,
})
}
export function updateDirectory(data) {
return request({
url: `/default-api/metadataConfig/cata/edit`,
method: 'put',
data,
})
}
export function getDirectoryAsset(params) {
return request({
url: '/default-api/metadataConfig/cata/atree',
method: 'get',
params,
})
}
export function getDirectory(id) {
return request({
url: `/default-api/metadataConfig/cata/${id}`,
method: 'get',
})
}

171
vue-fastapi-frontend/src/views/metadataConfig/clas/components/AssetMoveDialog.vue

@ -0,0 +1,171 @@
<template>
<el-dialog width="800px" append-to-body :title="title" v-model="open">
<el-form label-width="100px" ref="formRef" :model="form" :rules="rules">
<el-row :gutter="16">
<el-col :span="11">
<el-form-item label="当前资产" prop="dataAstCnName">
<el-input :disabled="true" v-model="form.dataAstCnName" />
</el-form-item>
<el-form-item label="当前资产简介" prop="dataAstDesc">
<el-input
placeholder="自动带入"
type="textarea"
:disabled="true"
:rows="8"
v-model="form.dataAstDesc"
/>
</el-form-item>
</el-col>
<el-col :span="2">
<div class="arrow">
<span>········</span>
<el-icon><Right /></el-icon>
</div>
</el-col>
<el-col :span="11">
<el-form-item label="目标分类" prop="contentOnumAfter">
<el-tree-select
check-strictly
value-key="contentOnum"
placeholder="选择目标分类"
:default-expand-all="true"
:disabled="disabled"
:clearable="true"
:data="localDirectoryTree"
:props="{
value: 'contentOnum',
label: 'contentName',
children: 'children',
}"
v-model="form.contentOnumAfter"
@node-click="handleTargetCatalogNodeClick"
/>
</el-form-item>
<el-form-item label="目标分类简介" prop="contentIntrAfter">
<el-input
placeholder="自动带入"
type="textarea"
:disabled="true"
:rows="8"
v-model="form.contentIntrAfter"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" :disabled="disabled" @click="submitForm"
>确定</el-button
>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { nextTick } from 'vue'
import { moveDirectoryAsset } from '@/api/metadataConfig/directory'
const props = defineProps({
directoryTree: {
type: Array,
required: true,
},
})
const filterTree = (tree, conditionFn) => {
return tree
.map((node) => {
//
const filteredChildren = node.children
? filterTree(node.children, conditionFn)
: []
//
if (conditionFn(node)) {
//
return {
...node,
children: filteredChildren,
}
}
// null
return null
})
.filter(Boolean) // null
}
const localDirectoryTree = computed(() => {
const tree = props.directoryTree
return filterTree(tree, (node) => node.contentOnum && !node.astOnum) //
})
const title = ref('')
const open = ref(false)
const disabled = ref(false)
const { proxy } = getCurrentInstance()
const form = ref({})
const rules = ref({
targetContentOnum: [
{ required: true, message: '目标分类不能为空', trigger: 'blur' },
],
})
const formRef = ref(null)
const openDialog = (row) => {
open.value = true
form.value = {
relaOnum: undefined,
contentOnum: undefined,
contentOnumAfter: undefined,
}
if (row.relaOnum) {
form.value = {
...form.value,
...row,
}
}
nextTick(() => {
formRef.value.clearValidate()
})
}
const handleTargetCatalogNodeClick = (data) => {
form.value = {
...form.value,
contentIntrAfter: data.contentIntr,
}
}
const emit = defineEmits(['onSuccess'])
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
moveDirectoryAsset(form.value).then((response) => {
proxy.$modal.msgSuccess('移动成功')
open.value = false
emit('onSuccess')
})
}
})
}
const cancel = () => {
open.value = false
}
defineExpose({ title, disabled, openDialog })
</script>
<style lang="scss" scoped>
.arrow {
display: flex;
font-size: 18px;
text-align: center;
margin: 8px auto;
span {
line-height: 18px;
}
}
</style>

193
vue-fastapi-frontend/src/views/metadataConfig/clas/components/FormDialog.vue

@ -0,0 +1,193 @@
<template>
<el-dialog width="600px" append-to-body :title="title" v-model="open">
<el-form label-width="100px" ref="formRef" :model="form" :rules="rules">
<el-form-item label="分类名称" prop="contentName">
<el-input
placeholder="请输入分类名称"
:disabled="disabled"
v-model="form.contentName"
/>
</el-form-item>
<el-form-item label="上级分类" prop="suprContentOnum">
<el-tree-select
check-strictly
value-key="contentOnum"
placeholder="请选择上级分类"
:default-expand-all="true"
:disabled="disabled"
:clearable="true"
:data="localDirectoryTree"
:props="{
value: 'contentOnum',
label: 'contentName',
children: 'children',
}"
v-model="form.suprContentOnum"
>
</el-tree-select>
</el-form-item>
<el-form-item label="负责人" prop="contentPic">
<el-input
placeholder="请输入负责人"
:disabled="disabled"
v-model="form.contentPic"
/>
</el-form-item>
<el-form-item label="分类简介" prop="contentIntr">
<el-input
placeholder="请输入分类简介"
type="textarea"
:disabled="disabled"
:rows="8"
v-model="form.contentIntr"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" :disabled="disabled" @click="submitForm"
>确定</el-button
>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { computed, nextTick } from 'vue'
import {
getDirectory,
addDirectory,
updateDirectory,
getDirectoryAsset,
} from '@/api/metadataConfig/directory'
const props = defineProps({
directoryTree: {
type: Array,
required: true,
},
})
const filterTree = (tree, conditionFn) => {
return tree
.map((node) => {
//
const filteredChildren = node.children
? filterTree(node.children, conditionFn)
: []
//
if (conditionFn(node)) {
//
return {
...node,
children: filteredChildren,
}
}
// null
return null
})
.filter(Boolean) // null
}
const localDirectoryTree = computed(() => {
const tree = props.directoryTree
return filterTree(tree, (node) => node.contentOnum && !node.astOnum) //
})
const title = ref('')
const open = ref(false)
const disabled = ref(false)
const { proxy } = getCurrentInstance()
const form = ref({})
const rules = ref({
contentName: [
{ required: true, message: '分类名称不能为空', trigger: 'blur' },
],
suprContentOnum: [
{ required: true, message: '上级分类不能为空', trigger: 'blur' },
],
})
const formRef = ref(null)
const openDialog = (row) => {
open.value = true
form.value = {
contentName: undefined,
suprContentOnum: undefined,
contentPic: undefined,
contentStat: '1', // 0-1-2-
contentIntr: undefined,
children: [],
}
if (row.contentOnum || row.suprContentOnum) {
form.value = {
...form.value,
...row,
assets:
row.children &&
row.children.length &&
row.children.find((i) => i.astOnum)
? [...row.children].map((i) => i.astOnum)
: [], //
}
}
nextTick(() => {
formRef.value.clearValidate()
})
}
const addTreeNodeId = (tree) => {
return tree.map((node, index) => {
return {
...node,
id: node.dataAssetCatalogAstno || index,
children:
node.children && node.children.length
? addTreeNodeId(node.children)
: [],
}
})
}
const emit = defineEmits(['onSuccess'])
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
const children = form.value.assets.reduce((arr, cur) => {
const item = form.value.children.find((i) => i.astOnum === cur)
if (!item) {
arr.push({
contentOnum: form.value.contentOnum,
astOnum: cur,
})
} else {
arr.push(item)
}
return arr
}, [])
form.value = {
...form.value,
children,
}
const request = form.value.contentOnum ? updateDirectory : addDirectory
request(form.value).then((response) => {
proxy.$modal.msgSuccess(
form.value.contentOnum ? '修改成功' : '新增成功'
)
open.value = false
emit('onSuccess')
})
}
})
}
const cancel = () => {
open.value = false
}
defineExpose({ title, disabled, openDialog })
</script>

175
vue-fastapi-frontend/src/views/metadataConfig/clas/components/MergerDialog.vue

@ -0,0 +1,175 @@
<template>
<el-dialog width="800px" append-to-body :title="title" v-model="open">
<el-form label-width="100px" ref="formRef" :model="form" :rules="rules">
<el-row :gutter="16">
<el-col :span="11">
<el-form-item label="当前目录" prop="contentName">
<el-input :disabled="true" v-model="form.contentName" />
</el-form-item>
<el-form-item label="当前目录简介" prop="contentIntr">
<el-input
placeholder="自动带入"
type="textarea"
:disabled="true"
:rows="8"
v-model="form.contentIntr"
/>
</el-form-item>
</el-col>
<el-col :span="2">
<div class="arrow">
<span>········</span>
<el-icon><Right /></el-icon>
</div>
</el-col>
<el-col :span="11">
<el-form-item label="目标目录" prop="contentOnumAfter">
<el-tree-select
check-strictly
value-key="contentOnum"
placeholder="选择目标目录"
:default-expand-all="true"
:disabled="disabled"
:clearable="true"
:data="localDirectoryTree"
:props="{
value: 'contentOnum',
label: 'contentName',
children: 'children',
}"
v-model="form.contentOnumAfter"
@node-click="handleTargetCatalogNodeClick"
/>
</el-form-item>
<el-form-item label="目标目录简介" prop="contentIntrAfter">
<el-input
placeholder="自动带入"
type="textarea"
:disabled="true"
:rows="8"
v-model="form.contentIntrAfter"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" :disabled="disabled" @click="submitForm"
>确定</el-button
>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { nextTick } from 'vue'
import { mergeDirectory } from '@/api/metadataConfig/directory'
const props = defineProps({
directoryTree: {
type: Array,
required: true,
},
})
const filterTree = (tree, conditionFn) => {
return tree
.map((node) => {
//
const filteredChildren = node.children
? filterTree(node.children, conditionFn)
: []
//
if (conditionFn(node)) {
//
return {
...node,
children: filteredChildren,
}
}
// null
return null
})
.filter(Boolean) // null
}
const localDirectoryTree = computed(() => {
const tree = props.directoryTree
return filterTree(tree, (node) => node.contentOnum && !node.astOnum) //
})
const title = ref('')
const open = ref(false)
const disabled = ref(false)
const { proxy } = getCurrentInstance()
const form = ref({})
const rules = ref({
contentOnumAfter: [
{ required: true, message: '目标目录不能为空', trigger: 'blur' },
],
})
const formRef = ref(null)
const openDialog = (row) => {
open.value = true
form.value = {
contentOnum: undefined,
suprContentOnum: undefined,
contentIntr: undefined,
contentOnumAfter: undefined,
suprContentOnumAfter: undefined,
contentIntrAfter: undefined,
}
if (row.contentOnum) {
form.value = {
...form.value,
...row,
}
}
nextTick(() => {
formRef.value.clearValidate()
})
}
const handleTargetCatalogNodeClick = (data) => {
form.value = {
...form.value,
suprContentOnumAfter: data.suprContentOnum,
contentIntrAfter: data.contentIntr,
}
}
const emit = defineEmits(['onSuccess'])
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
mergeDirectory(form.value).then((response) => {
proxy.$modal.msgSuccess('合并成功')
open.value = false
emit('onSuccess')
})
}
})
}
const cancel = () => {
open.value = false
}
defineExpose({ title, disabled, openDialog })
</script>
<style lang="scss" scoped>
.arrow {
display: flex;
font-size: 18px;
text-align: center;
margin: 8px auto;
span {
line-height: 18px;
}
}
</style>

173
vue-fastapi-frontend/src/views/metadataConfig/clas/components/MoveDialog.vue

@ -0,0 +1,173 @@
<template>
<el-dialog width="800px" append-to-body :title="title" v-model="open">
<el-form label-width="100px" ref="formRef" :model="form" :rules="rules">
<el-row :gutter="16">
<el-col :span="11">
<el-form-item label="当前分类" prop="contentName">
<el-input :disabled="true" v-model="form.contentName" />
</el-form-item>
<el-form-item label="当前分类简介" prop="contentIntr">
<el-input
placeholder="自动带入"
type="textarea"
:disabled="true"
:rows="8"
v-model="form.contentIntr"
/>
</el-form-item>
</el-col>
<el-col :span="2">
<div class="arrow">
<span>········</span>
<el-icon><Right /></el-icon>
</div>
</el-col>
<el-col :span="11">
<el-form-item label="目标分类" prop="suprContentOnumAfter">
<el-tree-select
check-strictly
value-key="contentOnum"
placeholder="选择目标分类"
:default-expand-all="true"
:disabled="disabled"
:clearable="true"
:data="localDirectoryTree"
:props="{
value: 'contentOnum',
label: 'contentName',
children: 'children',
}"
v-model="form.suprContentOnumAfter"
@node-click="handleTargetCatalogNodeClick"
/>
</el-form-item>
<el-form-item label="目标分类简介" prop="contentIntrAfter">
<el-input
placeholder="自动带入"
type="textarea"
:disabled="true"
:rows="8"
v-model="form.contentIntrAfter"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" :disabled="disabled" @click="submitForm"
>确定</el-button
>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { nextTick } from 'vue'
import { moveDirectory } from '@/api/metadataConfig/directory'
const props = defineProps({
directoryTree: {
type: Array,
required: true,
},
})
const filterTree = (tree, conditionFn) => {
return tree
.map((node) => {
//
const filteredChildren = node.children
? filterTree(node.children, conditionFn)
: []
//
if (conditionFn(node)) {
//
return {
...node,
children: filteredChildren,
}
}
// null
return null
})
.filter(Boolean) // null
}
const localDirectoryTree = computed(() => {
const tree = props.directoryTree
return filterTree(tree, (node) => node.contentOnum && !node.astOnum) //
})
const title = ref('')
const open = ref(false)
const disabled = ref(false)
const { proxy } = getCurrentInstance()
const form = ref({})
const rules = ref({
suprContentOnumAfter: [
{ required: true, message: '目标分类不能为空', trigger: 'blur' },
],
})
const formRef = ref(null)
const openDialog = (row) => {
open.value = true
form.value = {
contentOnum: undefined,
contentIntr: undefined,
suprContentOnum: undefined,
suprContentOnumAfter: undefined,
contentIntrAfter: undefined,
}
if (row.contentOnum) {
form.value = {
...form.value,
...row,
}
}
nextTick(() => {
formRef.value.clearValidate()
})
}
const handleTargetCatalogNodeClick = (data) => {
form.value = {
...form.value,
contentIntrAfter: data.contentIntr,
}
}
const emit = defineEmits(['onSuccess'])
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
moveDirectory(form.value).then((response) => {
proxy.$modal.msgSuccess('移动成功')
open.value = false
emit('onSuccess')
})
}
})
}
const cancel = () => {
open.value = false
}
defineExpose({ title, disabled, openDialog })
</script>
<style lang="scss" scoped>
.arrow {
display: flex;
font-size: 18px;
text-align: center;
margin: 8px auto;
span {
line-height: 18px;
}
}
</style>

452
vue-fastapi-frontend/src/views/metadataConfig/clas/index.vue

@ -1,10 +1,178 @@
<template>
<div class="app-container">
<el-row :gutter="16">
<el-col :span="5">
<el-input
v-model="filterText"
style="width: 100%"
placeholder="搜索分类名称"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<div class="tree-box">
<el-tree
class="tree"
ref="treeRef"
node-key="tempId"
:default-expand-all="true"
:highlight-current="true"
:expand-on-click-node="false"
:data="directoryTree"
:props="defaultProps"
:filter-node-method="filterNode"
:current-node-key="currentNode.tempId"
@node-click="handleNodeClick"
>
<template #default="{ data }">
<div class="custom-tree-node">
<el-space :size="2">
<el-icon v-if="!isCollectionDirectory(data)">
<Folder />
</el-icon>
<el-icon v-else :style="isCollectionDirectory(data) ? { color: '#E6A23C' } : {}">
<FolderRemove />
</el-icon>
<span :style="isCollectionDirectory(data) ? { color: '#E6A23C' } : {}">
{{ data.contentName || data.dataAstCnName }}
</span> </el-space>
<div
v-if="!isCollectionDirectory(data)"
class="tree-node__action"
>
<template v-if="isAsset(data)">
<el-button
v-if="!isCollected(data)"
link
type="warning"
icon="Star"
@click="(e) => handleCollect(data, e)"
></el-button>
<el-button
v-else
link
type="warning"
style="margin-right: -2px"
@click="(e) => handleCollectionCancel(data, e)"
>
<el-icon slot="icon" size="18" color="#E6A23C">
<StarFilled />
</el-icon>
</el-button>
</template>
<el-dropdown
v-if="
!isCollection(data) &&
(isDirectory(data) || isRoot(data)) &&
hasPermiOr([
'dataAsset:directory:add',
'dataAsset:directory:edit',
'dataAsset:directory:remove',
'dataAsset:directory:move',
'dataAsset:directory:merge',
])
"
placement="right-start"
@command="(command) => handleCommand(command, data)"
>
<el-button
style="margin-left: 4px"
link
type="primary"
icon="Menu"
></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-if="
isRoot(data) &&
hasPermiOr(['dataAsset:directory:add'])
"
command="handleAddDialogOpen"
>
新增分类
</el-dropdown-item>
<template v-if="isDirectory(data)">
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:directory:add'])"
command="handleAddDialogOpen"
>
新增分类
</el-dropdown-item>
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:directory:edit'])"
command="handleEditDialogOpen"
>
修改分类
</el-dropdown-item>
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:directory:remove'])"
command="handleDelete"
>
删除分类
</el-dropdown-item>
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:directory:move'])"
command="handleMoveDialogOpen"
>
移动分类
</el-dropdown-item>
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:directory:merge'])"
command="handleMergerDialogOpen"
>
合并分类
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown
v-if="
!isCollection(data) &&
isAsset(data) &&
hasPermiOr([
'dataAsset:asset:remove',
'dataAsset:asst:move',
])
"
placement="right-start"
@command="(command) => handleCommand(command, data)"
>
<el-button
style="margin-left: 4px"
link
type="primary"
icon="Menu"
></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:asset:remove'])"
command="handleAssetDelete"
>
删除资产
</el-dropdown-item>
<el-dropdown-item
v-if="hasPermiOr(['dataAsset:asst:move'])"
command="handleAssetMoveDialogOpen"
>
移动资产
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
</el-tree>
</div>
</el-col>
<el-col :span="19">
<el-row :gutter="10" class="mb8">
<el-form :inline="true" :model="queryForm" >
<el-form-item label="标签一级分类">
<el-input v-model="queryForm.clasPriClas" placeholder="请输入标签一级分类" clearable />
</el-form-item>
<el-form-item label="标签名称">
<el-input v-model="queryForm.clasName" placeholder="请输入标签名称" clearable />
</el-form-item>
@ -55,9 +223,7 @@
>
<el-table-column type="selection" width="55" />
<el-table-column prop="clasOnum" label="标签序号" width="200" />
<el-table-column prop="clasPriClas" label="标签一级分类" width="150" />
<el-table-column prop="clasScdClas" label="标签二级分类" width="150" />
<el-table-column prop="clasThreClas" label="标签三级分类" width="150" />
<el-table-column prop="clasName" label="标签名称" width="200" />
<el-table-column prop="clasTmpl" label="标签模版" width="200" />
<el-table-column prop="clasEffFlag" label="有效标志" width="100">
@ -80,6 +246,10 @@
v-model:limit="queryForm.pageSize"
@pagination="getList"
/>
</el-col>
</el-row>
<el-dialog
:title="title"
v-model="open"
@ -97,21 +267,33 @@
<el-form-item label="标签序号" prop="clasPriClas">
<el-input v-model="form.clasOnum" autocomplete="off" type="number" :disabled="title != '新增标签信息'"/>
</el-form-item>
<el-form-item label="标签一级分类" prop="clasPriClas">
<el-input v-model="form.clasPriClas" autocomplete="off" />
</el-form-item>
<el-form-item label="标签二级分类" prop="clasScdClas">
<el-input v-model="form.clasScdClas" autocomplete="off" />
</el-form-item>
<el-form-item label="标签三级分类" prop="clasThreClas">
<el-input v-model="form.clasThreClas" autocomplete="off" />
</el-form-item>
<el-form-item label="标签名称" prop="clasName">
<el-input v-model="form.clasName" autocomplete="off" />
</el-form-item>
<el-form-item label="标签模版" prop="clasTmpl">
<el-input v-model="form.clasTmpl" autocomplete="off" />
</el-form-item>
<el-form-item label="标签分类">
<el-tree-select
check-strictly
value-key="contentOnum"
placeholder="请选择标签分类"
:default-expand-all="true"
:clearable="true"
:data="directoryTree"
:check-strictly="false"
:props="{
value: 'contentOnum',
label: 'contentName',
children: 'children',
// disabled: (data) => data.children && data.children.length > 0, //
}"
v-model="form.beltBatchContent"
>
</el-tree-select>
</el-form-item>
<el-form-item label="有效标志" prop="clasEffFlag">
<el-select v-model="form.clasEffFlag" placeholder="请选择有效标志">
<el-option label="有效" value="1" />
@ -124,22 +306,59 @@
<el-button type="primary" @click="submitForm">保存</el-button>
</template>
</el-dialog>
<FormDialog
ref="formDialogRef"
:directoryTree="directoryTree"
@onSuccess="setDirectoryTree"
/>
<MoveDialog
ref="moveDialogRef"
:directoryTree="directoryTree"
@onSuccess="setDirectoryTree"
/>
<MergerDialog
ref="mergerDialogRef"
:directoryTree="directoryTree"
@onSuccess="setDirectoryTree"
/>
<AssetMoveDialog
ref="assetMoveDialogRef"
:directoryTree="directoryTree"
@onSuccess="setDirectoryTree"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
import auth from '@/plugins/auth'
import {
listMetadataClas,
addMetadataClas,
updateMetadataClas,
delMetadataClas
} from '@/api/metadataConfig/metadataConfig'
import {
getDirectoryTree,
delDirectory,
delDirectoryAsset,
addDirectoryCollection,
cancelDirectoryCollection,
} from '@/api/metadataConfig/directory'
const { hasPermiOr } = auth
const { proxy } = getCurrentInstance()
import FormDialog from './components/FormDialog.vue'
import MoveDialog from './components/MoveDialog.vue'
import MergerDialog from './components/MergerDialog.vue'
import AssetMoveDialog from './components/AssetMoveDialog.vue'
const queryForm = reactive({
clasPriClas: '',
clasName: '',
beltBatchContent:null,
pageNum: 1,
pageSize: 10
})
@ -149,32 +368,185 @@ const total = ref(0) // 总条数
const loading = ref(false)
const open = ref(false)
const title = ref('')
const handleMoveDialogOpen = (data) => {
moveDialogRef.value.title = '移动分类'
moveDialogRef.value.openDialog(data)
}
const mergerDialogRef = ref(null)
const handleMergerDialogOpen = (data) => {
mergerDialogRef.value.title = '合并分类'
mergerDialogRef.value.openDialog(data)
}
const handleCollect = (data, e) => {
e.stopPropagation()
addDirectoryCollection({
dataAstNo: String(data.dataAstNo),
userId: String(userStore.onum),
}).then(() => {
proxy.$modal.msgSuccess('收藏成功')
setDirectoryTree()
})
}
const handleCollectionCancel = (data, e) => {
e.stopPropagation()
cancelDirectoryCollection(data.relaOnum).then(() => {
proxy.$modal.msgSuccess('取消成功')
setDirectoryTree()
})
}
const clasFormRef = ref(null)
const form = reactive({
clasOnum: null,
clasPriClas: '',
clasScdClas: '',
clasThreClas: '',
beltBatchContent: null,
clasName: '',
clasTmpl: '',
clasEffFlag: '',
recSubmPrsn: ''
})
const currentNode = ref({})
const handleCommand = (command, data) => {
const strategy = {
handleAddDialogOpen: handleAddDialogOpen,
handleEditDialogOpen: handleEditDialogOpen,
handleDelete: handleDelete,
handleMoveDialogOpen: handleMoveDialogOpen,
handleMergerDialogOpen: handleMergerDialogOpen,
handleAssetDelete: handleAssetDelete,
handleAssetMoveDialogOpen: handleAssetMoveDialogOpen,
}
strategy[command](data)
}
const formDialogRef = ref(null)
const handleAddDialogOpen = (data) => {
formDialogRef.value.title = '新增分类'
formDialogRef.value.openDialog({
suprContentOnum: data.contentOnum,
})
}
const handleAssetDelete = (data) => {
ElMessageBox.confirm(`确定删除 ${data.dataAstCnName} 资产吗?`, '资产删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
delDirectoryAsset({
...data,
relaStatus: '0', // 0-1-
}).then(() => {
proxy.$modal.msgSuccess('删除成功')
setDirectoryTree()
})
})
}
const assetMoveDialogRef = ref(null)
const handleAssetMoveDialogOpen = (data) => {
assetMoveDialogRef.value.title = '移动资产'
assetMoveDialogRef.value.openDialog(data)
}
const handleEditDialogOpen = (data) => {
formDialogRef.value.title = '修改分类'
formDialogRef.value.openDialog(data)
}
const moveDialogRef = ref(null)
const handleDelete = (data) => {
ElMessageBox.confirm(
`确定删除 ${data.contentName} 分类吗?`,
'分类删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
delDirectory(data.contentOnum).then(() => {
proxy.$modal.msgSuccess('删除成功')
setDirectoryTree()
})
})
}
const rules = {
clasPriClas: [{ required: true, message: '请输入标签一级分类', trigger: 'blur' }],
clasOnum: [{ required: true, message: '请输入标签序号', trigger: 'blur' }],
clasName: [{ required: true, message: '请输入标签名称', trigger: 'blur' }],
clasEffFlag: [{ required: true, message: '请选择有效标志', trigger: 'change' }]
}
const selectedRows = ref([])
const directoryTableData = ref([])
function handleSelectionChange(val) {
selectedRows.value = val
}
//
const directoryTree = ref([])
const setDirectoryTree = () => {
return getDirectoryTree({
pageSize: 999,
}).then(({ rows }) => {
directoryTree.value = addTreeNodeId(rows)
})
}
/** 增加临时ID作为树节点的唯一键值 */
const addTreeNodeId = (tree) => {
return tree.map((node) => {
return {
...node,
tempId: node.astOnum || node.contentOnum,
children:
node.children && node.children.length
? addTreeNodeId(node.children)
: [],
}
})
}
setDirectoryTree().then(async () => {
if (directoryTree.value.length) {
currentNode.value = directoryTree.value[0]
directoryTableData.value = directoryTree.value[0].children || []
}
})
const handleNodeClick = async (data) => {
if(data.contentOnum==1){
queryForm.beltBatchContent=null
form.beltBatchContent=null
}else{
form.beltBatchContent=data.contentOnum
queryForm.beltBatchContent=data.contentOnum
}
handleSearch();
}
const filterText = ref(undefined)
const treeRef = ref(null)
watch(filterText, (val) => {
treeRef.value.filter(val)
})
const filterNode = (value, data) => {
if (!value) return true
if (data.contentName) return data.contentName.includes(value)
if (data.dataAstCnName) return data.dataAstCnName.includes(value)
}
//
const isRoot = (data) => data.contentOnum === 1
//
const isCollectionDirectory = (data) => data.contentName === '临时'
const isCollection = (data) => {
return false
}
//
const isDirectory = (data) => data.contentOnum && !isRoot(data) && !data.astOnum
//
const isAsset = (data) => data.astOnum
async function getList() {
loading.value = true
@ -211,9 +583,7 @@ function openAddDialog() {
title.value = '新增标签信息'
Object.assign(form, {
clasOnum: null,
clasPriClas: '',
clasScdClas: '',
clasThreClas: '',
clasName: '',
clasTmpl: '',
clasEffFlag: '',
@ -298,3 +668,41 @@ onMounted(() => {
})
</script>
<style lang="scss" scoped>
.tree-box {
overflow: auto;
}
.tree {
margin-top: 10px;
min-width: 260px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.tree-node__action {
padding: 0 8px;
display: flex;
justify-content: flex-end;
}
}
:deep(
.el-descriptions__body
.el-descriptions__table.is-bordered
.el-descriptions__cell
) {
width: 80px !important;
}
.faq {
white-space: pre-wrap;
}
iframe {
border: none;
}
</style>

Loading…
Cancel
Save