diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index a6a330b..d210301 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -1,10 +1,11 @@ import useBoolean from './use-boolean'; -import useLoading from './use-loading'; +import useLoading, { type LoadingApiInst } from './use-loading'; import useCountDown from './use-count-down'; import useContext from './use-context'; import useSvgIconRender from './use-svg-icon-render'; import useHookTable from './use-table'; +export { LoadingApiInst }; export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable }; export * from './use-signal'; diff --git a/packages/hooks/src/use-loading.ts b/packages/hooks/src/use-loading.ts index b8f89ad..27e331e 100644 --- a/packages/hooks/src/use-loading.ts +++ b/packages/hooks/src/use-loading.ts @@ -1,5 +1,12 @@ +import type { Ref } from 'vue'; import useBoolean from './use-boolean'; +export interface LoadingApiInst { + loading: Ref; + startLoading: () => void; + endLoading: () => void; +} + /** * Loading * diff --git a/packages/materials/src/libs/admin-layout/global.d.ts b/packages/materials/src/libs/admin-layout/global.d.ts new file mode 100644 index 0000000..26884bf --- /dev/null +++ b/packages/materials/src/libs/admin-layout/global.d.ts @@ -0,0 +1,3 @@ +interface Window { + $loading?: import('@sa/hooks').LoadingApiInst; +} diff --git a/packages/materials/src/libs/admin-layout/index.vue b/packages/materials/src/libs/admin-layout/index.vue index 543f7dc..c526699 100644 --- a/packages/materials/src/libs/admin-layout/index.vue +++ b/packages/materials/src/libs/admin-layout/index.vue @@ -1,5 +1,6 @@ + + + + diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index e47c07e..a7810c0 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -49,6 +49,7 @@ const local: App.I18n.Schema = { updateSuccess: 'Update Success', updateFailed: 'Update Failed', userCenter: 'User Center', + downloadFail: 'File download failed', success: 'Success', fail: 'Fail', stop: 'Stop', diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 70415e2..1e3e025 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -49,6 +49,7 @@ const local: App.I18n.Schema = { updateSuccess: '更新成功', updateFailed: '更新失败', userCenter: '个人中心', + downloadFail: '文件下载失败', success: '成功', fail: '失败', stop: '停止', diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts index f6f86e4..e616be8 100644 --- a/src/typings/app.d.ts +++ b/src/typings/app.d.ts @@ -299,6 +299,7 @@ declare namespace App { updateSuccess: string; updateFailed: string; userCenter: string; + downloadFail: string; success: string; fail: string; stop: string; diff --git a/src/typings/global.d.ts b/src/typings/global.d.ts index 1454782..c9900ae 100644 --- a/src/typings/global.d.ts +++ b/src/typings/global.d.ts @@ -9,6 +9,7 @@ interface Window { $message?: import('naive-ui').MessageProviderInst; /** Notification instance */ $notification?: import('naive-ui').NotificationProviderInst; + $loading?: import('@sa/hooks').LoadingApiInst; } interface ViewTransition { diff --git a/src/utils/download.ts b/src/utils/download.ts new file mode 100644 index 0000000..703ee75 --- /dev/null +++ b/src/utils/download.ts @@ -0,0 +1,57 @@ +import { localStg } from '@/utils/storage'; +import { getServiceBaseURL } from '@/utils/service'; +import { $t } from '@/locales'; + +const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; +const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); + +export function downloadJson(data: Blob, fileName: string, withRandomName = true) { + try { + let realFileName = fileName; + if (withRandomName) { + realFileName = `${fileName}-${new Date().getTime()}.json`; + } + downloadByData(data, realFileName); + } catch { + window.$message?.error($t('common.downloadFail')); + } finally { + window.$loading?.endLoading(); + } +} + +export function downloadByData(data: BlobPart, filename: string) { + const blobData = [data]; + const blob = new Blob(blobData, { type: 'application/octet-stream' }); + + const blobURL = window.URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.style.display = 'none'; + tempLink.href = blobURL; + tempLink.setAttribute('download', filename); + if (typeof tempLink.download === 'undefined') { + tempLink.setAttribute('target', '_blank'); + } + document.body.appendChild(tempLink); + tempLink.click(); + document.body.removeChild(tempLink); + window.URL.revokeObjectURL(blobURL); +} + +export const downloadFetch = (url: string, body: any, fileName: string) => { + window.$loading?.startLoading(); + const token = localStg.get('token')!; + const namespaceId = localStg.get('namespaceId')!; + fetch(`${baseURL}${url}?t=${new Date().getTime()}`, { + method: 'post', + body: JSON.stringify(body), + headers: { + 'SNAIL-JOB-AUTH': token, + 'SNAIL-JOB-NAMESPACE-ID': namespaceId, + 'Content-Type': 'application/json;charset=utf-8;' + } + }) + .then(async response => response.blob()) + .then(data => downloadJson(data, fileName)) + .catch(() => window.$message?.error($t('common.downloadFail'))) + .finally(() => window.$loading?.endLoading()); +}; diff --git a/src/views/retry/scene/index.vue b/src/views/retry/scene/index.vue index c3f68ac..8a314c3 100644 --- a/src/views/retry/scene/index.vue +++ b/src/views/retry/scene/index.vue @@ -2,13 +2,13 @@ import { NButton, NTag } from 'naive-ui'; import { ref } from 'vue'; import { useBoolean } from '@sa/hooks'; -import type { UploadFileInfo } from 'naive-ui'; import { fetchGetRetryScenePageList, fetchUpdateSceneStatus } from '@/service/api'; import { $t } from '@/locales'; import { useAppStore } from '@/store/modules/app'; import { useTable, useTableOperate } from '@/hooks/common/table'; import { DelayLevel, backOffRecord, routeKeyRecord } from '@/constants/business'; import StatusSwitch from '@/components/common/status-switch.vue'; +import { downloadFetch } from '@/utils/download'; import SceneOperateDrawer from './modules/scene-operate-drawer.vue'; import SceneSearch from './modules/scene-search.vue'; import SceneDetailDrawer from './modules/scene-detail-drawer.vue'; @@ -32,6 +32,11 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP sceneStatus: null }, columns: () => [ + { + type: 'selection', + align: 'center', + width: 48 + }, { key: 'sceneName', title: $t('page.retryScene.sceneName'), @@ -181,12 +186,8 @@ function triggerInterval(backOff: number, maxRetryCount: number) { return desc.substring(1, desc.length); } -async function beforeUpload(fileData: { file: UploadFileInfo; fileList: UploadFileInfo[] }) { - if (fileData.file.file?.type !== 'application/json') { - window.$message?.error('只能上传json格式的文件,请重新上传'); - return false; - } - return true; +function handleExport() { + downloadFetch('/scene-config/export', checkedRowKeys.value, $t('page.retryScene.title')); } @@ -210,26 +211,13 @@ async function beforeUpload(fileData: { file: UploadFileInfo; fileList: UploadFi @refresh="getData" >