系统数据迁移(系统基础数据导出、导入)功能开发
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