fix:fix site info
This commit is contained in:
+18
-16
@@ -33,7 +33,24 @@ export const menus = [
|
|||||||
{ path: '/acs/images/categories', title: '镜像分类' }
|
{ path: '/acs/images/categories', title: '镜像分类' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ path: '/acs/nodes', title: '节点管理' }
|
{
|
||||||
|
path: '/acs/nodes', title: '节点管理'
|
||||||
|
},{
|
||||||
|
path: '/audit',
|
||||||
|
title: '站点审计',
|
||||||
|
icon: 'Monitor',
|
||||||
|
children: [
|
||||||
|
{ path: '/audit/all', title: '所有站点' },
|
||||||
|
{ path: '/audit/violation', title: '违规站点' }
|
||||||
|
]
|
||||||
|
},{
|
||||||
|
path:'/setting',
|
||||||
|
title:'全局设置管理',
|
||||||
|
icon:'Setting',
|
||||||
|
children:[
|
||||||
|
{path:'/setting/global',title:'全局设置'}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -45,20 +62,5 @@ export const menus = [
|
|||||||
// { path: '/system/operation-log', title: '操作日志' },
|
// { path: '/system/operation-log', title: '操作日志' },
|
||||||
{ path: '/system/domain-whitelist', title: '域名白名单' }
|
{ path: '/system/domain-whitelist', title: '域名白名单' }
|
||||||
]
|
]
|
||||||
},{
|
|
||||||
path: '/audit',
|
|
||||||
title: '站点审计',
|
|
||||||
icon: 'Monitor',
|
|
||||||
children: [
|
|
||||||
{ path: '/audit/all', title: '所有站点' },
|
|
||||||
{ path: '/audit/violation', title: '违规站点' }
|
|
||||||
]
|
|
||||||
},{
|
|
||||||
path:'/setting',
|
|
||||||
title:'全局设置管理',
|
|
||||||
icon:'Setting',
|
|
||||||
children:[
|
|
||||||
{path:'/setting/global',title:'全局设置'}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -733,7 +733,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, reactive, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import {getUserInfo, userLogin} from "@/api/login.js";
|
import {getUserInfo, userLogin} from "@/api/login.js";
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
@@ -801,6 +801,92 @@ const route = useRoute();
|
|||||||
const vmInfo = ref({});
|
const vmInfo = ref({});
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
// 缓存相关
|
||||||
|
const dataCache = ref(new Map()); // 缓存不同instance_id的数据
|
||||||
|
const currentInstanceId = ref(route.query.instance_id);
|
||||||
|
const isFromNavigation = ref(false); // 标记是否来自导航返回
|
||||||
|
|
||||||
|
// 缓存管理函数
|
||||||
|
const getCacheKey = (instanceId) => `vm_${instanceId}`;
|
||||||
|
|
||||||
|
const getCachedData = (instanceId) => {
|
||||||
|
const cacheKey = getCacheKey(instanceId);
|
||||||
|
return dataCache.value.get(cacheKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCachedData = (instanceId, data) => {
|
||||||
|
const cacheKey = getCacheKey(instanceId);
|
||||||
|
const cacheData = {
|
||||||
|
...data,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
instanceId: instanceId
|
||||||
|
};
|
||||||
|
dataCache.value.set(cacheKey, cacheData);
|
||||||
|
console.log(`缓存虚拟机数据: ${instanceId}`, cacheData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCacheValid = (cachedData, maxAge = 5 * 60 * 1000) => { // 默认5分钟有效期
|
||||||
|
if (!cachedData || !cachedData.timestamp) return false;
|
||||||
|
return (Date.now() - cachedData.timestamp) < maxAge;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldUseCache = (instanceId) => {
|
||||||
|
// 检查是否来自列表页面的新进入
|
||||||
|
const fromSource = sessionStorage.getItem('vmDetailFrom');
|
||||||
|
const fromTimestamp = sessionStorage.getItem('vmDetailTimestamp');
|
||||||
|
|
||||||
|
// 如果是从列表页面新进入的,不使用缓存
|
||||||
|
if (fromSource === 'list' && fromTimestamp) {
|
||||||
|
const timeDiff = Date.now() - parseInt(fromTimestamp);
|
||||||
|
if (timeDiff < 2000) { // 2秒内的新进入
|
||||||
|
console.log('从列表页面新进入,不使用缓存');
|
||||||
|
// 清除标记
|
||||||
|
sessionStorage.removeItem('vmDetailFrom');
|
||||||
|
sessionStorage.removeItem('vmDetailTimestamp');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是相同的instance_id且来自导航返回,优先使用缓存
|
||||||
|
if (isFromNavigation.value && instanceId === currentInstanceId.value) {
|
||||||
|
const cachedData = getCachedData(instanceId);
|
||||||
|
const isValid = cachedData && isCacheValid(cachedData);
|
||||||
|
console.log(`缓存检查结果: instanceId=${instanceId}, isValid=${isValid}`);
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存当前数据到缓存
|
||||||
|
const saveDataToCache = (instanceId = null) => {
|
||||||
|
const targetInstanceId = instanceId || route.query.instance_id;
|
||||||
|
if (!targetInstanceId) return;
|
||||||
|
|
||||||
|
const dataToCache = {
|
||||||
|
vmInfo: { ...vmInfo.value },
|
||||||
|
logsList: [...logsList.value],
|
||||||
|
portsList: [...portsList.value],
|
||||||
|
networkRulesList: [...networkRulesList.value],
|
||||||
|
snapshotsList: [...snapshotsList.value],
|
||||||
|
dataVolumes: [...dataVolumes.value],
|
||||||
|
// 分页状态
|
||||||
|
logsPage: logsPage.value,
|
||||||
|
logsPageSize: logsPageSize.value,
|
||||||
|
portsPage: portsPage.value,
|
||||||
|
portsPageSize: portsPageSize.value,
|
||||||
|
networkRulesPage: networkRulesPage.value,
|
||||||
|
networkRulesPageSize: networkRulesPageSize.value,
|
||||||
|
// 总数
|
||||||
|
logsTotal: logsTotal.value,
|
||||||
|
portsTotal: portsTotal.value,
|
||||||
|
networkRulesTotal: networkRulesTotal.value,
|
||||||
|
volumesTotal: volumesTotal.value
|
||||||
|
};
|
||||||
|
|
||||||
|
setCachedData(targetInstanceId, dataToCache);
|
||||||
|
};
|
||||||
|
|
||||||
// 标签页相关
|
// 标签页相关
|
||||||
const activeTabName = ref('0'); // 默认选中第一个标签
|
const activeTabName = ref('0'); // 默认选中第一个标签
|
||||||
|
|
||||||
@@ -1136,6 +1222,107 @@ const fetchInstanceStatus = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
|
// 加载所有数据的统一函数
|
||||||
|
const loadAllData = async (instanceId = null, useCache = true) => {
|
||||||
|
const targetInstanceId = instanceId || route.query.instance_id;
|
||||||
|
|
||||||
|
// 检查是否使用缓存
|
||||||
|
if (useCache && shouldUseCache(targetInstanceId)) {
|
||||||
|
const cachedData = getCachedData(targetInstanceId);
|
||||||
|
console.log(`使用缓存数据加载所有信息: ${targetInstanceId}`);
|
||||||
|
|
||||||
|
// 从缓存恢复所有数据
|
||||||
|
vmInfo.value = cachedData.vmInfo || {};
|
||||||
|
logsList.value = cachedData.logsList || [];
|
||||||
|
portsList.value = cachedData.portsList || [];
|
||||||
|
networkRulesList.value = cachedData.networkRulesList || [];
|
||||||
|
snapshotsList.value = cachedData.snapshotsList || [];
|
||||||
|
dataVolumes.value = cachedData.dataVolumes || [];
|
||||||
|
|
||||||
|
// 恢复分页状态
|
||||||
|
if (cachedData.logsPage) logsPage.value = cachedData.logsPage;
|
||||||
|
if (cachedData.logsPageSize) logsPageSize.value = cachedData.logsPageSize;
|
||||||
|
if (cachedData.portsPage) portsPage.value = cachedData.portsPage;
|
||||||
|
if (cachedData.portsPageSize) portsPageSize.value = cachedData.portsPageSize;
|
||||||
|
if (cachedData.networkRulesPage) networkRulesPage.value = cachedData.networkRulesPage;
|
||||||
|
if (cachedData.networkRulesPageSize) networkRulesPageSize.value = cachedData.networkRulesPageSize;
|
||||||
|
|
||||||
|
// 恢复总数
|
||||||
|
if (cachedData.logsTotal) logsTotal.value = cachedData.logsTotal;
|
||||||
|
if (cachedData.portsTotal) portsTotal.value = cachedData.portsTotal;
|
||||||
|
if (cachedData.networkRulesTotal) networkRulesTotal.value = cachedData.networkRulesTotal;
|
||||||
|
if (cachedData.volumesTotal) volumesTotal.value = cachedData.volumesTotal;
|
||||||
|
|
||||||
|
// 重置loading状态
|
||||||
|
loading.value = false;
|
||||||
|
logsLoading.value = false;
|
||||||
|
portsLoading.value = false;
|
||||||
|
networkRulesLoading.value = false;
|
||||||
|
snapshotsLoading.value = false;
|
||||||
|
volumesLoading.value = false;
|
||||||
|
|
||||||
|
// 延迟初始化图表
|
||||||
|
setTimeout(() => {
|
||||||
|
initCharts();
|
||||||
|
fetchMonitorData();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新请求所有数据
|
||||||
|
console.log(`重新请求所有数据: ${targetInstanceId}`);
|
||||||
|
await Promise.all([
|
||||||
|
fetchVmInfo(targetInstanceId, false),
|
||||||
|
fetchPortsList(),
|
||||||
|
fetchLogsList(),
|
||||||
|
fetchAccessControlList(),
|
||||||
|
fetchNetworkRulesList(),
|
||||||
|
fetchSnapshotsList(),
|
||||||
|
fetchDataVolumesList(),
|
||||||
|
fetchInstanceStatus()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 延迟初始化图表,确保DOM已经渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
initCharts();
|
||||||
|
fetchMonitorData();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// 保存到缓存
|
||||||
|
saveDataToCache(targetInstanceId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听路由参数变化
|
||||||
|
watch(() => route.query.instance_id, async (newInstanceId, oldInstanceId) => {
|
||||||
|
if (!newInstanceId) return;
|
||||||
|
|
||||||
|
// 保存旧数据到缓存(如果存在)
|
||||||
|
if (oldInstanceId && oldInstanceId !== newInstanceId) {
|
||||||
|
console.log(`保存旧虚拟机数据到缓存: ${oldInstanceId}`);
|
||||||
|
saveDataToCache(oldInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测是否是instance_id变化
|
||||||
|
const isInstanceIdChanged = newInstanceId !== currentInstanceId.value;
|
||||||
|
|
||||||
|
if (isInstanceIdChanged) {
|
||||||
|
console.log(`虚拟机ID变化: ${currentInstanceId.value} -> ${newInstanceId}`);
|
||||||
|
currentInstanceId.value = newInstanceId;
|
||||||
|
isFromNavigation.value = false; // 重置导航标记
|
||||||
|
|
||||||
|
// 加载新的虚拟机数据(不使用缓存)
|
||||||
|
await loadAllData(newInstanceId, false);
|
||||||
|
} else {
|
||||||
|
// 相同的instance_id,可能是从其他页面返回
|
||||||
|
isFromNavigation.value = true;
|
||||||
|
console.log(`返回相同虚拟机: ${newInstanceId}`);
|
||||||
|
|
||||||
|
// 尝试使用缓存
|
||||||
|
await loadAllData(newInstanceId, true);
|
||||||
|
}
|
||||||
|
}, { immediate: false });
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (route.query.instance_id) {
|
if (route.query.instance_id) {
|
||||||
// 恢复上次选中的标签页
|
// 恢复上次选中的标签页
|
||||||
@@ -1144,20 +1331,12 @@ onMounted(() => {
|
|||||||
activeTabName.value = savedTab;
|
activeTabName.value = savedTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchVmInfo();
|
// 设置当前instance_id
|
||||||
fetchPortsList();
|
currentInstanceId.value = route.query.instance_id;
|
||||||
fetchLogsList();
|
isFromNavigation.value = false;
|
||||||
fetchAccessControlList();
|
|
||||||
fetchNetworkRulesList();
|
|
||||||
fetchSnapshotsList();
|
|
||||||
fetchDataVolumesList();
|
|
||||||
fetchInstanceStatus();
|
|
||||||
|
|
||||||
// 延迟初始化图表,确保DOM已经渲染
|
// 加载数据
|
||||||
setTimeout(() => {
|
loadAllData();
|
||||||
initCharts();
|
|
||||||
fetchMonitorData();
|
|
||||||
}, 500);
|
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('缺少虚拟机ID参数');
|
ElMessage.error('缺少虚拟机ID参数');
|
||||||
goBack();
|
goBack();
|
||||||
@@ -1165,10 +1344,38 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 获取虚拟机信息
|
// 获取虚拟机信息
|
||||||
const fetchVmInfo = async () => {
|
const fetchVmInfo = async (instanceId = null, useCache = true) => {
|
||||||
|
const targetInstanceId = instanceId || route.query.instance_id;
|
||||||
|
|
||||||
|
// 检查是否应该使用缓存
|
||||||
|
if (useCache && shouldUseCache(targetInstanceId)) {
|
||||||
|
const cachedData = getCachedData(targetInstanceId);
|
||||||
|
console.log(`使用缓存数据加载虚拟机: ${targetInstanceId}`);
|
||||||
|
|
||||||
|
// 从缓存恢复数据
|
||||||
|
vmInfo.value = cachedData.vmInfo || {};
|
||||||
|
logsList.value = cachedData.logsList || [];
|
||||||
|
portsList.value = cachedData.portsList || [];
|
||||||
|
networkRulesList.value = cachedData.networkRulesList || [];
|
||||||
|
snapshotsList.value = cachedData.snapshotsList || [];
|
||||||
|
dataVolumes.value = cachedData.dataVolumes || [];
|
||||||
|
|
||||||
|
// 重置loading状态
|
||||||
|
loading.value = false;
|
||||||
|
logsLoading.value = false;
|
||||||
|
portsLoading.value = false;
|
||||||
|
networkRulesLoading.value = false;
|
||||||
|
snapshotsLoading.value = false;
|
||||||
|
volumesLoading.value = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getVmAdminContainer(route.query.instance_id);
|
console.log(`重新请求虚拟机数据: ${targetInstanceId}`);
|
||||||
|
|
||||||
|
const res = await getVmAdminContainer(targetInstanceId);
|
||||||
const serverRes = await selectServer({server_id:res.data.data.server_id})
|
const serverRes = await selectServer({server_id:res.data.data.server_id})
|
||||||
const planRes = await selectServerPlan({plan_id:res.data.data.plan_id,server_type:"hyperV"})
|
const planRes = await selectServerPlan({plan_id:res.data.data.plan_id,server_type:"hyperV"})
|
||||||
const imageRes= await Mirrorinfo({image_id:res.data.data.image_id,server_type:"hyperV"})
|
const imageRes= await Mirrorinfo({image_id:res.data.data.image_id,server_type:"hyperV"})
|
||||||
@@ -1286,7 +1493,9 @@ const submitAddPort = async () => {
|
|||||||
if (res && res.data && res.data.code === 200) {
|
if (res && res.data && res.data.code === 200) {
|
||||||
ElMessage.success('添加端口成功');
|
ElMessage.success('添加端口成功');
|
||||||
showAddPortDialog.value = false;
|
showAddPortDialog.value = false;
|
||||||
fetchPortsList();
|
await fetchPortsList();
|
||||||
|
// 更新缓存
|
||||||
|
saveDataToCache();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.data.message || '添加端口失败');
|
ElMessage.error(res.data.message || '添加端口失败');
|
||||||
}
|
}
|
||||||
@@ -1311,7 +1520,9 @@ const handleDeletePort = async (port) => {
|
|||||||
const res = await deletePort({ port_forward_id: port.id });
|
const res = await deletePort({ port_forward_id: port.id });
|
||||||
if (res && res.data && res.data.code === 200) {
|
if (res && res.data && res.data.code === 200) {
|
||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
fetchPortsList();
|
await fetchPortsList();
|
||||||
|
// 更新缓存
|
||||||
|
saveDataToCache();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.data.message || '删除失败');
|
ElMessage.error(res.data.message || '删除失败');
|
||||||
}
|
}
|
||||||
@@ -1324,14 +1535,23 @@ const handleDeletePort = async (port) => {
|
|||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
|
// 标记这是返回操作,为了后续可能的缓存使用
|
||||||
|
sessionStorage.setItem('vmDetailFrom', 'back');
|
||||||
|
sessionStorage.setItem('vmDetailTimestamp', Date.now().toString());
|
||||||
router.go(-1);
|
router.go(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 刷新数据
|
// 刷新数据
|
||||||
const refreshData = () => {
|
const refreshData = () => {
|
||||||
fetchVmInfo();
|
const instanceId = route.query.instance_id;
|
||||||
fetchPortsList();
|
console.log(`手动刷新数据,清除缓存: ${instanceId}`);
|
||||||
fetchLogsList();
|
|
||||||
|
// 清除当前虚拟机的缓存
|
||||||
|
const cacheKey = getCacheKey(instanceId);
|
||||||
|
dataCache.value.delete(cacheKey);
|
||||||
|
|
||||||
|
// 重新加载数据(不使用缓存)
|
||||||
|
loadAllData(instanceId, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 启动虚拟机
|
// 启动虚拟机
|
||||||
@@ -2234,6 +2454,12 @@ window.addEventListener('resize', resizeCharts);
|
|||||||
|
|
||||||
// 组件卸载前清理
|
// 组件卸载前清理
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
// 保存当前数据到缓存
|
||||||
|
if (route.query.instance_id) {
|
||||||
|
console.log(`组件卸载,保存数据到缓存: ${route.query.instance_id}`);
|
||||||
|
saveDataToCache();
|
||||||
|
}
|
||||||
|
|
||||||
// 移除事件监听
|
// 移除事件监听
|
||||||
window.removeEventListener('resize', resizeCharts);
|
window.removeEventListener('resize', resizeCharts);
|
||||||
|
|
||||||
|
|||||||
@@ -252,6 +252,9 @@ const handleSizeChange = (val) => {
|
|||||||
|
|
||||||
// 管理虚拟机
|
// 管理虚拟机
|
||||||
const handleManage = (row) => {
|
const handleManage = (row) => {
|
||||||
|
// 标记这是从列表页面进入详情页面,不是返回操作
|
||||||
|
sessionStorage.setItem('vmDetailFrom', 'list');
|
||||||
|
sessionStorage.setItem('vmDetailTimestamp', Date.now().toString());
|
||||||
router.push(`/servers/vm?instance_id=${row.id}`);
|
router.push(`/servers/vm?instance_id=${row.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -302,7 +302,7 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 数据卷信息 -->
|
<!-- 数据卷信息 -->
|
||||||
<el-tab-pane label="数据卷信息" name="2">
|
<!-- <el-tab-pane label="数据卷信息" name="2">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">数据卷信息</h3>
|
<h3 class="tab-title">数据卷信息</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -321,7 +321,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<el-empty v-if="volumeInfoList.length === 0" description="暂无数据" />
|
<el-empty v-if="volumeInfoList.length === 0" description="暂无数据" />
|
||||||
</el-tab-pane>
|
</el-tab-pane> -->
|
||||||
|
|
||||||
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
@@ -705,7 +705,7 @@ const fetchNetworkList = async () => {
|
|||||||
const fetchVolumeInfoList = async () => {
|
const fetchVolumeInfoList = async () => {
|
||||||
volumeInfoLoading.value = true;
|
volumeInfoLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getVolumeList({instance_id:route.query.container_id});
|
const res = await getVolumeList({instance_id:route.query.container_id,page:1,count:10});
|
||||||
console.log("获取数据卷信息",res);
|
console.log("获取数据卷信息",res);
|
||||||
if(res.data.code == 200){
|
if(res.data.code == 200){
|
||||||
volumeInfoList.value = res.data.data;
|
volumeInfoList.value = res.data.data;
|
||||||
|
|||||||
@@ -573,7 +573,7 @@
|
|||||||
<div class="time-info">{{ scope.row.become_time }}</div>
|
<div class="time-info">{{ scope.row.become_time }}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="规格" prop="plan_name" width="80" />
|
<el-table-column label="规格" prop="name" width="80" />
|
||||||
<el-table-column label="用户ID" prop="user_id" width="80" />
|
<el-table-column label="用户ID" prop="user_id" width="80" />
|
||||||
<el-table-column label="状态" width="100" align="center">
|
<el-table-column label="状态" width="100" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@@ -1025,6 +1025,9 @@ const totalTrafficErrorMessage = ref('');
|
|||||||
|
|
||||||
// 返回按钮功能
|
// 返回按钮功能
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
|
// 标记返回操作
|
||||||
|
sessionStorage.setItem('serverDetailFrom', 'back');
|
||||||
|
sessionStorage.setItem('serverDetailTimestamp', Date.now().toString());
|
||||||
router.back();
|
router.back();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1234,18 +1237,6 @@ const initData = async () => {
|
|||||||
let cons = await getContainer(containerBox);
|
let cons = await getContainer(containerBox);
|
||||||
if (cons && cons.data) {
|
if (cons && cons.data) {
|
||||||
user_servers.value = cons.data.data || [];
|
user_servers.value = cons.data.data || [];
|
||||||
for (const item of user_servers.value){
|
|
||||||
try{
|
|
||||||
const res = await selectServerPlan({
|
|
||||||
server_type: 'dockerContainer',
|
|
||||||
plan_id: item.plan_id
|
|
||||||
})
|
|
||||||
item.plan_name = res.data.data.name;
|
|
||||||
}catch(error){
|
|
||||||
console.error("获取容器列表失败:", error);
|
|
||||||
ElMessage.error("获取容器列表失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
total.value = cons.data.count || 0;
|
total.value = cons.data.count || 0;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+215
-84
@@ -4,7 +4,7 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<h2 class="title">所有站点</h2>
|
<h2 class="title">所有站点</h2>
|
||||||
<el-tag type="info" effect="plain" class="count-tag">共 {{ pagination.total }} 个容器</el-tag>
|
<el-tag type="info" effect="plain" class="count-tag">共 {{ pagination.total }} 个站点</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<el-button type="primary" @click="handleRefresh" :icon="Refresh" class="action-btn">刷新</el-button>
|
<el-button type="primary" @click="handleRefresh" :icon="Refresh" class="action-btn">刷新</el-button>
|
||||||
@@ -18,28 +18,28 @@
|
|||||||
<div class="stat-icon"><el-icon><Monitor /></el-icon></div>
|
<div class="stat-icon"><el-icon><Monitor /></el-icon></div>
|
||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-value">{{ pagination.total }}</div>
|
<div class="stat-value">{{ pagination.total }}</div>
|
||||||
<div class="stat-label">总容器数</div>
|
<div class="stat-label">总站点数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card normal-card">
|
<div class="stat-card normal-card">
|
||||||
<div class="stat-icon"><el-icon><CircleCheck /></el-icon></div>
|
<div class="stat-icon"><el-icon><CircleCheck /></el-icon></div>
|
||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-value">{{ siteStats.normal }}</div>
|
<div class="stat-value">{{ siteStats.normal }}</div>
|
||||||
<div class="stat-label">已构建容器</div>
|
<div class="stat-label">正常连接</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card warning-card">
|
<div class="stat-card warning-card">
|
||||||
<div class="stat-icon"><el-icon><Warning /></el-icon></div>
|
<div class="stat-icon"><el-icon><Warning /></el-icon></div>
|
||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-value">{{ siteStats.warning }}</div>
|
<div class="stat-value">{{ siteStats.warning }}</div>
|
||||||
<div class="stat-label">未构建/未知容器</div>
|
<div class="stat-label">连接失败</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-card violation-card">
|
<div class="stat-card violation-card">
|
||||||
<div class="stat-icon"><el-icon><CircleClose /></el-icon></div>
|
<div class="stat-icon"><el-icon><CircleClose /></el-icon></div>
|
||||||
<div class="stat-content">
|
<div class="stat-content">
|
||||||
<div class="stat-value">{{ siteStats.violation }}</div>
|
<div class="stat-value">{{ siteStats.violation }}</div>
|
||||||
<div class="stat-label">异常容器</div>
|
<div class="stat-label">违规站点</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,20 +68,45 @@
|
|||||||
stripe
|
stripe
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column prop="container_id" label="容器id" width="300"/>
|
<el-table-column prop="container_id" label="容器ID" width="280" show-overflow-tooltip />
|
||||||
<el-table-column label="容器状态" align="center">
|
<el-table-column prop="url" label="访问地址" min-width="200" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-link :href="row.url" target="_blank" type="primary" v-if="row.url">
|
||||||
:type="getStatusType(row.status)"
|
{{ row.url }}
|
||||||
effect="plain"
|
</el-link>
|
||||||
size="small"
|
<span v-else class="text-muted">无访问地址</span>
|
||||||
>
|
</template>
|
||||||
{{ getStatusText(row.status) }}
|
</el-table-column>
|
||||||
|
<el-table-column label="连接类型" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getConnectTypeColor(row.connect_type)" size="small">
|
||||||
|
{{ getConnectTypeText(row.connect_type) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="lastCheck" label="最后检查时间" />
|
<el-table-column label="连接状态" width="100" align="center">
|
||||||
<el-table-column prop="createTime" label="创建时间" />
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="getConnectionStatusType(row.connect)"
|
||||||
|
effect="plain"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ getConnectionStatusText(row.connect) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="违规状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="row.is_violation ? 'danger' : 'success'"
|
||||||
|
effect="plain"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ row.is_violation ? '违规' : '正常' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||||
<el-table-column label="操作" fixed="right" align="center">
|
<el-table-column label="操作" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@@ -161,67 +186,154 @@ const siteStats = reactive({
|
|||||||
// 是否需要获取统计数据的标志
|
// 是否需要获取统计数据的标志
|
||||||
const needsStatsUpdate = ref(true)
|
const needsStatsUpdate = ref(true)
|
||||||
|
|
||||||
|
// 缓存所有站点数据
|
||||||
|
const allSitesCache = ref([])
|
||||||
|
const cacheTimestamp = ref(0)
|
||||||
|
const cacheExpiry = 60000 // 1分钟缓存
|
||||||
|
|
||||||
// 对话框相关
|
// 对话框相关
|
||||||
const detailDialogVisible = ref(false)
|
const detailDialogVisible = ref(false)
|
||||||
const currentSite = ref(null)
|
const currentSite = ref(null)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 获取所有容器数据的函数
|
||||||
|
const getAllContainers = async () => {
|
||||||
|
try {
|
||||||
|
// 检查缓存是否有效
|
||||||
|
const now = Date.now()
|
||||||
|
if (allSitesCache.value.length > 0 && (now - cacheTimestamp.value) < cacheExpiry) {
|
||||||
|
console.log('使用缓存的站点数据')
|
||||||
|
return allSitesCache.value
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('开始获取所有容器数据...')
|
||||||
|
let allContainers = []
|
||||||
|
let currentPage = 1
|
||||||
|
const pageSize = 100 // 每次请求100个容器
|
||||||
|
let hasMoreData = true
|
||||||
|
|
||||||
|
while (hasMoreData) {
|
||||||
|
const params = {
|
||||||
|
page: currentPage,
|
||||||
|
count: pageSize,
|
||||||
|
server_id: '',
|
||||||
|
user_id: '',
|
||||||
|
key: queryParams.domain || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`正在获取第${currentPage}页容器数据...`)
|
||||||
|
const response = await getSiteList(params)
|
||||||
|
|
||||||
|
if (response && response.data && response.data.data) {
|
||||||
|
const containerList = response.data.data || []
|
||||||
|
allContainers = allContainers.concat(containerList)
|
||||||
|
|
||||||
|
console.log(`第${currentPage}页获取到${containerList.length}个容器,总计${allContainers.length}个`)
|
||||||
|
|
||||||
|
// 检查是否还有更多数据
|
||||||
|
if (containerList.length < pageSize) {
|
||||||
|
hasMoreData = false
|
||||||
|
console.log('已获取所有容器数据')
|
||||||
|
} else {
|
||||||
|
currentPage++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasMoreData = false
|
||||||
|
console.log('API响应异常,停止获取')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有容器,合并所有web_list数据
|
||||||
|
let allWebSites = []
|
||||||
|
|
||||||
|
allContainers.forEach(container => {
|
||||||
|
if (container.web_list && Array.isArray(container.web_list)) {
|
||||||
|
const containerWebSites = container.web_list.map(webItem => ({
|
||||||
|
...webItem,
|
||||||
|
container_info: {
|
||||||
|
container_id: container.container_id || container.id,
|
||||||
|
container_name: container.name || container.container_name,
|
||||||
|
server_id: container.server_id,
|
||||||
|
user_id: container.user_id
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
allWebSites = allWebSites.concat(containerWebSites)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 将合并后的数据转换为页面需要的格式
|
||||||
|
const transformedData = allWebSites.map(item => {
|
||||||
|
let webAudit = {}
|
||||||
|
try {
|
||||||
|
webAudit = JSON.parse(item.web_audit || '{}')
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('解析web_audit失败:', e)
|
||||||
|
webAudit = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
container_id: item.container_id,
|
||||||
|
connect_id: item.connect_id,
|
||||||
|
url: webAudit.url || `http://${item.server_ip}:${item.server_port}`,
|
||||||
|
domain: item.domain || webAudit.web_key,
|
||||||
|
connect_type: item.connect_type,
|
||||||
|
server_ip: item.server_ip,
|
||||||
|
server_port: item.server_port,
|
||||||
|
container_ip: item.container_ip,
|
||||||
|
container_port: item.container_port,
|
||||||
|
floating_ip: item.floating_ip,
|
||||||
|
state: item.state,
|
||||||
|
connect: webAudit.connect || false,
|
||||||
|
is_violation: webAudit.is_violation || false,
|
||||||
|
violation_keys: webAudit.violation_keys || [],
|
||||||
|
createTime: formatTime(item.created_at),
|
||||||
|
ssl_cert: item.ssl_cert,
|
||||||
|
container_info: item.container_info
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
allSitesCache.value = transformedData
|
||||||
|
cacheTimestamp.value = now
|
||||||
|
|
||||||
|
console.log(`总计获取到${transformedData.length}个站点数据`)
|
||||||
|
return transformedData
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取所有容器数据失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取站点列表数据
|
// 获取站点列表数据
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 构造API请求参数
|
// 获取所有站点数据
|
||||||
const params = {
|
const allSitesData = await getAllContainers()
|
||||||
page: queryParams.pageNum,
|
|
||||||
count: queryParams.pageSize,
|
// 应用搜索筛选
|
||||||
server_id: '', // 可以根据需要添加服务器ID筛选
|
let filteredData = allSitesData
|
||||||
user_id: '', // 可以根据需要添加用户ID筛选
|
if (queryParams.domain) {
|
||||||
key: queryParams.domain || '' // 使用域名作为搜索关键字
|
filteredData = filteredData.filter(site =>
|
||||||
|
site.container_id?.toLowerCase().includes(queryParams.domain.toLowerCase()) ||
|
||||||
|
site.url?.toLowerCase().includes(queryParams.domain.toLowerCase()) ||
|
||||||
|
site.domain?.toLowerCase().includes(queryParams.domain.toLowerCase())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用API获取站点列表
|
// 实现前端分页
|
||||||
const response = await getSiteList(params)
|
const startIndex = (queryParams.pageNum - 1) * queryParams.pageSize
|
||||||
console.log("获取站点列表结果",response)
|
const endIndex = startIndex + queryParams.pageSize
|
||||||
if (response && response.data) {
|
const paginatedData = filteredData.slice(startIndex, endIndex)
|
||||||
// 处理API返回的数据
|
|
||||||
const apiData = response.data.data || []
|
siteList.value = paginatedData
|
||||||
|
pagination.total = filteredData.length
|
||||||
// 将API数据转换为页面需要的格式
|
|
||||||
const transformedData = apiData.map(item => ({
|
// 更新统计数据(基于所有筛选后的数据,不是分页后的数据)
|
||||||
container_id: item.container_id ,
|
updateStats(filteredData)
|
||||||
domain: item.domain || item.Domain || item.web_key,
|
|
||||||
title: item.title || item.Title || item.web_name || '未知站点',
|
|
||||||
status: getStatusFromApi(item.container_state),
|
|
||||||
lastCheck: formatTime(item.become_time),
|
|
||||||
createTime: formatTime(item.create_time || item.CreateTime || item.created_at || item.CreatedAt),
|
|
||||||
description: item.description || item.Description || '',
|
|
||||||
checkHistory: [] // API可能不返回历史记录,可以单独获取
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 如果有状态筛选,在前端进行过滤
|
|
||||||
let filteredData = transformedData
|
|
||||||
|
|
||||||
|
|
||||||
siteList.value = filteredData
|
|
||||||
pagination.total = response.data.total || response.data.count || filteredData.length
|
|
||||||
|
|
||||||
// 检查API是否返回了统计信息
|
|
||||||
if (response.data.stats) {
|
|
||||||
// 如果API返回了统计信息,直接使用
|
|
||||||
updateStatsFromApi(response.data.stats)
|
|
||||||
needsStatsUpdate.value = false
|
|
||||||
} else {
|
|
||||||
// 如果需要获取统计数据且是第一页,获取全部数据进行统计
|
|
||||||
if (needsStatsUpdate.value && queryParams.pageNum === 1) {
|
|
||||||
await getFullStatsData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
siteList.value = []
|
|
||||||
pagination.total = 0
|
|
||||||
updateStats([])
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取站点列表失败:', error)
|
console.error('获取站点列表失败:', error)
|
||||||
@@ -287,9 +399,9 @@ const formatTime = (timeStr) => {
|
|||||||
const updateStats = (data = []) => {
|
const updateStats = (data = []) => {
|
||||||
console.log("更新统计数据",data)
|
console.log("更新统计数据",data)
|
||||||
siteStats.total = data.length
|
siteStats.total = data.length
|
||||||
siteStats.normal = data.filter(site => site.status === 'normal').length
|
siteStats.normal = data.filter(site => site.connect && !site.is_violation).length
|
||||||
siteStats.warning = data.filter(site => site.status === 'warning').length
|
siteStats.warning = data.filter(site => !site.connect).length
|
||||||
siteStats.violation = data.filter(site => site.status === 'violation').length
|
siteStats.violation = data.filter(site => site.is_violation).length
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从API统计信息更新
|
// 从API统计信息更新
|
||||||
@@ -339,25 +451,35 @@ const getFullStatsData = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态类型
|
// 获取连接类型颜色
|
||||||
const getStatusType = (status) => {
|
const getConnectTypeColor = (type) => {
|
||||||
const statusMap = {
|
const typeMap = {
|
||||||
normal: 'success',
|
'port_forward': 'primary',
|
||||||
warning: 'warning',
|
'domain': 'success',
|
||||||
violation: 'danger'
|
'floating_ip': 'warning'
|
||||||
}
|
}
|
||||||
return statusMap[status] || 'info'
|
return typeMap[type] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态文本
|
// 获取连接类型文本
|
||||||
const getStatusText = (status) => {
|
const getConnectTypeText = (type) => {
|
||||||
const statusMap = {
|
const typeMap = {
|
||||||
normal: '已构建', // 对应容器状态 2
|
'port_forward': '端口转发',
|
||||||
warning: '未构建', // 对应容器状态 1 和 3(未知)
|
'domain': '域名绑定',
|
||||||
violation: '异常' // 对应容器状态 0(未支付) 和 4(已删除)
|
'floating_ip': '浮动IP'
|
||||||
}
|
}
|
||||||
return statusMap[status] || '未知'
|
return typeMap[type] || type || '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取连接状态类型
|
||||||
|
const getConnectionStatusType = (connect) => {
|
||||||
|
return connect ? 'success' : 'danger'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取连接状态文本
|
||||||
|
const getConnectionStatusText = (connect) => {
|
||||||
|
return connect ? '已连接' : '连接失败'
|
||||||
|
}
|
||||||
|
|
||||||
// 查询按钮
|
// 查询按钮
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
@@ -401,7 +523,10 @@ const handleRefresh = () => {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
needsStatsUpdate.value = true // 刷新时需要更新统计
|
// 清除缓存,强制重新获取数据
|
||||||
|
allSitesCache.value = []
|
||||||
|
cacheTimestamp.value = 0
|
||||||
|
needsStatsUpdate.value = true
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,4 +831,10 @@ onMounted(() => {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义样式 */
|
||||||
|
.text-muted {
|
||||||
|
color: #909399;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -97,20 +97,33 @@
|
|||||||
stripe
|
stripe
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column prop="container_id" label="容器id" width="300"/>
|
<el-table-column prop="container_id" label="容器ID" width="280" show-overflow-tooltip />
|
||||||
<el-table-column label="容器状态" align="center">
|
<el-table-column prop="url" label="违规地址" min-width="200" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-link :href="row.url" target="_blank" type="danger" v-if="row.url">
|
||||||
:type="getStatusType(row.status)"
|
{{ row.url }}
|
||||||
effect="plain"
|
</el-link>
|
||||||
size="small"
|
<span v-else class="text-muted">无访问地址</span>
|
||||||
>
|
</template>
|
||||||
{{ getStatusText(row.status) }}
|
</el-table-column>
|
||||||
|
<el-table-column label="违规类型" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="danger" size="small" v-if="row.violation_keys && row.violation_keys.length > 0">
|
||||||
|
{{ row.violation_keys.join(', ') }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag type="warning" size="small" v-else>
|
||||||
|
检测到违规
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="lastCheck" label="最后检查时间" />
|
<el-table-column label="连接类型" width="100" align="center">
|
||||||
<el-table-column prop="createTime" label="创建时间" />
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getConnectTypeColor(row.connect_type)" size="small">
|
||||||
|
{{ getConnectTypeText(row.connect_type) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||||
<el-table-column label="操作" fixed="right" align="center">
|
<el-table-column label="操作" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@@ -278,6 +291,11 @@ const violationStats = reactive({
|
|||||||
pending: 0
|
pending: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 缓存所有站点数据
|
||||||
|
const allSitesCache = ref([])
|
||||||
|
const cacheTimestamp = ref(0)
|
||||||
|
const cacheExpiry = 60000 // 1分钟缓存
|
||||||
|
|
||||||
// 对话框相关
|
// 对话框相关
|
||||||
const detailDialogVisible = ref(false)
|
const detailDialogVisible = ref(false)
|
||||||
const processDialogVisible = ref(false)
|
const processDialogVisible = ref(false)
|
||||||
@@ -355,69 +373,170 @@ const mockViolationData = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 获取所有容器数据的函数
|
||||||
|
const getAllContainers = async () => {
|
||||||
|
try {
|
||||||
|
// 检查缓存是否有效
|
||||||
|
const now = Date.now()
|
||||||
|
if (allSitesCache.value.length > 0 && (now - cacheTimestamp.value) < cacheExpiry) {
|
||||||
|
console.log('使用缓存的站点数据')
|
||||||
|
return allSitesCache.value
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('开始获取所有容器数据...')
|
||||||
|
let allContainers = []
|
||||||
|
let currentPage = 1
|
||||||
|
const pageSize = 100 // 每次请求100个容器
|
||||||
|
let hasMoreData = true
|
||||||
|
|
||||||
|
while (hasMoreData) {
|
||||||
|
const params = {
|
||||||
|
page: currentPage,
|
||||||
|
count: pageSize,
|
||||||
|
server_id: '',
|
||||||
|
user_id: '',
|
||||||
|
key: queryParams.domain || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`正在获取第${currentPage}页容器数据...`)
|
||||||
|
const response = await getSiteList(params)
|
||||||
|
|
||||||
|
if (response && response.data && response.data.data) {
|
||||||
|
const containerList = response.data.data || []
|
||||||
|
allContainers = allContainers.concat(containerList)
|
||||||
|
|
||||||
|
console.log(`第${currentPage}页获取到${containerList.length}个容器,总计${allContainers.length}个`)
|
||||||
|
|
||||||
|
// 检查是否还有更多数据
|
||||||
|
if (containerList.length < pageSize) {
|
||||||
|
hasMoreData = false
|
||||||
|
console.log('已获取所有容器数据')
|
||||||
|
} else {
|
||||||
|
currentPage++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasMoreData = false
|
||||||
|
console.log('API响应异常,停止获取')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有容器,合并所有web_list数据
|
||||||
|
let allWebSites = []
|
||||||
|
|
||||||
|
allContainers.forEach(container => {
|
||||||
|
if (container.web_list && Array.isArray(container.web_list)) {
|
||||||
|
const containerWebSites = container.web_list.map(webItem => ({
|
||||||
|
...webItem,
|
||||||
|
container_info: {
|
||||||
|
container_id: container.container_id || container.id,
|
||||||
|
container_name: container.name || container.container_name,
|
||||||
|
server_id: container.server_id,
|
||||||
|
user_id: container.user_id
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
allWebSites = allWebSites.concat(containerWebSites)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 将合并后的数据转换为页面需要的格式
|
||||||
|
const transformedData = allWebSites.map(item => {
|
||||||
|
let webAudit = {}
|
||||||
|
try {
|
||||||
|
webAudit = JSON.parse(item.web_audit || '{}')
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('解析web_audit失败:', e)
|
||||||
|
webAudit = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
container_id: item.container_id,
|
||||||
|
connect_id: item.connect_id,
|
||||||
|
url: webAudit.url || `http://${item.server_ip}:${item.server_port}`,
|
||||||
|
domain: item.domain || webAudit.web_key,
|
||||||
|
connect_type: item.connect_type,
|
||||||
|
server_ip: item.server_ip,
|
||||||
|
server_port: item.server_port,
|
||||||
|
container_ip: item.container_ip,
|
||||||
|
container_port: item.container_port,
|
||||||
|
floating_ip: item.floating_ip,
|
||||||
|
state: item.state,
|
||||||
|
connect: webAudit.connect || false,
|
||||||
|
is_violation: webAudit.is_violation || false,
|
||||||
|
violation_keys: webAudit.violation_keys || [],
|
||||||
|
createTime: formatTime(item.created_at),
|
||||||
|
ssl_cert: item.ssl_cert,
|
||||||
|
violationType: 'content',
|
||||||
|
severity: 'moderate',
|
||||||
|
processStatus: 'pending',
|
||||||
|
violationTime: formatTime(item.created_at),
|
||||||
|
reportCount: 1,
|
||||||
|
isBlocked: false,
|
||||||
|
violationDescription: webAudit.violation_keys ? webAudit.violation_keys.join(', ') : '检测到违规内容',
|
||||||
|
processHistory: [],
|
||||||
|
reportHistory: [],
|
||||||
|
container_info: item.container_info
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
allSitesCache.value = transformedData
|
||||||
|
cacheTimestamp.value = now
|
||||||
|
|
||||||
|
console.log(`总计获取到${transformedData.length}个站点数据`)
|
||||||
|
return transformedData
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取所有容器数据失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取违规站点列表数据
|
// 获取违规站点列表数据
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 构造API请求参数
|
// 获取所有站点数据
|
||||||
const params = {
|
const allSitesData = await getAllContainers()
|
||||||
page: queryParams.pageNum,
|
|
||||||
count: queryParams.pageSize,
|
// 只保留违规站点
|
||||||
key: queryParams.domain || '' // 使用域名作为搜索关键字
|
let violationData = allSitesData.filter(item => item.is_violation)
|
||||||
|
|
||||||
|
// 前端筛选逻辑
|
||||||
|
let filteredData = violationData
|
||||||
|
|
||||||
|
if (queryParams.violationType) {
|
||||||
|
filteredData = filteredData.filter(site => site.violationType === queryParams.violationType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用API获取违规站点列表
|
if (queryParams.severity) {
|
||||||
const response = await getAuditList(params)
|
filteredData = filteredData.filter(site => site.severity === queryParams.severity)
|
||||||
|
|
||||||
if (response && response.data) {
|
|
||||||
// 处理API返回的数据
|
|
||||||
const apiData = response.data.data || []
|
|
||||||
|
|
||||||
// 将API数据转换为页面需要的格式
|
|
||||||
const transformedData = apiData.map(item => ({
|
|
||||||
container_id: item.container_id || item.id || item.ID,
|
|
||||||
domain: item.domain || item.Domain || item.web_key,
|
|
||||||
title: item.title || item.Title || item.web_name || '违规站点',
|
|
||||||
status: getStatusFromApi(item.container_state),
|
|
||||||
lastCheck: formatTime(item.become_time),
|
|
||||||
createTime: formatTime(item.create_time || item.CreateTime || item.created_at || item.CreatedAt),
|
|
||||||
violationType: getViolationTypeFromApi(item.violation_type || item.type),
|
|
||||||
severity: getSeverityFromApi(item.severity || item.level),
|
|
||||||
processStatus: getProcessStatusFromApi(item.process_status || item.status),
|
|
||||||
violationTime: formatTime(item.violation_time || item.created_at || item.CreatedAt),
|
|
||||||
reportCount: item.report_count || item.count || 0,
|
|
||||||
isBlocked: item.is_blocked || item.blocked || false,
|
|
||||||
violationDescription: item.description || item.violation_desc || '检测到违规内容',
|
|
||||||
processHistory: [], // API可能不返回处理历史,可以单独获取
|
|
||||||
reportHistory: [] // API可能不返回举报历史,可以单独获取
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 前端筛选逻辑
|
|
||||||
let filteredData = transformedData
|
|
||||||
|
|
||||||
if (queryParams.violationType) {
|
|
||||||
filteredData = filteredData.filter(site => site.violationType === queryParams.violationType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queryParams.severity) {
|
|
||||||
filteredData = filteredData.filter(site => site.severity === queryParams.severity)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queryParams.processStatus) {
|
|
||||||
filteredData = filteredData.filter(site => site.processStatus === queryParams.processStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
violationList.value = filteredData
|
|
||||||
pagination.total = response.data.total || response.data.count || filteredData.length
|
|
||||||
|
|
||||||
// 更新统计数据
|
|
||||||
updateStats(filteredData)
|
|
||||||
} else {
|
|
||||||
violationList.value = []
|
|
||||||
pagination.total = 0
|
|
||||||
updateStats([])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (queryParams.processStatus) {
|
||||||
|
filteredData = filteredData.filter(site => site.processStatus === queryParams.processStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用搜索筛选
|
||||||
|
if (queryParams.domain) {
|
||||||
|
filteredData = filteredData.filter(site =>
|
||||||
|
site.container_id?.toLowerCase().includes(queryParams.domain.toLowerCase()) ||
|
||||||
|
site.url?.toLowerCase().includes(queryParams.domain.toLowerCase()) ||
|
||||||
|
site.domain?.toLowerCase().includes(queryParams.domain.toLowerCase())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现前端分页
|
||||||
|
const startIndex = (queryParams.pageNum - 1) * queryParams.pageSize
|
||||||
|
const endIndex = startIndex + queryParams.pageSize
|
||||||
|
const paginatedData = filteredData.slice(startIndex, endIndex)
|
||||||
|
|
||||||
|
violationList.value = paginatedData
|
||||||
|
pagination.total = filteredData.length
|
||||||
|
|
||||||
|
// 更新统计数据(基于所有筛选后的数据,不是分页后的数据)
|
||||||
|
updateStats(filteredData)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取违规站点列表失败:', error)
|
console.error('获取违规站点列表失败:', error)
|
||||||
ElMessage.error('获取违规站点列表失败')
|
ElMessage.error('获取违规站点列表失败')
|
||||||
@@ -538,24 +657,24 @@ const updateStats = (data = []) => {
|
|||||||
violationStats.pending = data.filter(site => site.processStatus === 'pending').length
|
violationStats.pending = data.filter(site => site.processStatus === 'pending').length
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态类型
|
// 获取连接类型颜色
|
||||||
const getStatusType = (status) => {
|
const getConnectTypeColor = (type) => {
|
||||||
const statusMap = {
|
const typeMap = {
|
||||||
normal: 'success',
|
'port_forward': 'primary',
|
||||||
warning: 'warning',
|
'domain': 'success',
|
||||||
violation: 'danger'
|
'floating_ip': 'warning'
|
||||||
}
|
}
|
||||||
return statusMap[status] || 'info'
|
return typeMap[type] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态文本
|
// 获取连接类型文本
|
||||||
const getStatusText = (status) => {
|
const getConnectTypeText = (type) => {
|
||||||
const statusMap = {
|
const typeMap = {
|
||||||
normal: '已构建', // 对应容器状态 2
|
'port_forward': '端口转发',
|
||||||
warning: '未构建', // 对应容器状态 1 和 3(未知)
|
'domain': '域名绑定',
|
||||||
violation: '异常' // 对应容器状态 0(未支付) 和 4(已删除)
|
'floating_ip': '浮动IP'
|
||||||
}
|
}
|
||||||
return statusMap[status] || '未知'
|
return typeMap[type] || type || '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取违规类型颜色
|
// 获取违规类型颜色
|
||||||
@@ -663,6 +782,9 @@ const handleRefresh = () => {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
|
// 清除缓存,强制重新获取数据
|
||||||
|
allSitesCache.value = []
|
||||||
|
cacheTimestamp.value = 0
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,4 +1160,10 @@ onMounted(() => {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义样式 */
|
||||||
|
.text-muted {
|
||||||
|
color: #909399;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -199,9 +199,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
|
import { ref, reactive, computed, onMounted, nextTick, watch, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Search, Plus, Loading } from '@element-plus/icons-vue'
|
import { Search, Plus, Loading } from '@element-plus/icons-vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
getTickerList,
|
getTickerList,
|
||||||
getTicketDetail,
|
getTicketDetail,
|
||||||
@@ -212,6 +213,10 @@ import {
|
|||||||
parseFilesToImages
|
parseFilesToImages
|
||||||
} from '@/api/ticket'
|
} from '@/api/ticket'
|
||||||
|
|
||||||
|
// 路由相关
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 管理员ID列表(客服ID)
|
// 管理员ID列表(客服ID)
|
||||||
const adminUserIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // 假设这些ID是客服ID
|
const adminUserIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // 假设这些ID是客服ID
|
||||||
|
|
||||||
@@ -229,6 +234,11 @@ const hasMore = ref(true)
|
|||||||
const refreshTimer = ref(null)
|
const refreshTimer = ref(null)
|
||||||
const refreshInterval = 5000 // 5秒刷新一次
|
const refreshInterval = 5000 // 5秒刷新一次
|
||||||
|
|
||||||
|
// 页面可见性状态
|
||||||
|
const isPageVisible = ref(true)
|
||||||
|
// 当前路由路径,用于检测路由变化
|
||||||
|
const currentRoutePath = ref(route.path)
|
||||||
|
|
||||||
// 工单数据
|
// 工单数据
|
||||||
const ticketList = ref([])
|
const ticketList = ref([])
|
||||||
const currentTicket = ref(null)
|
const currentTicket = ref(null)
|
||||||
@@ -751,6 +761,11 @@ const startAutoRefresh = () => {
|
|||||||
stopAutoRefresh()
|
stopAutoRefresh()
|
||||||
|
|
||||||
refreshTimer.value = setInterval(() => {
|
refreshTimer.value = setInterval(() => {
|
||||||
|
// 检查页面可见性和路由是否还在当前页面
|
||||||
|
if (!isPageVisible.value || route.path !== currentRoutePath.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 如果当前有工单选中,则刷新聊天记录
|
// 如果当前有工单选中,则刷新聊天记录
|
||||||
if (currentTicket.value && selectedTicketId.value) {
|
if (currentTicket.value && selectedTicketId.value) {
|
||||||
// 静默刷新聊天记录,不显示loading状态
|
// 静默刷新聊天记录,不显示loading状态
|
||||||
@@ -867,7 +882,38 @@ const refreshTicketMessages = async (workId) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理页面可见性变化
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
isPageVisible.value = !document.hidden
|
||||||
|
|
||||||
|
if (isPageVisible.value && route.path === currentRoutePath.value) {
|
||||||
|
// 页面变为可见且还在当前路由时,重新启动定时器
|
||||||
|
startAutoRefresh()
|
||||||
|
} else {
|
||||||
|
// 页面不可见或路由已改变时,停止定时器
|
||||||
|
stopAutoRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
watch(() => route.path, (newPath, oldPath) => {
|
||||||
|
if (newPath !== currentRoutePath.value) {
|
||||||
|
// 路由已经离开当前页面,立即停止所有请求
|
||||||
|
console.log('路由变化:从', oldPath, '到', newPath, ',停止工单数据请求')
|
||||||
|
stopAutoRefresh()
|
||||||
|
isPageVisible.value = false
|
||||||
|
} else if (newPath === currentRoutePath.value) {
|
||||||
|
// 路由返回到当前页面,重新启动请求
|
||||||
|
console.log('路由返回到工单页面,重新启动数据请求')
|
||||||
|
isPageVisible.value = true
|
||||||
|
startAutoRefresh()
|
||||||
|
}
|
||||||
|
}, { immediate: false })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 记录当前路由路径
|
||||||
|
currentRoutePath.value = route.path
|
||||||
|
|
||||||
// 获取工单列表
|
// 获取工单列表
|
||||||
fetchTicketList()
|
fetchTicketList()
|
||||||
|
|
||||||
@@ -880,6 +926,9 @@ onMounted(() => {
|
|||||||
listElement.addEventListener('scroll', handleScroll)
|
listElement.addEventListener('scroll', handleScroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听页面可见性变化
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||||
|
|
||||||
// 启动自动刷新
|
// 启动自动刷新
|
||||||
startAutoRefresh()
|
startAutoRefresh()
|
||||||
})
|
})
|
||||||
@@ -893,6 +942,24 @@ onBeforeUnmount(() => {
|
|||||||
if (listElement) {
|
if (listElement) {
|
||||||
listElement.removeEventListener('scroll', handleScroll)
|
listElement.removeEventListener('scroll', handleScroll)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 移除页面可见性监听
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件激活时(keep-alive场景)
|
||||||
|
onActivated(() => {
|
||||||
|
console.log('组件激活,重新启动工单数据请求')
|
||||||
|
currentRoutePath.value = route.path
|
||||||
|
isPageVisible.value = true
|
||||||
|
startAutoRefresh()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件失活时(keep-alive场景)
|
||||||
|
onDeactivated(() => {
|
||||||
|
console.log('组件失活,停止工单数据请求')
|
||||||
|
isPageVisible.value = false
|
||||||
|
stopAutoRefresh()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user