Browse Source

智能问答

master
xueyinfei 2 months ago
parent
commit
b789d32e0c
  1. 147
      vue-fastapi-backend/module_admin/controller/data_asset_controller.py
  2. 166
      vue-fastapi-backend/module_admin/dao/data_asset_dao.py
  3. 12
      vue-fastapi-backend/module_admin/dao/data_ast_content_dao.py
  4. 54
      vue-fastapi-backend/module_admin/entity/do/data_ast_content_do.py
  5. 96
      vue-fastapi-backend/module_admin/entity/vo/data_asset_vo.py
  6. 176
      vue-fastapi-backend/module_admin/service/data_asset_service.py
  7. 2
      vue-fastapi-backend/module_admin/service/data_ast_content_service.py
  8. 2
      vue-fastapi-backend/server.py
  9. 1
      vue-fastapi-frontend/package.json
  10. 7
      vue-fastapi-frontend/src/api/aichat/aichat.js
  11. 25
      vue-fastapi-frontend/src/api/dataAsset/assetDetail.js
  12. 6
      vue-fastapi-frontend/src/components/codemirror/SQLCodeMirror.vue
  13. 2
      vue-fastapi-frontend/src/utils/request.js
  14. 7
      vue-fastapi-frontend/src/views/aichat/MdRenderer.vue
  15. 2
      vue-fastapi-frontend/src/views/aichat/aichat.vue
  16. 568
      vue-fastapi-frontend/src/views/dataAsset/assetDetail/index.vue
  17. 66
      vue-fastapi-frontend/src/views/dataint/dataquery/DataQueryButton.vue
  18. 534
      vue-fastapi-frontend/src/views/dataint/dataquery/dataquerychat.vue
  19. 414
      vue-fastapi-frontend/src/views/dataint/dataquery/index.vue
  20. 5
      vue-fastapi-frontend/src/views/meta/metaInfo/index.vue
  21. 6
      vue-fastapi-frontend/vite.config.js

147
vue-fastapi-backend/module_admin/controller/data_asset_controller.py

