ADD: 修改数据合并逻辑,新增 event-source-polyfill包,实现长连接通信,展示合并进度
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@types/event-source-polyfill": "^1.0.5",
|
||||
"@vue-flow/core": "^1.45.0",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"axios": "^1.7.3",
|
||||
@@ -23,6 +24,7 @@
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"element-plus": "^2.7.8",
|
||||
"event-source-polyfill": "^1.0.31",
|
||||
"html2canvas": "^1.4.1",
|
||||
"md5": "^2.3.0",
|
||||
"mitt": "^3.0.1",
|
||||
|
||||
@@ -1,190 +1,226 @@
|
||||
import { ElMessage, ElTreeSelect } from 'element-plus';
|
||||
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios, {
|
||||
AxiosError,
|
||||
type AxiosInstance,
|
||||
type AxiosRequestConfig,
|
||||
type AxiosResponse,
|
||||
type InternalAxiosRequestConfig
|
||||
} from 'axios'
|
||||
import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
|
||||
import { LOGIN_URL } from '@/config'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ResultData } from '@/api/interface'
|
||||
import { type ResultData } from '@/api/interface'
|
||||
import { ResultEnum } from '@/enums/httpEnum'
|
||||
import { checkStatus } from './helper/checkStatus'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import router from '@/routers'
|
||||
import {refreshToken} from '@/api/user/login'
|
||||
import { refreshToken } from '@/api/user/login'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
|
||||
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
|
||||
loading?: boolean;
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const config = {
|
||||
// 默认地址请求地址,可在 .env.** 文件中修改
|
||||
baseURL: import.meta.env.VITE_API_URL as string,
|
||||
// 设置超时时间
|
||||
timeout: ResultEnum.TIMEOUT as number,
|
||||
// 跨域时候允许携带凭证
|
||||
withCredentials: true,
|
||||
// post请求指定数据类型以及编码
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||
// 默认地址请求地址,可在 .env.** 文件中修改
|
||||
baseURL: import.meta.env.VITE_API_URL as string,
|
||||
// 设置超时时间
|
||||
timeout: ResultEnum.TIMEOUT as number,
|
||||
// 跨域时候允许携带凭证
|
||||
withCredentials: true,
|
||||
// post请求指定数据类型以及编码
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' }
|
||||
}
|
||||
|
||||
class RequestHttp {
|
||||
service: AxiosInstance
|
||||
service: AxiosInstance
|
||||
|
||||
public constructor(config: AxiosRequestConfig) {
|
||||
// 创建实例
|
||||
this.service = axios.create(config)
|
||||
public constructor(config: AxiosRequestConfig) {
|
||||
// 创建实例
|
||||
this.service = axios.create(config)
|
||||
|
||||
/**
|
||||
* @description 请求拦截器
|
||||
* 客户端发送请求 -> [请求拦截器] -> 服务器
|
||||
* token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
|
||||
*/
|
||||
this.service.interceptors.request.use(
|
||||
(config: CustomAxiosRequestConfig) => {
|
||||
isFirst = true
|
||||
const userStore = useUserStore()
|
||||
// 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
|
||||
config.loading ?? (config.loading = true)
|
||||
config.loading && showFullScreenLoading()
|
||||
if (config.headers && typeof config.headers.set === 'function') {
|
||||
config.headers.set('Authorization', 'Bearer ' + userStore.accessToken)
|
||||
config.headers.set('Is-Refresh-Token', userStore.isRefreshToken+"")
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
let isFirst = true
|
||||
/**
|
||||
* @description 响应拦截器
|
||||
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
|
||||
*/
|
||||
this.service.interceptors.response.use(
|
||||
async (response: AxiosResponse) => {
|
||||
const { data } = response
|
||||
const userStore = useUserStore()
|
||||
tryHideFullScreenLoading()
|
||||
|
||||
if(data.code === ResultEnum.ACCESSTOKEN_EXPIRED){
|
||||
// 用长token去换短token
|
||||
userStore.setAccessToken(userStore.refreshToken)
|
||||
userStore.setIsRefreshToken(true)
|
||||
const result = await refreshToken()
|
||||
if (result) { //获取新token成功的话
|
||||
// 有新的token后,重新请求
|
||||
userStore.setAccessToken(result.data.accessToken)
|
||||
userStore.setRefreshToken(result.data.refreshToken)
|
||||
userStore.setIsRefreshToken(false)
|
||||
userStore.setExp(1000 * 60 * 60 * 24 * 30)
|
||||
response.config.headers.Authorization = `Bearer ${result.data.accessToken}`//重新请求前需要将更新后的新token更换掉之前无效的token,不然会死循环
|
||||
const resp = await this.service.request(response.config)
|
||||
return resp
|
||||
} else {
|
||||
// 刷新失效,跳转登录页
|
||||
/**
|
||||
* @description 请求拦截器
|
||||
* 客户端发送请求 -> [请求拦截器] -> 服务器
|
||||
* token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
|
||||
*/
|
||||
this.service.interceptors.request.use(
|
||||
(config: CustomAxiosRequestConfig) => {
|
||||
isFirst = true
|
||||
const userStore = useUserStore()
|
||||
// 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
|
||||
config.loading ?? (config.loading = true)
|
||||
config.loading && showFullScreenLoading()
|
||||
if (config.headers && typeof config.headers.set === 'function') {
|
||||
config.headers.set('Authorization', 'Bearer ' + userStore.accessToken)
|
||||
config.headers.set('Is-Refresh-Token', userStore.isRefreshToken + '')
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
// 登陆失效
|
||||
if (data.code === ResultEnum.OVERDUE) {
|
||||
console.log("登陆失效")
|
||||
userStore.setAccessToken('')
|
||||
userStore.setRefreshToken('')
|
||||
userStore.setIsRefreshToken(false)
|
||||
userStore.setUserInfo({ id:'',name: '' })
|
||||
userStore.setExp(0)
|
||||
await router.replace(LOGIN_URL)
|
||||
if(isFirst){//临时处理token失效弹窗多次
|
||||
ElMessage.error(data.message)
|
||||
isFirst = false
|
||||
}
|
||||
return Promise.reject(data)
|
||||
)
|
||||
|
||||
let isFirst = true
|
||||
/**
|
||||
* @description 响应拦截器
|
||||
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
|
||||
*/
|
||||
this.service.interceptors.response.use(
|
||||
async (response: AxiosResponse) => {
|
||||
const { data } = response
|
||||
const userStore = useUserStore()
|
||||
tryHideFullScreenLoading()
|
||||
|
||||
if (data.code === ResultEnum.ACCESSTOKEN_EXPIRED) {
|
||||
// 用长token去换短token
|
||||
userStore.setAccessToken(userStore.refreshToken)
|
||||
userStore.setIsRefreshToken(true)
|
||||
const result = await refreshToken()
|
||||
if (result) {
|
||||
//获取新token成功的话
|
||||
// 有新的token后,重新请求
|
||||
userStore.setAccessToken(result.data.accessToken)
|
||||
userStore.setRefreshToken(result.data.refreshToken)
|
||||
userStore.setIsRefreshToken(false)
|
||||
userStore.setExp(1000 * 60 * 60 * 24 * 30)
|
||||
response.config.headers.Authorization = `Bearer ${result.data.accessToken}` //重新请求前需要将更新后的新token更换掉之前无效的token,不然会死循环
|
||||
const resp = await this.service.request(response.config)
|
||||
return resp
|
||||
} else {
|
||||
// 刷新失效,跳转登录页
|
||||
}
|
||||
}
|
||||
// 登陆失效
|
||||
if (data.code === ResultEnum.OVERDUE) {
|
||||
console.log('登陆失效')
|
||||
userStore.setAccessToken('')
|
||||
userStore.setRefreshToken('')
|
||||
userStore.setIsRefreshToken(false)
|
||||
userStore.setUserInfo({ id: '', name: '' })
|
||||
userStore.setExp(0)
|
||||
await router.replace(LOGIN_URL)
|
||||
if (isFirst) {
|
||||
//临时处理token失效弹窗多次
|
||||
ElMessage.error(data.message)
|
||||
isFirst = false
|
||||
}
|
||||
return Promise.reject(data)
|
||||
}
|
||||
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
|
||||
if (data.code && data.code !== ResultEnum.SUCCESS) {
|
||||
if (data.message.includes('&')) {
|
||||
let formattedMessage = data.message.split('&').join('<br>')
|
||||
if (data.message.includes(':')) {
|
||||
formattedMessage = formattedMessage.replace(':', '')
|
||||
}
|
||||
ElMessage.error({ message: formattedMessage, dangerouslyUseHTMLString: true })
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
ElMessage.error(data.message)
|
||||
return Promise.reject(data)
|
||||
}
|
||||
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
|
||||
|
||||
if (userStore.exp <= Date.now() && userStore.exp !== 0) {
|
||||
userStore.setAccessToken('')
|
||||
userStore.setRefreshToken('')
|
||||
userStore.setIsRefreshToken(false)
|
||||
userStore.setUserInfo({ id: '', name: '' })
|
||||
userStore.setExp(0)
|
||||
ElMessage.error('登录已过期,请重新登录!')
|
||||
await router.replace(LOGIN_URL)
|
||||
return Promise.reject(data)
|
||||
}
|
||||
return data
|
||||
},
|
||||
async (error: AxiosError) => {
|
||||
const { response } = error
|
||||
tryHideFullScreenLoading()
|
||||
console.log('error', error.message)
|
||||
// 请求超时 && 网络错误单独判断,没有 response
|
||||
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
|
||||
if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
|
||||
// 根据服务器响应的错误状态码,做不同的处理
|
||||
if (response) checkStatus(response.status)
|
||||
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
|
||||
if (!window.navigator.onLine) router.replace('/500')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 常用请求方法封装
|
||||
*/
|
||||
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.get(url, { params, ..._object })
|
||||
}
|
||||
|
||||
post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.post(url, params, _object)
|
||||
}
|
||||
|
||||
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.put(url, params, _object)
|
||||
}
|
||||
|
||||
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.delete(url, { params, ..._object })
|
||||
}
|
||||
|
||||
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, { ..._object, responseType: 'blob' })
|
||||
}
|
||||
|
||||
upload(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, {
|
||||
..._object,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对excel的上传,默认返回的是blob类型,Excel没问题时返回json特殊处理
|
||||
*/
|
||||
uploadExcel(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, {
|
||||
..._object,
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 添加SSE连接方法
|
||||
sse(url: string, params?: any): EventSource {
|
||||
const userStore = useUserStore()
|
||||
// 构造带参数的URL
|
||||
let requestUrl = config.baseURL + url
|
||||
if (params) {
|
||||
const searchParams = new URLSearchParams()
|
||||
for (const key in params) {
|
||||
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
||||
searchParams.append(key, String(params[key]))
|
||||
}
|
||||
}
|
||||
requestUrl += '?' + searchParams.toString()
|
||||
}
|
||||
// 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
|
||||
if (data.code && data.code !== ResultEnum.SUCCESS) {
|
||||
if(data.message.includes('&')){
|
||||
let formattedMessage = data.message.split('&').join('<br>');
|
||||
if (data.message.includes(':')) {
|
||||
formattedMessage = formattedMessage.replace(':', '')
|
||||
}
|
||||
ElMessage.error({ message: formattedMessage, dangerouslyUseHTMLString: true });
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
ElMessage.error(data.message)
|
||||
return Promise.reject(data)
|
||||
}
|
||||
// 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
|
||||
|
||||
if (userStore.exp <= Date.now() && userStore.exp !== 0) {
|
||||
userStore.setAccessToken('')
|
||||
userStore.setRefreshToken('')
|
||||
userStore.setIsRefreshToken(false)
|
||||
userStore.setUserInfo({ id:'',name: '' })
|
||||
userStore.setExp(0)
|
||||
ElMessage.error('登录已过期,请重新登录!')
|
||||
await router.replace(LOGIN_URL)
|
||||
return Promise.reject(data)
|
||||
}
|
||||
return data
|
||||
},
|
||||
async (error: AxiosError) => {
|
||||
const { response } = error
|
||||
tryHideFullScreenLoading()
|
||||
console.log('error', error.message)
|
||||
// 请求超时 && 网络错误单独判断,没有 response
|
||||
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试')
|
||||
if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误!请您稍后重试')
|
||||
// 根据服务器响应的错误状态码,做不同的处理
|
||||
if (response) checkStatus(response.status)
|
||||
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
|
||||
if (!window.navigator.onLine) router.replace('/500')
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 常用请求方法封装
|
||||
*/
|
||||
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.get(url, { params, ..._object })
|
||||
}
|
||||
|
||||
post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.post(url, params, _object)
|
||||
}
|
||||
|
||||
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.put(url, params, _object)
|
||||
}
|
||||
|
||||
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
|
||||
return this.service.delete(url, { params, ..._object })
|
||||
}
|
||||
|
||||
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, { ..._object, responseType: 'blob' })
|
||||
}
|
||||
|
||||
upload(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, {
|
||||
..._object,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对excel的上传,默认返回的是blob类型,Excel没问题时返回json特殊处理
|
||||
*/
|
||||
uploadExcel(url: string, params?: object, _object = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, {
|
||||
..._object,
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
responseType: 'blob',
|
||||
})
|
||||
}
|
||||
// 创建EventSource连接
|
||||
const eventSource = new EventSourcePolyfill(requestUrl, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + userStore.accessToken
|
||||
}
|
||||
})
|
||||
|
||||
// 设置默认的Authorization头部
|
||||
eventSource.addEventListener('open', function () {
|
||||
console.log('SSE连接已建立')
|
||||
})
|
||||
|
||||
return eventSource
|
||||
}
|
||||
}
|
||||
|
||||
export default new RequestHttp(config)
|
||||
|
||||
@@ -141,16 +141,6 @@ export const exportPlanCheckData = (params: any) => {
|
||||
)
|
||||
}
|
||||
|
||||
// 导入子检测计划检测结果数据
|
||||
export const importSubPlanCheckData = (params: Plan.ResPlan) => {
|
||||
return http.upload(`/adPlan/importSubPlanCheckData`, params)
|
||||
}
|
||||
|
||||
// 合并子检测计划检测结果数据
|
||||
export const mergeSubPlanCheckData = (params: Plan.ResPlan) => {
|
||||
return http.upload(`/adPlan/mergePlanCheckData?planId=${params.id}`)
|
||||
}
|
||||
|
||||
//根据误差体系id获取测试项
|
||||
export const getPqErrSysTestItemList = (params: {errorSysId : string}) => {
|
||||
return http.get(`/pqErrSys/getTestItems?id=${params.errorSysId}`)
|
||||
@@ -159,4 +149,9 @@ export const getPqErrSysTestItemList = (params: {errorSysId : string}) => {
|
||||
// 获取计划项目成员
|
||||
export const getMemberList = (params: {id : string}) => {
|
||||
return http.get(`/adPlan/getMemberList?planId=${params.id}`)
|
||||
}
|
||||
|
||||
// 导入并合并子检测计划检测结果数据
|
||||
export const importAndMergePlanCheckData = (params: Plan.ResPlan) => {
|
||||
return http.upload(`/adPlan/importAndMergePlanCheckData`, params)
|
||||
}
|
||||
@@ -1,33 +1,48 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" :title="parameter.title" :destroy-on-close="true" width="450px" draggable>
|
||||
<el-form class="drawer-multiColumn-form" label-width="100px">
|
||||
<el-form-item label="">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
action="#"
|
||||
class="upload"
|
||||
:http-request="uploadZip"
|
||||
accept=".zip"
|
||||
:auto-upload="!parameter.confirmMessage"
|
||||
:on-change="handleChange"
|
||||
:on-remove="handleRemove"
|
||||
>
|
||||
<slot name="empty">
|
||||
<el-icon class="el-icon--upload">
|
||||
<upload-filled />
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
<spam>点击上传</spam>
|
||||
</div>
|
||||
</slot>
|
||||
<template #tip>
|
||||
<slot name="tip">
|
||||
<div class="el-upload__tip">请上传 .zip 标准格式文件</div>
|
||||
</slot>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="parameter.title"
|
||||
:destroy-on-close="true"
|
||||
width="450px"
|
||||
:close-on-click-modal="!parameter.progressBar"
|
||||
draggable
|
||||
>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
action="#"
|
||||
class="upload"
|
||||
:limit="1"
|
||||
:http-request="uploadZip"
|
||||
accept=".zip"
|
||||
:auto-upload="!parameter.confirmMessage"
|
||||
:on-change="handleChange"
|
||||
:on-remove="handleRemove"
|
||||
:disabled="fileDisabled"
|
||||
>
|
||||
<slot name="empty">
|
||||
<el-button type="primary" icon="Upload">点击上传</el-button>
|
||||
</slot>
|
||||
<template #tip>
|
||||
<slot name="tip">
|
||||
<div class="el-upload__tip">请上传 .zip 标准格式文件</div>
|
||||
</slot>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-progress
|
||||
v-if="parameter.progressBar"
|
||||
:status="progressData.status"
|
||||
:percentage="progressData.percentage"
|
||||
></el-progress>
|
||||
<el-text v-if="parameter.progressBar && progressData.status === 'exception'" size="small" type="danger">
|
||||
{{ progressData.message }}
|
||||
</el-text>
|
||||
<el-text v-if="parameter.progressBar && progressData.status === 'success'" size="small" type="success">
|
||||
{{ progressData.message }}
|
||||
</el-text>
|
||||
<el-text v-if="parameter.progressBar && progressData.status === ''" size="small" type="info">
|
||||
{{ progressData.message }}
|
||||
</el-text>
|
||||
|
||||
<template #footer v-if="parameter.confirmMessage">
|
||||
<el-button :disabled="disable" type="primary" @click="uploadSubmit">开始导入</el-button>
|
||||
</template>
|
||||
@@ -37,16 +52,20 @@
|
||||
<script setup lang="ts" name="ImportZip">
|
||||
import { ref } from 'vue'
|
||||
import type { UploadInstance, UploadProps, UploadRequestOptions } from 'element-plus'
|
||||
import http from '@/api'
|
||||
|
||||
export interface ZipParameterProps {
|
||||
title: string // 标题
|
||||
patternId?: string // 模式ID
|
||||
planId?: string // 计划ID
|
||||
importApi?: (params: any) => Promise<any> // 批量导入的Api
|
||||
confirmMessage?: string // 提示信息
|
||||
progressBar?: boolean // 进度条
|
||||
}
|
||||
// dialog状态
|
||||
const dialogVisible = ref(false)
|
||||
const disable = ref(true)
|
||||
const fileDisabled = ref(false)
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
|
||||
// 父组件传过来的参数
|
||||
@@ -63,44 +82,141 @@ const acceptParams = (params: ZipParameterProps) => {
|
||||
}
|
||||
|
||||
// 文件上传
|
||||
const uploadZip = async (param: UploadRequestOptions) => {
|
||||
const uploadZip = (param: UploadRequestOptions) => {
|
||||
let zipFormData = new FormData()
|
||||
zipFormData.append('file', param.file)
|
||||
if (parameter.value.patternId) {
|
||||
zipFormData.append('patternId', parameter.value.patternId)
|
||||
}
|
||||
await parameter.value.importApi!(zipFormData).then(res => handleImportResponse(res))
|
||||
if (parameter.value.planId) {
|
||||
zipFormData.append('planId', parameter.value.planId)
|
||||
}
|
||||
if (parameter.value.progressBar) {
|
||||
initSSE()
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
parameter.value.importApi!(zipFormData).then(res => handleImportResponse(res))
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handleImportResponse = (res: any) => {
|
||||
if (res.code === 'A0000') {
|
||||
ElMessage.success('导入成功')
|
||||
if (!parameter.value.progressBar) {
|
||||
if (res.code === 'A0000') {
|
||||
ElMessage.success('导入成功')
|
||||
} else {
|
||||
ElMessage.error(res.message)
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('result', res.data)
|
||||
} else {
|
||||
ElMessage.error(res.message)
|
||||
//
|
||||
}
|
||||
dialogVisible.value = false
|
||||
emit('result', res.data)
|
||||
}
|
||||
|
||||
const uploadSubmit = () => {
|
||||
if (!uploadRef.value) {
|
||||
return ElMessage.warning('请选择文件!')
|
||||
}
|
||||
progressData.value = {
|
||||
percentage: 0,
|
||||
status: '',
|
||||
message: ''
|
||||
}
|
||||
ElMessageBox.confirm(parameter.value.confirmMessage, '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
disable.value = true
|
||||
fileDisabled.value = true
|
||||
uploadRef.value?.submit()
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(() => {
|
||||
disable.value = false
|
||||
fileDisabled.value = false
|
||||
})
|
||||
}
|
||||
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
|
||||
disable.value = uploadFiles.length === 0
|
||||
progressData.value = {
|
||||
percentage: 0,
|
||||
status: '',
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
|
||||
disable.value = uploadFiles.length === 0
|
||||
progressData.value = {
|
||||
percentage: 0,
|
||||
status: '',
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
|
||||
const progressData = ref({
|
||||
percentage: 0,
|
||||
status: '',
|
||||
message: ''
|
||||
})
|
||||
const eventSource = ref<EventSource | null>(null)
|
||||
|
||||
const initSSE = () => {
|
||||
eventSource.value = http.sse('/sse/createSse')
|
||||
|
||||
eventSource.value.onmessage = event => {
|
||||
console.log('收到消息内容是:', event.data)
|
||||
const res = JSON.parse(event.data)
|
||||
progressData.value.percentage = res.data
|
||||
progressData.value.message = res.message
|
||||
if (res.code === 'A0002') {
|
||||
fileDisabled.value = false
|
||||
disable.value = false
|
||||
progressData.value.status = 'exception'
|
||||
ElMessage.error(res.message)
|
||||
}
|
||||
if (progressData.value.percentage === 100) {
|
||||
progressData.value.status = 'success'
|
||||
eventSource.value!.close()
|
||||
ElMessage.success('导入成功')
|
||||
dialogVisible.value = false
|
||||
emit('result', true)
|
||||
}
|
||||
}
|
||||
|
||||
eventSource.value.onerror = error => {
|
||||
console.warn('SSE 连接出错:', error)
|
||||
eventSource.value!.close()
|
||||
}
|
||||
}
|
||||
// 添加一个手动关闭EventSource的函数
|
||||
const closeEventSource = () => {
|
||||
if (eventSource.value) {
|
||||
eventSource.value.close()
|
||||
eventSource.value = null
|
||||
console.log('SSE连接已关闭')
|
||||
}
|
||||
}
|
||||
// 监听 dialogVisible 的变化,确保在对话框关闭时清理资源
|
||||
watch(dialogVisible, newVal => {
|
||||
if (!newVal) {
|
||||
// 延迟执行,确保在组件完全关闭后清理
|
||||
setTimeout(() => {
|
||||
closeEventSource()
|
||||
fileDisabled.value = false
|
||||
disable.value = false
|
||||
progressData.value = {
|
||||
percentage: 0,
|
||||
status: '',
|
||||
message: ''
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
closeEventSource()
|
||||
})
|
||||
defineExpose({
|
||||
acceptParams
|
||||
})
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
<template>
|
||||
<div class='table-box'>
|
||||
<ProTable
|
||||
ref='proTable'
|
||||
:columns='columns'
|
||||
:request-api='getTableList'
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<el-button type='primary' :icon='DataAnalysis'>分析</el-button>
|
||||
<el-button type='primary' :icon='Upload' @click='handleExport'>导出csv</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" :columns="columns" :request-api="getTableList">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<el-button type="primary" :icon="DataAnalysis">分析</el-button>
|
||||
<el-button type="primary" :icon="Upload" @click="handleExport">导出csv</el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='tsx' name='useProTable'>
|
||||
<script setup lang="tsx" name="useProTable">
|
||||
// 根据实际路径调整
|
||||
import TimeControl from '@/components/TimeControl/index.vue'
|
||||
import {type AuditLog} from '@/api/system/log/interface/log.ts'
|
||||
import ProTable from '@/components/ProTable/index.vue'
|
||||
import {DataAnalysis, Upload} from '@element-plus/icons-vue'
|
||||
import type {ColumnProps, ProTableInstance} from '@/components/ProTable/interface'
|
||||
import {reactive, ref} from 'vue'
|
||||
import {getAuditLog, exportCsv} from '@/api/system/log/index.ts'
|
||||
import {useDownload} from "@/hooks/useDownload";
|
||||
import {exportPqDev} from "@/api/device/device";
|
||||
|
||||
import { DataAnalysis, Upload } from '@element-plus/icons-vue'
|
||||
import type { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { exportCsv, getAuditLog } from '@/api/system/log'
|
||||
import { useDownload } from '@/hooks/useDownload'
|
||||
|
||||
// defineOptions({
|
||||
// name: 'log'
|
||||
@@ -39,94 +29,92 @@ const endDate = ref('')
|
||||
const proTable = ref<ProTableInstance>()
|
||||
|
||||
const getTableList = async (params: any) => {
|
||||
let newParams = JSON.parse(JSON.stringify(params))
|
||||
newParams.searchEndTime = endDate.value
|
||||
newParams.searchBeginTime = startDate.value
|
||||
return getAuditLog(newParams)
|
||||
let newParams = JSON.parse(JSON.stringify(params))
|
||||
newParams.searchEndTime = endDate.value
|
||||
newParams.searchBeginTime = startDate.value
|
||||
return getAuditLog(newParams)
|
||||
}
|
||||
|
||||
|
||||
// 表格配置项
|
||||
const columns = reactive<ColumnProps<AuditLog.ReqAuditLogParams>[]>([
|
||||
{type: 'selection', fixed: 'left', width: 70},
|
||||
{type: 'index', fixed: 'left', width: 70, label: '序号'},
|
||||
{
|
||||
prop: 'userName',
|
||||
label: '操作用户',
|
||||
search: {el: 'input'},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
prop: 'ip',
|
||||
label: 'IP',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
prop: 'logTime',
|
||||
label: '记录时间',
|
||||
minWidth: 180,
|
||||
search: {
|
||||
render: () => {
|
||||
return (
|
||||
<div class='flx-flex-start'>
|
||||
<TimeControl
|
||||
include={['日', '周', '月', '自定义']}
|
||||
default={'月'}
|
||||
onUpdate-dates={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
const columns = reactive<ColumnProps[]>([
|
||||
{ type: 'selection', fixed: 'left', width: 70 },
|
||||
{ type: 'index', fixed: 'left', width: 70, label: '序号' },
|
||||
{
|
||||
prop: 'userName',
|
||||
label: '操作用户',
|
||||
search: { el: 'input' },
|
||||
minWidth: 100
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '事件描述',
|
||||
minWidth: 450,
|
||||
render: (scope) => {
|
||||
return (scope.row.userName + '在' + scope.row.logTime + '执行了' + scope.row.reCheckType + scope.row.operate + '操作,结果为' + scope.row.result + '。')
|
||||
{
|
||||
prop: 'ip',
|
||||
label: 'IP',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'logTime',
|
||||
label: '记录时间',
|
||||
minWidth: 180,
|
||||
search: {
|
||||
render: () => {
|
||||
return (
|
||||
<div class="flx-flex-start">
|
||||
<TimeControl
|
||||
include={['日', '周', '月', '自定义']}
|
||||
default={'月'}
|
||||
onUpdate-dates={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '事件描述',
|
||||
minWidth: 450,
|
||||
render(scope) {
|
||||
return `${scope.row.userName}在${scope.row.logTime}执行了【${scope.row.operateType}】${scope.row.operate}操作,结果为${scope.row.result}。`
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'result',
|
||||
label: '事件结果',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'warn',
|
||||
label: '告警标志',
|
||||
minWidth: 100,
|
||||
render: scope => {
|
||||
return (
|
||||
<el-tag type={scope.row.warn == 1 ? 'danger' : 'success'}>
|
||||
{scope.row.warn == 1 ? '已告警' : '未告警'}
|
||||
</el-tag>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'operateType',
|
||||
label: '日志类型',
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'result',
|
||||
label: '事件结果',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
prop: 'warn',
|
||||
label: '告警标志',
|
||||
minWidth: 100,
|
||||
render: scope => {
|
||||
return (
|
||||
<el-tag type={scope.row.warn == 1 ? 'danger' : 'success'}>{scope.row.warn == 1 ? '已告警' : '未告警'}</el-tag>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'operateType',
|
||||
label: '日志类型',
|
||||
width: 100,
|
||||
},
|
||||
])
|
||||
|
||||
// 处理日期变化的回调函数
|
||||
const handleDateChange = (startDateTemp: string, endDateTemp: string) => {
|
||||
startDate.value = startDateTemp
|
||||
endDate.value = endDateTemp
|
||||
startDate.value = startDateTemp
|
||||
endDate.value = endDateTemp
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
// 获取当前的搜索参数
|
||||
let searchParam = proTable.value?.searchParam || {}
|
||||
// 获取当前的搜索参数
|
||||
let searchParam = proTable.value?.searchParam || {}
|
||||
|
||||
// 将开始时间和结束时间添加到搜索参数中
|
||||
searchParam.searchBeginTime = startDate.value
|
||||
searchParam.searchEndTime = endDate.value
|
||||
// 将开始时间和结束时间添加到搜索参数中
|
||||
searchParam.searchBeginTime = startDate.value
|
||||
searchParam.searchEndTime = endDate.value
|
||||
|
||||
useDownload(exportCsv, '日志列表', searchParam, false, '.csv')
|
||||
useDownload(exportCsv, '日志列表', searchParam, false, '.csv')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -62,21 +62,12 @@
|
||||
>
|
||||
数据下载
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="planFormContent && planFormContent?.children.length > 0"
|
||||
v-auth.plan="'add_subplan'"
|
||||
icon="Upload"
|
||||
type="primary"
|
||||
@click="importSubCheckDataClick"
|
||||
>
|
||||
导入检测结果
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!isTabPlanFather && planFormContent && planFormContent?.children.length > 0"
|
||||
v-auth.plan="'add_subplan'"
|
||||
icon="Box"
|
||||
type="primary"
|
||||
@click="mergeSubCheckDataClick"
|
||||
@click="importAndMergePlanCheckDataClick"
|
||||
>
|
||||
数据合并
|
||||
</el-button>
|
||||
@@ -181,17 +172,16 @@ import { reactive, ref } from 'vue'
|
||||
import PlanPopup from '@/views/plan/planList/components/planPopup.vue' // 导入子组件
|
||||
import { Plan } from '@/api/plan/interface'
|
||||
import { useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||
import { ColumnProps, ProTableInstance, SearchRenderScope } from '@/components/ProTable/interface'
|
||||
import type { ColumnProps, ProTableInstance, SearchRenderScope } from '@/components/ProTable/interface'
|
||||
import {
|
||||
deletePlan,
|
||||
exportPlanCheckData,
|
||||
exportSubPlan,
|
||||
getDevListByPlanId,
|
||||
importSubPlanCheckData,
|
||||
mergeSubPlanCheckData,
|
||||
importAndMergePlanCheckData,
|
||||
subPlanBindDev
|
||||
} from '@/api/plan/plan'
|
||||
import { Device } from '@/api/device/interface/device'
|
||||
import { type Device } from '@/api/device/interface/device'
|
||||
import { useDictStore } from '@/stores/modules/dict'
|
||||
import DevTransfer from '@/views/plan/planList/components/devTransfer.vue'
|
||||
import { useHandleData } from '@/hooks/useHandleData'
|
||||
@@ -667,11 +657,14 @@ const exportPlanCheckResultData = async (selectedListIds: string[]) => {
|
||||
})
|
||||
}
|
||||
|
||||
const importSubCheckDataClick = () => {
|
||||
const importAndMergePlanCheckDataClick = () => {
|
||||
const params = {
|
||||
title: '导入计划检测结果',
|
||||
title: '合并检测计划检测结果',
|
||||
confirmMessage: `确定合并【${planFormContent.value?.name}】的检测数据吗`,
|
||||
patternId: dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? '',
|
||||
importApi: importSubPlanCheckData
|
||||
importApi: importAndMergePlanCheckData,
|
||||
progressBar: true,
|
||||
planId: planId.value
|
||||
}
|
||||
planCheckDataImportZip.value?.acceptParams(params)
|
||||
}
|
||||
@@ -682,22 +675,6 @@ const importResult = async (success: boolean | undefined) => {
|
||||
}
|
||||
}
|
||||
|
||||
const mergeSubCheckDataClick = () => {
|
||||
ElMessageBox.confirm(`确定合并【${planFormContent.value?.name}】的检测数据吗`, '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
const params = {
|
||||
id: planFormContent.value?.id
|
||||
}
|
||||
mergeSubPlanCheckData(params).then(res => {
|
||||
ElMessage.success('合并成功')
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
defineExpose({ open, handleTableDataUpdate })
|
||||
|
||||
interface ChildrenPlanProps {
|
||||
|
||||
Reference in New Issue
Block a user