diff --git a/nc_wms_web/src/App.vue b/nc_wms_web/src/App.vue index 6198035..c7d0af1 100644 --- a/nc_wms_web/src/App.vue +++ b/nc_wms_web/src/App.vue @@ -10,11 +10,12 @@ diff --git a/nc_wms_web/src/api/business/wms/base/address/address-api.ts b/nc_wms_web/src/api/business/wms/base/address/address-api.ts index fdbf794..e462acf 100644 --- a/nc_wms_web/src/api/business/wms/base/address/address-api.ts +++ b/nc_wms_web/src/api/business/wms/base/address/address-api.ts @@ -5,7 +5,7 @@ * @Date: 2024-12-26 15:35:23 * @Copyright 友仓 */ -import {postRequest, getRequest, getDownload} from '/@/lib/axios'; +import {postRequest, getRequest, getDownload2} from '/@/lib/axios'; export const addressApi = { @@ -71,8 +71,8 @@ export const addressApi = { /** * 导出 @author hj */ - exportAddress: (taskId: string) => { - return getDownload(`/address/exportAddress/${taskId}`, {}); + exportAddress: (taskId: string,signal?: AbortSignal) => { + return getDownload2(`/address/exportAddress/${taskId}`, {},signal); } }; diff --git a/nc_wms_web/src/api/business/wms/base/item/item-api.ts b/nc_wms_web/src/api/business/wms/base/item/item-api.ts index f0cda22..b87b931 100644 --- a/nc_wms_web/src/api/business/wms/base/item/item-api.ts +++ b/nc_wms_web/src/api/business/wms/base/item/item-api.ts @@ -5,7 +5,7 @@ * @Date: 2024-11-25 17:08:18 * @Copyright 友仓 */ -import {postRequest, getRequest, getDownload} from '/@/lib/axios'; +import {postRequest, getRequest, getDownload2} from '/@/lib/axios'; export const itemApi = { @@ -62,8 +62,8 @@ export const itemApi = { /** * 导出 @author hj */ - exportItems: (taskId: string) => { - return getDownload(`/item/exportItems/${taskId}`,{}); - } + exportItems: (taskId: string,signal?: AbortSignal) => { + return getDownload2(`/item/exportItems/${taskId}`,{},signal); + }, }; diff --git a/nc_wms_web/src/lib/axios.ts b/nc_wms_web/src/lib/axios.ts index 84da1c4..962a734 100644 --- a/nc_wms_web/src/lib/axios.ts +++ b/nc_wms_web/src/lib/axios.ts @@ -7,12 +7,12 @@ * @Email: lab1024@163.com * @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012 */ -import { message, Modal } from 'ant-design-vue'; +import {message, Modal} from 'ant-design-vue'; import axios from 'axios'; -import { localRead } from '/@/utils/local-util'; -import { useUserStore } from '/@/store/modules/system/user'; -import { decryptData, encryptData } from './encrypt'; -import { DATA_TYPE_ENUM } from '../constants/common-const'; +import {localRead} from '/@/utils/local-util'; +import {useUserStore} from '/@/store/modules/system/user'; +import {decryptData, encryptData} from './encrypt'; +import {DATA_TYPE_ENUM} from '../constants/common-const'; import _ from 'lodash'; import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js'; @@ -21,109 +21,109 @@ const TOKEN_HEADER = 'Authorization'; // 创建axios对象 const smartAxios = axios.create({ - baseURL: import.meta.env.VITE_APP_API_URL, + baseURL: import.meta.env.VITE_APP_API_URL, }); // 退出系统 function logout() { - useUserStore().logout(); - location.href = '/'; + useUserStore().logout(); + location.href = '/'; } // ================================= 请求拦截器 ================================= smartAxios.interceptors.request.use( - (config) => { - // 在发送请求之前消息头加入token token - const token = localRead(LocalStorageKeyConst.USER_TOKEN); - if (token) { - config.headers[TOKEN_HEADER] = 'Bearer ' + token; - } else { - delete config.headers[TOKEN_HEADER]; + (config) => { + // 在发送请求之前消息头加入token token + const token = localRead(LocalStorageKeyConst.USER_TOKEN); + if (token) { + config.headers[TOKEN_HEADER] = 'Bearer ' + token; + } else { + delete config.headers[TOKEN_HEADER]; + } + return config; + }, + (error) => { + // 对请求错误做些什么 + return Promise.reject(error); } - return config; - }, - (error) => { - // 对请求错误做些什么 - return Promise.reject(error); - } ); // ================================= 响应拦截器 ================================= // 添加响应拦截器 smartAxios.interceptors.response.use( - (response) => { - // 根据content-type ,判断是否为 json 数据 - let contentType = response.headers['content-type'] ? response.headers['content-type'] : response.headers['Content-Type']; - if (contentType.indexOf('application/json') === -1) { - return Promise.resolve(response); - } + (response) => { + // 根据content-type ,判断是否为 json 数据 + let contentType = response.headers['content-type'] ? response.headers['content-type'] : response.headers['Content-Type']; + if (contentType.indexOf('application/json') === -1) { + return Promise.resolve(response); + } - // 如果是json数据 - if (response.data && response.data instanceof Blob) { - return Promise.reject(response.data); - } + // 如果是json数据 + if (response.data && response.data instanceof Blob) { + return Promise.reject(response.data); + } - // 如果是加密数据 - if (response.data.dataType === DATA_TYPE_ENUM.ENCRYPT.value) { - response.data.encryptData = response.data.data; - let decryptStr = decryptData(response.data.data); - if (decryptStr) { - response.data.data = JSON.parse(decryptStr); - } - } + // 如果是加密数据 + if (response.data.dataType === DATA_TYPE_ENUM.ENCRYPT.value) { + response.data.encryptData = response.data.data; + let decryptStr = decryptData(response.data.data); + if (decryptStr) { + response.data.data = JSON.parse(decryptStr); + } + } - const res = response.data; - if (res.code && res.code !== 1) { - // `token` 过期或者账号已在别处登录 - if (res.code === 30007 || res.code === 30008) { - message.destroy(); - message.error('您没有登录,请重新登录'); - setTimeout(logout, 300); - return Promise.reject(response); - } + const res = response.data; + if (res.code && res.code !== 1) { + // `token` 过期或者账号已在别处登录 + if (res.code === 30007 || res.code === 30008) { + message.destroy(); + message.error('您没有登录,请重新登录'); + setTimeout(logout, 300); + return Promise.reject(response); + } - // 等保安全的登录提醒 - if (res.code === 30010 || res.code === 30011) { - Modal.error({ - title: '重要提醒', - content: res.msg, - }); - return Promise.reject(response); - } + // 等保安全的登录提醒 + if (res.code === 30010 || res.code === 30011) { + Modal.error({ + title: '重要提醒', + content: res.msg, + }); + return Promise.reject(response); + } - // 长时间未操作系统,需要重新登录 - if (res.code === 30012) { - Modal.error({ - title: '重要提醒', - content: res.msg, - onOk: logout, - }); - setTimeout(logout, 3000); - return Promise.reject(response); - } - message.destroy(); - message.error(res.msg); - return Promise.reject(response); - } else { - return Promise.resolve(res); + // 长时间未操作系统,需要重新登录 + if (res.code === 30012) { + Modal.error({ + title: '重要提醒', + content: res.msg, + onOk: logout, + }); + setTimeout(logout, 3000); + return Promise.reject(response); + } + message.destroy(); + message.error(res.msg); + return Promise.reject(response); + } else { + return Promise.resolve(res); + } + }, + (error) => { + // 对响应错误做点什么 + if (error.message.indexOf('timeout') !== -1) { + message.destroy(); + message.error('网络超时'); + } else if (error.message === 'Network Error') { + message.destroy(); + message.error('网络连接错误'); + } else if (error.message.indexOf('Request') !== -1) { + message.destroy(); + message.error('网络发生错误'); + } + return Promise.reject(error); } - }, - (error) => { - // 对响应错误做点什么 - if (error.message.indexOf('timeout') !== -1) { - message.destroy(); - message.error('网络超时'); - } else if (error.message === 'Network Error') { - message.destroy(); - message.error('网络连接错误'); - } else if (error.message.indexOf('Request') !== -1) { - message.destroy(); - message.error('网络发生错误'); - } - return Promise.reject(error); - } ); // ================================= 对外提供请求方法:通用请求,get, post, 下载download等 ================================= @@ -132,7 +132,7 @@ smartAxios.interceptors.response.use( * get请求 */ export const getRequest = (url, params) => { - return request({ url, method: 'get', params }); + return request({url, method: 'get', params}); }; /** @@ -140,18 +140,18 @@ export const getRequest = (url, params) => { * @param config */ export const request = (config) => { - return smartAxios.request(config); + return smartAxios.request(config); }; /** * post请求 */ export const postRequest = (url, data) => { - return request({ - data, - url, - method: 'post', - }); + return request({ + data, + url, + method: 'post', + }); }; // ================================= 加密 ================================= @@ -160,91 +160,107 @@ export const postRequest = (url, data) => { * 加密请求参数的post请求 */ export const postEncryptRequest = (url, data) => { - return request({ - data: { encryptData: encryptData(data) }, - url, - method: 'post', - }); + return request({ + data: {encryptData: encryptData(data)}, + url, + method: 'post', + }); }; // ================================= 下载 ================================= export const postDownload = function (url, data) { - request({ - method: 'post', - url, - data, - responseType: 'blob', - }) - .then((data) => { - handleDownloadData(data); + request({ + method: 'post', + url, + data, + responseType: 'blob', }) - .catch((error) => { - handleDownloadError(error); - }); + .then((data) => { + handleDownloadData(data); + }) + .catch((error) => { + handleDownloadError(error); + }); }; /** * 文件下载 */ export const getDownload = function (url, params) { - request({ - method: 'get', - url, - params, - responseType: 'blob', - }) - .then((data) => { - handleDownloadData(data); + request({ + method: 'get', + url, + params, + responseType: 'blob', }) - .catch((error) => { - handleDownloadError(error); - }); + .then((data) => { + handleDownloadData(data); + }) + .catch((error) => { + handleDownloadError(error); + }); +}; + +export const getDownload2 = function (url: string, params: any, signal?: AbortSignal) { + request({ + method: 'get', + url, + params, + responseType: 'blob', + signal // 注入取消信号 + }) + .then((data) => { + handleDownloadData(data); + }) + .catch((error) => { + message.success('取消成功'); + }); }; function handleDownloadError(error) { - if (error instanceof Blob) { - const fileReader = new FileReader(); - fileReader.readAsText(error); - fileReader.onload = () => { - const msg = fileReader.result; - const jsonMsg = JSON.parse(msg); - message.destroy(); - message.error(jsonMsg.msg); - }; - } else { - message.destroy(); - message.error('网络发生错误', error); - } + if (error instanceof Blob) { + const fileReader = new FileReader(); + fileReader.readAsText(error); + fileReader.onload = () => { + const msg = fileReader.result; + const jsonMsg = JSON.parse(msg); + message.destroy(); + message.error(jsonMsg.msg); + }; + } else { + message.destroy(); + message.error('网络发生错误', error); + } } function handleDownloadData(response) { - if (!response) { - return; - } + if (!response) { + return; + } - // 获取返回类型 - let contentType = _.isUndefined(response.headers['content-type']) ? response.headers['Content-Type'] : response.headers['content-type']; + // 获取返回类型 + let contentType = _.isUndefined(response.headers['content-type']) ? response.headers['Content-Type'] : response.headers['content-type']; - // 构建下载数据 - let url = window.URL.createObjectURL(new Blob([response.data], { type: contentType })); - let link = document.createElement('a'); - link.style.display = 'none'; - link.href = url; + // 构建下载数据 + let url = window.URL.createObjectURL(new Blob([response.data], {type: contentType})); + let link = document.createElement('a'); + link.style.display = 'none'; + link.href = url; - // 从消息头获取文件名 - let str = _.isUndefined(response.headers['content-disposition']) - ? response.headers['Content-Disposition'].split(';')[1] - : response.headers['content-disposition'].split(';')[1]; + // 从消息头获取文件名 + let str = _.isUndefined(response.headers['content-disposition']) + ? response.headers['Content-Disposition'].split(';')[1] + : response.headers['content-disposition'].split(';')[1]; - let filename = _.isUndefined(str.split('fileName=')[1]) ? str.split('filename=')[1] : str.split('fileName=')[1]; - link.setAttribute('download', decodeURIComponent(filename)); + let filename = _.isUndefined(str.split('fileName=')[1]) ? str.split('filename=')[1] : str.split('fileName=')[1]; + link.setAttribute('download', decodeURIComponent(filename)); - // 触发点击下载 - document.body.appendChild(link); - link.click(); + // 触发点击下载 + document.body.appendChild(link); + link.click(); - // 下载完释放 - document.body.removeChild(link); // 下载完成移除元素 - window.URL.revokeObjectURL(url); // 释放掉blob对象 + // 下载完释放 + document.body.removeChild(link); // 下载完成移除元素 + window.URL.revokeObjectURL(url); // 释放掉blob对象 } diff --git a/nc_wms_web/src/theme/color.ts b/nc_wms_web/src/theme/color.ts index 72609ad..e3b805a 100644 --- a/nc_wms_web/src/theme/color.ts +++ b/nc_wms_web/src/theme/color.ts @@ -1,63 +1,63 @@ export const themeColors = [ - // 蓝色 - { - primaryColor: '#1677ff', - activeColor: '#0958d9', - hoverColor: '#4096ff', - }, - // 蓝色2 - { - primaryColor: '#2F54EB', - activeColor: '#1d39c4', - hoverColor: '#597ef7', - }, - // 绿色 - { - primaryColor: '#00b96b', - activeColor: '#00945b', - hoverColor: '#20c77c', - }, + // 蓝色 + { + primaryColor: '#1677ff', + activeColor: '#0958d9', + hoverColor: '#4096ff', + }, + // 蓝色2 + { + primaryColor: '#2F54EB', + activeColor: '#1d39c4', + hoverColor: '#597ef7', + }, + // 绿色 + { + primaryColor: '#00b96b', + activeColor: '#00945b', + hoverColor: '#20c77c', + }, - // 红色 - { - primaryColor: '#F5222D', - activeColor: '#cf1322', - hoverColor: '#ff4d4f', - }, - // 青色 - { - primaryColor: '#13c2c2', - activeColor: '#08979c', - hoverColor: '#36cfc9', - }, - // 粉色 - { - primaryColor: '#EB2F96', - activeColor: '#c41d7f', - hoverColor: '#f759ab', - }, - // 紫色 - { - primaryColor: '#722ED1', - activeColor: '#531dab', - hoverColor: '#9254de', - }, - // 极光绿 - { - primaryColor: '#52c41a', - activeColor: '#389e0d', - hoverColor: '#73d13d', - }, - // 深绿 - { - primaryColor: '#009688', - activeColor: '#007069', - hoverColor: '#1aa391', - }, - // 橙色 - { - primaryColor: '#fa541c', - activeColor: '#d4380d', - hoverColor: '#ff7a45', - }, + // 红色 + { + primaryColor: '#F5222D', + activeColor: '#cf1322', + hoverColor: '#ff4d4f', + }, + // 青色 + { + primaryColor: '#13c2c2', + activeColor: '#08979c', + hoverColor: '#36cfc9', + }, + // 粉色 + { + primaryColor: '#EB2F96', + activeColor: '#c41d7f', + hoverColor: '#f759ab', + }, + // 紫色 + { + primaryColor: '#722ED1', + activeColor: '#531dab', + hoverColor: '#9254de', + }, + // 极光绿 + { + primaryColor: '#52c41a', + activeColor: '#389e0d', + hoverColor: '#73d13d', + }, + // 深绿 + { + primaryColor: '#009688', + activeColor: '#007069', + hoverColor: '#1aa391', + }, + // 橙色 + { + primaryColor: '#fa541c', + activeColor: '#d4380d', + hoverColor: '#ff7a45', + }, ]; diff --git a/nc_wms_web/src/views/business/wms/base/address/address-list.vue b/nc_wms_web/src/views/business/wms/base/address/address-list.vue index a403970..bf62a62 100644 --- a/nc_wms_web/src/views/business/wms/base/address/address-list.vue +++ b/nc_wms_web/src/views/business/wms/base/address/address-list.vue @@ -59,7 +59,7 @@ @@ -75,7 +75,7 @@ @@ -101,7 +101,7 @@ bordered :loading="tableLoading" :pagination="false" - :scroll="{ x: 1500, y: 350 }" + :scroll="{ x: 1500, y: 450 }" :row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }" > @@ -498,97 +498,103 @@ async function onImportItems() { } //导出 -const progressTitle = ref('文件下载中,请稍等...'); -const progressPercent = ref(0); +const progressTitle = ref('文件下载中,请稍等...');//标题 +const progressPercent = ref(0);//进度百分比 const progressStatus = ref('active'); -const currentTaskId = ref(''); -const open = ref(false); -const pollTimer = ref(); // 使用ref保存定时器引用 +const currentTaskId = ref('');//当前任务ID +const open = ref(false);//打开进度条窗口 +const timerId = ref(null); // 定时器引用 +const isExporting = ref(false); // 防止重复提交 +const abortController = ref(null);// 用于取消请求 -// 重置所有进度状态 -const resetProgress = () => { - progressPercent.value = 0; - progressStatus.value = 'active'; - progressTitle.value = '文件下载中,请稍等...'; -}; +const onExportItems = async () => { + if (isExporting.value) return; -// 轮询检查进度 -const startProgressPolling = async (taskId: string) => { try { - // 启动轮询(调整为更合理的1秒间隔) - pollTimer.value = setInterval(async () => { + isExporting.value = true; + abortController.value = new AbortController(); // 创建控制器 + open.value = true; + resetProgressState(); // 重置进度状态 + + // 创建导出任务 + const {data: taskId} = await addressApi.createExportTask(); + currentTaskId.value = taskId; + + // 发起导出请求 + try { + itemApi.exportItems(taskId, abortController.value.signal) + } catch (error) { + handleExportError(); + } + + // 清理之前的定时器 + if (timerId.value) { + clearInterval(timerId.value); + } + + // 启动轮询 + timerId.value = setInterval(async () => { try { - const { data: progress } = await addressApi.getExportProgress(taskId); + const {data: progress} = await addressApi.getExportProgress(currentTaskId.value); + progressPercent.value = progress; if (progress >= 100) { - handleComplete(); - clearInterval(pollTimer.value); + handleExportSuccess(); } + } catch (error) { - handlePollingError(); - clearInterval(pollTimer.value); + handleExportError(); } - }, 200); // 调整为1秒减少请求压力 + }, 1000); + } catch (error) { - handlePollingError(); + handleExportError(); + } finally { + isExporting.value = false; } }; -// 处理完成状态 -const handleComplete = () => { +// 处理导出成功 +const handleExportSuccess = () => { + if (timerId.value) { + clearInterval(timerId.value); + } progressStatus.value = 'success'; progressTitle.value = '文件下载完成'; - // 立即关闭模态框,使用提示告知用户 - open.value = false; - resetProgress(); + setTimeout(() => { + open.value = false; + resetProgressState(); + }, 1000); }; -// 处理轮询错误 -const handlePollingError = () => { +// 处理导出失败 +const handleExportError = () => { + if (timerId.value) clearInterval(timerId.value); progressStatus.value = 'exception'; progressPercent.value = 0; - progressTitle.value = '获取进度失败'; + open.value = false; + resetProgressState(); }; -const onExportItems = async () => { - try { - // 清除已有定时器防止重复请求 - if (pollTimer.value) clearInterval(pollTimer.value); +// 重置进度状态 +const resetProgressState = () => { + progressPercent.value = 0; + progressStatus.value = 'active'; + progressTitle.value = '文件下载中,请稍等...'; + currentTaskId.value = ''; +}; - // 重置状态(包括清除旧任务ID) - resetProgress(); - currentTaskId.value = ''; - open.value = true; - - // 获取任务ID - const { data: taskId } = await addressApi.createExportTask(); - currentTaskId.value = taskId; - - // 启动导出(添加超时处理) - await Promise.race([ - itemApi.exportItems(taskId), - new Promise((_, reject) => - setTimeout(() => reject(new Error('导出超时')), 30_000) - ) - ]); - - // 开始轮询进度 - await startProgressPolling(taskId); - } catch (error: any) { - open.value = false; - message.error('导出失败: ' + error.message); - - // 异常时清除定时器 - if (pollTimer.value) clearInterval(pollTimer.value); - resetProgress(); +//取消 +const onCancel = () => { + // 中止进行中的请求 + open.value = false; + if (abortController.value) { + abortController.value.abort(); + abortController.value = null; } }; -const onClose = () => { - resetProgress(); -}; - onMounted(queryData);