@ -0,0 +1,147 @@
from fastapi import APIRouter, Depends, Form, Request
from sqlalchemy.ext.asyncio import AsyncSession
from config.get_db import get_db
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
from module_admin.service.login_service import LoginService
from module_admin.service.data_asset_service import DataAssetService
from module_admin.entity.vo.data_asset_vo import DataAssetBatchModel, DataAssetPageQueryModel, DataAssetResultModel, DataAssetSearchModel
from utils.log_util import logger
from utils.page_util import PageResponseModel
from utils.response_util import ResponseUtil
from typing import List
from config.enums import BusinessType
from utils.common_util import bytes2file_response
from module_admin.annotation.log_annotation import Log
dataAssetController = APIRouter(prefix='/system/dataAsset')
@dataAssetController.get('/list', response_model=PageResponseModel)
async def get_data_asset_list(
request: Request,
data_asset_page_query: DataAssetPageQueryModel = Depends(DataAssetPageQueryModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
"""
获取数据资产信息列表
:param request: 请求对象
:param data_asset_page_query: 查询参数
:param query_db: 数据库会话
:return: 数据资产信息列表
"""
# 获取分页数据
data_asset_page_query_result = await DataAssetService.get_data_asset_list_services(
query_db, data_asset_page_query, is_page=True
)
logger.info('获取数据资产信息列表成功')
return ResponseUtil.success(model_content=data_asset_page_query_result)
@dataAssetController.post('/batch')
async def batch_process_data_asset(
request: Request,
batch_data: DataAssetBatchModel,
query_db: AsyncSession = Depends(get_db),
):
"""
批量处理数据资产信息新增/修改/删除
:param request: 请求对象
:param batch_data: 批量处理数据
:param query_db: 数据库会话
:return: 处理结果
"""
try:
# 批量处理数据资产
result = await DataAssetService.batch_process_data_asset_services(query_db, batch_data)
# 记录处理结果
logger.info(f'批量处理数据资产成功,成功: {result.success_count},失败: {result.failed_count}')
# 返回处理结果
return ResponseUtil.success(data=result)
except Exception as e:
logger.error(f'批量处理数据资产失败: {str(e)}')
return ResponseUtil.error(msg=f"处理失败: {str(e)}")
@dataAssetController.get('/sources', response_model=List[str])
async def get_data_asset_sources(
request: Request,
query_db: AsyncSession = Depends(get_db),
):
"""
获取所有数据资产来源列表
:param request: 请求对象
:param query_db: 数据库会话
:return: 数据资产来源列表
"""
sources = await DataAssetService.get_data_asset_sources_services(query_db)
logger.info('获取数据资产来源列表成功')
return ResponseUtil.success(data=sources)
@dataAssetController.get('/search', response_model=PageResponseModel)
async def search_data_assets(
request: Request,
search_params: DataAssetSearchModel = Depends(DataAssetSearchModel.as_query),
query_db: AsyncSession = Depends(get_db),
):
"""
综合查询数据资产信息
:param request: 请求对象
:param search_params: 查询参数
:param query_db: 数据库会话
:return: 查询结果
"""
# 将查询参数转换为字典,剔除None值
params_dict = {k: v for k, v in search_params.model_dump().items()
if v is not None and k not in ['page_num', 'page_size']}
# 执行查询
search_result = await DataAssetService.search_data_assets_services(
query_db,
params_dict,
search_params.page_num,
search_params.page_size
)
logger.info('综合查询数据资产信息成功')
return ResponseUtil.success(model_content=search_result)
# @dataAssetController.get('/search', response_model=PageResponseModel)
# async def search_data_assets(
# request: Request,
# search_params: DataAssetSearchModel = Depends(DataAssetSearchModel.as_query),
# query_db: AsyncSession = Depends(get_db),
# ):
@dataAssetController.post("/export")
@Log(title="数据资产", business_type=BusinessType.EXPORT)
async def export_data_assets(
request: Request,
data_asset_query: DataAssetPageQueryModel = Form(),
query_db: AsyncSession = Depends(get_db),
):
"""
导出数据资产信息
:param request: 请求对象
:param data_asset_query: 查询参数
:param query_db: 数据库会话
:return: Excel文件流
"""
data_assets = await DataAssetService.get_data_asset_list_services(
query_db, data_asset_query, is_page=False
)
excel_data = await DataAssetService.export_data_asset_list_services(data_assets)
logger.info('导出成功')
return ResponseUtil.streaming(data=bytes2file_response(excel_data))

166
vue-fastapi-backend/module_admin/dao/data_asset_dao.py

@ -0,0 +1,166 @@
from sqlalchemy import select, update, delete, insert
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.entity.do.data_ast_content_do import DataAssetInfo
from module_admin.entity.vo.data_asset_vo import DataAssetItemModel, DataAssetPageQueryModel
from utils.page_util import PageUtil
from typing import List, Dict, Any
class DataAssetDao:
"""
数据资产信息模块数据库操作层
"""
@classmethod
async def get_data_asset_list(cls, db: AsyncSession, query_object: DataAssetPageQueryModel, is_page: bool = False):
"""
根据查询参数获取数据资产信息列表
:param db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 数据资产信息列表对象
"""
query = (
select(DataAssetInfo)
.where(
DataAssetInfo.ast_no == query_object.ast_no if query_object.ast_no else True,
DataAssetInfo.data_ast_eng_name.like(f'%{query_object.data_ast_eng_name}%') if query_object.data_ast_eng_name else True,
DataAssetInfo.data_ast_cn_name.like(f'%{query_object.data_ast_cn_name}%') if query_object.data_ast_cn_name else True,
DataAssetInfo.data_ast_type == query_object.data_ast_type if query_object.data_ast_type else True,
DataAssetInfo.data_ast_stat == query_object.data_ast_stat if query_object.data_ast_stat else True,
DataAssetInfo.data_ast_src == query_object.data_ast_src if query_object.data_ast_src else True,
)
.order_by(DataAssetInfo.data_ast_estb_time.desc())
)
data_asset_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
return data_asset_list
@classmethod
async def get_data_asset_by_ast_no(cls, db: AsyncSession, ast_no: str):
"""
根据资产编号获取数据资产信息
:param db: orm对象
:param ast_no: 资产编号
:return: 数据资产信息对象
"""
data_asset = (
await db.execute(select(DataAssetInfo).where(DataAssetInfo.ast_no == ast_no))
).scalars().first()
return data_asset
@classmethod
async def insert_data_asset(cls, db: AsyncSession, data_asset: Dict[str, Any]):
"""
插入数据资产信息
:param db: orm对象
:param data_asset: 数据资产信息字典
:return: 插入的数据资产对象
"""
db_data_asset = DataAssetInfo(**data_asset)
db.add(db_data_asset)
await db.flush()
return db_data_asset
@classmethod
async def update_data_asset(cls, db: AsyncSession, ast_no: str, data_asset: Dict[str, Any]):
"""
更新数据资产信息
:param db: orm对象
:param ast_no: 资产编号
:param data_asset: 数据资产信息字典
:return: 更新结果
"""
result = await db.execute(
update(DataAssetInfo)
.where(DataAssetInfo.ast_no == ast_no)
.values(**data_asset)
)
return result.rowcount
@classmethod
async def delete_data_asset(cls, db: AsyncSession, ast_no: str):
"""
删除数据资产信息
:param db: orm对象
:param ast_no: 资产编号
:return: 删除结果
"""
result = await db.execute(
delete(DataAssetInfo)
.where(DataAssetInfo.ast_no == ast_no)
)
return result.rowcount
@classmethod
async def get_data_asset_sources(cls, db: AsyncSession):
"""
获取所有数据资产来源列表
:param db: orm对象
:return: 数据资产来源列表
"""
# 使用distinct查询不重复的数据资产来源
result = await db.execute(
select(DataAssetInfo.data_ast_src)
.distinct()
.where(DataAssetInfo.data_ast_src.is_not(None))
.order_by(DataAssetInfo.data_ast_src)
)
sources = result.scalars().all()
return sources
@classmethod
async def search_data_assets(cls, db: AsyncSession, search_params: dict, is_page: bool = False, page_num: int = 1, page_size: int = 10):
"""
综合查询数据资产信息
:param db: orm对象
:param search_params: 查询参数字典
:param is_page: 是否分页
:param page_num: 页码
:param page_size: 每页大小
:return: 查询结果
"""
query = select(DataAssetInfo)
# 构建查询条件
conditions = []
# 名称查询(支持英文名称和中文名称)
if search_params.get('name'):
name_keyword = f"%{search_params['name']}%"
conditions.append(
(DataAssetInfo.data_ast_eng_name.like(name_keyword)) |
(DataAssetInfo.data_ast_cn_name.like(name_keyword))
)
# 数据资产类型
if search_params.get('data_ast_type'):
conditions.append(DataAssetInfo.data_ast_type == search_params['data_ast_type'])
# 资产应用场景
if search_params.get('data_ast_screen'):
conditions.append(DataAssetInfo.data_ast_screen == search_params['data_ast_screen'])
# 数据资产标签
if search_params.get('data_ast_clas'):
conditions.append(DataAssetInfo.data_ast_clas.like(f"%{search_params['data_ast_clas']}%"))
# 数据资产来源
if search_params.get('data_ast_src'):
conditions.append(DataAssetInfo.data_ast_src == search_params['data_ast_src'])
# 将所有条件添加到查询
for condition in conditions:
query = query.where(condition)
# 排序
query = query.order_by(DataAssetInfo.data_ast_estb_time.desc())
# 分页查询
result = await PageUtil.paginate(db, query, page_num, page_size, is_page)
return result

12
vue-fastapi-backend/module_admin/dao/data_ast_content_dao.py

@ -4,7 +4,7 @@ from sqlalchemy.orm import aliased
from sqlalchemy import delete, func, not_, select, update, or_, and_, desc from sqlalchemy import delete, func, not_, select, update, or_, and_, desc
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from module_admin.entity.do.data_ast_content_do import DataAstContent,DataAstContentRela,DataAstInfo,DataAstBookmarkRela,DataAstIndx from module_admin.entity.do.data_ast_content_do import DataAstContent,DataAstContentRela,DataAssetInfo,DataAstBookmarkRela,DataAstIndx
from module_admin.entity.do.user_do import SysUser from module_admin.entity.do.user_do import SysUser
from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel,DataCatalogChild,DataAstBookmarkRelaRequest,DataAstIndxRequest,DataAstIndxResponse from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel,DataCatalogChild,DataAstBookmarkRelaRequest,DataAstIndxRequest,DataAstIndxResponse
from utils.page_util import PageUtil from utils.page_util import PageUtil
@ -151,7 +151,7 @@ class DataCatalogDAO:
async def get_catalog_list(cls, db: AsyncSession, query_object: DataCatalogPageQueryModel, user_id: int, user_name: str, is_page: bool = False): async def get_catalog_list(cls, db: AsyncSession, query_object: DataCatalogPageQueryModel, user_id: int, user_name: str, is_page: bool = False):
# 创建别名对象 # 创建别名对象
t1 = aliased(DataAstContentRela, name='t1') t1 = aliased(DataAstContentRela, name='t1')
t2 = aliased(DataAstInfo, name='t2') t2 = aliased(DataAssetInfo, name='t2')
t3 = aliased(DataAstBookmarkRela, name='t3') t3 = aliased(DataAstBookmarkRela, name='t3')
# 构建子查询1(对应subquery_t1) # 构建子查询1(对应subquery_t1)
@ -447,7 +447,7 @@ class DataCatalogDAO:
:return: 去重后的数据资产树数据 :return: 去重后的数据资产树数据
""" """
# 创建别名对象 # 创建别名对象
a = aliased(DataAstInfo, name='a') a = aliased(DataAssetInfo, name='a')
b = aliased(DataAstContentRela, name='b') b = aliased(DataAstContentRela, name='b')
# 构建查询 # 构建查询
@ -714,7 +714,7 @@ class DataCatalogDAO:
""" """
# 创建别名对象 # 创建别名对象
t1 = aliased(DataAstContentRela, name='t1') t1 = aliased(DataAstContentRela, name='t1')
t2 = aliased(DataAstInfo, name='t2') t2 = aliased(DataAssetInfo, name='t2')
t3 = aliased(DataAstBookmarkRela, name='t3') t3 = aliased(DataAstBookmarkRela, name='t3')
@ -756,10 +756,10 @@ class DataCatalogDAO:
# 获取通过资产序号,获取资产编号 # 获取通过资产序号,获取资产编号
para_ast_no = ( para_ast_no = (
select( select(
DataAstInfo.ast_no DataAssetInfo.ast_no
) )
.where( .where(
DataAstInfo.data_ast_no == catalog['data_ast_no'] DataAssetInfo.data_ast_no == catalog['data_ast_no']
) )
) )

54
vue-fastapi-backend/module_admin/entity/do/data_ast_content_do.py

@ -1,6 +1,9 @@
from datetime import datetime from datetime import datetime
from sqlalchemy import Column, DateTime, Integer, String, Text, DateTime, ForeignKey, Date, Double from sqlalchemy import Column, DateTime, Integer, String, Text, DateTime, ForeignKey, Date, Double
from config.database import Base from config.database import Base
from sqlalchemy.sql import func
# 定义数据资产目录表 # 定义数据资产目录表
class DataAstContent(Base): class DataAstContent(Base):
@ -34,23 +37,44 @@ class DataAstContentRela(Base):
class DataAstInfo(Base): # class DataAstInfo(Base):
__tablename__ = "t_data_ast_info" # __tablename__ = "t_data_ast_info"
data_ast_no = Column(Integer, primary_key=True, autoincrement=True, comment='数据资产编号') # data_ast_no = Column(Integer, primary_key=True, autoincrement=True, comment='数据资产编号')
data_ast_eng_name = Column(String(255), nullable=False, comment='数据资产英文名称') # data_ast_eng_name = Column(String(255), nullable=False, comment='数据资产英文名称')
data_ast_cn_name = Column(String(255), nullable=True, comment='数据资产中文名称') # data_ast_cn_name = Column(String(255), nullable=True, comment='数据资产中文名称')
data_ast_type = Column(String(50), nullable=True, comment='数据资产类型') # data_ast_type = Column(String(50), nullable=True, comment='数据资产类型')
data_ast_stat = Column(String(50), nullable=True, comment='数据资产状态') # data_ast_stat = Column(String(50), nullable=True, comment='数据资产状态')
data_ast_desc = Column(Text, nullable=True, comment='数据资产描述/说明') # data_ast_desc = Column(Text, nullable=True, comment='数据资产描述/说明')
data_ast_clas = Column(String(255), nullable=True, comment='数据资产标签') # data_ast_clas = Column(String(255), nullable=True, comment='数据资产标签')
data_ast_cont = Column(Text, nullable=True, comment='数据资产内容') # data_ast_cont = Column(Text, nullable=True, comment='数据资产内容')
data_ast_faq = Column(Text, nullable=True, comment='数据资产常见问题') # data_ast_faq = Column(Text, nullable=True, comment='数据资产常见问题')
data_ast_estb_time = Column(DateTime, default=datetime.now, comment='数据资产建立时间') # data_ast_estb_time = Column(DateTime, default=datetime.now, comment='数据资产建立时间')
data_ast_upd_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='数据资产更新时间') # data_ast_upd_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='数据资产更新时间')
data_ast_src = Column(String(255), nullable=True, comment='数据资产来源') # data_ast_src = Column(String(255), nullable=True, comment='数据资产来源')
ast_no = Column(Integer, nullable=True, comment='资产编号') # ast_no = Column(Integer, nullable=True, comment='资产编号')
class DataAssetInfo(Base):
"""
数据资产信息表
"""
__tablename__ = 't_data_ast_info'
data_ast_no = Column(Integer, primary_key=True, autoincrement=True, comment='资产序号')
data_ast_eng_name = Column(String(255), nullable=False, comment='表英文名称')
data_ast_cn_name = Column(String(255), nullable=True, comment='表中文名称')
data_ast_type = Column(String(50), nullable=True, comment='表类型(表、报表、数据应用等)')
data_ast_stat = Column(String(50), nullable=True, comment='表状态(有效/废弃)')
data_ast_desc = Column(Text, nullable=True, comment='资产描述/说明')
data_ast_screen = Column(String(255), nullable=True, comment='资产应用场景')
data_ast_scren_clas = Column(String(255), nullable=True, comment='应用场景分类')
data_ast_clas = Column(String(255), nullable=True, comment='资产标签')
data_ast_cont = Column(Text, nullable=True, comment='资产内容')
data_ast_faq = Column(Text, nullable=True, comment='资产常见问题')
data_ast_estb_time = Column(DateTime, nullable=True, server_default=func.now(), comment='资产建立时间')
data_ast_upd_time = Column(DateTime, nullable=True, server_default=func.now(), onupdate=func.now(), comment='资产更新时间')
data_ast_src = Column(String(255), nullable=True, comment='资产来源')
ast_no = Column(Integer, nullable=True, comment='资产编号')
class DataAstBookmarkRela(Base): class DataAstBookmarkRela(Base):

96
vue-fastapi-backend/module_admin/entity/vo/data_asset_vo.py

@ -0,0 +1,96 @@
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from typing import List, Optional
from module_admin.annotation.pydantic_annotation import as_query
class DataAssetItemModel(BaseModel):
"""
数据资产信息项模型
"""
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True)
ast_no: int = Field(default=None, alias="astNo", description='数据资产编号(必填)')
data_ast_eng_name: str = Field(default=None, alias="dataAstEngName", description='数据资产英文名称(必填)')
data_ast_cn_name: str = Field(default=None, alias="dataAstCnName", description='数据资产中文名称')
data_ast_type: str = Field(default=None, alias="dataAstType", description='数据资产类型(表/报表/数据应用)(必填)')
data_ast_stat: str = Field(default=None, alias="dataAstStat", description='数据资产状态(有效/废弃)(必填)')
data_ast_desc: str = Field(default=None, alias="dataAstDesc", description='数据资产描述')
data_ast_screen: str = Field(default=None, alias="dataAstScreen", description='资产应用场景(API/智能问答等)')
data_ast_scren_clas: str = Field(default=None, alias="dataAstScrenClas", description='应用场景分类')
data_ast_clas: str = Field(default=None, alias="dataAstClas", description='数据资产标签')
data_ast_cont: str = Field(default=None, alias="dataAstCont", description='数据资产内容')
data_ast_faq: str = Field(default=None, alias="dataAstFaq", description='数据资产常见问题')
data_ast_src: str = Field(default=None, alias="dataAstSrc", description='数据资产来源(必填)')
version_no: str = Field(default="1", alias="versionNo", description='版本号(默认最新)')
ctrl_flag: str = Field(default=None, alias="ctrlFlag", description='操作类型 1:插入 2:删除 3:更新')
class DataAssetBatchModel(BaseModel):
"""
数据资产批量操作模型
"""
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True)
data_assets: List[DataAssetItemModel] = Field(default=[], alias="dataAssets", description='数据资产列表')
class DataAssetResultModel(BaseModel):
"""
数据资产操作结果模型
"""
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True)
success_count: int = Field(default=0, alias="successCount", description='成功数量')
failed_count: int = Field(default=0, alias="failedCount", description='失败数量')
success_items: List[str] = Field(default=[], alias="successItems", description='成功项')
failed_items: List[dict] = Field(default=[], alias="failedItems", description='失败项及原因')
class DataAssetQueryModel(BaseModel):
"""
数据资产查询模型
"""
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True)
ast_no: Optional[str] = Field(default=None, alias="astNo", description='资产编号')
data_ast_eng_name: Optional[str] = Field(default=None, alias="dataAstEngName", description='资产英文名称')
data_ast_cn_name: Optional[str] = Field(default=None, alias="dataAstCnName", description='资产中文名称')
data_ast_type: Optional[str] = Field(default=None, alias="dataAstType", description='资产类型')
data_ast_stat: Optional[str] = Field(default=None, alias="dataAstStat", description='资产状态')
data_ast_src: Optional[str] = Field(default=None, alias="dataAstSrc", description='资产来源')
begin_time: Optional[str] = Field(default=None, alias="beginTime", description='开始时间')
end_time: Optional[str] = Field(default=None, alias="endTime", description='结束时间')
@as_query
class DataAssetPageQueryModel(DataAssetQueryModel):
"""
数据资产分页查询模型
"""
page_num: int = Field(default=1, alias="pageNum", description='当前页码')
page_size: int = Field(default=10, alias="pageSize", description='每页记录数')
@as_query
class DataAssetSearchModel(BaseModel):
"""
数据资产综合查询模型
"""
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True)
name: Optional[str] = Field(default=None, alias="name", description='资产名称(英文名或中文名)')
data_ast_type: Optional[str] = Field(default=None, alias="dataAstType", description='资产类型')
data_ast_screen: Optional[str] = Field(default=None, alias="dataAstScreen", description='资产应用场景')
data_ast_clas: Optional[str] = Field(default=None, alias="dataAstClas", description='资产标签')
data_ast_src: Optional[str] = Field(default=None, alias="dataAstSrc", description='资产来源')
page_num: int = Field(default=1, alias="pageNum", description='当前页码')
page_size: int = Field(default=10, alias="pageSize", description='每页记录数')

