feat: 重试场景新增导入导出功能

This commit is contained in:
xlsea 2024-05-27 16:18:32 +08:00
parent 78d9e4817c
commit 6c139f0ee0
11 changed files with 185 additions and 31 deletions

View File

@ -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';

View File

@ -1,5 +1,12 @@
import type { Ref } from 'vue';
import useBoolean from './use-boolean';
export interface LoadingApiInst {
loading: Ref<boolean>;
startLoading: () => void;
endLoading: () => void;
}
/**
* Loading
*

View File

@ -0,0 +1,3 @@
interface Window {
$loading?: import('@sa/hooks').LoadingApiInst;
}

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, createTextVNode, defineComponent } from 'vue';
import { useLoading } from '@sa/hooks';
import type { AdminLayoutProps } from '../../types';
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
import style from './index.module.css';
@ -8,6 +9,21 @@ defineOptions({
name: 'AdminLayout'
});
const loading = useLoading(false);
const MainContextHolder = defineComponent({
name: 'MainContextHolder',
setup() {
function register() {
window.$loading = loading;
}
register();
return () => createTextVNode();
}
});
const props = withDefaults(defineProps<AdminLayoutProps>(), {
mode: 'vertical',
scrollMode: 'content',
@ -201,13 +217,16 @@ function handleClickMask() {
</template>
<!-- Main Content -->
<main
:id="isContentScroll ? scrollElId : undefined"
class="flex flex-col flex-grow"
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
>
<slot></slot>
</main>
<NSpin class="h-full" content-class="h-full" :show="loading.loading.value">
<MainContextHolder />
<main
:id="isContentScroll ? scrollElId : undefined"
class="h-full flex flex-col flex-grow"
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
>
<slot></slot>
</main>
</NSpin>
<!-- Footer -->
<template v-if="showFooter">

View File

@ -0,0 +1,75 @@
<script setup lang="ts">
import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui';
import { request } from '@/service/request';
defineOptions({
name: 'FlieUpload'
});
interface Props {
accept?: string;
action?: string;
}
defineProps<Props>();
const beforeUpload = (fileData: { file: UploadFileInfo; fileList: UploadFileInfo[] }) => {
if (fileData.file.file?.type !== 'application/json') {
window.$message?.error('只能上传json格式的文件请重新上传');
return false;
}
return true;
};
const handleImport = ({
file,
data: uploadData,
headers,
withCredentials,
action,
onFinish,
onError,
onProgress
}: UploadCustomRequestOptions) => {
const formData = new FormData();
if (uploadData) {
Object.keys(uploadData).forEach(key => {
formData.append(key, uploadData[key as keyof UploadCustomRequestOptions['data']]);
});
}
formData.append('file', file.file as File);
request<string>({
url: action,
method: 'post',
data: formData,
withCredentials,
headers: headers as Record<string, string>,
onUploadProgress: ({ progress }) => {
onProgress({ percent: Math.ceil(progress!) });
}
})
.then(() => {
onFinish();
})
.catch(() => onError());
};
</script>
<template>
<NUpload
:action="action"
:accept="accept"
:custom-request="handleImport"
:show-file-list="false"
@before-upload="beforeUpload"
>
<NButton size="small" ghost type="primary">
<template #icon>
<IconPajamasImport class="text-icon" />
</template>
{{ $t('common.import') }}
</NButton>
</NUpload>
</template>
<style scoped lang="scss"></style>

View File

@ -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',

View File

@ -49,6 +49,7 @@ const local: App.I18n.Schema = {
updateSuccess: '更新成功',
updateFailed: '更新失败',
userCenter: '个人中心',
downloadFail: '文件下载失败',
success: '成功',
fail: '失败',
stop: '停止',

View File

@ -299,6 +299,7 @@ declare namespace App {
updateSuccess: string;
updateFailed: string;
userCenter: string;
downloadFail: string;
success: string;
fail: string;
stop: string;

View File

@ -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 {

57
src/utils/download.ts Normal file
View File

@ -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());
};

View File

@ -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'));
}
</script>
@ -210,26 +211,13 @@ async function beforeUpload(fileData: { file: UploadFileInfo; fileList: UploadFi
@refresh="getData"
>
<template #addAfter>
<NUpload action="https://www.mocky.io/v2/5e4bafc63100007100d8b70f" @before-upload="beforeUpload">
<NButton size="small" ghost type="primary">
<template #icon>
<IconPajamasImport class="text-icon" />
</template>
{{ $t('common.import') }}
</NButton>
</NUpload>
<NButton size="small" ghost type="primary">
<FileUpload action="/scene-config/import" accept="application/json" />
<NButton size="small" ghost type="primary" @click="handleExport">
<template #icon>
<IconPajamasExport class="text-icon" />
</template>
{{ $t('common.export') }}
</NButton>
<NButton size="small" ghost type="primary">
<template #icon>
<IconIcRoundContentCopy class="text-icon" />
</template>
{{ $t('common.batchCopy') }}
</NButton>
</template>
</TableHeaderOperation>
</template>