feat: 重试场景新增导入导出功能
This commit is contained in:
parent
78d9e4817c
commit
6c139f0ee0
@ -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';
|
||||
|
@ -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
|
||||
*
|
||||
|
3
packages/materials/src/libs/admin-layout/global.d.ts
vendored
Normal file
3
packages/materials/src/libs/admin-layout/global.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
interface Window {
|
||||
$loading?: import('@sa/hooks').LoadingApiInst;
|
||||
}
|
@ -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">
|
||||
|
75
src/components/common/file-upload.vue
Normal file
75
src/components/common/file-upload.vue
Normal 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>
|
@ -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',
|
||||
|
@ -49,6 +49,7 @@ const local: App.I18n.Schema = {
|
||||
updateSuccess: '更新成功',
|
||||
updateFailed: '更新失败',
|
||||
userCenter: '个人中心',
|
||||
downloadFail: '文件下载失败',
|
||||
success: '成功',
|
||||
fail: '失败',
|
||||
stop: '停止',
|
||||
|
1
src/typings/app.d.ts
vendored
1
src/typings/app.d.ts
vendored
@ -299,6 +299,7 @@ declare namespace App {
|
||||
updateSuccess: string;
|
||||
updateFailed: string;
|
||||
userCenter: string;
|
||||
downloadFail: string;
|
||||
success: string;
|
||||
fail: string;
|
||||
stop: string;
|
||||
|
1
src/typings/global.d.ts
vendored
1
src/typings/global.d.ts
vendored
@ -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
57
src/utils/download.ts
Normal 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());
|
||||
};
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user