diff --git a/frontend/package.json b/frontend/package.json
index 167ae9c..94f2c90 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -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",
diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts
index e634f4e..1a9636b 100644
--- a/frontend/src/api/index.ts
+++ b/frontend/src/api/index.ts
@@ -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('
')
+ 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(url: string, params?: object, _object = {}): Promise> {
+ return this.service.get(url, { params, ..._object })
+ }
+
+ post(url: string, params?: object | string, _object = {}): Promise> {
+ return this.service.post(url, params, _object)
+ }
+
+ put(url: string, params?: object, _object = {}): Promise> {
+ return this.service.put(url, params, _object)
+ }
+
+ delete(url: string, params?: any, _object = {}): Promise> {
+ return this.service.delete(url, { params, ..._object })
+ }
+
+ download(url: string, params?: object, _object = {}): Promise {
+ return this.service.post(url, params, { ..._object, responseType: 'blob' })
+ }
+
+ upload(url: string, params?: object, _object = {}): Promise {
+ return this.service.post(url, params, {
+ ..._object,
+ headers: { 'Content-Type': 'multipart/form-data' }
+ })
+ }
+
+ /**
+ * 针对excel的上传,默认返回的是blob类型,Excel没问题时返回json特殊处理
+ */
+ uploadExcel(url: string, params?: object, _object = {}): Promise {
+ 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('
');
- 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(url: string, params?: object, _object = {}): Promise> {
- return this.service.get(url, { params, ..._object })
- }
-
- post(url: string, params?: object | string, _object = {}): Promise> {
- return this.service.post(url, params, _object)
- }
-
- put(url: string, params?: object, _object = {}): Promise> {
- return this.service.put(url, params, _object)
- }
-
- delete(url: string, params?: any, _object = {}): Promise> {
- return this.service.delete(url, { params, ..._object })
- }
-
- download(url: string, params?: object, _object = {}): Promise {
- return this.service.post(url, params, { ..._object, responseType: 'blob' })
- }
-
- upload(url: string, params?: object, _object = {}): Promise {
- return this.service.post(url, params, {
- ..._object,
- headers: { 'Content-Type': 'multipart/form-data' }
- })
- }
-
- /**
- * 针对excel的上传,默认返回的是blob类型,Excel没问题时返回json特殊处理
- */
- uploadExcel(url: string, params?: object, _object = {}): Promise {
- 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)
diff --git a/frontend/src/api/plan/plan.ts b/frontend/src/api/plan/plan.ts
index a232ae7..43b0f1e 100644
--- a/frontend/src/api/plan/plan.ts
+++ b/frontend/src/api/plan/plan.ts
@@ -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)
}
\ No newline at end of file
diff --git a/frontend/src/components/ImportZip/index.vue b/frontend/src/components/ImportZip/index.vue
index 2304cd2..8c2590a 100644
--- a/frontend/src/components/ImportZip/index.vue
+++ b/frontend/src/components/ImportZip/index.vue
@@ -1,33 +1,48 @@
-
-
-
-
-
-
-
-
-
- 点击上传
-
-
-
-
- 请上传 .zip 标准格式文件
-
-
-
-
-
+
+
+
+ 点击上传
+
+
+
+ 请上传 .zip 标准格式文件
+
+
+
+
+
+ {{ progressData.message }}
+
+
+ {{ progressData.message }}
+
+
+ {{ progressData.message }}
+
+
开始导入
@@ -37,16 +52,20 @@
-
+
diff --git a/frontend/src/views/plan/planList/components/childrenPlan.vue b/frontend/src/views/plan/planList/components/childrenPlan.vue
index 6dfada1..61adf36 100644
--- a/frontend/src/views/plan/planList/components/childrenPlan.vue
+++ b/frontend/src/views/plan/planList/components/childrenPlan.vue
@@ -62,21 +62,12 @@
>
数据下载
-
- 导入检测结果
-
数据合并
@@ -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 {