feat: 重试场景新增导入导出功能
This commit is contained in:
parent
78d9e4817c
commit
6c139f0ee0
@ -1,10 +1,11 @@
|
|||||||
import useBoolean from './use-boolean';
|
import useBoolean from './use-boolean';
|
||||||
import useLoading from './use-loading';
|
import useLoading, { type LoadingApiInst } from './use-loading';
|
||||||
import useCountDown from './use-count-down';
|
import useCountDown from './use-count-down';
|
||||||
import useContext from './use-context';
|
import useContext from './use-context';
|
||||||
import useSvgIconRender from './use-svg-icon-render';
|
import useSvgIconRender from './use-svg-icon-render';
|
||||||
import useHookTable from './use-table';
|
import useHookTable from './use-table';
|
||||||
|
|
||||||
|
export { LoadingApiInst };
|
||||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
||||||
|
|
||||||
export * from './use-signal';
|
export * from './use-signal';
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
|
import type { Ref } from 'vue';
|
||||||
import useBoolean from './use-boolean';
|
import useBoolean from './use-boolean';
|
||||||
|
|
||||||
|
export interface LoadingApiInst {
|
||||||
|
loading: Ref<boolean>;
|
||||||
|
startLoading: () => void;
|
||||||
|
endLoading: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loading
|
* 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">
|
<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 type { AdminLayoutProps } from '../../types';
|
||||||
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
|
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
|
||||||
import style from './index.module.css';
|
import style from './index.module.css';
|
||||||
@ -8,6 +9,21 @@ defineOptions({
|
|||||||
name: 'AdminLayout'
|
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>(), {
|
const props = withDefaults(defineProps<AdminLayoutProps>(), {
|
||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
scrollMode: 'content',
|
scrollMode: 'content',
|
||||||
@ -201,13 +217,16 @@ function handleClickMask() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main
|
<NSpin class="h-full" content-class="h-full" :show="loading.loading.value">
|
||||||
:id="isContentScroll ? scrollElId : undefined"
|
<MainContextHolder />
|
||||||
class="flex flex-col flex-grow"
|
<main
|
||||||
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
|
:id="isContentScroll ? scrollElId : undefined"
|
||||||
>
|
class="h-full flex flex-col flex-grow"
|
||||||
<slot></slot>
|
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
|
||||||
</main>
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</main>
|
||||||
|
</NSpin>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<template v-if="showFooter">
|
<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',
|
updateSuccess: 'Update Success',
|
||||||
updateFailed: 'Update Failed',
|
updateFailed: 'Update Failed',
|
||||||
userCenter: 'User Center',
|
userCenter: 'User Center',
|
||||||
|
downloadFail: 'File download failed',
|
||||||
success: 'Success',
|
success: 'Success',
|
||||||
fail: 'Fail',
|
fail: 'Fail',
|
||||||
stop: 'Stop',
|
stop: 'Stop',
|
||||||
|
@ -49,6 +49,7 @@ const local: App.I18n.Schema = {
|
|||||||
updateSuccess: '更新成功',
|
updateSuccess: '更新成功',
|
||||||
updateFailed: '更新失败',
|
updateFailed: '更新失败',
|
||||||
userCenter: '个人中心',
|
userCenter: '个人中心',
|
||||||
|
downloadFail: '文件下载失败',
|
||||||
success: '成功',
|
success: '成功',
|
||||||
fail: '失败',
|
fail: '失败',
|
||||||
stop: '停止',
|
stop: '停止',
|
||||||
|
1
src/typings/app.d.ts
vendored
1
src/typings/app.d.ts
vendored
@ -299,6 +299,7 @@ declare namespace App {
|
|||||||
updateSuccess: string;
|
updateSuccess: string;
|
||||||
updateFailed: string;
|
updateFailed: string;
|
||||||
userCenter: string;
|
userCenter: string;
|
||||||
|
downloadFail: string;
|
||||||
success: string;
|
success: string;
|
||||||
fail: string;
|
fail: string;
|
||||||
stop: 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;
|
$message?: import('naive-ui').MessageProviderInst;
|
||||||
/** Notification instance */
|
/** Notification instance */
|
||||||
$notification?: import('naive-ui').NotificationProviderInst;
|
$notification?: import('naive-ui').NotificationProviderInst;
|
||||||
|
$loading?: import('@sa/hooks').LoadingApiInst;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ViewTransition {
|
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 { NButton, NTag } from 'naive-ui';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useBoolean } from '@sa/hooks';
|
import { useBoolean } from '@sa/hooks';
|
||||||
import type { UploadFileInfo } from 'naive-ui';
|
|
||||||
import { fetchGetRetryScenePageList, fetchUpdateSceneStatus } from '@/service/api';
|
import { fetchGetRetryScenePageList, fetchUpdateSceneStatus } from '@/service/api';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||||
import { DelayLevel, backOffRecord, routeKeyRecord } from '@/constants/business';
|
import { DelayLevel, backOffRecord, routeKeyRecord } from '@/constants/business';
|
||||||
import StatusSwitch from '@/components/common/status-switch.vue';
|
import StatusSwitch from '@/components/common/status-switch.vue';
|
||||||
|
import { downloadFetch } from '@/utils/download';
|
||||||
import SceneOperateDrawer from './modules/scene-operate-drawer.vue';
|
import SceneOperateDrawer from './modules/scene-operate-drawer.vue';
|
||||||
import SceneSearch from './modules/scene-search.vue';
|
import SceneSearch from './modules/scene-search.vue';
|
||||||
import SceneDetailDrawer from './modules/scene-detail-drawer.vue';
|
import SceneDetailDrawer from './modules/scene-detail-drawer.vue';
|
||||||
@ -32,6 +32,11 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
|
|||||||
sceneStatus: null
|
sceneStatus: null
|
||||||
},
|
},
|
||||||
columns: () => [
|
columns: () => [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
align: 'center',
|
||||||
|
width: 48
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'sceneName',
|
key: 'sceneName',
|
||||||
title: $t('page.retryScene.sceneName'),
|
title: $t('page.retryScene.sceneName'),
|
||||||
@ -181,12 +186,8 @@ function triggerInterval(backOff: number, maxRetryCount: number) {
|
|||||||
return desc.substring(1, desc.length);
|
return desc.substring(1, desc.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function beforeUpload(fileData: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
|
function handleExport() {
|
||||||
if (fileData.file.file?.type !== 'application/json') {
|
downloadFetch('/scene-config/export', checkedRowKeys.value, $t('page.retryScene.title'));
|
||||||
window.$message?.error('只能上传json格式的文件,请重新上传');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -210,26 +211,13 @@ async function beforeUpload(fileData: { file: UploadFileInfo; fileList: UploadFi
|
|||||||
@refresh="getData"
|
@refresh="getData"
|
||||||
>
|
>
|
||||||
<template #addAfter>
|
<template #addAfter>
|
||||||
<NUpload action="https://www.mocky.io/v2/5e4bafc63100007100d8b70f" @before-upload="beforeUpload">
|
<FileUpload action="/scene-config/import" accept="application/json" />
|
||||||
<NButton size="small" ghost type="primary">
|
<NButton size="small" ghost type="primary" @click="handleExport">
|
||||||
<template #icon>
|
|
||||||
<IconPajamasImport class="text-icon" />
|
|
||||||
</template>
|
|
||||||
{{ $t('common.import') }}
|
|
||||||
</NButton>
|
|
||||||
</NUpload>
|
|
||||||
<NButton size="small" ghost type="primary">
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconPajamasExport class="text-icon" />
|
<IconPajamasExport class="text-icon" />
|
||||||
</template>
|
</template>
|
||||||
{{ $t('common.export') }}
|
{{ $t('common.export') }}
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton size="small" ghost type="primary">
|
|
||||||
<template #icon>
|
|
||||||
<IconIcRoundContentCopy class="text-icon" />
|
|
||||||
</template>
|
|
||||||
{{ $t('common.batchCopy') }}
|
|
||||||
</NButton>
|
|
||||||
</template>
|
</template>
|
||||||
</TableHeaderOperation>
|
</TableHeaderOperation>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user