Files
data-migration/relational_migration/relational_target/src/main/resources/static/index.html

554 lines
20 KiB
HTML
Raw Normal View History

2026-06-17 10:11:44 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据同步管理系统</title>
<!-- 引入 Element UI 样式 -->
<link rel="stylesheet" href="js/element-ui.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
background-color: #f5f7fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 8px;
margin-bottom: 30px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 28px;
margin-bottom: 10px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.card {
background: white;
border-radius: 8px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.card-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #e4e7ed;
color: #303133;
}
.button-group {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.button-item {
flex: 1;
min-width: 200px;
}
.date-input {
width: 200px;
margin-right: 15px;
}
.file-list {
max-height: 400px;
overflow-y: auto;
}
.log-area {
background: #f5f7fa;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 15px;
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.6;
}
.log-item {
margin-bottom: 5px;
padding: 5px;
border-radius: 3px;
}
.log-success {
background: #f0f9ff;
color: #67c23a;
}
.log-error {
background: #fef0f0;
color: #f56c6c;
}
.log-info {
background: #f4f4f5;
color: #909399;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-item {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
}
.environment-badge {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
margin-left: 15px;
}
.badge-source {
background: #e6a23c;
color: white;
}
.badge-target {
background: #409eff;
color: white;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<!-- 头部 -->
<div class="header">
<h1>
🗄️ 数据同步管理系统
<span :class="['environment-badge', isSourceServer ? 'badge-source' : 'badge-target']">
{{ serverTypeName }}
</span>
</h1>
<p> relational_migration - 数据导入导出管理平台</p>
</div>
<!-- 统计信息 -->
<div class="stats">
<div class="stat-item">
<div class="stat-value">{{ fileCount }}</div>
<div class="stat-label">远程文件数量</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ operationCount }}</div>
<div class="stat-label">操作次数</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ lastOperationTime || '--' }}</div>
<div class="stat-label">最后操作时间</div>
</div>
</div>
<!-- 环境配置卡片(可选显示) -->
<div class="card" v-if="showEnvSwitch">
<div class="card-title">⚙️ 环境切换(调试用)</div>
<el-alert
title="当前环境由配置文件控制,如需切换请修改 application.yml 中的 app.server-type 配置"
type="warning"
:closable="false"
show-icon
style="margin-bottom: 15px;">
</el-alert>
<el-form :inline="true">
<el-form-item label="当前配置">
<el-tag :type="isSourceServer ? 'warning' : 'primary'" size="medium">
{{ serverTypeName }}
</el-tag>
</el-form-item>
</el-form>
</div>
<!-- 数据操作卡片 -->
<div class="card">
<div class="card-title">📊 数据操作</div>
<el-form :inline="true" class="demo-form-inline">
<el-form-item label="日期">
<el-date-picker
v-model="selectedDate"
type="date"
placeholder="选择日期"
format="yyyyMMdd"
value-format="yyyyMMdd"
class="date-input">
</el-date-picker>
</el-form-item>
<el-form-item>
<div class="button-group">
<el-button
v-if="isSourceServer"
type="primary"
icon="el-icon-download"
@click="handleExport"
:loading="loading.export">
从二区库导出数据并上传横向隔离设备
</el-button>
<el-button
v-if="isTargetServer"
type="success"
icon="el-icon-upload2"
@click="handleImport"
:loading="loading.import">
从横向隔离设备拉取数据并入三区库
</el-button>
<el-button v-if="isSourceServer" type="warning" icon="el-icon-refresh" @click="handleTest" :loading="loading.test">
测试二区发送数据至横向隔离设备
</el-button>
<el-button v-if="isTargetServer" type="info" icon="el-icon-refresh-right" @click="handleTestR" :loading="loading.testR">
测试三区从横向隔离设备消费数据
</el-button>
</div>
</el-form-item>
</el-form>
</div>
<!-- 文件管理卡片 -->
<div class="card">
<div class="card-title">📁 远程文件管理</div>
<el-button type="primary" icon="el-icon-search" @click="getRemoteFiles" :loading="loading.getRemoteAll" style="margin-bottom: 15px;">
获取横向隔离设备远程文件列表
</el-button>
<div class="file-list" v-if="remoteFiles.length > 0">
<el-table :data="remoteFiles" stripe style="width: 100%">
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="文件名"></el-table-column>
<el-table-column label="操作" width="200">
<template slot-scope="scope">
<!-- <el-button size="mini" type="danger" @click="removeFile(scope.row)">
删除
</el-button>-->
</template>
</el-table-column>
</el-table>
</div>
<el-empty v-else description="暂无远程文件" :image-size="100"></el-empty>
</div>
<!-- 清理文件卡片 -->
<div class="card">
<div class="card-title">🗑️ 本地文件清理</div>
<el-form :inline="true">
<el-form-item label="保留天数">
<el-input-number v-model="cleanDays" :min="1" :max="365" label="请输入保留天数"></el-input-number>
</el-form-item>
<el-form-item>
<el-button v-if="isSourceServer" type="danger" icon="el-icon-delete" @click="cleanTwo" :loading="loading.removeTwo">
清理二区服务器上生成的数据
</el-button>
<el-button v-if="isTargetServer" type="danger" icon="el-icon-delete" @click="cleanThree" :loading="loading.removeThree">
清理三区服务器保留的数据
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 操作日志卡片 -->
<div class="card">
<div class="card-title">📋 操作日志</div>
<div class="log-area">
<div v-for="(log, index) in logs" :key="index" :class="['log-item', log.type]">
[{{ log.time }}] {{ log.message }}
</div>
<div v-if="logs.length === 0" class="log-info">
暂无操作日志
</div>
</div>
<el-button size="small" @click="clearLogs" style="margin-top: 10px;">
清空日志
</el-button>
</div>
</div>
</div>
<!-- 引入 Vue -->
<script src="js/vue.min.js"></script>
<!-- 引入 Element UI -->
<script src="js/element-ui-index.js"></script>
<!-- 引入 Axios -->
<script src="js/axios.min.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
selectedDate: '',
cleanDays: 7,
remoteFiles: [],
logs: [],
loading: {
export: false,
import: false,
test: false,
testR: false,
getRemoteAll: false,
removeTwo: false,
removeThree: false
},
fileCount: 0,
operationCount: 0,
lastOperationTime: '',
serverType: 'source',
serverTypeName: '源服务器',
showEnvSwitch: false,
// 默认选中 昨天
selectedDate: this.getYesterdayDate()
}
},
computed: {
isSourceServer() {
return this.serverType === 'source';
},
isTargetServer() {
return this.serverType === 'target';
}
},
mounted() {
this.loadServerConfig();
},
methods: {
// 获取昨天的日期,格式 yyyyMMdd
getYesterdayDate() {
const date = new Date();
date.setDate(date.getDate() - 1); // 减去一天 = 昨天
const y = date.getFullYear();
const m = (date.getMonth() + 1).toString().padStart(2, '0');
const d = date.getDate().toString().padStart(2, '0');
return `${y}${m}${d}`; // 输出 20250613 这种格式
},
// 加载服务器配置
async loadServerConfig() {
try {
const response = await axios.get('source/getConfig');
this.serverType = response.data.serverType;
this.serverTypeName = response.data.serverTypeName;
this.addLog(`系统初始化完成,当前环境:${this.serverTypeName}`, 'success');
this.getRemoteFiles();
} catch (error) {
console.error('加载配置失败:', error);
this.addLog('加载配置失败,使用默认配置', 'error');
this.getRemoteFiles();
}
},
// 添加日志
addLog(message, type = 'info') {
const now = new Date();
const timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
this.logs.unshift({
time: timeStr,
message: message,
type: type
});
// 保持最多100条日志
if (this.logs.length > 100) {
this.logs.pop();
}
},
// 清空日志
clearLogs() {
this.logs = [];
this.$message.info('日志已清空');
},
// 导出数据
async handleExport() {
this.loading.export = true;
try {
const params = this.selectedDate ? { date: this.selectedDate } : {};
await axios.get('source/export', { params });
this.$message.success('二区数据导出上传成功');
this.addLog('二区数据导出上传成功', 'success');
this.operationCount++;
this.lastOperationTime = new Date().toLocaleTimeString();
} catch (error) {
this.$message.error('数据导出失败: ' + (error.response?.data || error.message));
this.addLog('数据导出失败: ' + error.message, 'error');
} finally {
this.loading.export = false;
}
},
// 导入数据
async handleImport() {
this.loading.import = true;
try {
const params = this.selectedDate ? { date: this.selectedDate } : {};
await axios.get('source/import', { params });
this.$message.success('三区数据拉取导入成功');
this.addLog('三区数据拉取导入成功', 'success');
this.operationCount++;
this.lastOperationTime = new Date().toLocaleTimeString();
} catch (error) {
this.$message.error('数据导入失败: ' + (error.response?.data || error.message));
this.addLog('数据导入失败: ' + error.message, 'error');
} finally {
this.loading.import = false;
}
},
// 测试连接
async handleTest() {
this.loading.test = true;
try {
await axios.get('source/test');
this.$message.success('测试二区数据推送横向隔离设备成功');
this.addLog('测试二区数据推送横向隔离设备成功', 'success');
this.operationCount++;
this.lastOperationTime = new Date().toLocaleTimeString();
} catch (error) {
this.$message.error('测试二区数据推送横向隔离设备失败: ' + (error.response?.data || error.message));
this.addLog('测试二区数据推送横向隔离设备失败: ' + error.message, 'error');
} finally {
this.loading.test = false;
}
},
// 测试远程
async handleTestR() {
this.loading.testR = true;
try {
await axios.get('source/testR');
this.$message.success('三区拉取测试数据成功');
this.addLog('三区拉取测试数据成功', 'success');
this.operationCount++;
this.lastOperationTime = new Date().toLocaleTimeString();
} catch (error) {
this.$message.error('三区拉取测试数据失败: ' + (error.response?.data || error.message));
this.addLog('三区拉取测试数据失败: ' + error.message, 'error');
} finally {
this.loading.testR = false;
}
},
// 获取远程文件列表
async getRemoteFiles() {
this.loading.getRemoteAll = true;
try {
const response = await axios.get('source/getRemoteAll');
this.remoteFiles = response.data.map((name, index) => ({
id: index,
name: name
}));
this.fileCount = this.remoteFiles.length;
this.$message.success(`获取到 ${this.fileCount} 个远程文件`);
this.addLog(`获取远程文件列表成功,共 ${this.fileCount} 个文件`, 'success');
} catch (error) {
this.$message.error('获取远程文件失败: ' + (error.response?.data || error.message));
this.addLog('获取远程文件失败: ' + error.message, 'error');
} finally {
this.loading.getRemoteAll = false;
}
},
// 清理二区服务器历史导出数据
async cleanTwo() {
this.loading.removeTwo = true;
try {
const response = await axios.get('source/removeTwo', {
params: { a: this.cleanDays }
});
this.$message.success(response.data);
this.addLog(`清理二区服务器历史导出数据成功,保留${this.cleanDays}天内的文件`, 'success');
this.operationCount++;
this.lastOperationTime = new Date().toLocaleTimeString();
// 刷新文件列表
//this.getRemoteFiles();
} catch (error) {
this.$message.error('清理失败: ' + (error.response?.data || error.message));
this.addLog('清理二区服务器历史导出数据失败: ' + error.message, 'error');
} finally {
this.loading.removeTwo = false;
}
},
// 清理三区拉取数据
async cleanThree() {
this.loading.removeThree = true;
try {
const response = await axios.get('source/removeThree', {
params: { a: this.cleanDays }
});
this.$message.success(response.data);
this.addLog(`清理三区拉取数据成功,保留${this.cleanDays}天内的文件`, 'success');
this.operationCount++;
this.lastOperationTime = new Date().toLocaleTimeString();
// 刷新文件列表
//this.getRemoteFiles();
} catch (error) {
this.$message.error('清理失败: ' + (error.response?.data || error.message));
this.addLog('清理三区拉取数据失败: ' + error.message, 'error');
} finally {
this.loading.removeThree = false;
}
}
}
})
</script>
</body>
</html>