mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: import Sample SQL / Terminologies
This commit is contained in:
@@ -7,6 +7,7 @@ from apps.datasource.api import datasource, table_relation
|
||||
from apps.mcp import mcp
|
||||
from apps.system.api import login, user, aimodel, workspace, assistant
|
||||
from apps.terminology.api import terminology
|
||||
from apps.settings.api import base
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(login.router)
|
||||
@@ -14,6 +15,7 @@ api_router.include_router(user.router)
|
||||
api_router.include_router(workspace.router)
|
||||
api_router.include_router(assistant.router)
|
||||
api_router.include_router(aimodel.router)
|
||||
api_router.include_router(base.router)
|
||||
api_router.include_router(terminology.router)
|
||||
api_router.include_router(data_training.router)
|
||||
api_router.include_router(datasource.router)
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
from fastapi import APIRouter, File, UploadFile, Query
|
||||
from fastapi.responses import StreamingResponse, FileResponse
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from apps.chat.models.chat_model import AxisObj
|
||||
from apps.data_training.curd.data_training import page_data_training, create_training, update_training, delete_training, \
|
||||
@@ -16,7 +16,6 @@ from apps.data_training.curd.data_training import page_data_training, create_tra
|
||||
from apps.data_training.models.data_training_model import DataTrainingInfo
|
||||
from common.core.config import settings
|
||||
from common.core.deps import SessionDep, CurrentUser, Trans
|
||||
from common.core.file import FileRequest
|
||||
from common.utils.data_format import DataFormat
|
||||
|
||||
router = APIRouter(tags=["DataTraining"], prefix="/system/data-training")
|
||||
@@ -213,30 +212,3 @@ async def upload_excel(trans: Trans, current_user: CurrentUser, file: UploadFile
|
||||
}
|
||||
|
||||
return await asyncio.to_thread(inner)
|
||||
|
||||
|
||||
@router.post("/download-fail-info")
|
||||
async def download_excel(req: FileRequest):
|
||||
"""
|
||||
根据文件路径下载 Excel 文件
|
||||
"""
|
||||
filename = req.file
|
||||
file_path = os.path.join(path, filename)
|
||||
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(file_path):
|
||||
raise HTTPException(404, "File Not Exists")
|
||||
|
||||
# 检查文件是否是 Excel 文件
|
||||
if not filename.endswith('_error.xlsx'):
|
||||
raise HTTPException(400, "Only support _error.xlsx")
|
||||
|
||||
# 获取文件名
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
# 返回文件
|
||||
return FileResponse(
|
||||
path=file_path,
|
||||
filename=filename,
|
||||
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
from http.client import HTTPException
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from common.core.config import settings
|
||||
from common.core.file import FileRequest
|
||||
|
||||
router = APIRouter(tags=["System"], prefix="/system")
|
||||
|
||||
path = settings.EXCEL_PATH
|
||||
|
||||
|
||||
@router.post("/download-fail-info")
|
||||
async def download_excel(req: FileRequest):
|
||||
"""
|
||||
根据文件路径下载 Excel 文件
|
||||
"""
|
||||
filename = req.file
|
||||
file_path = os.path.join(path, filename)
|
||||
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(file_path):
|
||||
raise HTTPException(404, "File Not Exists")
|
||||
|
||||
# 检查文件是否是 Excel 文件
|
||||
if not filename.endswith('_error.xlsx'):
|
||||
raise HTTPException(400, "Only support _error.xlsx")
|
||||
|
||||
# 获取文件名
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
# 返回文件
|
||||
return FileResponse(
|
||||
path=file_path,
|
||||
filename=filename,
|
||||
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
@@ -1,68 +0,0 @@
|
||||
from fastapi import APIRouter
|
||||
from sqlmodel import select
|
||||
from apps.settings.models.setting_models import term_model
|
||||
from apps.settings.schemas.setting_schemas import term_schema_creator
|
||||
from common.core.deps import SessionDep
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
router = APIRouter(tags=["Settings"], prefix="/settings/terminology")
|
||||
|
||||
|
||||
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[term_model])
|
||||
async def pager(
|
||||
session: SessionDep,
|
||||
pageNum: int,
|
||||
pageSize: int
|
||||
):
|
||||
pagination = PaginationParams(page=pageNum, size=pageSize)
|
||||
paginator = Paginator(session)
|
||||
filters = {}
|
||||
return await paginator.get_paginated_response(
|
||||
stmt=term_model,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=term_model)
|
||||
async def get_terminology_by_id(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.get(term_model, id)
|
||||
return term
|
||||
|
||||
@router.post("", response_model=term_model)
|
||||
async def add_terminology(
|
||||
session: SessionDep,
|
||||
creator: term_schema_creator
|
||||
):
|
||||
terminology = term_model(**creator.model_dump())
|
||||
session.add(terminology)
|
||||
session.commit()
|
||||
return terminology
|
||||
|
||||
@router.put("", response_model=term_model)
|
||||
async def update_terminology(
|
||||
session: SessionDep,
|
||||
terminology: term_model
|
||||
):
|
||||
terminology.id = int(terminology.id)
|
||||
term = session.exec(select(term_model).where(term_model.id == terminology.id)).first()
|
||||
update_data = terminology.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(term, field, value)
|
||||
session.add(term)
|
||||
session.commit()
|
||||
return terminology
|
||||
|
||||
@router.delete("/{id}", response_model=term_model)
|
||||
async def delete_terminology(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.exec(select(term_model).where(term_model.id == id)).first()
|
||||
session.delete(term)
|
||||
session.commit()
|
||||
return {
|
||||
"message": f"Terminology with ID {id} deleted successfully."
|
||||
}
|
||||
@@ -16,13 +16,4 @@ export const professionalApi = {
|
||||
responseType: 'blob',
|
||||
requestOptions: { customError: true },
|
||||
}),
|
||||
downloadError: (path: any) =>
|
||||
request.post(
|
||||
`/system/terminology/download-fail-info`,
|
||||
{ file: path },
|
||||
{
|
||||
responseType: 'blob',
|
||||
requestOptions: { customError: true },
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const termApi = {
|
||||
export const settingsApi = {
|
||||
pager: (pageNumber: number, pageSize: number) =>
|
||||
request.get(`/settings/terminology/pager/${pageNumber}/${pageSize}`),
|
||||
add: (data: any) => request.post('/settings/terminology', data),
|
||||
edit: (data: any) => request.put('/settings/terminology', data),
|
||||
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
|
||||
query: (id: number) => request.get(`/settings/terminology/${id}`),
|
||||
|
||||
downloadError: (path: any) =>
|
||||
request.post(
|
||||
`/system/download-fail-info`,
|
||||
{ file: path },
|
||||
{
|
||||
responseType: 'blob',
|
||||
requestOptions: { customError: true },
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -15,13 +15,4 @@ export const trainingApi = {
|
||||
responseType: 'blob',
|
||||
requestOptions: { customError: true },
|
||||
}),
|
||||
downloadError: (path: any) =>
|
||||
request.post(
|
||||
`/system/data-training/download-fail-info`,
|
||||
{ file: path },
|
||||
{
|
||||
responseType: 'blob',
|
||||
requestOptions: { customError: true },
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
<template>
|
||||
<div class="sqlbot-table-container professional-container">
|
||||
<div class="sqlbot-tool">
|
||||
<div class="tool-left">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
class="sqlbot-search"
|
||||
placeholder="Search"
|
||||
clearable
|
||||
prefix-icon="el-icon-search"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="tool-right">
|
||||
<div class="tool-btn secondary-btn">
|
||||
<el-icon><IconOpeUpload /></el-icon>
|
||||
<span>Import Terminology</span>
|
||||
</div>
|
||||
<div class="tool-btn secondary-btn">
|
||||
<el-icon><IconOpeDownload /></el-icon>
|
||||
<span>Export Terminology</span>
|
||||
</div>
|
||||
<div class="tool-btn primary-btn" @click="openDialog">
|
||||
<el-icon><IconOpeAdd /></el-icon>
|
||||
<span>Add Terminology</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sqlbot-table">
|
||||
<el-table :data="state.tableData" style="width: 100%">
|
||||
<el-table-column prop="term" label="Term" width="280" />
|
||||
<el-table-column prop="definition" label="Definition" />
|
||||
<el-table-column prop="domain" label="Domain" width="180" />
|
||||
<el-table-column prop="id" label="Actions" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="table-operate">
|
||||
<div class="opt-btn" @click="editHandler(scope.row.id)">
|
||||
<el-icon><IconOpeEdit /></el-icon>
|
||||
</div>
|
||||
<div class="opt-btn" @click="deleteHandler(scope.row.id)">
|
||||
<el-icon><IconOpeDelete /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
title="Add Terminology"
|
||||
destroy-on-close
|
||||
width="500"
|
||||
class="sqlbot-dialog"
|
||||
@close="onFormClose"
|
||||
>
|
||||
<el-form
|
||||
ref="termFormRef"
|
||||
:model="state.form"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
class="sqlbot-form"
|
||||
size="large"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="Term name">
|
||||
<el-input v-model="state.form.term" clearable autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Definition">
|
||||
<el-input v-model="state.form.definition" clearable type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Domain">
|
||||
<el-select v-model="state.form.domain" placeholder="Please select a domain">
|
||||
<el-option label="domain1" value="domain1" />
|
||||
<el-option label="domain2" value="domain2" />
|
||||
<el-option label="domain3" value="domain3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeForm">Cancel</el-button>
|
||||
<el-button type="primary" @click="saveHandler"> save </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg'
|
||||
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg'
|
||||
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'
|
||||
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
|
||||
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
|
||||
import { termApi } from '@/api/setting'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const keyword = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const termFormRef = ref()
|
||||
const state = reactive({
|
||||
tableData: [],
|
||||
form: {
|
||||
id: '',
|
||||
term: '',
|
||||
definition: '',
|
||||
domain: '',
|
||||
},
|
||||
})
|
||||
const handleSearch = (e: any) => {
|
||||
console.info('search', e)
|
||||
}
|
||||
const editHandler = (id: any) => {
|
||||
termApi.query(id).then((res: any) => {
|
||||
state.form = res
|
||||
dialogFormVisible.value = true
|
||||
})
|
||||
}
|
||||
const deleteHandler = (id: any) => {
|
||||
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
termApi.delete(id).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Delete completed',
|
||||
})
|
||||
search()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: 'Delete canceled',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const resetForm = () => {
|
||||
if (!termFormRef.value) return
|
||||
Object.keys(state.form).forEach((key) => {
|
||||
state.form[key as keyof typeof state.form] = ''
|
||||
})
|
||||
}
|
||||
|
||||
const closeForm = () => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
const onFormClose = () => {
|
||||
resetForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
termApi.pager(1, 20).then((res: any) => {
|
||||
state.tableData = res.items
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
termApi.add(state.form).then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Add completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
termApi.edit(state.form).then((res: any) => {
|
||||
console.info('edit term', res)
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
})
|
||||
}
|
||||
const saveHandler = () => {
|
||||
if (state.form.id) {
|
||||
editTerm()
|
||||
} else {
|
||||
addTerm()
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sqlbot-table-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.sqlbot-tool {
|
||||
height: 42px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
.tool-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.sqlbot-search {
|
||||
width: 300px;
|
||||
--el-input-inner-height: 40px !important;
|
||||
--el-input-border-radius: 24px;
|
||||
--el-input-bg-color: #f5f7fa;
|
||||
--el-input-border-color: #e5e8ed;
|
||||
}
|
||||
}
|
||||
.tool-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
height: 35px;
|
||||
.secondary-btn {
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.primary-btn {
|
||||
background: var(--el-color-primary);
|
||||
color: white;
|
||||
}
|
||||
.tool-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
&:hover {
|
||||
background-color: #f1f3f4;
|
||||
}
|
||||
&.default {
|
||||
color: #2d2e31;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
i {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-table {
|
||||
border-radius: 6px;
|
||||
:deep(.ed-table) {
|
||||
--el-table-header-bg-color: #f5f7fa;
|
||||
--el-table-border-color: #ebeef5;
|
||||
--el-table-header-text-color: #606266;
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
td {
|
||||
height: 52px;
|
||||
}
|
||||
}
|
||||
.table-operate {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
.opt-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #5f6368;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: #f1f3f4;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
&:hover {
|
||||
background-color: #e8f0fe;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-form {
|
||||
width: 100%;
|
||||
.ed-form-item {
|
||||
margin-bottom: 16px;
|
||||
text-align: left;
|
||||
label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
/* .ed-input {
|
||||
--el-input-border-color: #e5e8ed;
|
||||
--el-input-border-radius: 4px;
|
||||
--el-input-inner-height: 40px !important;
|
||||
}
|
||||
.ed-select {
|
||||
--el-select-bg-color: #f5f7fa;
|
||||
--el-select-border-color: #e5e8ed;
|
||||
--el-select-border-radius: 4px;
|
||||
--el-select-inner-height: 40px !important;
|
||||
} */
|
||||
}
|
||||
}
|
||||
.sqlbot-dialog {
|
||||
width: 500px;
|
||||
|
||||
:deep(.ed-dialog__header) {
|
||||
text-align: left !important;
|
||||
font-size: 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<div class="setting-container">
|
||||
<el-tabs v-model="activeName" class="setting-tabs" @tab-click="handleClick">
|
||||
<el-tab-pane label="User" name="0">
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon><IconProfessional /></el-icon>
|
||||
<span>Terminology</span>
|
||||
</span>
|
||||
</template>
|
||||
<Professional v-if="activeName == '0'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Config" name="1">
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon><IconTrain /></el-icon>
|
||||
<span>Data training</span>
|
||||
</span>
|
||||
</template>
|
||||
Data training
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import IconProfessional from '@/assets/svg/professional.svg'
|
||||
import IconTrain from '@/assets/svg/train.svg'
|
||||
import Professional from './Professional.vue'
|
||||
const activeName = ref('0')
|
||||
|
||||
const handleClick = (tab: any, event: MouseEvent) => {
|
||||
console.info(tab, event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting-container {
|
||||
height: calc(100% - 48px);
|
||||
background-color: var(--white);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
padding: 24px;
|
||||
:deep(.ed-tabs) {
|
||||
--el-tabs-header-height: 55px;
|
||||
.ed-tabs__header {
|
||||
margin: 0 0 24px !important;
|
||||
}
|
||||
}
|
||||
.setting-tabs {
|
||||
height: 100%;
|
||||
:deep(.custom-tabs-label) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
column-gap: 4px;
|
||||
i {
|
||||
font-size: 20px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
:deep(.ed-tabs__item) {
|
||||
padding: 0 16px !important;
|
||||
&:hover {
|
||||
// color: var(--primary-color);
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,8 +13,8 @@ import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { genFileId, type UploadInstance, type UploadProps, type UploadRawFile } from 'element-plus'
|
||||
import { trainingApi } from '@/api/training.ts'
|
||||
import { useCache } from '@/utils/useCache.ts'
|
||||
import { settingsApi } from '@/api/setting.ts'
|
||||
|
||||
interface Form {
|
||||
id?: string | null
|
||||
@@ -105,7 +105,7 @@ const onSuccess = (response: any) => {
|
||||
fail_info: response.data.error_excel_filename,
|
||||
})
|
||||
)
|
||||
trainingApi
|
||||
settingsApi
|
||||
.downloadError(response.data.error_excel_filename)
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, nextTick, onMounted, reactive, ref, unref } from 'vue'
|
||||
import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
|
||||
import { trainingApi } from '@/api/training'
|
||||
import { settingsApi } from '@/api/setting.ts'
|
||||
import { formatTimestamp } from '@/utils/date'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
|
||||
@@ -124,7 +125,7 @@ const onSuccess = (response: any) => {
|
||||
fail_info: response.data.error_excel_filename,
|
||||
})
|
||||
)
|
||||
trainingApi
|
||||
settingsApi
|
||||
.downloadError(response.data.error_excel_filename)
|
||||
.then((res) => {
|
||||
const blob = new Blob([res], {
|
||||
|
||||
Reference in New Issue
Block a user