【feat】 优化快照比对

This commit is contained in:
Kris 2026-01-02 23:20:59 +08:00
parent d5b5f11aef
commit ea83fd6f0d
2 changed files with 181 additions and 63 deletions

View File

@ -298,17 +298,25 @@
}); });
// //
const handleLoginUrlPreset = (command) => { //
if (command === 'custom') { const handleLoginUrlPreset = (command) => {
// if (command === 'custom') {
isCustomLoginUrl.value = true; // isCustomLoginUrl.value = true;
loginForm.loginUrl = ''; // 便 loginForm.value.loginUrl = ''; // .value
} else { } else {
// isCustomLoginUrl.value = false;
isCustomLoginUrl.value = false; //
loginForm.loginUrl = command; // URL // login test
let targetUrl = command;
if (command === 'https://test.salesforce.com' && loginForm.value.loginType === 'legacy_credential') {
// /API
// test.salesforce.com
targetUrl = 'https://test.salesforce.com';
} }
};
loginForm.value.loginUrl = targetUrl;
}
};
// --- --- // --- ---
const rules = computed(() => { const rules = computed(() => {
@ -349,10 +357,14 @@
}); });
// --- --- // --- ---
const handleLoginTypeChange = () => { const handleLoginTypeChange = () => {
loginForm.value.grantType = 'password'; loginForm.value.grantType = 'password';
resetFormFields(); resetFormFields();
};
//
loginForm.value.loginUrl = 'https://login.salesforce.com';
isCustomLoginUrl.value = false;
};
const handleGrantTypeChange = () => { const handleGrantTypeChange = () => {
resetFormFields(); resetFormFields();

View File

@ -7,12 +7,12 @@
<el-table :data="snapshotList" v-loading="loading" border stripe> <el-table :data="snapshotList" v-loading="loading" border stripe>
<el-table-column prop="id" label="快照ID" width="80" /> <el-table-column prop="id" label="快照ID" width="80" />
<el-table-column prop="snapshotName" label="快照名称" min-width="150" /> <el-table-column prop="snapshotNumber" label="快照名称" min-width="150" />
<el-table-column prop="environmentName" label="环境" width="120" /> <el-table-column prop="environmentName" label="环境" width="120" />
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip /> <el-table-column prop="snapshotDesc" label="描述" min-width="200" show-overflow-tooltip />
<el-table-column prop="configCount" label="配置数量" width="100" align="center" /> <el-table-column prop="configCount" label="配置数量" width="100" align="center" />
<el-table-column prop="createdBy" label="创建人" width="120" /> <el-table-column prop="createBy" label="创建人" width="120" />
<el-table-column prop="createTime" label="创建时间" width="180" /> <el-table-column prop="createTime" label="创建时间" width="180" :formatter="(row) => formatDate(row.createTime)" />
<el-table-column label="操作" width="280" fixed="right"> <el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link :icon="View" @click="handleViewDetail(row)">查看</el-button> <el-button type="primary" link :icon="View" @click="handleViewDetail(row)">查看</el-button>
@ -33,8 +33,8 @@
<el-dialog v-model="createDialogVisible" title="创建快照" width="600px" @close="resetCreateForm"> <el-dialog v-model="createDialogVisible" title="创建快照" width="600px" @close="resetCreateForm">
<el-form :model="createForm" :rules="createRules" ref="createFormRef" label-width="100px"> <el-form :model="createForm" :rules="createRules" ref="createFormRef" label-width="100px">
<el-form-item label="快照名称" prop="snapshotName"> <el-form-item label="快照名称" prop="snapshotNumber">
<el-input v-model="createForm.snapshotName" placeholder="请输入快照名称" maxlength="50" /> <el-input v-model="createForm.snapshotNumber" placeholder="请输入快照名称" maxlength="50" />
</el-form-item> </el-form-item>
<el-form-item label="环境" prop="environmentId"> <el-form-item label="环境" prop="environmentId">
<el-select v-model="createForm.environmentId" placeholder="请选择环境" style="width: 100%"> <el-select v-model="createForm.environmentId" placeholder="请选择环境" style="width: 100%">
@ -46,9 +46,9 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="描述" prop="description"> <el-form-item label="描述" prop="snapshotDesc">
<el-input <el-input
v-model="createForm.description" v-model="createForm.snapshotDesc"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请输入快照描述" placeholder="请输入快照描述"
@ -62,30 +62,35 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="detailDialogVisible" title="快照详情" width="800px"> <el-dialog v-model="detailDialogVisible" title="快照详情" width="800px" :close-on-click-modal="false">
<el-descriptions :column="2" border> <div v-loading="detailLoading">
<el-descriptions-item label="快照ID">{{ currentSnapshot.id }}</el-descriptions-item> <el-descriptions :column="2" border>
<el-descriptions-item label="快照名称">{{ currentSnapshot.snapshotName }}</el-descriptions-item> <el-descriptions-item label="快照ID">{{ currentSnapshot.id }}</el-descriptions-item>
<el-descriptions-item label="环境">{{ currentSnapshot.environmentName }}</el-descriptions-item> <el-descriptions-item label="快照名称">{{ currentSnapshot.snapshotNumber }}</el-descriptions-item>
<el-descriptions-item label="配置数量">{{ currentSnapshot.configCount }}</el-descriptions-item> <el-descriptions-item label="环境">{{ currentSnapshot.environmentName }}</el-descriptions-item>
<el-descriptions-item label="创建人">{{ currentSnapshot.createdBy }}</el-descriptions-item> <el-descriptions-item label="配置数量">{{ currentSnapshot.configCount }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ currentSnapshot.createTime }}</el-descriptions-item> <el-descriptions-item label="创建人">{{ currentSnapshot.createBy }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ currentSnapshot.description }}</el-descriptions-item> <el-descriptions-item label="创建时间">{{ formatDate(currentSnapshot.createTime) }}</el-descriptions-item>
</el-descriptions> <el-descriptions-item label="描述" :span="2">{{ currentSnapshot.snapshotDesc }}</el-descriptions-item>
</el-descriptions>
<el-divider>配置详情</el-divider> <el-divider>配置详情</el-divider>
<el-table :data="snapshotConfigList" border stripe max-height="400"> <el-empty v-if="!detailLoading && snapshotConfigList.length === 0" description="暂无配置数据" />
<el-table-column prop="configKey" label="配置键" min-width="200" />
<el-table-column prop="configValue" label="配置值" min-width="250" show-overflow-tooltip /> <el-table v-else :data="snapshotConfigList" border stripe max-height="400">
<el-table-column prop="isSensitive" label="敏感配置" width="100" align="center"> <el-table-column prop="configKey" label="配置键" min-width="200" show-overflow-tooltip />
<template #default="{ row }"> <el-table-column prop="configValue" label="配置值" min-width="250" show-overflow-tooltip />
<el-tag :type="row.isSensitive ? 'danger' : 'success'"> <el-table-column prop="isSensitive" label="敏感配置" width="100" align="center">
{{ row.isSensitive ? '是' : '否' }} <template #default="{ row }">
</el-tag> <el-tag :type="row.isSensitive ? 'danger' : 'success'">
</template> {{ row.isSensitive ? '是' : '否' }}
</el-table-column> </el-tag>
</el-table> </template>
</el-table-column>
<el-table-column prop="createBy" label="创建人" width="120" show-overflow-tooltip />
</el-table>
</div>
</el-dialog> </el-dialog>
<el-dialog v-model="compareDialogVisible" title="快照对比" width="900px"> <el-dialog v-model="compareDialogVisible" title="快照对比" width="900px">
@ -94,7 +99,7 @@
<el-option <el-option
v-for="snap in snapshotList" v-for="snap in snapshotList"
:key="snap.id" :key="snap.id"
:label="snap.snapshotName" :label="snap.snapshotNumber"
:value="snap.id" :value="snap.id"
/> />
</el-select> </el-select>
@ -103,14 +108,18 @@
<el-option <el-option
v-for="snap in snapshotList" v-for="snap in snapshotList"
:key="snap.id" :key="snap.id"
:label="snap.snapshotName" :label="snap.snapshotNumber"
:value="snap.id" :value="snap.id"
/> />
</el-select> </el-select>
<el-button type="primary" @click="handleExecuteCompare" :loading="compareLoading">对比</el-button> <el-button type="primary" @click="handleExecuteCompare" :loading="compareLoading">对比</el-button>
</div> </div>
<el-table :data="compareResult" border stripe v-if="compareResult.length > 0"> <div v-if="compareReport" class="compare-report">
<pre>{{ compareReport }}</pre>
</div>
<el-table v-else-if="compareResult.length > 0" :data="compareResult" border stripe>
<el-table-column prop="configKey" label="配置键" min-width="200" /> <el-table-column prop="configKey" label="配置键" min-width="200" />
<el-table-column prop="value1" label="快照1值" min-width="200" show-overflow-tooltip /> <el-table-column prop="value1" label="快照1值" min-width="200" show-overflow-tooltip />
<el-table-column prop="value2" label="快照2值" min-width="200" show-overflow-tooltip /> <el-table-column prop="value2" label="快照2值" min-width="200" show-overflow-tooltip />
@ -122,6 +131,8 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-empty v-else-if="!compareLoading" description="请选择两个快照进行对比" />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -145,6 +156,7 @@
DocumentCopy, DocumentCopy,
Delete Delete
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import { formatDate } from '@/utils/index';
const props = defineProps({ const props = defineProps({
currentEnvironment: { currentEnvironment: {
@ -156,11 +168,13 @@
const loading = ref(false); const loading = ref(false);
const createLoading = ref(false); const createLoading = ref(false);
const compareLoading = ref(false); const compareLoading = ref(false);
const detailLoading = ref(false);
const snapshotList = ref([]); const snapshotList = ref([]);
const total = ref(0); const total = ref(0);
const environmentList = ref([]); const environmentList = ref([]);
const snapshotConfigList = ref([]); const snapshotConfigList = ref([]);
const compareResult = ref([]); const compareResult = ref([]);
const compareReport = ref('');
const queryParams = ref({ const queryParams = ref({
pageNum: 1, pageNum: 1,
@ -176,13 +190,13 @@
const currentSnapshot = ref({}); const currentSnapshot = ref({});
const createForm = ref({ const createForm = ref({
snapshotName: '', snapshotNumber: '',
environmentId: null, environmentId: null,
description: '' snapshotDesc: ''
}); });
const createRules = { const createRules = {
snapshotName: [ snapshotNumber: [
{ required: true, message: '请输入快照名称', trigger: 'blur' } { required: true, message: '请输入快照名称', trigger: 'blur' }
], ],
environmentId: [ environmentId: [
@ -234,9 +248,9 @@
const resetCreateForm = () => { const resetCreateForm = () => {
createForm.value = { createForm.value = {
snapshotName: '', snapshotNumber: '',
environmentId: null, environmentId: null,
description: '' snapshotDesc: ''
}; };
createFormRef.value?.clearValidate(); createFormRef.value?.clearValidate();
}; };
@ -266,23 +280,72 @@
}; };
const handleViewDetail = async (row) => { const handleViewDetail = async (row) => {
console.log('点击查看详情row 对象:', row);
console.log('row.id:', row.id);
console.log('row 的所有字段:', Object.keys(row));
const snapshotId = row.id || row.snapshotId || row.snapshotNumber;
if (!row || !snapshotId) {
ElMessage.error('快照ID不存在无法查看详情');
console.error('快照数据异常:', row);
return;
}
detailLoading.value = true;
try { try {
const response = await getSnapshotDetail(row.id); console.log('获取快照详情ID:', snapshotId);
const response = await getSnapshotDetail(snapshotId);
console.log('快照详情响应:', response);
if (response.code === 200) { if (response.code === 200) {
currentSnapshot.value = response.data.snapshot; const data = response.data;
snapshotConfigList.value = response.data.configs || []; if (data) {
detailDialogVisible.value = true; currentSnapshot.value = data.snapshot || data;
let configs = [];
if (data.snapshotContent) {
try {
configs = JSON.parse(data.snapshotContent);
} catch (e) {
console.error('解析 snapshotContent 失败:', e);
configs = [];
}
} else if (data.configs) {
configs = data.configs;
} else if (data.snapshotConfigs) {
configs = data.snapshotConfigs;
}
snapshotConfigList.value = Array.isArray(configs) ? configs : [];
console.log('当前快照:', currentSnapshot.value);
console.log('配置列表:', snapshotConfigList.value);
detailDialogVisible.value = true;
} else {
ElMessage.error('快照详情数据为空');
}
} else { } else {
ElMessage.error(response.msg || '获取快照详情失败'); ElMessage.error(response.msg || '获取快照详情失败');
} }
} catch (error) { } catch (error) {
ElMessage.error(error.msg || '获取快照详情失败'); console.error('获取快照详情失败:', error);
ElMessage.error(error.msg || error.message || '获取快照详情失败');
} finally {
detailLoading.value = false;
} }
}; };
const handleRestore = (row) => { const handleRestore = (row) => {
const snapshotId = row.id || row.snapshotId || row.snapshotNumber;
if (!row || !snapshotId) {
ElMessage.error('快照ID不存在无法恢复');
console.error('快照数据异常:', row);
return;
}
ElMessageBox.confirm( ElMessageBox.confirm(
`确定要恢复快照"${row.snapshotName}"吗?此操作将覆盖当前环境配置,请谨慎操作。`, `确定要恢复快照"${row.snapshotNumber}"吗?此操作将覆盖当前环境配置,请谨慎操作。`,
'恢复确认', '恢复确认',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',
@ -291,7 +354,7 @@
} }
).then(async () => { ).then(async () => {
try { try {
const response = await restoreSnapshot(row.id, {}); const response = await restoreSnapshot(snapshotId, {});
if (response.code === 200) { if (response.code === 200) {
ElMessage.success('快照恢复成功'); ElMessage.success('快照恢复成功');
getList(); getList();
@ -305,7 +368,15 @@
}; };
const handleCompare = (row) => { const handleCompare = (row) => {
compareForm.value.snapshotId1 = row.id; const snapshotId = row.id || row.snapshotId || row.snapshotNumber;
if (!row || !snapshotId) {
ElMessage.error('快照ID不存在无法对比');
console.error('快照数据异常:', row);
return;
}
compareForm.value.snapshotId1 = snapshotId;
compareDialogVisible.value = true; compareDialogVisible.value = true;
}; };
@ -321,13 +392,21 @@
} }
compareLoading.value = true; compareLoading.value = true;
compareResult.value = [];
compareReport.value = '';
try { try {
const response = await compareSnapshots( const response = await compareSnapshots(
compareForm.value.snapshotId1, compareForm.value.snapshotId1,
compareForm.value.snapshotId2 compareForm.value.snapshotId2
); );
if (response.code === 200) { if (response.code === 200) {
compareResult.value = response.data || []; if (response.data && Array.isArray(response.data)) {
compareResult.value = response.data;
} else if (response.msg) {
compareReport.value = response.msg;
} else {
ElMessage.warning('对比结果格式异常');
}
} else { } else {
ElMessage.error(response.msg || '对比快照失败'); ElMessage.error(response.msg || '对比快照失败');
} }
@ -339,8 +418,16 @@
}; };
const handleDelete = (row) => { const handleDelete = (row) => {
const snapshotId = row.id || row.snapshotId || row.snapshotNumber;
if (!row || !snapshotId) {
ElMessage.error('快照ID不存在无法删除');
console.error('快照数据异常:', row);
return;
}
ElMessageBox.confirm( ElMessageBox.confirm(
`确定要删除快照"${row.snapshotName}"吗?删除后无法恢复。`, `确定要删除快照"${row.snapshotNumber}"吗?删除后无法恢复。`,
'删除确认', '删除确认',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',
@ -349,7 +436,7 @@
} }
).then(async () => { ).then(async () => {
try { try {
const response = await delSnapshot(row.id); const response = await delSnapshot(snapshotId);
if (response.code === 200) { if (response.code === 200) {
ElMessage.success('删除成功'); ElMessage.success('删除成功');
getList(); getList();
@ -400,6 +487,25 @@
color: #909399; color: #909399;
} }
.compare-report {
margin-top: 20px;
padding: 20px;
background-color: #f5f7fa;
border-radius: 4px;
max-height: 500px;
overflow-y: auto;
}
.compare-report pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
line-height: 1.6;
color: #333;
}
:deep(.el-table) { :deep(.el-table) {
margin-top: 10px; margin-top: 10px;
} }