系统数据迁移(系统基础数据导出、导入)功能开发
This commit is contained in:
45
src/api/system/dataMigration/index.ts
Normal file
45
src/api/system/dataMigration/index.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { ErrorMessageMode } from '/#/axios';
|
||||
|
||||
enum Api {
|
||||
ExportDatas= '/system/dataMigration/exportDatas',
|
||||
DownloadDatas='/system/dataMigration/downloadDatas',
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 系统配置迁移-导出资源
|
||||
*/
|
||||
export async function exportDatas(params, mode: ErrorMessageMode = 'modal') {
|
||||
return defHttp.post(
|
||||
{
|
||||
url: Api.ExportDatas,
|
||||
data:params,
|
||||
},
|
||||
{
|
||||
errorMessageMode: mode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description: 根据uuid(目录名称)下载数据
|
||||
*/
|
||||
export async function downloadDatas(
|
||||
params?: object,
|
||||
mode: ErrorMessageMode = 'modal'
|
||||
) {
|
||||
return defHttp.download(
|
||||
{
|
||||
url: Api.DownloadDatas+"/"+params.uuid,
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
},
|
||||
{
|
||||
errorMessageMode: mode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
BIN
src/views/system/dataMigration/assets/sysconfig_import.png
Normal file
BIN
src/views/system/dataMigration/assets/sysconfig_import.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
262
src/views/system/dataMigration/components/export/index.vue
Normal file
262
src/views/system/dataMigration/components/export/index.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="export">
|
||||
<div class="l-export" ><span @click.stop="openConfirmDialog">导出所选项目{{configType}}</span></div>
|
||||
<div class="list-wrapper">
|
||||
<a-list size="small" bordered :data-source="state.options">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item><a-checkbox style="margin-right:30px" v-model:checked="item.checked"/>{{ item.name }}</a-list-item>
|
||||
</template>
|
||||
<template #header>
|
||||
<a-checkbox
|
||||
style="margin-right:30px"
|
||||
v-model:checked="state.checkAll"
|
||||
:indeterminate="state.indeterminate"
|
||||
@change="onCheckAllChange">
|
||||
</a-checkbox>
|
||||
选择全部
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref,watch,reactive,h} from 'vue';
|
||||
import {useMessage} from "/@/hooks/web/useMessage";
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { exportDatas,downloadDatas} from '/@/api/system/dataMigration';
|
||||
import { downloadByData } from '/@/utils/file/download';
|
||||
import { dateUtil } from '/@/utils/dateUtil';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const state = reactive({
|
||||
indeterminate: false,
|
||||
checkAll: false,
|
||||
options:[
|
||||
{
|
||||
checked:false,
|
||||
code:"租户",
|
||||
name:'租户'
|
||||
},
|
||||
/* {
|
||||
checked:false,
|
||||
code:"用户",
|
||||
name:'用户'
|
||||
}, */
|
||||
{
|
||||
checked:false,
|
||||
code:'角色',
|
||||
name:'角色'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'岗位',
|
||||
name:'岗位'
|
||||
},
|
||||
/* {
|
||||
checked:false,
|
||||
code:'部门',
|
||||
name:'部门'
|
||||
}, */
|
||||
{
|
||||
checked:false,
|
||||
code:'用户组',
|
||||
name:'用户组'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'菜单',
|
||||
name:'菜单'
|
||||
},
|
||||
|
||||
{
|
||||
checked:false,
|
||||
code:'表单',
|
||||
name:'表单'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'流程定义',
|
||||
name:'流程定义'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'系统变量',
|
||||
name:'系统变量'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'数据字典',
|
||||
name:'数据字典'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'桌面配置',
|
||||
name:'桌面配置'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'自定义接口',
|
||||
name:'自定义接口'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'角色-菜单授权',
|
||||
name:'权限:角色-菜单授权(菜单、按钮、列表、表单)'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'角色-自定义接口授权',
|
||||
name:'权限:角色-自定义接口授权'
|
||||
},
|
||||
/* {
|
||||
checked:false,
|
||||
code:'用户-角色授权',
|
||||
name:'权限:用户-角色授权'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'用户-用户组授权',
|
||||
name:'权限:用户-用户组授权'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'用户-岗位授权',
|
||||
name:'权限:用户-岗位授权'
|
||||
},
|
||||
{
|
||||
checked:false,
|
||||
code:'用户-部门授权',
|
||||
name:'权限:用户-部门授权'
|
||||
}, */
|
||||
{
|
||||
checked:false,
|
||||
code:'租户-菜单授权',
|
||||
name:'权限:租户-菜单授权'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
function onCheckAllChange(e: any){
|
||||
if(e.target.checked){
|
||||
state.options.forEach((item)=>{
|
||||
item.checked=true;
|
||||
});
|
||||
}else{
|
||||
state.options.forEach((item)=>{
|
||||
item.checked=false;
|
||||
});
|
||||
}
|
||||
state.indeterminate=false;
|
||||
}
|
||||
|
||||
|
||||
watch(
|
||||
() => state.options,
|
||||
val => {
|
||||
const checkedList=val.filter((item)=>item.checked);
|
||||
if(checkedList.length>0){
|
||||
if(checkedList.length< val.length){
|
||||
state.indeterminate=true;
|
||||
}else{
|
||||
state.checkAll=true;
|
||||
state.indeterminate=false;
|
||||
}
|
||||
|
||||
}else{
|
||||
state.indeterminate=false;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function openConfirmDialog(){
|
||||
const { notification,createConfirm} = useMessage();
|
||||
const checkedList=state.options.filter((item)=>item.checked);
|
||||
if(checkedList.length==0){
|
||||
notification.warning({
|
||||
message: '提示',
|
||||
description: '未选择任何项目'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: () => h('span', t('温馨提示')),
|
||||
content: () => t('确定导出所选项目吗?'),
|
||||
width:'500px',
|
||||
onOk: async () => {
|
||||
handleExport();
|
||||
},
|
||||
okText: () => t('确认'),
|
||||
cancelText: () => t('取消'),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleExport(){
|
||||
try {
|
||||
const checkedCodeList=state.options.filter((item)=>item.checked).map(item => item.code);
|
||||
let currentTime=dateUtil(new Date()).format('YYYY-MM-DD_HH_mm_ss');
|
||||
let result= await exportDatas({
|
||||
items:checkedCodeList
|
||||
});
|
||||
|
||||
if(result){
|
||||
const fileName=dateUtil(new Date()).format('YYYY-MM-DD_HH_mm_ss');
|
||||
const res = await downloadDatas({uuid:result});
|
||||
downloadByData(
|
||||
res.data,
|
||||
fileName+".zip"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.export{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
height:100%;
|
||||
width:100%;
|
||||
padding:30px 30px 30px 100px;
|
||||
border:1px solid #d6d6d6;
|
||||
border-radius:8px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.l-export{
|
||||
display:block;
|
||||
margin-left:20px;
|
||||
margin-bottom:20px;
|
||||
color: rgba(0,0,255,0.8);
|
||||
text-decoration:underline rgba(#0000ff,0.8);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: rgba(0,0,255,1);
|
||||
text-decoration:underline #0000ff;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.list-wrapper{
|
||||
height:calc(100% - 50px);
|
||||
.ant-list{
|
||||
width:100%;
|
||||
height:calc(100% - 30px);
|
||||
overflow:hidden;
|
||||
border:0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<span @click.stop="open">
|
||||
<slot></slot>
|
||||
<a-modal
|
||||
v-model:visible="data.visible"
|
||||
:title="t(`导入数据[${coverType=='override'?'覆盖模式':'新增模式'}]`)"
|
||||
:maskClosable="false"
|
||||
@ok="doUpload"
|
||||
@cancel="close"
|
||||
@click.stop=""
|
||||
>
|
||||
<div class="upload-box">
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
class="upload-box"
|
||||
name="file"
|
||||
accept=".json,.zip"
|
||||
:headers="data.headers"
|
||||
:max-count="1"
|
||||
@change="handleChange"
|
||||
:before-upload="beforeUpload"
|
||||
@remove="handleRemove"
|
||||
>
|
||||
<img :src="BgImg" />
|
||||
<div class="a-upload__text">{{ t('将文件拖到此处,或') }}<em>{{ t('点击上传') }}</em></div>
|
||||
</a-upload>
|
||||
</div>
|
||||
</a-modal>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive,ref} from 'vue';
|
||||
import BgImg from '../../assets/sysconfig_import.png';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
const props = defineProps({
|
||||
coverType: {
|
||||
type: Boolean
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const globSetting = useGlobSetting();
|
||||
const { notification } = useMessage();
|
||||
|
||||
const data: {
|
||||
visible: boolean;
|
||||
} = reactive({
|
||||
visible: false
|
||||
});
|
||||
|
||||
const fileList=ref([]);
|
||||
|
||||
|
||||
|
||||
async function open() {
|
||||
if(props.coverType!=='override'){
|
||||
alert("新增模式导入暂时不可用");
|
||||
return;
|
||||
}
|
||||
data.visible = true;
|
||||
}
|
||||
function close() {
|
||||
data.visible = false;
|
||||
fileList.value=[];
|
||||
}
|
||||
|
||||
|
||||
function handleRemove(file){
|
||||
const index = fileList.value.indexOf(file);
|
||||
const newFileList = fileList.value.slice();
|
||||
newFileList.splice(index, 1);
|
||||
fileList.value = newFileList;
|
||||
};
|
||||
|
||||
function beforeUpload(file){
|
||||
fileList.value = [...(fileList.value || []), file];
|
||||
return false;
|
||||
};
|
||||
|
||||
function doUpload(){
|
||||
if(fileList==undefined||fileList.value.length==0){
|
||||
notification.error({
|
||||
message: '提示',
|
||||
description: t('请选择文件'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const files=[];
|
||||
fileList.value.forEach(file => {
|
||||
files.push(file.originFileObj);
|
||||
});
|
||||
|
||||
const url="/system/dataMigration/importDatas";
|
||||
defHttp.uploadFile(
|
||||
{
|
||||
baseURL: globSetting.apiUrl,
|
||||
url:url,
|
||||
method: 'POST',
|
||||
},
|
||||
{
|
||||
name: 'file',
|
||||
file: files,
|
||||
data:{
|
||||
configType:props.configType,
|
||||
cover:props.coverType=='override'?true:false
|
||||
}
|
||||
}
|
||||
).then((data) => {
|
||||
notification.success({
|
||||
message: '提示',
|
||||
description: t('导入中...请稍后确认导入结果!'),
|
||||
});
|
||||
close();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
}).finally(()=>{
|
||||
// close();
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.upload-box {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.upload-demo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.a-upload-dragger {
|
||||
width: 615px;
|
||||
height: 370px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.a-upload__text {
|
||||
position: absolute;
|
||||
bottom: 100px;
|
||||
right: 100px;
|
||||
font-weight: bold;
|
||||
color: #1d2027;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: #4f83fd;
|
||||
}
|
||||
</style>
|
||||
43
src/views/system/dataMigration/components/import/index.vue
Normal file
43
src/views/system/dataMigration/components/import/index.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="import">
|
||||
<ImportSystemConfig coverType="new">
|
||||
<span class="item-action">新增模式导入</span>
|
||||
</ImportSystemConfig>
|
||||
<ImportSystemConfig coverType="override">
|
||||
<span class="item-action">覆盖模式导入</span>
|
||||
</ImportSystemConfig>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ImportSystemConfig from './ImportSystemConfig.vue';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.import{
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
height:100%;
|
||||
width:100%;
|
||||
gap:100px;
|
||||
border:1px solid #d6d6d6;
|
||||
border-radius:8px;
|
||||
}
|
||||
|
||||
.item-action {
|
||||
border:1px solid rgba(206, 206, 206, 1);
|
||||
padding:6px 8px;
|
||||
border-radius:8px;
|
||||
color:rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.item-action:hover {
|
||||
cursor: pointer;
|
||||
background:rgba(22, 119, 224, 1);
|
||||
color:white;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
91
src/views/system/dataMigration/index.vue
Normal file
91
src/views/system/dataMigration/index.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="system-data-migration">
|
||||
<a-tabs v-model:activeKey="tabActiveKey" class="main-tabs">
|
||||
<a-tab-pane key="import" tab="导出工作区">
|
||||
<Export/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="outport" tab="导入工作区" force-render>
|
||||
<Import/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onUnmounted, createVNode} from 'vue';
|
||||
import { PageWrapper } from '/@/components/Page';
|
||||
import Export from './components/export/index.vue';
|
||||
import Import from './components/import/index.vue';
|
||||
|
||||
const tabActiveKey = ref('import');
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.system-data-migration{
|
||||
position:absolute;
|
||||
height:100%;
|
||||
width:100%;
|
||||
background-color:white;
|
||||
padding-top:10px;
|
||||
}
|
||||
|
||||
.main-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav){
|
||||
border-bottom:0px;
|
||||
}
|
||||
:deep(.ant-tabs-nav-wrap) {
|
||||
width: 100% !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab) {
|
||||
min-width: 200px !important;
|
||||
width:calc(50% - 2px);
|
||||
display: block !important;
|
||||
text-align: center !important;
|
||||
font-size:16px;
|
||||
padding:8px 0px;
|
||||
&.ant-tabs-tab-active{
|
||||
// box-shadow: 10px 10px 100px rgba(#1677ff, 1) inset;
|
||||
.ant-tabs-tab-btn{
|
||||
// color:#fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab:first-child) {
|
||||
border-radius: 10px 0px 0px 10px;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab + .ant-tabs-tab){
|
||||
margin:0px 0px 0px 0px;
|
||||
border-radius: 0px 10px 10px 0px;
|
||||
}
|
||||
|
||||
|
||||
:deep(.ant-tabs-content-holder) {
|
||||
height:100%;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content) {
|
||||
height: 100% !important;
|
||||
overflow-y: auto;
|
||||
padding:10px 10px;
|
||||
}
|
||||
|
||||
|
||||
:deep(.ant-tabs-tabpane) {
|
||||
padding: 0 8px;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user