no message

main
HUOJIN\霍先森 2025-05-10 14:43:49 +08:00
parent 316d40908d
commit efb8bc00f8
7 changed files with 454 additions and 364 deletions

View File

@ -10,11 +10,12 @@
<template> <template>
<a-config-provider <a-config-provider
:locale="antdLocale" :locale="antdLocale"
:theme="{ :theme="{
algorithm: compactFlag ? theme.compactAlgorithm : theme.defaultAlgorithm, algorithm: compactFlag ? theme.compactAlgorithm : theme.defaultAlgorithm,
token: { token: {
fontFamily: '微软雅黑, Arial, sans-serif', fontFamily:'-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji',
fontsize: '14px',
colorPrimary: themeColors[colorIndex].primaryColor, colorPrimary: themeColors[colorIndex].primaryColor,
colorLink: themeColors[colorIndex].primaryColor, colorLink: themeColors[colorIndex].primaryColor,
colorLinkActive: themeColors[colorIndex].activeColor, colorLinkActive: themeColors[colorIndex].activeColor,
@ -33,68 +34,86 @@
}, },
}, },
}" }"
:transformCellText="transformCellText" :transformCellText="transformCellText"
> >
<!---全局loading---> <!---全局loading--->
<a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large"> <a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large">
<!--- 路由 --> <!--- 路由 -->
<RouterView /> <RouterView/>
</a-spin> </a-spin>
</a-config-provider> </a-config-provider>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { computed, h, useSlots } from 'vue'; import {computed, h, useSlots, watch} from 'vue';
import { messages } from '/@/i18n'; import {messages} from '/@/i18n';
import { useAppConfigStore } from '/@/store/modules/system/app-config'; import {useAppConfigStore} from '/@/store/modules/system/app-config';
import { useSpinStore } from '/@/store/modules/system/spin'; import {useSpinStore} from '/@/store/modules/system/spin';
import { theme } from 'ant-design-vue'; import {theme} from 'ant-design-vue';
import { themeColors } from '/@/theme/color.js'; import {themeColors} from '/@/theme/color.js';
import { Popover } from 'ant-design-vue'; import {Popover} from 'ant-design-vue';
import SmartCopyIcon from '/@/components/framework/smart-copy-icon/index.vue'; import SmartCopyIcon from '/@/components/framework/smart-copy-icon/index.vue';
import _ from 'lodash'; import _ from 'lodash';
const slots = useSlots(); const slots = useSlots();
const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale); const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale); const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale);
dayjs.locale(dayjsLocale); dayjs.locale(dayjsLocale);
// loading // loading
let spinStore = useSpinStore(); let spinStore = useSpinStore();
const spinning = computed(() => spinStore.loading); const spinning = computed(() => spinStore.loading);
// //
const compactFlag = computed(() => useAppConfigStore().compactFlag); const compactFlag = computed(() => useAppConfigStore().compactFlag);
// //
const colorIndex = computed(() => { const colorIndex = computed(() => {
return useAppConfigStore().colorIndex; return useAppConfigStore().colorIndex;
}); });
// //
const borderRadius = computed(() => { const borderRadius = computed(() => {
return useAppConfigStore().borderRadius; return useAppConfigStore().borderRadius;
}); });
function transformCellText({ text, column, record, index }) {
if (column && column.textEllipsisFlag === true) { function transformCellText({text, column, record, index}) {
return h( if (column && column.textEllipsisFlag === true) {
return h(
Popover, Popover,
{ placement: 'bottom' }, {placement: 'bottom'},
{ {
default: () => default: () =>
h('div', { style: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, id: `${column.dataIndex}${index}` }, text), h('div', {
style: {whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis'},
id: `${column.dataIndex}${index}`
}, text),
content: () => content: () =>
h('div', { style: { display: 'flex' } }, [ h('div', {style: {display: 'flex'}}, [
h('div', text), h('div', text),
h(SmartCopyIcon, { value: document.getElementById(`${column.dataIndex}${index}`).innerText }), h(SmartCopyIcon, {value: document.getElementById(`${column.dataIndex}${index}`).innerText}),
]), ]),
} }
); );
} else { } else {
return text; return text;
}
} }
}
// CSS
/*watch(() => colorIndex.value, (newVal) => {
document.documentElement.style.setProperty('--primary-color', themeColors[newVal].primaryColor);
}, { immediate: true });*/
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
:deep(.ant-table-column-sorters) { /*:deep(.ant-table-column-sorters) {
align-items: flex-start !important; align-items: flex-start !important;
} }*/
/*:deep(.ant-table-thead > tr > th) {
background-color: var(--primary-color) !important;
color: black !important;
}*/
:deep(.ant-table-thead > tr > th) {
background-color: #FAFAFA !important;
}
</style> </style>

View File

@ -5,7 +5,7 @@
* @Date: 2024-12-26 15:35:23 * @Date: 2024-12-26 15:35:23
* @Copyright * @Copyright
*/ */
import {postRequest, getRequest, getDownload} from '/@/lib/axios'; import {postRequest, getRequest, getDownload2} from '/@/lib/axios';
export const addressApi = { export const addressApi = {
@ -71,8 +71,8 @@ export const addressApi = {
/** /**
* @author hj * @author hj
*/ */
exportAddress: (taskId: string) => { exportAddress: (taskId: string,signal?: AbortSignal) => {
return getDownload(`/address/exportAddress/${taskId}`, {}); return getDownload2(`/address/exportAddress/${taskId}`, {},signal);
} }
}; };

View File

@ -5,7 +5,7 @@
* @Date: 2024-11-25 17:08:18 * @Date: 2024-11-25 17:08:18
* @Copyright * @Copyright
*/ */
import {postRequest, getRequest, getDownload} from '/@/lib/axios'; import {postRequest, getRequest, getDownload2} from '/@/lib/axios';
export const itemApi = { export const itemApi = {
@ -62,8 +62,8 @@ export const itemApi = {
/** /**
* @author hj * @author hj
*/ */
exportItems: (taskId: string) => { exportItems: (taskId: string,signal?: AbortSignal) => {
return getDownload(`/item/exportItems/${taskId}`,{}); return getDownload2(`/item/exportItems/${taskId}`,{},signal);
} },
}; };

View File

@ -7,12 +7,12 @@
* @Email: lab1024@163.com * @Email: lab1024@163.com
* @Copyright 1024 https://1024lab.net Since 2012 * @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 axios from 'axios';
import { localRead } from '/@/utils/local-util'; import {localRead} from '/@/utils/local-util';
import { useUserStore } from '/@/store/modules/system/user'; import {useUserStore} from '/@/store/modules/system/user';
import { decryptData, encryptData } from './encrypt'; import {decryptData, encryptData} from './encrypt';
import { DATA_TYPE_ENUM } from '../constants/common-const'; import {DATA_TYPE_ENUM} from '../constants/common-const';
import _ from 'lodash'; import _ from 'lodash';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js'; import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
@ -21,109 +21,109 @@ const TOKEN_HEADER = 'Authorization';
// 创建axios对象 // 创建axios对象
const smartAxios = axios.create({ const smartAxios = axios.create({
baseURL: import.meta.env.VITE_APP_API_URL, baseURL: import.meta.env.VITE_APP_API_URL,
}); });
// 退出系统 // 退出系统
function logout() { function logout() {
useUserStore().logout(); useUserStore().logout();
location.href = '/'; location.href = '/';
} }
// ================================= 请求拦截器 ================================= // ================================= 请求拦截器 =================================
smartAxios.interceptors.request.use( smartAxios.interceptors.request.use(
(config) => { (config) => {
// 在发送请求之前消息头加入token token // 在发送请求之前消息头加入token token
const token = localRead(LocalStorageKeyConst.USER_TOKEN); const token = localRead(LocalStorageKeyConst.USER_TOKEN);
if (token) { if (token) {
config.headers[TOKEN_HEADER] = 'Bearer ' + token; config.headers[TOKEN_HEADER] = 'Bearer ' + token;
} else { } else {
delete config.headers[TOKEN_HEADER]; delete config.headers[TOKEN_HEADER];
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
} }
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
); );
// ================================= 响应拦截器 ================================= // ================================= 响应拦截器 =================================
// 添加响应拦截器 // 添加响应拦截器
smartAxios.interceptors.response.use( smartAxios.interceptors.response.use(
(response) => { (response) => {
// 根据content-type ,判断是否为 json 数据 // 根据content-type ,判断是否为 json 数据
let contentType = response.headers['content-type'] ? response.headers['content-type'] : response.headers['Content-Type']; let contentType = response.headers['content-type'] ? response.headers['content-type'] : response.headers['Content-Type'];
if (contentType.indexOf('application/json') === -1) { if (contentType.indexOf('application/json') === -1) {
return Promise.resolve(response); return Promise.resolve(response);
} }
// 如果是json数据 // 如果是json数据
if (response.data && response.data instanceof Blob) { if (response.data && response.data instanceof Blob) {
return Promise.reject(response.data); return Promise.reject(response.data);
} }
// 如果是加密数据 // 如果是加密数据
if (response.data.dataType === DATA_TYPE_ENUM.ENCRYPT.value) { if (response.data.dataType === DATA_TYPE_ENUM.ENCRYPT.value) {
response.data.encryptData = response.data.data; response.data.encryptData = response.data.data;
let decryptStr = decryptData(response.data.data); let decryptStr = decryptData(response.data.data);
if (decryptStr) { if (decryptStr) {
response.data.data = JSON.parse(decryptStr); response.data.data = JSON.parse(decryptStr);
} }
} }
const res = response.data; const res = response.data;
if (res.code && res.code !== 1) { if (res.code && res.code !== 1) {
// `token` 过期或者账号已在别处登录 // `token` 过期或者账号已在别处登录
if (res.code === 30007 || res.code === 30008) { if (res.code === 30007 || res.code === 30008) {
message.destroy(); message.destroy();
message.error('您没有登录,请重新登录'); message.error('您没有登录,请重新登录');
setTimeout(logout, 300); setTimeout(logout, 300);
return Promise.reject(response); return Promise.reject(response);
} }
// 等保安全的登录提醒 // 等保安全的登录提醒
if (res.code === 30010 || res.code === 30011) { if (res.code === 30010 || res.code === 30011) {
Modal.error({ Modal.error({
title: '重要提醒', title: '重要提醒',
content: res.msg, content: res.msg,
}); });
return Promise.reject(response); return Promise.reject(response);
} }
// 长时间未操作系统,需要重新登录 // 长时间未操作系统,需要重新登录
if (res.code === 30012) { if (res.code === 30012) {
Modal.error({ Modal.error({
title: '重要提醒', title: '重要提醒',
content: res.msg, content: res.msg,
onOk: logout, onOk: logout,
}); });
setTimeout(logout, 3000); setTimeout(logout, 3000);
return Promise.reject(response); return Promise.reject(response);
} }
message.destroy(); message.destroy();
message.error(res.msg); message.error(res.msg);
return Promise.reject(response); return Promise.reject(response);
} else { } else {
return Promise.resolve(res); 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等 ================================= // ================================= 对外提供请求方法通用请求get post, 下载download等 =================================
@ -132,7 +132,7 @@ smartAxios.interceptors.response.use(
* get * get
*/ */
export const getRequest = (url, params) => { 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 * @param config
*/ */
export const request = (config) => { export const request = (config) => {
return smartAxios.request(config); return smartAxios.request(config);
}; };
/** /**
* post * post
*/ */
export const postRequest = (url, data) => { export const postRequest = (url, data) => {
return request({ return request({
data, data,
url, url,
method: 'post', method: 'post',
}); });
}; };
// ================================= 加密 ================================= // ================================= 加密 =================================
@ -160,91 +160,107 @@ export const postRequest = (url, data) => {
* post * post
*/ */
export const postEncryptRequest = (url, data) => { export const postEncryptRequest = (url, data) => {
return request({ return request({
data: { encryptData: encryptData(data) }, data: {encryptData: encryptData(data)},
url, url,
method: 'post', method: 'post',
}); });
}; };
// ================================= 下载 ================================= // ================================= 下载 =================================
export const postDownload = function (url, data) { export const postDownload = function (url, data) {
request({ request({
method: 'post', method: 'post',
url, url,
data, data,
responseType: 'blob', responseType: 'blob',
})
.then((data) => {
handleDownloadData(data);
}) })
.catch((error) => { .then((data) => {
handleDownloadError(error); handleDownloadData(data);
}); })
.catch((error) => {
handleDownloadError(error);
});
}; };
/** /**
* *
*/ */
export const getDownload = function (url, params) { export const getDownload = function (url, params) {
request({ request({
method: 'get', method: 'get',
url, url,
params, params,
responseType: 'blob', responseType: 'blob',
})
.then((data) => {
handleDownloadData(data);
}) })
.catch((error) => { .then((data) => {
handleDownloadError(error); 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) { function handleDownloadError(error) {
if (error instanceof Blob) { if (error instanceof Blob) {
const fileReader = new FileReader(); const fileReader = new FileReader();
fileReader.readAsText(error); fileReader.readAsText(error);
fileReader.onload = () => { fileReader.onload = () => {
const msg = fileReader.result; const msg = fileReader.result;
const jsonMsg = JSON.parse(msg); const jsonMsg = JSON.parse(msg);
message.destroy(); message.destroy();
message.error(jsonMsg.msg); message.error(jsonMsg.msg);
}; };
} else { } else {
message.destroy(); message.destroy();
message.error('网络发生错误', error); message.error('网络发生错误', error);
} }
} }
function handleDownloadData(response) { function handleDownloadData(response) {
if (!response) { if (!response) {
return; 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 url = window.URL.createObjectURL(new Blob([response.data], {type: contentType}));
let link = document.createElement('a'); let link = document.createElement('a');
link.style.display = 'none'; link.style.display = 'none';
link.href = url; link.href = url;
// 从消息头获取文件名 // 从消息头获取文件名
let str = _.isUndefined(response.headers['content-disposition']) let str = _.isUndefined(response.headers['content-disposition'])
? response.headers['Content-Disposition'].split(';')[1] ? response.headers['Content-Disposition'].split(';')[1]
: 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]; let filename = _.isUndefined(str.split('fileName=')[1]) ? str.split('filename=')[1] : str.split('fileName=')[1];
link.setAttribute('download', decodeURIComponent(filename)); link.setAttribute('download', decodeURIComponent(filename));
// 触发点击下载 // 触发点击下载
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
// 下载完释放 // 下载完释放
document.body.removeChild(link); // 下载完成移除元素 document.body.removeChild(link); // 下载完成移除元素
window.URL.revokeObjectURL(url); // 释放掉blob对象 window.URL.revokeObjectURL(url); // 释放掉blob对象
} }

View File

@ -1,63 +1,63 @@
export const themeColors = [ export const themeColors = [
// 蓝色 // 蓝色
{ {
primaryColor: '#1677ff', primaryColor: '#1677ff',
activeColor: '#0958d9', activeColor: '#0958d9',
hoverColor: '#4096ff', hoverColor: '#4096ff',
}, },
// 蓝色2 // 蓝色2
{ {
primaryColor: '#2F54EB', primaryColor: '#2F54EB',
activeColor: '#1d39c4', activeColor: '#1d39c4',
hoverColor: '#597ef7', hoverColor: '#597ef7',
}, },
// 绿色 // 绿色
{ {
primaryColor: '#00b96b', primaryColor: '#00b96b',
activeColor: '#00945b', activeColor: '#00945b',
hoverColor: '#20c77c', hoverColor: '#20c77c',
}, },
// 红色 // 红色
{ {
primaryColor: '#F5222D', primaryColor: '#F5222D',
activeColor: '#cf1322', activeColor: '#cf1322',
hoverColor: '#ff4d4f', hoverColor: '#ff4d4f',
}, },
// 青色 // 青色
{ {
primaryColor: '#13c2c2', primaryColor: '#13c2c2',
activeColor: '#08979c', activeColor: '#08979c',
hoverColor: '#36cfc9', hoverColor: '#36cfc9',
}, },
// 粉色 // 粉色
{ {
primaryColor: '#EB2F96', primaryColor: '#EB2F96',
activeColor: '#c41d7f', activeColor: '#c41d7f',
hoverColor: '#f759ab', hoverColor: '#f759ab',
}, },
// 紫色 // 紫色
{ {
primaryColor: '#722ED1', primaryColor: '#722ED1',
activeColor: '#531dab', activeColor: '#531dab',
hoverColor: '#9254de', hoverColor: '#9254de',
}, },
// 极光绿 // 极光绿
{ {
primaryColor: '#52c41a', primaryColor: '#52c41a',
activeColor: '#389e0d', activeColor: '#389e0d',
hoverColor: '#73d13d', hoverColor: '#73d13d',
}, },
// 深绿 // 深绿
{ {
primaryColor: '#009688', primaryColor: '#009688',
activeColor: '#007069', activeColor: '#007069',
hoverColor: '#1aa391', hoverColor: '#1aa391',
}, },
// 橙色 // 橙色
{ {
primaryColor: '#fa541c', primaryColor: '#fa541c',
activeColor: '#d4380d', activeColor: '#d4380d',
hoverColor: '#ff7a45', hoverColor: '#ff7a45',
}, },
]; ];

View File

@ -59,7 +59,7 @@
<a-modal <a-modal
v-model:open="open" v-model:open="open"
@cancel="onClose" @cancel="onCancel"
:closable="false" :closable="false"
:maskClosable="false" :maskClosable="false"
:destroyOnClose="true"> :destroyOnClose="true">
@ -75,7 +75,7 @@
</template> </template>
<template #footer> <template #footer>
<a-space> <a-space>
<a-button @click="onClose"></a-button> <a-button @click="onCancel"></a-button>
</a-space> </a-space>
</template> </template>
</a-modal> </a-modal>
@ -101,7 +101,7 @@
bordered bordered
:loading="tableLoading" :loading="tableLoading"
:pagination="false" :pagination="false"
:scroll="{ x: 1500, y: 350 }" :scroll="{ x: 1500, y: 450 }"
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }" :row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
> >
<template #bodyCell="{ text, record, column }"> <template #bodyCell="{ text, record, column }">
@ -443,53 +443,102 @@ async function onImportAddress() {
} }
// //
const progressTitle = ref('文件下载中,请稍等...'); const progressTitle = ref('文件下载中,请稍等...');//
const progressPercent = ref(0);// const progressPercent = ref(0);//
const progressStatus = ref('active');// const progressStatus = ref('active');
const currentTaskId = ref('');//ID const currentTaskId = ref('');//ID
const open = ref<boolean>(false);// const open = ref(false);//
const timerId = ref<NodeJS.Timeout | null>(null); //
const isExporting = ref(false); //
const abortController = ref<AbortController | null>(null);//
const onExportAddress = async () => { const onExportAddress = async () => {
if (isExporting.value) return;
try { try {
isExporting.value = true;
abortController.value = new AbortController(); //
open.value = true; open.value = true;
//ID resetProgressState(); //
//
const {data: taskId} = await addressApi.createExportTask(); const {data: taskId} = await addressApi.createExportTask();
currentTaskId.value = taskId; currentTaskId.value = taskId;
progressStatus.value = 'active';
// //
await addressApi.exportAddress(currentTaskId.value); try {
addressApi.exportAddress(taskId, abortController.value.signal)
} catch (error) {
handleExportError();
}
//
if (timerId.value) {
clearInterval(timerId.value);
}
// //
const timer = setInterval(async () => { timerId.value = setInterval(async () => {
try { try {
//
const {data: progress} = await addressApi.getExportProgress(currentTaskId.value); const {data: progress} = await addressApi.getExportProgress(currentTaskId.value);
progressPercent.value = progress; progressPercent.value = progress;
if (progress >= 100) { if (progress >= 100) {
clearInterval(timer); handleExportSuccess();
progressStatus.value = 'success';
progressTitle.value = '文件下载完成';
// 2
setTimeout(() => {
open.value = false;
progressPercent.value = 0;
progressTitle.value = '文件下载中,请稍等...';
}, 2000);
} }
} catch (error) { } catch (error) {
clearInterval(timer); handleExportError();
progressStatus.value = 'exception';
progressPercent.value = 0;
} }
}, 1000); // }, 1000);
} catch (error) { } catch (error) {
message.error('导出失败'); handleExportError();
} finally {
isExporting.value = false;
} }
}; };
const onClose = () => { //
const handleExportSuccess = () => {
if (timerId.value) {
clearInterval(timerId.value);
}
progressStatus.value = 'success';
progressTitle.value = '文件下载完成';
setTimeout(() => {
open.value = false;
resetProgressState();
}, 1000);
};
//
const handleExportError = () => {
if (timerId.value) clearInterval(timerId.value);
progressStatus.value = 'exception';
progressPercent.value = 0;
open.value = false; open.value = false;
resetProgressState();
};
//
const resetProgressState = () => {
progressPercent.value = 0;
progressStatus.value = 'active';
progressTitle.value = '文件下载中,请稍等...';
currentTaskId.value = '';
};
//
const onCancel = () => {
//
open.value = false;
if (abortController.value) {
abortController.value.abort();
abortController.value = null;
}
}; };
onMounted(queryData); onMounted(queryData);

View File

@ -65,7 +65,7 @@
<a-modal <a-modal
v-model:open="open" v-model:open="open"
@cancel="onClose" @cancel="onCancel"
:closable="false" :closable="false"
:maskClosable="false" :maskClosable="false"
:destroyOnClose="true"> :destroyOnClose="true">
@ -81,7 +81,7 @@
</template> </template>
<template #footer> <template #footer>
<a-space> <a-space>
<a-button @click="onClose"></a-button> <a-button @click="onCancel"></a-button>
</a-space> </a-space>
</template> </template>
</a-modal> </a-modal>
@ -498,97 +498,103 @@ async function onImportItems() {
} }
// //
const progressTitle = ref('文件下载中,请稍等...'); const progressTitle = ref('文件下载中,请稍等...');//
const progressPercent = ref(0); const progressPercent = ref(0);//
const progressStatus = ref('active'); const progressStatus = ref('active');
const currentTaskId = ref(''); const currentTaskId = ref('');//ID
const open = ref(false); const open = ref(false);//
const pollTimer = ref<NodeJS.Timeout>(); // 使ref const timerId = ref<NodeJS.Timeout | null>(null); //
const isExporting = ref(false); //
const abortController = ref<AbortController | null>(null);//
// const onExportItems = async () => {
const resetProgress = () => { if (isExporting.value) return;
progressPercent.value = 0;
progressStatus.value = 'active';
progressTitle.value = '文件下载中,请稍等...';
};
//
const startProgressPolling = async (taskId: string) => {
try { try {
// 1 isExporting.value = true;
pollTimer.value = setInterval(async () => { 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 { try {
const { data: progress } = await addressApi.getExportProgress(taskId); const {data: progress} = await addressApi.getExportProgress(currentTaskId.value);
progressPercent.value = progress; progressPercent.value = progress;
if (progress >= 100) { if (progress >= 100) {
handleComplete(); handleExportSuccess();
clearInterval(pollTimer.value);
} }
} catch (error) { } catch (error) {
handlePollingError(); handleExportError();
clearInterval(pollTimer.value);
} }
}, 200); // 1 }, 1000);
} catch (error) { } catch (error) {
handlePollingError(); handleExportError();
} finally {
isExporting.value = false;
} }
}; };
// //
const handleComplete = () => { const handleExportSuccess = () => {
if (timerId.value) {
clearInterval(timerId.value);
}
progressStatus.value = 'success'; progressStatus.value = 'success';
progressTitle.value = '文件下载完成'; progressTitle.value = '文件下载完成';
// 使 setTimeout(() => {
open.value = false; open.value = false;
resetProgress(); resetProgressState();
}, 1000);
}; };
// //
const handlePollingError = () => { const handleExportError = () => {
if (timerId.value) clearInterval(timerId.value);
progressStatus.value = 'exception'; progressStatus.value = 'exception';
progressPercent.value = 0; progressPercent.value = 0;
progressTitle.value = '获取进度失败'; open.value = false;
resetProgressState();
}; };
const onExportItems = async () => { //
try { const resetProgressState = () => {
// progressPercent.value = 0;
if (pollTimer.value) clearInterval(pollTimer.value); progressStatus.value = 'active';
progressTitle.value = '文件下载中,请稍等...';
currentTaskId.value = '';
};
// ID //
resetProgress(); const onCancel = () => {
currentTaskId.value = ''; //
open.value = true; open.value = false;
if (abortController.value) {
// ID abortController.value.abort();
const { data: taskId } = await addressApi.createExportTask(); abortController.value = null;
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 onClose = () => {
resetProgress();
};
onMounted(queryData); onMounted(queryData);
</script> </script>