176
vue-fastapi-backend/module_admin/service/data_asset_service.py

@ -0,0 +1,176 @@
from sqlalchemy.ext.asyncio import AsyncSession
from module_admin.dao.data_asset_dao import DataAssetDao
from module_admin.entity.vo.data_asset_vo import DataAssetItemModel, DataAssetBatchModel, DataAssetResultModel, DataAssetPageQueryModel
from exceptions.exception import ServiceException
from typing import Dict, Any, List
from utils.common_util import export_list2excel
class DataAssetService:
"""
数据资产信息模块服务层
"""
@classmethod
async def get_data_asset_list_services(
cls, query_db: AsyncSession, query_object: DataAssetPageQueryModel, is_page: bool = False
):
"""
获取数据资产信息列表service
:param query_db: orm对象
:param query_object: 查询参数对象
:param is_page: 是否开启分页
:return: 数据资产信息列表对象
"""
data_asset_list_result = await DataAssetDao.get_data_asset_list(query_db, query_object, is_page)
return data_asset_list_result
@classmethod
async def batch_process_data_asset_services(
cls, query_db: AsyncSession, batch_object: DataAssetBatchModel
):
"""
批量处理数据资产信息service
:param query_db: orm对象
:param batch_object: 批量处理参数对象
:return: 处理结果对象
"""
result = DataAssetResultModel()
for item in batch_object.data_assets:
try:
# 转换对象为字典,并移除ctrl_flag,version_no字段
data_dict = item.model_dump()
ctrl_flag = data_dict.pop("ctrl_flag")
version_no = data_dict.pop("version_no")
# 检查数据资产是否存在
existing_asset = await DataAssetDao.get_data_asset_by_ast_no(query_db, item.ast_no)
# 根据ctrl_flag进行不同的操作
if ctrl_flag == "1": # 插入
if existing_asset:
raise ServiceException(f"数据资产编号 {item.ast_no} 已存在,无法插入")
await DataAssetDao.insert_data_asset(query_db, data_dict)
result.success_count += 1
result.success_items.append(item.ast_no)
elif ctrl_flag == "2": # 删除
if not existing_asset:
raise ServiceException(f"数据资产编号 {item.ast_no} 不存在,无法删除")
await DataAssetDao.delete_data_asset(query_db, item.ast_no)
result.success_count += 1
result.success_items.append(item.ast_no)
elif ctrl_flag == "3": # 更新
if not existing_asset:
raise ServiceException(f"数据资产编号 {item.ast_no} 不存在,无法更新")
await DataAssetDao.update_data_asset(query_db, item.ast_no, data_dict)
result.success_count += 1
result.success_items.append(item.ast_no)
else:
raise ServiceException(f"未知的操作类型: {ctrl_flag}")
except Exception as e:
result.failed_count += 1
result.failed_items.append({"ast_no": item.ast_no, "error": str(e)})
# 提交事务
await query_db.commit()
return result
@classmethod
async def get_data_asset_sources_services(cls, query_db: AsyncSession):
"""
获取所有数据资产来源service
:param query_db: orm对象
:return: 数据资产来源列表
"""
sources = await DataAssetDao.get_data_asset_sources(query_db)
# 构造父节点
formatted_data = [
{
"name": "表数据资产",
"children": [
{
"id": str(index), # 转换为字符串类型id
"name": source # 原始来源名称
}
for index, source in enumerate(sources, 1) # 从1开始编号
]
},
{
"name": "接口数据资产",
"children": []
},
{
"name": "数据治理资产",
"children": []
}
]
return formatted_data
@classmethod
async def search_data_assets_services(
cls, query_db: AsyncSession, search_params: dict, page_num: int = 1, page_size: int = 10, is_page: bool = True
):
"""
综合查询数据资产信息service
:param query_db: orm对象
:param search_params: 查询参数字典
:param page_num: 页码
:param page_size: 每页大小
:param is_page: 是否分页
:return: 查询结果
"""
search_result = await DataAssetDao.search_data_assets(
query_db, search_params, is_page, page_num, page_size
)
return search_result
@staticmethod
async def export_data_asset_list_services(data_assets_list: list):
mapping_dict = {
"astNo": "资产编号",
"dataAstEngName": "表英文名称",
"dataAstCnName": "表中文名称",
"dataAstType": "表类型",
"dataAstStat": "表状态",
"dataAstDesc": "表描述",
"dataAstScreen": "资产应用场景",
"dataAstScrenClas": "应用场景分类",
"dataAstClas": "资产标签",
"dataAstCont": "资产内容",
"dataAstFaq": "资产常见问题",
"dataAstSrc": "资产来源",
"dataAstEstbTime": "资产建立时间",
"dataAstUpdTime": "资产更新时间"
}
# 1. 获取映射关系的键顺序(确保列顺序一致)
keys_order = list(mapping_dict.keys())
new_data = []
for item in data_assets_list:
item_dict = item.__dict__ if not isinstance(item, dict) else item
cleaned_item = {k: (v if v is not None else "") for k, v in item_dict.items()}
# 处理状态字段
cleaned_item["dataAstStat"] = "有效" if cleaned_item.get("dataAstStat") == "有效" else "废弃"
# 按mapping_dict的键顺序构建数据
mapped_item = {
mapping_dict[key]: cleaned_item.get(key, "")
for key in keys_order
}
new_data.append(mapped_item)
# 直接调用export_list2excel,只传入数据参数
return export_list2excel(new_data)

2
vue-fastapi-backend/module_admin/service/data_ast_content_service.py

@ -669,7 +669,7 @@ class DataCatalogService:
indx_list_dict = await DataCatalogDAO.get_data_ast_indx_list(query_db, query_object) indx_list_dict = await DataCatalogDAO.get_data_ast_indx_list(query_db, query_object)
indx_names = [item["indx_name"] for item in indx_list_dict] indx_names = [item["indx_name"] for item in indx_list_dict]
indx_vals = [item["indx_val"] for item in indx_list_dict] indx_vals = [item["indx_val"] for item in indx_list_dict]
CurrentConfig.ONLINE_HOST = "/assets/js/echarts.min.js" CurrentConfig.ONLINE_HOST = "/assets/js/"
# 创建独立图表 # 创建独立图表
pie = ( pie = (

2
vue-fastapi-backend/server.py

@ -34,6 +34,7 @@ from module_admin.controller.fccbd_controller import fccbdController
from module_admin.controller.cdplb_controller import cdplbController from module_admin.controller.cdplb_controller import cdplbController
from module_admin.controller.sscf_controller import sscfController from module_admin.controller.sscf_controller import sscfController
from module_admin.controller.vecset_controller import vecsetController from module_admin.controller.vecset_controller import vecsetController
from module_admin.controller.data_asset_controller import dataAssetController
from sub_applications.handle import handle_sub_applications from sub_applications.handle import handle_sub_applications
from utils.common_util import worship from utils.common_util import worship
from utils.log_util import logger from utils.log_util import logger
@ -101,6 +102,7 @@ controller_list = [
{'router': cdplbController, 'tags': ['智能数据-词典批量补充']}, {'router': cdplbController, 'tags': ['智能数据-词典批量补充']},
{'router': sscfController, 'tags': ['智能数据-短句配置']}, {'router': sscfController, 'tags': ['智能数据-短句配置']},
{'router': vecsetController, 'tags': ['智能数据-全句配置']}, {'router': vecsetController, 'tags': ['智能数据-全句配置']},
{'router': dataAssetController, 'tags': ['系统管理-数据资产详情']},
] ]
for controller in controller_list: for controller in controller_list:

1
vue-fastapi-frontend/package.json

@ -29,6 +29,7 @@
"@vueuse/core": "10.11.0", "@vueuse/core": "10.11.0",
"ant-design-vue": "^4.1.1", "ant-design-vue": "^4.1.1",
"axios": "0.28.1", "axios": "0.28.1",
"codemirror": "^5.65.19",
"codemirror-editor-vue3": "^2.8.0", "codemirror-editor-vue3": "^2.8.0",
"echarts": "5.5.1", "echarts": "5.5.1",
"element-plus": "2.8.0", "element-plus": "2.8.0",

7
vue-fastapi-frontend/src/api/aichat/aichat.js

@ -48,3 +48,10 @@ export async function addChat(data) {
data: data data: data
}) })
} }
export async function postDataQuery(data) {
return request({
url: '/dataquery-api/chat/nl2sql_client_chat',
method: 'post',
data: data
})
}

25
vue-fastapi-frontend/src/api/dataAsset/assetDetail.js

@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getSearch(params) {
return request({
url: '/default-api/system/dataAsset/search',
method: 'get',
params,
})
}
export function batch(data) {
return request({
url: '/default-api/system/dataAsset/batch',
method: 'post',
data,
})
}
export function deptTreeSelect() {
return request({
url: '/default-api/system/dataAsset/sources',
method: 'get'
})
}

6
vue-fastapi-frontend/src/components/codemirror/SQLCodeMirror.vue

@ -59,11 +59,15 @@ const sqlOptions = {
lint: true, // json lint: true, // json
} }
const value = ref("") const value = ref("")
watch(() => props.data, watch(() => value.value,
(val) =>{ (val) =>{
value.value = sqlFormatter.format(val) value.value = sqlFormatter.format(val)
} }
) )
onMounted(()=>{
value.value = props.data
}
)
</script> </script>

2
vue-fastapi-frontend/src/utils/request.js

@ -17,7 +17,7 @@ const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分 // axios中请求配置有baseURL选项,表示请求URL公共部分
// baseURL: import.meta.env.VITE_APP_BASE_API, // baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时 // 超时
timeout: 10000 timeout: 100000
}) })
// request拦截器 // request拦截器

7
vue-fastapi-frontend/src/views/aichat/MdRenderer.vue

@ -22,6 +22,7 @@
</template> </template>
<script setup> <script setup>
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
import markdown from './markdown.vue' import markdown from './markdown.vue'
import antvg6 from './antv-g6.vue' import antvg6 from './antv-g6.vue'
import chatTable from './chatTable.vue' import chatTable from './chatTable.vue'
@ -42,13 +43,13 @@ function fullscreenG6(data){
function downLoadTable(data){ function downLoadTable(data){
const worksheet = XLSX.utils.json_to_sheet(data); const worksheet = XLSX.utils.json_to_sheet(data);
// 簿簿 // 簿簿
const workbook = XLSX.utils.book_new(); const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
// Excel // Excel
XLSX.writeFile(workbook, '导出数据.xlsx'); const excelBuffer = XLSX.write(workbook,{bookType:'xlsx',type:'array'});
const excel = new Blob([excelBuffer], { type: 'application/octet-stream' });
saveAs(excel, '导出文件.xlsx');
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

2
vue-fastapi-frontend/src/views/aichat/aichat.vue

@ -409,7 +409,7 @@ function formatDate(date) {
function downloadFile(file,bucket,sessionId){ function downloadFile(file,bucket,sessionId){
let data = {file,bucket,sessionId} let data = {file,bucket,sessionId}
proxy.download("aichat/file/download", { proxy.download("/default-api/aichat/file/download", {
...data, ...data,
}, file); }, file);
} }

568
vue-fastapi-frontend/src/views/dataAsset/assetDetail/index.vue

@ -3,82 +3,57 @@
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="5"> <el-col :span="5">
<el-card shadow="never"> <el-card shadow="never">
<el-input <el-input v-model="filterText" style="width: 100%" placeholder="搜索系统名称">
v-model="filterText"
style="width: 100%"
placeholder="搜索系统名称"
>
<template #prefix> <template #prefix>
<el-icon><Search /></el-icon> <el-icon>
<Search />
</el-icon>
</template> </template>
</el-input> </el-input>
<el-tree <el-tree style="margin-top: 10px" ref="treeRef" node-key="id" :default-expand-all="true"
style="margin-top: 10px" :highlight-current="true" :expand-on-click-node="false" :data="treeData" :props="defaultProps"
ref="treeRef" :filter-node-method="filterNode" :current-node-key="currentNode.id" @node-click="handleNodeClick">
node-key="id" <!-- <template #default="{ node, data }">
:default-expand-all="true"
:highlight-current="true"
:expand-on-click-node="false"
:data="treeData"
:props="defaultProps"
:filter-node-method="filterNode"
:current-node-key="currentNode.id"
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<el-space :size="2"> <el-space :size="2">
<el-icon v-if="!data.isLeaf"><Folder /></el-icon> <el-icon v-if="!data.isLeaf">
<el-icon v-else><Document /></el-icon> <Folder />
</el-icon>
<el-icon v-else>
<Document />
</el-icon>
<span>{{ node.label }}</span> <span>{{ node.label }}</span>
</el-space> </el-space>
</template> </template> -->
</el-tree> </el-tree>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="19"> <el-col :span="19">
<el-form <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
:model="queryParams" <el-form-item label="名称" prop="name">
ref="queryRef" <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 160px"
:inline="true" @keyup.enter="handleQuery" />
v-show="showSearch" </el-form-item>
label-width="68px" <el-form-item label="类型" prop="dataAstType">
> <el-select v-model="queryParams.dataAstType" placeholder="请选择" clearable style="width: 160px">
<el-form-item label="名称"> <el-option v-for="(item, index) in dataAstType_list" :key="index" :label="item" :value="item" />
<el-input </el-select>
v-model="queryParams.name"
placeholder="请输入名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="类型"> <el-form-item label="标签" prop="dataAstClas">
<el-input <el-input v-model="queryParams.dataAstClas" placeholder="请输入" clearable style="width: 160px"
v-model="queryParams.name" @keyup.enter="handleQuery" />
placeholder="请输入类型"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="标签"> <el-form-item label="应用场景" prop="dataAstScreen">
<el-input <el-select v-model="queryParams.dataAstScreen" placeholder="请选择" clearable style="width: 160px">
v-model="queryParams.name" <el-option v-for="(item, index) in dataAstScreen_list" :key="index" :label="item" :value="item" />
placeholder="请输入标签" </el-select>
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery" <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
>搜索</el-button
>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button <el-button
type="primary" type="primary"
plain plain
@ -87,219 +62,173 @@
v-hasPermi="['dataAsset:assetDetail:add']" v-hasPermi="['dataAsset:assetDetail:add']"
>新增</el-button >新增</el-button
> >
</el-col> </el-col> -->
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
type="success" v-hasPermi="['dataAsset:assetDetail:edit']">修改</el-button>
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['dataAsset:assetDetail:edit']"
>修改</el-button
>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
type="danger" v-hasPermi="['dataAsset:assetDetail:remove']">删除</el-button>
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['dataAsset:assetDetail:remove']"
>删除</el-button
>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="warning" plain icon="Download" @click="handleExport"
type="warning" v-hasPermi="['dataAsset:assetDetail:export']">导出</el-button>
plain
icon="Download"
@click="handleExport"
v-hasPermi="['dataAsset:assetDetail:export']"
>导出</el-button
>
</el-col> </el-col>
<right-toolbar <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
v-model:showSearch="showSearch"
@queryTable="getList"
:columns="columns"
></right-toolbar>
</el-row> </el-row>
<el-table <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
v-loading="loading"
:data="list"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" align="center" /> <el-table-column type="selection" width="50" align="center" />
<el-table-column <el-table-column label="资产编号" align="center" key="" prop="astNo" v-if="columns[0].visible" />
label="编号" <el-table-column label="表来源系统" align="center" width="150" key="" prop="dataAstSrc" v-if="columns[1].visible" />
align="center" <el-table-column label="表英文名称" align="center" width="200" key="" prop="dataAstEngName" v-if="columns[2].visible" />
key="" <el-table-column label="表中文名称" align="center" width="200" key="" prop="dataAstCnName" v-if="columns[3].visible" />
prop="" <el-table-column label="表类型" align="center" width="100" key="" prop="dataAstType" v-if="columns[4].visible" />
v-if="columns[0].visible" <el-table-column label="资产标签" align="center" width="150" key="" prop="dataAstClas" v-if="columns[5].visible" />
/> <el-table-column label="资产描述" align="center" width="150" key="" prop="dataAstDesc" v-if="columns[6].visible" />
<el-table-column <el-table-column label="资产应用场景" align="center" width="150" key="" prop="dataAstScreen" v-if="columns[7].visible" />
label="来源系统" <el-table-column label="应用场景分类" align="center" width="150" key="" prop="dataAstScrenClas" v-if="columns[8].visible" />
align="center" <el-table-column label="常见问题" align="center" width="150" key="" prop="dataAstFaq" v-if="columns[9].visible" />
key="" <el-table-column label="负责人" align="center" width="150" key="" prop="dataAstCont" v-if="columns[10].visible" />
prop="" <el-table-column label="建立时间" align="center" width="200" key="" prop="dataAstEstbTime" v-if="columns[11].visible" />
v-if="columns[1].visible" <el-table-column label="更新时间" align="center" width="200" key="" prop="dataAstUpdTime" v-if="columns[12].visible" />
/> <el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width">
<el-table-column
label="英文名称"
align="center"
key=""
prop=""
v-if="columns[2].visible"
/>
<el-table-column
label="中文名称"
align="center"
key=""
prop=""
v-if="columns[3].visible"
/>
<el-table-column
label="类型"
align="center"
key=""
prop=""
v-if="columns[4].visible"
/>
<el-table-column
label="描述"
align="center"
key=""
prop=""
v-if="columns[5].visible"
/>
<el-table-column
label="标签"
align="center"
key=""
prop=""
v-if="columns[6].visible"
/>
<el-table-column
label="负责人"
align="center"
key=""
prop=""
v-if="columns[7].visible"
/>
<el-table-column
label="建立时间"
align="center"
key=""
prop=""
v-if="columns[8].visible"
/>
<el-table-column
label="更新时间"
align="center"
key=""
prop=""
v-if="columns[9].visible"
/>
<el-table-column
label="操作"
align="center"
width="100"
class-name="small-padding fixed-width"
>
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
<el-button <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
link v-hasPermi="['dataAsset:assetDetail:edit']"></el-button>
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['dataAsset:assetDetail:edit']"
></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
link v-hasPermi="['dataAsset:assetDetail:remove']"></el-button>
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['dataAsset:assetDetail:remove']"
></el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-show="total > 0" v-model:limit="queryParams.pageSize" @pagination="getList" />
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-col> </el-col>
</el-row> </el-row>
<!-- -->
<el-dialog title="编辑资产" v-model="open" width="600px" append-to-body>
<el-form :model="form" :rules="rules" ref="assetRef" label-width="120px">
<el-form-item label="资产编号" prop="astNo">
<el-input v-model="form.astNo" placeholder="请输入" />
</el-form-item>
<el-form-item label="资产英文名称" prop="dataAstEngName">
<el-input v-model="form.dataAstEngName" placeholder="请输入" />
</el-form-item>
<el-form-item label="资产中文名称" prop="dataAstCnName">
<el-input v-model="form.dataAstCnName" placeholder="请输入" />
</el-form-item>
<el-form-item label="资产类型" prop="dataAstType">
<el-select v-model="form.dataAstType" placeholder="请选择" clearable style="width: 100%">
<el-option label="表" value="表" />
<el-option label="报表" value="报表" />
</el-select>
</el-form-item>
<el-form-item label="资产状态" prop="dataAstStat">
<el-select v-model="form.dataAstStat" placeholder="请选择" clearable style="width: 100%">
<el-option label="有效" value="1" />
<el-option label="作废" value="0" />
</el-select>
</el-form-item>
<el-form-item label="资产应用场景" prop="dataAstScreen">
<el-select v-model="form.dataAstScreen" placeholder="请选择" clearable style="width: 100%">
<el-option label="智能助手" value="智能助手" />
<el-option label="API" value="API" />
</el-select>
</el-form-item>
<el-form-item label="应用场景分类" prop="dataAstScrenClas">
<el-select v-model="form.dataAstScrenClas" placeholder="请选择" clearable style="width: 100%">
<el-option v-for="(item, index) in dataAstScrenClas_list" :key="index" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="资产标签" prop="dataAstClas">
<el-input v-model="form.dataAstClas" placeholder="请输入" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="资产描述" prop="dataAstDesc">
<el-input v-model="form.dataAstDesc" placeholder="请输入" />
</el-form-item>
<el-form-item label="资产内容" prop="dataAstCont">
<el-input v-model="form.dataAstCont" placeholder="请输入" />
</el-form-item>
<el-form-item label="资产常见问题" prop="dataAstFaq">
<el-input v-model="form.dataAstFaq" placeholder="请输入" />
</el-form-item>
<el-form-item label="数据资产来源" prop="dataAstSrc">
<el-tree-select v-model="form.dataAstSrc" :data="treeDataChildren"
:props="{ value: 'name', label: 'name', children: 'children' }" value-key="id" placeholder="请选择"
check-strictly />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup name="AssetDetail"> <script setup name="AssetDetail">
import { getSearch, batch, deptTreeSelect } from "@/api/dataAsset/assetDetail";
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
} }
const currentNode = ref({}) const currentNode = ref({})
const treeData = ref([ const treeData = ref([])
{ const treeDataChildren = ref([]);
catalogId: '-1',
name: '系统',
isLeaf: 0,
children: [
{
parentId: '-1',
id: '1',
name: 'wind',
isLeaf: 1,
},
{
parentId: '-1',
id: '2',
name: 'O32',
isLeaf: 1,
},
],
},
])
const handleNodeClick = (data) => { /** 查询部门下拉树结构 */
currentNode.value = data function getDeptTree() {
} deptTreeSelect().then(response => {
treeData.value = response.data;
if (response.data && response.data[0] && response.data[0].children) {
treeDataChildren.value = response.data[0].children
}
});
};
/** 节点单击事件 */
function handleNodeClick(data) {
queryParams.value.dataAstSrc = data.name;
handleQuery();
};
const filterText = ref('')
const filterText = ref(undefined)
const treeRef = ref(null)
watch(filterText, (val) => {
treeRef.value.filter(val)
})
/** 根据名称筛选部门树 */
watch(filterText, val => {
proxy.$refs["treeRef"].filter(val);
});
/** 通过条件过滤节点 */
const filterNode = (value, data) => { const filterNode = (value, data) => {
if (!value) { if (!value) return true;
return true return data.name.indexOf(value) !== -1;
} };
return data.catalogName.includes(value)
}
const columns = ref([ const columns = ref([
{ key: 0, label: `编号`, visible: true }, { key: 0, label: `资产编号`, visible: true },
{ key: 1, label: `来源系统`, visible: true }, { key: 1, label: `来源系统`, visible: true },
{ key: 2, label: `英文名称`, visible: true }, { key: 2, label: `英文名称`, visible: true },
{ key: 3, label: `中文名称`, visible: true }, { key: 3, label: `中文名称`, visible: true },
{ key: 4, label: `类型`, visible: true }, { key: 4, label: `类型`, visible: true },
{ key: 5, label: `描述`, visible: true }, { key: 5, label: `资产标签`, visible: true },
{ key: 6, label: `标签`, visible: true }, { key: 6, label: `资产描述`, visible: true },
{ key: 7, label: `负责人`, visible: true }, { key: 7, label: `资产应用场景`, visible: true },
{ key: 8, label: `建立时间`, visible: true }, { key: 8, label: `应用场景分类`, visible: true },
{ key: 9, label: `更新时间`, visible: true }, { key: 9, label: `常见问题`, visible: true },
{ key: 10, label: `负责人`, visible: true },
{ key: 11, label: `建立时间`, visible: true },
{ key: 12, label: `更新时间`, visible: true },
]) ])
const loading = ref(false) const loading = ref(false)
const list = ref([]) const list = ref([])
@ -309,28 +238,191 @@ const queryParams = ref({
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
dataAstType: undefined,
dataAstClas: undefined,
dataAstScreen: undefined,
dataAstSrc: undefined,
}) })
const getList = () => {} const getList = () => {
loading.value = true;
getSearch(proxy.addDateRange(queryParams.value)).then(res => {
loading.value = false;
list.value = res.rows;
total.value = res.total;
});
}
const handleQuery = () => { const handleQuery = () => {
queryParams.value.pageNum = 1 queryParams.value.pageNum = 1
getList() getList()
} }
const resetQuery = () => { const resetQuery = () => {
queryParams.value.dataAstSrc = undefined
proxy.resetForm('queryRef') proxy.resetForm('queryRef')
proxy.$refs.treeRef.setCurrentKey(null);
handleQuery() handleQuery()
} }
const handleAdd = () => { }
const handleUpdate = (row) => {
open.value = true;
proxy.resetForm("assetRef");
form.value = row || ids.value[0];
}
const handleDelete = (row) => {
const asset = row ? [row] : ids.value;
let submitasset = [];
asset.map(item => {
item.ctrlFlag = "2"
const handleAdd = () => {} submitasset.push(
const handleUpdate = (row) => {} {
const handleDelete = (row) => {} "astNo": String(item.astNo),
const handleExport = () => {} "dataAstEngName": item.dataAstEngName,
"dataAstCnName": item.dataAstCnName,
"dataAstType": item.dataAstType,
"dataAstStat": item.dataAstStat,
"dataAstDesc": item.dataAstDesc,
"dataAstScreen": item.dataAstScreen,
"dataAstScrenClas": item.dataAstScrenClas,
"dataAstClas": item.dataAstClas?item.dataAstClas:"",
"dataAstCont": item.dataAstCont,
"dataAstFaq": item.dataAstFaq,
"dataAstSrc": item.dataAstSrc,
"versionNo": item.versionNo,
"ctrlFlag": item.ctrlFlag,
}
)
})
proxy.$modal.confirm('是否确认删除?').then(function () {
return batch({ dataAssets: submitasset });
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => { });
}
const handleExport = () => {
proxy.download("/system/dataAsset/export", {
...queryParams.value,
}, `dataAsset_${new Date().getTime()}.xlsx`);
}
const ids = ref([]) const ids = ref([])
const single = ref(true) const single = ref(true)
const multiple = ref(true) const multiple = ref(true)
const handleSelectionChange = (selection) => { const handleSelectionChange = (selection) => {
ids.value = selection.map((item) => item.userId) ids.value = selection
single.value = selection.length != 1 single.value = selection.length != 1
multiple.value = !selection.length multiple.value = !selection.length
} }
// const dataAstType_list = computed(() => {
// const unique = new Set();
// list.value.forEach(item => {
// if (item.dataAstType) {
// unique.add(item.dataAstType);
// }
// });
// return Array.from(unique);
// });
const dataAstType_list = computed(() => {
return ['表', '报表','其他应用'];
});
const dataAstClas_list = computed(() => {
const unique = new Set();
list.value.forEach(item => {
if (item.dataAstClas) {
unique.add(item.dataAstClas);
}
});
return Array.from(unique);
});
// const dataAstScreen_list = computed(() => {
// const unique = new Set();
// list.value.forEach(item => {
// if (item.dataAstScreen) {
// unique.add(item.dataAstScreen);
// }
// });
// return Array.from(unique);
// });
const dataAstScreen_list = computed(() => {
return ['智能助手', 'API'];
});
const dataAstStat_list = computed(() => {
const unique = new Set();
list.value.forEach(item => {
if (item.dataAstStat) {
unique.add(item.dataAstStat);
}
});
return Array.from(unique);
});
const dataAstScrenClas_list = computed(() => {
const unique = new Set();
list.value.forEach(item => {
if (item.dataAstScrenClas) {
unique.add(item.dataAstScrenClas);
}
});
return Array.from(unique);
});
const open = ref(false);
const data = reactive({
form: {},
rules: {
astNo: [{ required: true, message: "请输入", trigger: "blur" }],
dataAstEngName: [{ required: true, message: "请输入", trigger: "blur" }],
dataAstType: [{ required: true, message: "请选择", trigger: "blur" }],
dataAstStat: [{ required: true, message: "请选择", trigger: "blur" }],
}
});
const { form, rules } = toRefs(data);
function submitForm() {
proxy.$refs["assetRef"].validate(valid => {
if (valid) {
form.value.ctrlFlag = "3";
let submitform = {
"astNo": String(form.value.astNo),
"dataAstEngName": form.value.dataAstEngName,
"dataAstCnName": form.value.dataAstCnName,
"dataAstType": form.value.dataAstType,
"dataAstStat": form.value.dataAstStat,
"dataAstDesc": form.value.dataAstDesc,
"dataAstScreen": form.value.dataAstScreen?form.value.dataAstScreen:"",
"dataAstScrenClas": form.value.dataAstScrenClas?form.value.dataAstScrenClas:"",
"dataAstClas": form.value.dataAstClas?form.value.dataAstClas:"",
"dataAstCont": form.value.dataAstCont?form.value.dataAstCont:"",
"dataAstFaq": form.value.dataAstFaq?form.value.dataAstFaq:"",
"dataAstSrc": form.value.dataAstSrc,
"versionNo": form.value.versionNo,
"ctrlFlag": form.value.ctrlFlag,
}
batch({ dataAssets: [submitform] }).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
}
});
};
function cancel() {
open.value = false;
form.value = {};
proxy.resetForm("assetRef");
};
getDeptTree();
getList();
</script> </script>

66
vue-fastapi-frontend/src/views/dataint/dataquery/DataQueryButton.vue

@ -0,0 +1,66 @@
<template>
<div>
<el-text type="info">
<span class="ml-4" style="margin-left: 4px">{{ datetimeFormat(data.time) }}</span>
</el-text>
</div>
<div v-if="data.type === 'answer'">
<span>
<el-tooltip effect="dark" content="编辑" placement="top" popper-class="operate-tooltip">
<el-button text @click="edit" style="padding: 0">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="下载" placement="top" popper-class="operate-tooltip">
<el-button text @click="download" style="padding: 0">
<el-icon><Download /></el-icon>
</el-button>
</el-tooltip>
</span>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import Clipboard from 'vue-clipboard3'
import { datetimeFormat } from '@/utils/time'
import {updateChat} from "@/api/aichat/aichat.js";
import {EditPen} from "@element-plus/icons-vue";
const { proxy } = getCurrentInstance();
const route = useRoute()
const {
params: { id }
} = route
const props = defineProps({
data: {
type: Object,
default: () => {}
},
index: Number
})
const thumbDownReason = ref("")
const visible = ref(false)
const emit = defineEmits([ 'edit', 'download'])
function edit() {
emit('edit')
}
function download(){
emit('download')
}
</script>
<style lang="scss">
.operate-tooltip {
z-index: 99999 !important;
}
</style>

534
vue-fastapi-frontend/src/views/dataint/dataquery/dataquerychat.vue

@ -15,18 +15,26 @@
<template v-for="(item, index) in chatList" :key="index"> <template v-for="(item, index) in chatList" :key="index">
<!-- 问题 --> <!-- 问题 -->
<div v-if="item.type === 'question'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400"> <div v-if="item.type === 'question'">
<div class="avatar"> <div class="item-content mb-16 lighter" style="display:flex;justify-content:flex-end;gap:10px;margin-bottom: 16px;font-weight: 400">
<el-avatar style="width:30px;height: 30px;background: #3370FF"> <div>
<img src="@/assets/aichat/user-icon.svg" style="width: 30px" alt="" /> <div class="text break-all pre-wrap" style="word-break: break-all;white-space: pre-wrap;">
</el-avatar> {{ item.content }}
</div> </div>
<div class="content">
<div class="text break-all pre-wrap" style="word-break: break-all;white-space: pre-wrap;">
{{ item.content }}
</div> </div>
<div class="avatar">
<el-avatar style="width:30px;height: 30px;background: #3370FF">
<img src="@/assets/aichat/user-icon.svg" style="width: 30px" alt="" />
</el-avatar>
</div>
</div>
<div style="display:flex;justify-content:flex-end;gap:10px;margin-bottom: 16px;font-weight: 400">
<el-text type="info">
<span class="ml-4" style="margin-left: 4px">{{ datetimeFormat(item.time) }}</span>
</el-text>
</div> </div>
</div> </div>
<!-- 回答 --> <!-- 回答 -->
<div v-if="item.type === 'answer'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400"> <div v-if="item.type === 'answer'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400">
<div class="avatar"> <div class="avatar">
@ -34,24 +42,109 @@
</div> </div>
<div class="content"> <div class="content">
<el-card shadow="always" class="dialog-card"> <el-card shadow="always" class="dialog-card">
<MdRenderer :is_large="is_large" :chatIndex="index" :source="item.content" @fullscreenG6="fullscreen"></MdRenderer> <el-form
:model="item"
ref="formRef"
label-width="125px">
<el-form-item label="原始问题:">
<el-input v-model="item.content.question" placeholder="请补充其它条件"></el-input>
</el-form-item>
<el-form-item v-if="item.content.query.length > 0" label="查询条件:">
<template v-for="filter in item.content.query">
<div v-if="filter.ct_type === 'mselect'" style="display:flex;width: 100%;margin-bottom: 10px">
<span style="width: 150px">{{filter.name + ": "}}</span>
<el-select v-model="filter.default_value" multiple style="width: 100%;" clearable>
<el-option v-for="opt in filter.options" :key="opt" :value="opt" :label="opt"></el-option>
</el-select>
</div>
<div v-if="filter.ct_type === 'datePicker'" style="display:flex;width: 100%;margin-bottom: 10px">
<span style="width: 150px">{{filter.name + ": "}}</span>
<el-date-picker
v-model="filter.default_value"
type="date"
placeholder="Pick a day"
/>
</div>
<div v-if="filter.ct_type === 'dateRangePicker'" style="display:flex;width: 100%;margin-bottom: 10px">
<span style="width: 150px">{{filter.name + ": "}}</span>
<el-date-picker
v-model="filter.dateRangeValue"
type="daterange"
range-separator="-"
start-placeholder="Start date"
end-placeholder="End date"
/>
</div>
<div v-if="filter.ct_type === 'radioGroup'" style="display:flex;width: 100%;margin-bottom: 10px">
<span style="width: 150px">{{filter.name + ": "}}</span>
<el-radio-group v-model="filter.default_value">
<el-radio :key="'radio'+opt" v-for="opt in filter.options" :value="opt">{{ opt }}</el-radio>
</el-radio-group>
</div>
<div v-if="filter.ct_type === 'input'" style="display:flex;width: 100%;margin-bottom: 10px">
<span style="width: 150px">{{filter.name + ": "}}</span>
<el-input style="width: calc(100% - 150px)" v-model="filter.default_value"/>
</div>
</template>
</el-form-item>
<el-form-item v-if="item.content.result.length > 0" label="输出结果:">
<span v-for="result in item.content.result">{{result.name}}</span>
</el-form-item>
<el-form-item label="数据结果:">
<template v-if="item.content.data">
<el-tabs type="border-card" v-model="item.content.tab" style="width: 100%" @tabChange="changeTab(item.content)">
<el-tab-pane label="数据结果" name="result">
<div style="max-height: calc(100vh - 400px);overflow: auto">
<el-table :data="item.content.data" style="height: 100%">
<el-table-column v-for="(val,key) in item.content.data[0]" :prop="key" :label="key"/>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="分析结果" name="analysis" v-if="item.content.html_content && item.content.html_content !== ''">
<iframe :srcdoc="item.content.html_content"
style="width: 100%;height: 300px;border: none;
overflow-y:hidden; overflow-x: auto"></iframe>
</el-tab-pane>
<el-tab-pane label="sql" name="sql">
<SQLCodeMirror ref="codemirror" v-if="item.content.tab === 'sql'" :data="item.content.sql.replace('\n',' ')"></SQLCodeMirror>
</el-tab-pane>
</el-tabs>
</template>
<template v-else>
<span>查询结果为空请重新提问或者点击编辑按钮辅助提问^_^</span>
</template>
</el-form-item>
</el-form>
</el-card> </el-card>
<div class="flex-between mt-8" style="display: flex; justify-content: space-between; align-items: center; margin-top: 8px"> <div class="flex-between" style="display: flex; justify-content: space-between; align-items: center;margin-top: 16px">
<div> <DataQueryButton :data="item" @edit="edit(item)" @download="download(item)"></DataQueryButton>
<el-button
type="primary"
v-if="item.isStop && !item.isEnd"
@click="startChat(index)"
link
>重新生成
</el-button>
<el-button type="primary" v-else-if="!item.isEnd" @click="stopChat(index)" link
>停止回答
</el-button>
</div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="item.type === 'answer_err'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400">
<div class="avatar">
<img src="@/assets/logo/logo2.png" height="30px" />
</div>
<div content="content" style="padding-left:40px">
<el-card shadow="always" class="dialog-card">
<span style="font-size: 14px">{{item.content}}</span>
</el-card>
<div class="flex-between" style="display: flex; justify-content: space-between; align-items: center;margin-top: 16px">
<DataQueryButton :data="item"></DataQueryButton>
</div>
</div>
</div>
<div v-if="item.type === 'loading'" class="item-content mb-16 lighter" style="margin-bottom: 16px;font-weight: 400">
<div class="avatar">
<img src="@/assets/logo/logo2.png" height="30px" />
</div>
<div content="content" style="padding-left:40px">
<el-card shadow="always" class="dialog-card">
<span style="font-size: 14px">思考中...</span>
</el-card>
</div>
</div>
</template> </template>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -61,22 +154,16 @@
<el-input <el-input
ref="quickInputRef" ref="quickInputRef"
v-model="inputValue" v-model="inputValue"
:placeholder="'@选择机器人,Ctrl+Enter 换行,Enter发送'" :placeholder="'Ctrl+Enter 换行,Enter发送'"
:autosize="{ minRows: 1, maxRows: 4 }" :autosize="{ minRows: 1, maxRows: 4 }"
type="textarea" type="textarea"
:maxlength="100000" :maxlength="100000"
@input="handleInput"
@keydown.enter="sendChatHandle($event)" @keydown.enter="sendChatHandle($event)"
/> />
<div class="operate flex align-center"> <div class="operate flex align-center">
<el-button <el-button text class="sent-button" :disabled="chatList.length>0 && chatList[chatList.length-1].type === 'loading'" @click="sendChatHandle">
text <img v-show="chatList.length>0 && chatList[chatList.length-1].type === 'loading'" src="@/assets/aichat/icon_send.svg" alt="" />
class="sent-button" <img v-show="chatList.length === 0 || chatList[chatList.length-1].type !== 'loading'" src="@/assets/aichat/icon_send_colorful.svg" alt="" />
:disabled="isDisabledChart || loading"
@click="sendChatHandle"
>
<img v-show="isDisabledChart || loading" src="@/assets/aichat/icon_send.svg" alt="" />
<img v-show="!isDisabledChart && !loading" src="@/assets/aichat/icon_send_colorful.svg" alt="" />
</el-button> </el-button>
</div> </div>
</div> </div>
@ -85,24 +172,16 @@
</template> </template>
<script setup> <script setup>
import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue' import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router' import {postDataQuery} from "@/api/aichat/aichat"
import MdRenderer from '@/views/aichat/MdRenderer.vue' import { datetimeFormat } from '@/utils/time'
import {getToken} from "@/utils/auth.js"; import {v4 as uuidv4} from "uuid";
import {postChatMessage} from "@/api/aichat/aichat.js"
import cache from "@/plugins/cache.js";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import {addChat} from "@/api/aichat/aichat"; import SQLCodeMirror from "@/components/codemirror/SQLCodeMirror.vue";
import DataQueryButton from "@/views/dataint/dataquery/DataQueryButton.vue";
defineOptions({ name: 'AiChat' }) defineOptions({ name: 'AiChat' })
const route = useRoute()
const fullscreenG6Data = ref(null)
const showFullscreenG6Data = ref(false)
const {
params: { accessToken, id },
query: { mode }
} = route
const props = defineProps({ const props = defineProps({
is_large: Boolean,
cookieSessionId: String, cookieSessionId: String,
data: { data: {
type: Object, type: Object,
@ -132,36 +211,27 @@ const loading = ref(false)
const inputValue = ref('') const inputValue = ref('')
const chartOpenId = ref('') const chartOpenId = ref('')
const chatList = ref([]) const chatList = ref([])
const answerList = ref([])
const controller = ref(null) const controller = ref(null)
const popoverVisible = ref(false)
const currentMachine = ref([])
const currentFiles = ref([])
const upload = reactive({
//
open: false,
//
title: "",
//
isUploading: false,
//
headers: { Authorization: "Bearer " + getToken() },
//
url: import.meta.env.VITE_APP_BASE_API + "/aichat/upload",
data: {"sessionId":Cookies.get("chatSessionId")}
});
const isDisabledChart = computed( const isDisabledChart = computed(
() => !(inputValue.value.trim()) () => !(inputValue.value.trim())
) )
const emit = defineEmits(['scroll']) const emit = defineEmits(['scroll','showEdit','download'])
function changeTab(content){
}
function setScrollBottom() { function setScrollBottom() {
// //
scrollDiv.value.setScrollTop(getMaxHeight()) scrollDiv.value.setScrollTop(getMaxHeight())
} }
function edit(data){
emit('showEdit',data)
}
function download(data){
emit('download',data)
}
/** /**
* 滚动条距离最上面的高度 * 滚动条距离最上面的高度
@ -188,25 +258,6 @@ const handleScroll = () => {
} }
} }
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true;
};
const handleFileSuccess = (response, file, fileList) => {
currentFiles.value.push({file:response.data.file,bucket:response.data.bucket})
upload.open = false;
upload.isUploading = false;
proxy.$modal.msgSuccess(response.msg);
// proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "", { dangerouslyUseHTMLString: true });
};
/** 提交上传文件 */
function submitFileForm() {
proxy.$refs["uploadRef"].submit();
}
watch( watch(
() => props.chatId, () => props.chatId,
(val) => { (val) => {
@ -231,69 +282,12 @@ watch(
watch(() => props.cookieSessionId, value => upload.data = {sessionId:value}) watch(() => props.cookieSessionId, value => upload.data = {sessionId:value})
function regenerationChart(index){
let question = chatList.value[index - 1]
let chat = {
"type":"question",
"content":question.content,
"time": formatDate(new Date()),
"file": question.file}
chatList.value.push(chat)
let data = {
"query": chat.content,
"user_id": cache.local.get("username"),
"robot": currentMachine.value.length>0?currentMachine.value[0]:"",
"session_id": Cookies.get("chatSessionId"),
"doc": chat.file,
"history": []
}
sendChatMessage(data)
}
function changeThumb(index,chat){ function changeThumb(index,chat){
chatList.value[index].operate = chat.operate chatList.value[index].operate = chat.operate
chatList.value[index].thumbDownReason = chat.thumbDownReason chatList.value[index].thumbDownReason = chat.thumbDownReason
} }
function openUpload(){
upload.open = true
}
function chooseMachine(val){
inputValue.value = inputValue.value.slice(0,-1)
currentMachine.value = [val]
popoverVisible.value = false
}
function closeMachinePop(){
if (inputValue.value.endsWith('@')){
inputValue.value = inputValue.value.slice(0,-1)
}
popoverVisible.value = false
}
function removeMachine(){
currentMachine.value = []
}
function fullscreen(data){
fullscreenG6Data.value = data
showFullscreenG6Data.value = true
}
function clickoutside() {
if (inputValue.value.endsWith('@')){
inputValue.value = inputValue.value.slice(0,-1)
}
popoverVisible.value = false
}
function removeFile(index){
currentFiles.value = currentFiles.value.slice(index,1)
}
function handleInput() {
popoverVisible.value = inputValue.value.endsWith("@");
}
function padZero(num) { function padZero(num) {
return num < 10 ? '0' + num : num; return num < 10 ? '0' + num : num;
@ -310,70 +304,144 @@ function formatDate(date) {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
} }
function downloadFile(file,bucket,sessionId){
let data = {file,bucket,sessionId}
proxy.download("aichat/file/download", {
...data,
}, file);
}
async function sendChatHandle(event) { async function sendChatHandle(event) {
if (!event.ctrlKey) { if (!event.ctrlKey) {
// ctrl // ctrl
event.preventDefault() event.preventDefault()
if (inputValue.value.trim() && (chatList.value.length === 0|| if (inputValue.value.trim().length>0) {
(chatList.value[chatList.value.length - 1].isStop || let query = {
chatList.value[chatList.value.length - 1].isEnd))) {
chatList.value.push({
"type": "question", "type": "question",
"content": inputValue.value.trim(), "content": inputValue.value.trim(),
"time": formatDate(new Date()), "time": formatDate(new Date()),
"sessionId": Cookies.get("chatSessionId"), }
"sessionName": chatList.value.length > 0 ? chatList.value[0].content.substring(0, 20) : inputValue.value.trim().substring(0, 20), handleSend(query,inputValue.value.trim())
"file": currentFiles.value
})
let question = JSON.parse(JSON.stringify(chatList.value[chatList.value.length - 1]))
question.file = JSON.stringify(question.file)
await addChat(question)
nextTick(() => {
//
scrollDiv.value.setScrollTop(getMaxHeight())
})
let data = {
"query": inputValue.value.trim(),
"user_id": cache.local.get("username"),
"robot": currentMachine.value.length > 0 ? currentMachine.value[0] : "",
"session_id": Cookies.get("chatSessionId"),
"doc": currentFiles.value,
"history": []
}
inputValue.value = '' inputValue.value = ''
sendChatMessage(data)
} }
} else { } else {
// ctrl+ // ctrl+
inputValue.value += '\n' inputValue.value += '\n'
} }
} }
function handleSend(queryObj,inputVal){
queryObj.sessionId = Cookies.get("chatSessionId")
queryObj.sessionName = chatList.value.length > 0 ? chatList.value[0].content.substring(0, 20) : inputValue.value.trim().substring(0, 20),
chatList.value.push(queryObj)
chatList.value.push({type:'loading'})
// let question = JSON.parse(JSON.stringify(chatList.value[chatList.value.length - 1]))
// await addChat(question)
nextTick(() => {
//
scrollDiv.value.setScrollTop(getMaxHeight())
})
let data = {
query: inputVal,
}
sendChatMessage(data)
}
function generate_existing_names(existing_names, paramName){
if (existing_names.length > 0){
if (paramName in existing_names){
let lastPara = paramName.charAt(paramName.length -1)
let num = parseInt(lastPara)
if (isNaN(num)){
paramName += "1"
}else{
paramName = paramName.replace(lastPara,num+1)
}
generate_existing_names(existing_names, paramName)
}else {
existing_names.push(paramName)
}
}else {
existing_names.push(paramName)
}
}
function sendChatMessage(data){ function sendChatMessage(data){
controller.value = new AbortController() postDataQuery(data).then(res=>{
postChatMessage(data,{signal:controller.value.signal}).then(res=>{ let control_params = res.filters
if (res.status !== 200){ let data = res.data //
chatList.value.push({"type":"answer","content":[{"type":"text","content":"服务异常,错误码:"+res.status}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":'',"thumbDownReason":''}) let sql = res.sql // sql
let datas = res.datas // tree
let html_content = res.html_content //echarts
let existing_names = []
let query = [] //
let question = '' //
let result = [] //
for (let i = 0; i < control_params.length; i++) {
let param = control_params[i]
generate_existing_names(existing_names,param.name)
if (param.ct_type === 'mselect'
|| param.ct_type === 'datePicker'
|| param.ct_type === 'dateRangePicker'
|| param.ct_type === 'input'
){
if ( ['维度筛选', '时间筛选', '分组条件', '查询其它条件'].includes(param.cd_type)){
if (param.ct_type === 'dateRangePicker'){
param.dateRangeValue = [param.default_value.start_date,param.default_value.end_date]
}
query.push(param)
}
}
if (param.ct_type === 'output' && param.cd_type === '输出结果'){
result.push(param)
}
if (param.name === '原始问题'){
question = param.default_value
}
}
if (chatList.value[chatList.value.length - 1].type === 'loading'){
chatList.value[chatList.value.length - 1] = {
type:'answer',
"time": formatDate(new Date()),
content:{
query:query,
question: question === ''? chatList.value[chatList.value.length - 2].content:question,
result: result,
data: data,
sql: sql,
datas: datas,
html_content: html_content,
tab:'result'
}
}
}else { }else {
currentFiles.value = [] chatList.value.push(
chatList.value.push({"type":"answer","content":[],"isEnd":false,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName, "operate":'',"thumbDownReason":''}) {
const reader = res.body.getReader() type:'answer',
const write = getWrite(reader) "time": formatDate(new Date()),
reader.read().then(write).then(()=> { content:{
let answer = JSON.parse(JSON.stringify(chatList.value[chatList.value.length - 1])) query:query,
answer.content = JSON.stringify(answer.content) question: question === ''? chatList.value[chatList.value.length - 1].content:question,
addChat(answer) result: result,
}) data: data,
sql: sql,
datas: datas,
html_content: html_content,
tab:'result'
}
}
)
} }
}).catch((e) => {
chatList.value.push({"type":"answer","content":[{"type":"text","content":"服务异常"}],"isEnd":true,"isStop":false,"sessionId":chatList.value[0].sessionId,"sessionName":chatList.value[0].sessionName,"operate":"","thumbDownReason":""}) }).catch(err=>{
if (chatList.value[chatList.value.length - 1].type === 'loading'){
chatList.value[chatList.value.length - 1] = {
type:'answer_err',
content: "服务器繁忙,请稍后再试",
"time": formatDate(new Date()),
}
}else {
chatList.value.push(
{
type:'answer_err',
"time": formatDate(new Date()),
content: "服务器繁忙,请稍后再试"
}
)
}
}) })
} }
watch( watch(
@ -381,103 +449,17 @@ watch(
() => { () => {
nextTick(() => { nextTick(() => {
// //
scrollDiv.value.setScrollTop(getMaxHeight()) if (chatList.value.length >0){
scrollDiv.value.setScrollTop(getMaxHeight())
}
}) })
},{ },{
deep:true, immediate:true deep:true, immediate:true
} }
) )
const getWrite = (reader) => {
let tempResult = '';
/**
* 处理流式数据
* @param {done, value} obj - 包含 done value 属性的对象
*/
const write_stream = ({ done,value }) => {
try {
if (done) {
return
}
const decoder = new TextDecoder('utf-8');
let str = decoder.decode(value, { stream: true });
// chunk
tempResult += str;
console.log(tempResult)
const split = tempResult.match(/data:.*}\r\n/g);
if (split) {
str = split.join('');
tempResult = tempResult.replace(str, '');
} else {
return reader.read().then(write_stream);
}
// str "data:"
if (str && str.startsWith('data:')) {
split.forEach((chunkStr) => {
const chunk = JSON.parse(chunkStr.replace('data:', '').trim());
if (chunk.docs && chunk.docs[0].length > 0){
//
chatList.value[chatList.value.length - 1].content.push({"content":chunk.docs[0],"type":"docs"})
}else if (chunk.G6_ER && chunk.G6_ER.length > 0){
//ER
chatList.value[chatList.value.length - 1].content.push({"content":chunk.G6_ER,"type":"G6_ER"})
}else if (chunk.html_image && chunk.html_image.length > 0){
//htmlecharts
chatList.value[chatList.value.length - 1].content.push({"content":chunk.html_image,"type":"html_image"})
}else if (chunk.table && chunk.table.length > 0){
//
chatList.value[chatList.value.length - 1].content.push({"content":chunk.table,"type":"table"})
}else {
//
let last_answer = chatList.value[chatList.value.length - 1].content
chunk.choices[0].delta.content = chunk.choices[0].delta.content.replace("\n","\n\n")
if (last_answer.length > 0) {
if(last_answer[last_answer.length - 1].type === 'text'){
chatList.value[chatList.value.length - 1].content[last_answer.length - 1].content += chunk.choices[0].delta.content
}else{
chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"})
}
}else{
chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"})
}
}
nextTick(() => {
//
scrollDiv.value.setScrollTop(getMaxHeight())
})
if (chunk.isEnd || chunk.is_end){
chatList.value[chatList.value.length - 1].isEnd = true
chatList.value[chatList.value.length - 1].time = formatDate(new Date())
nextTick(() => {
//
scrollDiv.value.setScrollTop(getMaxHeight())
})
return Promise.resolve();
}
});
}
} catch (e) {
return Promise.reject(e);
}
return reader.read().then(write_stream);
};
return write_stream
};
const stopChat = (index) => {
chatList.value[index].isStop = true
if (controller.value !== null){
controller.value.abort()
}
let answer = JSON.parse(JSON.stringify(chatList.value[index]))
answer.content = JSON.stringify(answer.content)
addChat(answer)
}
const startChat = (index) => {
regenerationChart(index)
}
defineExpose({ defineExpose({
setScrollBottom setScrollBottom,handleSend
}) })
</script> </script>

414
vue-fastapi-frontend/src/views/dataint/dataquery/index.vue

@ -16,14 +16,181 @@
</div> </div>
<div class="chat-embed__main"> <div class="chat-embed__main">
<dataquerychat <dataquerychat
ref="AiChatRef" ref="dataQueryRef"
:record="currentRecordList" :record="chatDataList"
:is_large="is_large"
:cookieSessionId = "sessionId"
class="AiChat-embed" class="AiChat-embed"
@scroll="handleScroll" @scroll="handleScroll"
@showEdit="showEdit"
@download ="download"
></dataquerychat> ></dataquerychat>
</div> </div>
<el-drawer
v-model="showDrawer"
size="65%"
title="数据问答工作台"
>
<el-row :gutter="20">
<el-col :span="8">
<div class="head-container">
<el-input
v-model="chooseData"
placeholder="请输入搜索可选数据"
clearable
prefix-icon="Search"
style="margin-bottom: 20px"
/>
</div>
<div class="head-container" style="height: calc(100vh - 170px);overflow: auto">
<el-tree
:data="treeData"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="dataQueryTreeRef"
node-key="id"
highlight-current
default-expand-all
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<div class="custom-tree-node">
<span>{{ node.label }}</span>
<div v-if="data.showStar">
<el-link v-if="!data.checked" :underline="false" type="warning">
<el-icon><Star /></el-icon>
</el-link>
<el-link v-if="data.checked" :underline="false" type="warning">
<el-icon><StarFilled /></el-icon>
</el-link>
</div>
</div>
</template>
</el-tree>
</div>
</el-col>
<el-col :span="16">
<el-divider content-position="left">条件设置</el-divider>
<el-table :data="tableData">
<el-table-column label="字段名称" prop="name" width="120"></el-table-column>
<el-table-column label="条件类型" prop="cd_type" width="130">
<template #default="scope">
<el-select v-model="scope.row.cd_type">
<el-option key="时间筛选" :value="'时间筛选'" label="时间筛选"></el-option>
<el-option key="维度筛选" :value="'维度筛选'" label="维度筛选"></el-option>
<el-option key="分组条件" :value="'分组条件'" label="分组条件"></el-option>
<el-option key="查询其它条件" :value="'查询其它条件'" label="查询其它条件"></el-option>
<el-option key="输出结果" :value="'输出结果'" label="输出结果"></el-option>
<el-option key="输出其它条件" :value="'输出其它条件'" label="输出其它条件"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="控件类型" prop="ct_type" width="130">
<template #default="scope">
<el-select v-model="scope.row.ct_type">
<el-option key="datePicker" :value="'datePicker'" label="单日筛选"></el-option>
<el-option key="dateRangePicker" :value="'dateRangePicker'" label="周期筛选"></el-option>
<el-option key="input" :value="'input'" label="输入框"></el-option>
<el-option key="select" :value="'select'" label="单项筛选"></el-option>
<el-option key="mselect" :value="'mselect'" label="多项筛选"></el-option>
<el-option key="radioGroup" :value="'radioGroup'" label="是否筛选"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="条件取值">
<template #default="scope">
<template v-if="scope.row.ct_type === 'mselect'">
<el-select v-model="scope.row.default_value" multiple style="width: 100%;" clearable>
<el-option v-for="opt in scope.row.options" :key="opt" :value="opt" :label="opt"></el-option>
</el-select>
</template>
<template v-else-if="scope.row.ct_type === 'datePicker'">
<el-date-picker
v-model="scope.row.default_value"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="Pick a day"
/>
</template>
<template v-else-if="scope.row.ct_type === 'dateRangePicker'">
<el-date-picker
v-model="scope.row.dateRangeValue"
type="daterange"
style="width: 100%;"
range-separator="-"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
start-placeholder="Start date"
end-placeholder="End date"
/>
</template>
<template v-else-if="scope.row.ct_type === 'radioGroup'">
<el-radio-group v-model="scope.row.default_value">
<el-radio :key="'radio'+opt" v-for="opt in scope.row.options" :value="opt">{{ opt }}</el-radio>
</el-radio-group>
</template>
<template v-else>
<el-input v-model="scope.row.default_value"></el-input>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-tooltip
style="margin-right: 10px"
class="box-item"
effect="dark"
content="上移"
placement="top-start"
v-if="scope.$index > 0"
>
<el-link :underline="false" v-if="scope.$index > 0" @click="changeTableIndex('top',scope.$index)" style="margin-right: 10px" type="primary"><el-icon><Top /></el-icon></el-link>
</el-tooltip>
<el-tooltip
style="margin-right: 10px"
class="box-item"
effect="dark"
content="下移"
placement="top-start"
v-if="scope.$index < tableData.length-1"
>
<el-link :underline="false" v-if="scope.$index < tableData.length-1" @click="changeTableIndex('bottom',scope.$index)" style="margin-right: 10px" type="primary"><el-icon><Bottom /></el-icon></el-link>
</el-tooltip>
<el-tooltip
style="margin-right: 10px"
class="box-item"
effect="dark"
content="删除"
placement="top-start"
>
<el-link :underline="false" @click="remove(scope.$index)" type="danger"><el-icon><Delete /></el-icon></el-link>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="operate-textarea flex chat-width" style="margin-top: 20px">
<el-input
ref="quickInputRef"
v-model="inputValue"
:placeholder="'Ctrl+Enter 换行,Enter发送'"
:autosize="{ minRows: 1, maxRows: 4 }"
type="textarea"
:maxlength="100000"
/>
<div class="operate flex align-center">
<el-button
text
class="sent-button"
:disabled="inputValue === ''"
@click="sendChatHandle"
>
<img v-show="inputValue === ''" src="@/assets/aichat/icon_send.svg" alt="" />
<img v-show="inputValue !== ''" src="@/assets/aichat/icon_send_colorful.svg" alt="" />
</el-button>
</div>
</div>
</el-col>
</el-row>
</el-drawer>
</div> </div>
</template> </template>
@ -32,28 +199,20 @@ import { ref, onMounted, reactive, nextTick, computed } from 'vue'
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { listChatHistory, getChatList, DeleteChatSession } from "@/api/aichat/aichat";
import Dataquerychat from "./dataquerychat.vue"; import Dataquerychat from "./dataquerychat.vue";
const route = useRoute() const route = useRoute()
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const { const chatDataList = ref([])
params: { accessToken } const dataQueryRef = ref()
} = route
const props = defineProps({
is_large: Boolean,
chatDataList: Array,
})
const AiChatRef = ref()
const chatLogeData = ref([]) const chatLogeData = ref([])
const show = ref(false) const show = ref(false)
const showDrawer = ref(false)
const currentRecordList = ref([]) const currentRecordList = ref([])
const chooseData = ref("")
const treeData = ref([])
const tableData = ref([])
const inputValue = ref("")
const sessionId = ref(Cookies.get("chatSessionId")) // Id 'new' const sessionId = ref(Cookies.get("chatSessionId")) // Id 'new'
const mouseId = ref('')
function handleScroll(event) { function handleScroll(event) {
if (event.scrollTop === 0 && currentRecordList.value.length>0) { if (event.scrollTop === 0 && currentRecordList.value.length>0) {
@ -61,52 +220,185 @@ function handleScroll(event) {
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height) event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
} }
} }
function sendChatHandle(){
let queryObj = {
"type": "question",
"content": inputValue.value.trim(),
"time": formatDate(new Date()),
}
let inputVal = inputValue.value
dataQueryRef.value.handleSend(queryObj,inputVal);
showDrawer.value = false
}
function clickListHandle(item){ function clickListHandle(item){
getChatList(item.sessionId).then(res=>{
currentRecordList.value = [] }
let array = res.data function padZero(num) {
for (let i = 0; i < array.length; i++) { return num < 10 ? '0' + num : num;
if (array[i].type === 'answer'){ }
array[i].content = JSON.parse(array[i].content) function formatDate(date) {
} const year = date.getFullYear();
if (array[i].type === 'question'){ const month = padZero(date.getMonth() + 1); // 0+1
array[i].file = JSON.parse(array[i].file) const day = padZero(date.getDate());
const hours = padZero(date.getHours());
const minutes = padZero(date.getMinutes());
const seconds = padZero(date.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
function remove(index){
uncheckTree(tableData.value[index].name)
tableData.value.splice(index,1)
}
function uncheckTree(name){
if (treeData.value.length>0){
for (let i = 0; i < treeData.value.length; i++) {
if (treeData.value[i].children && treeData.value[i].children.length>0){
for (let j = 0; j < treeData.value[i].children.length; j++) {
if (treeData.value[i].children[j].value === name){
treeData.value[i].children[j].checked = false
}
}
} }
currentRecordList.value.push(array[i])
AiChatRef.value.setScrollBottom()
} }
show.value = false }
sessionId.value = item.sessionId }
Cookies.set("chatSessionId",sessionId.value) function changeTableIndex(type,index){
}) if(type === 'top'){
} let temp = tableData.value[index]
watch(() => props.chatDataList, value => currentRecordList.value = JSON.parse(JSON.stringify(value))) tableData.value[index] = tableData.value[index - 1]
onMounted( tableData.value[index - 1] = temp
()=>{ }
if (Cookies.get("chatSessionId")){ if (type === 'bottom'){
// let temp = tableData.value[index]
getChatList(Cookies.get("chatSessionId")).then(res=>{ tableData.value[index] = tableData.value[index + 1]
let array = res.data tableData.value[index + 1] = temp
if (array && array.length >0){ }
for (let i = 0; i < array.length; i++) { }
if (array[i].type === 'answer'){ function showEdit(data){
array[i].content = JSON.parse(array[i].content) let array = JSON.parse(JSON.stringify(data.content.datas))
} let compareArr = JSON.parse(JSON.stringify(data.content.query))
if (array[i].type === 'question'){ compareArr.push(...JSON.parse(JSON.stringify(data.content.result)))
array[i].file = JSON.parse(array[i].file) treeData.value = []
} if (array.length >0){
for (let i = 0; i < array.length; i++) {
let tab_cn_name = array[i].tab_cn_name;
let fld_cn_name = array[i].fld_cn_name;
let fld_type = array[i].fld_type;
if (treeData.value.length>0){
if (treeData.value.some(item=>item.label === tab_cn_name)){
for (let j = 0; j < treeData.value.length; j++) {
if (treeData.value[j].label === tab_cn_name){
treeData.value[j].children.push({
label:fld_cn_name +" | "+ fld_type,
value: fld_cn_name,
checked: compareArr.length>0?compareArr.some(item=>item.name === fld_cn_name):false,
showStar: true
})
} }
} }
currentRecordList.value = array }else {
}).then(()=>{ treeData.value.push({
AiChatRef.value.setScrollBottom() label: tab_cn_name,
}) value: tab_cn_name,
checked: false,
showStar: false,
children:[{
label:fld_cn_name +" | "+ fld_type,
value: fld_cn_name,
checked: false,
showStar: true
}]
})
}
}else { }else {
Cookies.set("chatSessionId",uuidv4()) treeData.value.push({
label: tab_cn_name,
value: tab_cn_name,
checked: false,
showStar: false,
children:[{
label:fld_cn_name +" | "+ fld_type,
value: fld_cn_name,
checked: false,
showStar: true,
}]
})
}
}
}
tableData.value = []
tableData.value.push(...data.content.query)
tableData.value.push(...data.content.result)
showDrawer.value = true
}
watch(chooseData, val => {
proxy.$refs["dataQueryTreeRef"].filter(val);
});
function download(data){
}
function handleNodeClick(data){
if (tableData.value.length>0){
if (tableData.value.some(item=>item.name===data.value)){
let index = -1;
for (let i = 0; i < tableData.value.length; i++) {
if (tableData.value[i].name === data.value){
index = i;
}
}
if (index >=0){
tableData.value.splice(index, 1);
} }
}else {
tableData.value.push({
name:data.value,
cd_type:'',
ct_type:'',
default_value:''
})
} }
) }
data.checked = !data.checked
}
const filterNode = (value, data) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
function formatDefaultValue(item){
let value = item.default_value
if (item.cd_type === '输出结果'){
return item.name
}
if (item.ct_type === 'dateRangePicker'){
if (item.dateRangeValue){
return "开始日期为"+item.dateRangeValue[0]+"截止日期为"+item.dateRangeValue[1]
}else {
return ""
}
}
return value
}
watch(tableData,(newValue,oldValueValue) =>{
if (tableData.value.length>0){
let resultList = []
let queryList = []
for (let i = 0; i < tableData.value.length; i++) {
if (tableData.value[i].cd_type === '输出结果'){
resultList.push(formatDefaultValue(tableData.value[i]))
}else{
queryList.push(tableData.value[i].name+"为"+formatDefaultValue(tableData.value[i]))
}
}
if (resultList.length > 0){
queryList.push("输出结果为"+resultList.join("和"))
}
let result = "查询"+queryList.join(",")
inputValue.value = result
}
},{ deep: true})
</script> </script>
<style lang="scss"> <style lang="scss">
@ -203,4 +495,12 @@ onMounted(
.ml-24 { .ml-24 {
margin-left: 24px; margin-left: 24px;
} }
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style> </style>

5
vue-fastapi-frontend/src/views/meta/metaInfo/index.vue

@ -311,7 +311,7 @@
<el-tab-pane label="业务关系" name="businessRelation">业务关系</el-tab-pane> <el-tab-pane label="业务关系" name="businessRelation">业务关系</el-tab-pane>
<el-tab-pane label="血缘关系" name="bloodRelation">血缘关系</el-tab-pane> <el-tab-pane label="血缘关系" name="bloodRelation">血缘关系</el-tab-pane>
<el-tab-pane label="存储过程" name="proc"> <el-tab-pane label="存储过程" name="proc">
<SQLCodeMirror :data="procStr"></SQLCodeMirror> <SQLCodeMirror v-if="activeColumnTab === 'proc'" :data="procStr"></SQLCodeMirror>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
@ -545,7 +545,6 @@
import SQLCodeMirror from "@/components/codemirror/SQLCodeMirror.vue"; import SQLCodeMirror from "@/components/codemirror/SQLCodeMirror.vue";
import cache from "@/plugins/cache"; import cache from "@/plugins/cache";
const data = reactive({ const data = reactive({
queryParams:{ queryParams:{
ssysCd:'', ssysCd:'',
@ -608,6 +607,7 @@
const activeColumnTab = ref("column"); const activeColumnTab = ref("column");
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const changedColumns = ref([]) const changedColumns = ref([])
function changeColumnTab(){ function changeColumnTab(){
if (activeColumnTab.value === 'proc'){ if (activeColumnTab.value === 'proc'){
procStr.value = "--基金量化产品监管报送存储过程:ADS.SP_ADS_SAC_QNTPRD_ALL\n" + procStr.value = "--基金量化产品监管报送存储过程:ADS.SP_ADS_SAC_QNTPRD_ALL\n" +
@ -1058,6 +1058,7 @@
getDatabaseList() getDatabaseList()
handleQuery() handleQuery()
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

6
vue-fastapi-frontend/vite.config.js

@ -41,6 +41,12 @@ export default defineConfig(({ mode, command }) => {
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/aichat-api/, '') rewrite: (p) => p.replace(/^\/aichat-api/, '')
}, },
'/dataquery-api': {
target: 'http://47.121.207.11:6006',
// target: 'http://127.0.0.1:8000',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dataquery-api/, '')
},
"/ds-api": { "/ds-api": {
target: 'http://47.121.207.11:12345', target: 'http://47.121.207.11:12345',
changeOrigin: true, changeOrigin: true,

Loading…
Cancel
Save