fix(web): 修复代码格式和文件清理问题

清理无用文件和资源,调整代码格式统一双引号,修复部分组件路径引用问题

删除大量无用图片资源和组件文件,统一代码风格使用双引号,修复国际化文件路径引用,调整部分组件逻辑,优化项目结构
This commit is contained in:
zhangtao
2026-05-01 01:05:16 +08:00
parent 80263e98e1
commit b7800e2ddd
595 changed files with 24834 additions and 103011 deletions
+290 -290
View File
@@ -23,13 +23,13 @@
</template>
<el-descriptions :column="12" border :direction="'vertical'">
<el-descriptions-item label="Redis版本">
{{ cache.info?.redis_version || '-' }}
{{ cache.info?.redis_version || "-" }}
</el-descriptions-item>
<el-descriptions-item label="运行模式">
{{ cache.info?.redis_mode === 'standalone' ? '单机' : '集群' }}
{{ cache.info?.redis_mode === "standalone" ? "单机" : "集群" }}
</el-descriptions-item>
<el-descriptions-item label="端口">
{{ cache.info?.tcp_port || '-' }}
{{ cache.info?.tcp_port || "-" }}
</el-descriptions-item>
<el-descriptions-item label="客户端数">
{{ cache.info?.connected_clients || 0 }}
@@ -38,23 +38,23 @@
{{ cache.info?.uptime_in_days || 0 }}
</el-descriptions-item>
<el-descriptions-item label="使用内存">
{{ cache.info?.used_memory_human || '-' }}
{{ cache.info?.used_memory_human || "-" }}
</el-descriptions-item>
<el-descriptions-item label="使用CPU">
{{
cache.info?.used_cpu_user_children
? parseFloat(cache.info.used_cpu_user_children).toFixed(2)
: '-'
: "-"
}}
</el-descriptions-item>
<el-descriptions-item label="内存配置">
{{ cache.info?.maxmemory_human || '-' }}
{{ cache.info?.maxmemory_human || "-" }}
</el-descriptions-item>
<el-descriptions-item label="AOF是否开启">
{{ cache.info?.aof_enabled === '0' ? '否' : '是' }}
{{ cache.info?.aof_enabled === "0" ? "否" : "是" }}
</el-descriptions-item>
<el-descriptions-item label="RDB是否成功">
{{ cache.info?.rdb_last_bgsave_status || '-' }}
{{ cache.info?.rdb_last_bgsave_status || "-" }}
</el-descriptions-item>
<el-descriptions-item label="Key数量">
{{ cache.db_size || 0 }}
@@ -325,334 +325,334 @@
</template>
<script lang="ts" setup>
import CacheAPI, {
type CacheInfo,
type CacheForm,
type CacheMonitor,
type RedisInfo,
} from '@/api/module_monitor/cache';
import * as echarts from 'echarts';
import CacheAPI, {
type CacheInfo,
type CacheForm,
type CacheMonitor,
type RedisInfo,
} from "@/api/module_monitor/cache";
import * as echarts from "echarts";
// 响应式状态定义
const cacheNames = ref<CacheInfo[]>([]);
const cacheKeys = ref<string[]>([]);
const loading = ref(true);
const subLoading = ref(false);
const nowCacheName = ref('');
const commandstats = ref<HTMLElement | null>(null);
const usedmemory = ref<HTMLElement | null>(null);
const cache = ref<CacheMonitor>({
info: {} as RedisInfo,
command_stats: [],
db_size: 0,
});
const cacheForm = ref<CacheForm>({
cache_name: '',
cache_key: '',
cache_value: '',
});
// 响应式状态定义
const cacheNames = ref<CacheInfo[]>([]);
const cacheKeys = ref<string[]>([]);
const loading = ref(true);
const subLoading = ref(false);
const nowCacheName = ref("");
const commandstats = ref<HTMLElement | null>(null);
const usedmemory = ref<HTMLElement | null>(null);
const cache = ref<CacheMonitor>({
info: {} as RedisInfo,
command_stats: [],
db_size: 0,
});
const cacheForm = ref<CacheForm>({
cache_name: "",
cache_key: "",
cache_value: "",
});
let commandstatsInstance: echarts.ECharts | null = null;
let usedmemoryInstance: echarts.ECharts | null = null;
let commandstatsInstance: echarts.ECharts | null = null;
let usedmemoryInstance: echarts.ECharts | null = null;
const resetCacheForm = () => {
cacheKeys.value = [];
const resetCacheForm = () => {
cacheKeys.value = [];
cacheForm.value = {
cache_name: "",
cache_key: "",
cache_value: "",
};
};
// 缓存名称相关方法
const getCacheNameList = async () => {
try {
loading.value = true;
const response = await CacheAPI.getCacheNames();
cacheNames.value = response.data.data;
resetCacheForm();
} catch (error) {
console.error("获取缓存列表出错:", error);
} finally {
loading.value = false;
}
};
// 刷新缓存列表
const refreshCacheNames = () => {
getCacheNameList();
};
// 清理缓存名称
const handleClearCacheName = async (row: CacheInfo) => {
try {
await CacheAPI.deleteCacheName(row.cache_name);
refreshCacheNames();
} catch (error) {
console.error("清理缓存名称出错:", error);
}
};
// 缓存键名相关方法
const getCacheKeyList = async (row?: CacheInfo) => {
try {
const cacheName = row?.cache_name || nowCacheName.value;
if (!cacheName) return;
subLoading.value = true;
const response = await CacheAPI.getCacheKeys(cacheName);
cacheKeys.value = response.data.data;
nowCacheName.value = cacheName;
cacheForm.value = {
cache_name: '',
cache_key: '',
cache_value: '',
cache_name: cacheName,
cache_key: "",
cache_value: "",
};
};
} catch (error) {
console.error("获取缓存键名列表出错:", error);
} finally {
subLoading.value = false;
}
};
// 缓存名称相关方法
const getCacheNameList = async () => {
try {
loading.value = true;
const response = await CacheAPI.getCacheNames();
cacheNames.value = response.data.data;
resetCacheForm();
} catch (error) {
console.error('获取缓存列表出错:', error);
} finally {
loading.value = false;
}
};
// 刷新键名列表
const refreshCacheKeys = () => {
getCacheKeyList();
};
// 刷新缓存列表
const refreshCacheNames = () => {
getCacheNameList();
};
// 清理缓存名称
const handleClearCacheName = async (row: CacheInfo) => {
try {
await CacheAPI.deleteCacheName(row.cache_name);
refreshCacheNames();
} catch (error) {
console.error('清理缓存名称出错:', error);
}
};
// 缓存键名相关方法
const getCacheKeyList = async (row?: CacheInfo) => {
try {
const cacheName = row?.cache_name || nowCacheName.value;
if (!cacheName) return;
subLoading.value = true;
const response = await CacheAPI.getCacheKeys(cacheName);
cacheKeys.value = response.data.data;
nowCacheName.value = cacheName;
cacheForm.value = {
cache_name: cacheName,
cache_key: '',
cache_value: '',
};
} catch (error) {
console.error('获取缓存键名列表出错:', error);
} finally {
subLoading.value = false;
}
};
// 刷新键名列表
const refreshCacheKeys = () => {
// 清理缓存键名
async function handleClearCacheKey(cacheKey: string) {
try {
await CacheAPI.deleteCacheKey(cacheKey);
getCacheKeyList();
};
// 清理缓存键名
async function handleClearCacheKey(cacheKey: string) {
try {
await CacheAPI.deleteCacheKey(cacheKey);
getCacheKeyList();
} catch (error) {
console.error('清理缓存键名出错:', error);
}
} catch (error) {
console.error("清理缓存键名出错:", error);
}
}
// 缓存内容相关方法
async function handleCacheValue(cacheKey: string) {
try {
loading.value = true;
const response = await CacheAPI.getCacheValue(nowCacheName.value, cacheKey);
cacheForm.value = response.data.data;
} catch (error) {
console.error('获取缓存内容失败:', error);
} finally {
loading.value = false;
}
// 缓存内容相关方法
async function handleCacheValue(cacheKey: string) {
try {
loading.value = true;
const response = await CacheAPI.getCacheValue(nowCacheName.value, cacheKey);
cacheForm.value = response.data.data;
} catch (error) {
console.error("获取缓存内容失败:", error);
} finally {
loading.value = false;
}
}
// 清理全部缓存
const handleClearCacheAll = async () => {
ElMessageBox.confirm('确定要清理全部缓存吗?', '危险!', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
// 清理全部缓存
const handleClearCacheAll = async () => {
ElMessageBox.confirm("确定要清理全部缓存吗?", "危险!", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
return await CacheAPI.deleteCacheAll();
})
.then(async () => {
return await CacheAPI.deleteCacheAll();
})
.then(() => {
getCacheNameList();
})
.catch((error) => {
if (error !== 'cancel') {
console.error('清理全部缓存失败:', error);
}
});
};
// 监控数据获取
const getInfo = async () => {
try {
loading.value = true;
const response = await CacheAPI.getCacheInfo();
cache.value = response.data.data || {
info: {},
command_stats: [],
dbSize: 0,
};
initCharts();
} catch (error) {
console.error('获取缓存监控数据失败:', error);
} finally {
loading.value = false;
}
};
// 图表初始化
const initCharts = () => {
if (!commandstats.value || !usedmemory.value) return;
commandstatsInstance = echarts.init(commandstats.value, 'macarons');
usedmemoryInstance = echarts.init(usedmemory.value, 'macarons');
const commandStatsOption = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)',
},
series: [
{
name: '命令',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: cache.value.command_stats || [],
animationEasing: 'cubicInOut',
animationDuration: 1000,
},
],
};
const usedMemory = cache.value.info?.used_memory_human || '0';
const usedMemoryOption = {
tooltip: {
formatter: `{b} <br/>{a} : ${usedMemory}`,
},
series: [
{
name: '峰值',
type: 'gauge',
min: 0,
max: 1000,
detail: {
formatter: usedMemory,
},
data: [
{
value: parseFloat(usedMemory) || 0,
name: '内存消耗',
},
],
},
],
};
commandstatsInstance.setOption(commandStatsOption);
usedmemoryInstance.setOption(usedMemoryOption);
void nextTick(() => {
commandstatsInstance?.resize();
usedmemoryInstance?.resize();
.then(() => {
getCacheNameList();
})
.catch((error) => {
if (error !== "cancel") {
console.error("清理全部缓存失败:", error);
}
});
};
// 监控数据获取
const getInfo = async () => {
try {
loading.value = true;
const response = await CacheAPI.getCacheInfo();
cache.value = response.data.data || {
info: {},
command_stats: [],
dbSize: 0,
};
initCharts();
} catch (error) {
console.error("获取缓存监控数据失败:", error);
} finally {
loading.value = false;
}
};
// 图表初始化
const initCharts = () => {
if (!commandstats.value || !usedmemory.value) return;
commandstatsInstance = echarts.init(commandstats.value, "macarons");
usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
const commandStatsOption = {
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
series: [
{
name: "命令",
type: "pie",
roseType: "radius",
radius: [15, 95],
center: ["50%", "38%"],
data: cache.value.command_stats || [],
animationEasing: "cubicInOut",
animationDuration: 1000,
},
],
};
// 生命周期钩子
onMounted(() => {
getCacheNameList();
getInfo();
});
const usedMemory = cache.value.info?.used_memory_human || "0";
const usedMemoryOption = {
tooltip: {
formatter: `{b} <br/>{a} : ${usedMemory}`,
},
series: [
{
name: "峰值",
type: "gauge",
min: 0,
max: 1000,
detail: {
formatter: usedMemory,
},
data: [
{
value: parseFloat(usedMemory) || 0,
name: "内存消耗",
},
],
},
],
};
onUnmounted(() => {
commandstatsInstance?.dispose();
usedmemoryInstance?.dispose();
commandstatsInstance.setOption(commandStatsOption);
usedmemoryInstance.setOption(usedMemoryOption);
void nextTick(() => {
commandstatsInstance?.resize();
usedmemoryInstance?.resize();
});
};
// 生命周期钩子
onMounted(() => {
getCacheNameList();
getInfo();
});
onUnmounted(() => {
commandstatsInstance?.dispose();
usedmemoryInstance?.dispose();
});
</script>
<style scoped lang="scss">
/* 与 .app-container 纵向 flex 衔接:tabs 占满剩余高度 */
.cache-page-tabs {
/* 与 .app-container 纵向 flex 衔接:tabs 占满剩余高度 */
.cache-page-tabs {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
min-height: 0;
:deep(.el-tabs__header) {
flex-shrink: 0;
}
:deep(.el-tabs__content) {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
min-height: 0;
:deep(.el-tabs__header) {
flex-shrink: 0;
}
:deep(.el-tabs__content) {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
:deep(.el-tab-pane) {
box-sizing: border-box;
flex: 1;
min-height: 0;
overflow: auto;
}
overflow: hidden;
}
.monitor-tab {
:deep(.el-tab-pane) {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16px;
height: 100%;
min-height: 0;
}
.monitor-charts-row {
flex: 1;
align-items: stretch;
min-height: 0;
overflow: auto;
}
}
.cache-chart-col {
display: flex;
flex-direction: column;
min-height: 0;
}
.monitor-tab {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16px;
height: 100%;
min-height: 0;
}
.cache-chart-card {
.monitor-charts-row {
flex: 1;
align-items: stretch;
min-height: 0;
}
.cache-chart-col {
display: flex;
flex-direction: column;
min-height: 0;
}
.cache-chart-card {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
:deep(.el-card__body) {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
:deep(.el-card__body) {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
}
}
.chart-container {
flex: 1;
height: 100%;
min-height: 200px;
}
.chart-container {
flex: 1;
height: 100%;
min-height: 200px;
}
.cache-mgmt-tab {
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.cache-mgmt-tab {
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.cache-mgmt-row {
flex: 1;
align-items: stretch;
min-height: 0;
}
.cache-mgmt-row {
flex: 1;
align-items: stretch;
min-height: 0;
}
.cache-mgmt-col {
display: flex;
flex-direction: column;
min-height: 0;
}
.cache-mgmt-col {
display: flex;
flex-direction: column;
min-height: 0;
}
.cache-mgmt-card {
.cache-mgmt-card {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
:deep(.el-card__body) {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
:deep(.el-card__body) {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
}
}
</style>
@@ -156,127 +156,127 @@
</template>
<script lang="ts" setup>
defineOptions({
name: 'OnlineUser',
inheritAttrs: false,
});
defineOptions({
name: "OnlineUser",
inheritAttrs: false,
});
import OnlineAPI, { type OnlineUserPageQuery } from '@/api/module_monitor/online';
import { reactive } from 'vue';
import CrudToolbarLeft from '@/components/CURD/CrudToolbarLeft.vue';
import CrudToolbarRight from '@/components/CURD/CrudToolbarRight.vue';
import PageSearch from '@/components/CURD/PageSearch.vue';
import PageContent from '@/components/CURD/PageContent.vue';
import { useCrudList } from '@/components/CURD/useCrudList';
import type { ISearchConfig, IContentConfig } from '@/components/CURD/types';
import OnlineAPI, { type OnlineUserPageQuery } from "@/api/module_monitor/online";
import { reactive } from "vue";
import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
import PageSearch from "@/components/CURD/PageSearch.vue";
import PageContent from "@/components/CURD/PageContent.vue";
import { useCrudList } from "@/components/CURD/useCrudList";
import type { ISearchConfig, IContentConfig } from "@/components/CURD/types";
const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
const searchConfig = reactive<ISearchConfig>({
permPrefix: 'module_monitor:online',
colon: true,
isExpandable: false,
showNumber: 3,
form: { labelWidth: 'auto' },
formItems: [
{
prop: 'ipaddr',
label: 'IP地址',
type: 'input',
attrs: { placeholder: '请输入IP地址', clearable: true },
},
{
prop: 'name',
label: '用户名',
type: 'input',
attrs: { placeholder: '请输入用户名', clearable: true },
},
{
prop: 'login_location',
label: '登录位置',
type: 'input',
attrs: { placeholder: '请输入登录位置', clearable: true },
},
],
});
const contentCols = reactive<
Array<{
prop?: string;
label?: string;
show?: boolean;
}>
>([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'session_id', label: '会话编号', show: true },
{ prop: 'login_type', label: '登录类型', show: true },
{ prop: 'name', label: '登录名称', show: true },
{ prop: 'user_name', label: '用户账号', show: true },
{ prop: 'ipaddr', label: '主机', show: true },
{ prop: 'login_location', label: '登录地点', show: true },
{ prop: 'os', label: '操作系统', show: true },
{ prop: 'login_time', label: '登录时间', show: true },
{ prop: 'operation', label: '操作', show: true },
]);
const contentConfig = reactive<IContentConfig<OnlineUserPageQuery>>({
permPrefix: 'module_monitor:online',
cols: contentCols as IContentConfig['cols'],
hideColumnFilter: false,
toolbar: [],
defaultToolbar: ['refresh', 'filter'],
pagination: {
pageSize: 10,
pageSizes: [10, 20, 30, 50],
const searchConfig = reactive<ISearchConfig>({
permPrefix: "module_monitor:online",
colon: true,
isExpandable: false,
showNumber: 3,
form: { labelWidth: "auto" },
formItems: [
{
prop: "ipaddr",
label: "IP地址",
type: "input",
attrs: { placeholder: "请输入IP地址", clearable: true },
},
request: { page_no: 'page_no', page_size: 'page_size' },
indexAction: async (params) => {
const res = await OnlineAPI.listOnline(params as OnlineUserPageQuery);
return {
total: res.data.data.total,
list: res.data.data.items,
};
{
prop: "name",
label: "用户名",
type: "input",
attrs: { placeholder: "请输入用户名", clearable: true },
},
});
{
prop: "login_location",
label: "登录位置",
type: "input",
attrs: { placeholder: "请输入登录位置", clearable: true },
},
],
});
async function handleSubmit(session_id: string) {
ElMessageBox.confirm(`确认强制退出会话 ${session_id}?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
await OnlineAPI.deleteOnline(session_id);
refreshList();
} catch (error: unknown) {
console.error(error);
}
})
.catch(() => {
ElMessageBox.close();
});
}
const contentCols = reactive<
Array<{
prop?: string;
label?: string;
show?: boolean;
}>
>([
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "session_id", label: "会话编号", show: true },
{ prop: "login_type", label: "登录类型", show: true },
{ prop: "name", label: "登录名称", show: true },
{ prop: "user_name", label: "用户账号", show: true },
{ prop: "ipaddr", label: "主机", show: true },
{ prop: "login_location", label: "登录地点", show: true },
{ prop: "os", label: "操作系统", show: true },
{ prop: "login_time", label: "登录时间", show: true },
{ prop: "operation", label: "操作", show: true },
]);
async function handleClear() {
ElMessageBox.confirm('确认强制退出所有用户?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
const contentConfig = reactive<IContentConfig<OnlineUserPageQuery>>({
permPrefix: "module_monitor:online",
cols: contentCols as IContentConfig["cols"],
hideColumnFilter: false,
toolbar: [],
defaultToolbar: ["refresh", "filter"],
pagination: {
pageSize: 10,
pageSizes: [10, 20, 30, 50],
},
request: { page_no: "page_no", page_size: "page_size" },
indexAction: async (params) => {
const res = await OnlineAPI.listOnline(params as OnlineUserPageQuery);
return {
total: res.data.data.total,
list: res.data.data.items,
};
},
});
async function handleSubmit(session_id: string) {
ElMessageBox.confirm(`确认强制退出会话 ${session_id}?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
await OnlineAPI.deleteOnline(session_id);
refreshList();
} catch (error: unknown) {
console.error(error);
}
})
.then(async () => {
try {
await OnlineAPI.clearOnline();
refreshList();
} catch (error: unknown) {
console.error(error);
}
})
.catch(() => {
ElMessageBox.close();
});
}
.catch(() => {
ElMessageBox.close();
});
}
async function handleClear() {
ElMessageBox.confirm("确认强制退出所有用户?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
await OnlineAPI.clearOnline();
refreshList();
} catch (error: unknown) {
console.error(error);
}
})
.catch(() => {
ElMessageBox.close();
});
}
</script>
<style lang="scss" scoped></style>
@@ -259,351 +259,351 @@
</template>
<script setup lang="ts">
defineOptions({
name: 'ResourceMonitor',
inheritAttrs: false,
});
defineOptions({
name: "ResourceMonitor",
inheritAttrs: false,
});
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Folder, Document, UploadFilled, QuestionFilled } from '@element-plus/icons-vue';
import {
ResourceAPI,
type ResourceItem,
type ResourcePageQuery,
} from '@/api/module_monitor/resource';
import CrudToolbarLeft from '@/components/CURD/CrudToolbarLeft.vue';
import CrudToolbarRight from '@/components/CURD/CrudToolbarRight.vue';
import PageSearch from '@/components/CURD/PageSearch.vue';
import PageContent from '@/components/CURD/PageContent.vue';
import EnhancedDialog from '@/components/CURD/EnhancedDialog.vue';
import { useCrudList } from '@/components/CURD/useCrudList';
import type { IContentConfig, ISearchConfig } from '@/components/CURD/types';
import { ref, reactive } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Folder, Document, UploadFilled, QuestionFilled } from "@element-plus/icons-vue";
import {
ResourceAPI,
type ResourceItem,
type ResourcePageQuery,
} from "@/api/module_monitor/resource";
import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
import PageSearch from "@/components/CURD/PageSearch.vue";
import PageContent from "@/components/CURD/PageContent.vue";
import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
import { useCrudList } from "@/components/CURD/useCrudList";
import type { IContentConfig, ISearchConfig } from "@/components/CURD/types";
const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
const selectedItems = ref<ResourceItem[]>([]);
const breadcrumbList = ref([{ name: '资源根目录', path: '/' }]);
const showHiddenFiles = ref(false);
const selectedItems = ref<ResourceItem[]>([]);
const breadcrumbList = ref([{ name: "资源根目录", path: "/" }]);
const showHiddenFiles = ref(false);
const currentPath = ref('/');
const currentPath = ref("/");
const searchConfig = reactive<ISearchConfig>({
permPrefix: 'module_monitor:resource',
colon: true,
isExpandable: false,
showNumber: 3,
form: { labelWidth: 'auto' },
searchButtonPerm: 'module_monitor:resource:query',
resetButtonPerm: 'module_monitor:resource:query',
formItems: [
{
prop: 'name',
label: '关键词',
type: 'input',
attrs: { placeholder: '请输入文件名或目录名', clearable: true, style: { width: '200px' } },
},
],
});
const contentConfig = reactive<IContentConfig<ResourcePageQuery>>({
permPrefix: 'module_monitor:resource',
cols: [],
hideColumnFilter: true,
toolbar: [],
defaultToolbar: ['refresh'],
pk: 'file_url',
pagination: {
pageSize: 10,
pageSizes: [10, 20, 30, 50],
const searchConfig = reactive<ISearchConfig>({
permPrefix: "module_monitor:resource",
colon: true,
isExpandable: false,
showNumber: 3,
form: { labelWidth: "auto" },
searchButtonPerm: "module_monitor:resource:query",
resetButtonPerm: "module_monitor:resource:query",
formItems: [
{
prop: "name",
label: "关键词",
type: "input",
attrs: { placeholder: "请输入文件名或目录名", clearable: true, style: { width: "200px" } },
},
request: { page_no: 'page_no', page_size: 'page_size' },
indexAction: async (params) => {
const merged: ResourcePageQuery = {
...(params as ResourcePageQuery),
include_hidden: showHiddenFiles.value,
};
if (currentPath.value && currentPath.value !== '/') {
merged.path = currentPath.value;
}
const res = await ResourceAPI.listResource(merged);
return {
total: res.data.data.total,
list: res.data.data.items,
};
},
});
],
});
const uploadDialogVisible = ref(false);
const createDirDialogVisible = ref(false);
const renameDialogVisible = ref(false);
const uploading = ref(false);
const contentConfig = reactive<IContentConfig<ResourcePageQuery>>({
permPrefix: "module_monitor:resource",
cols: [],
hideColumnFilter: true,
toolbar: [],
defaultToolbar: ["refresh"],
pk: "file_url",
pagination: {
pageSize: 10,
pageSizes: [10, 20, 30, 50],
},
request: { page_no: "page_no", page_size: "page_size" },
indexAction: async (params) => {
const merged: ResourcePageQuery = {
...(params as ResourcePageQuery),
include_hidden: showHiddenFiles.value,
};
if (currentPath.value && currentPath.value !== "/") {
merged.path = currentPath.value;
}
const res = await ResourceAPI.listResource(merged);
return {
total: res.data.data.total,
list: res.data.data.items,
};
},
});
const uploadRef = ref();
const uploadFileList = ref<any[]>([]);
const uploadDialogVisible = ref(false);
const createDirDialogVisible = ref(false);
const renameDialogVisible = ref(false);
const uploading = ref(false);
const createDirForm = reactive({
dir_name: '',
});
const uploadRef = ref();
const uploadFileList = ref<any[]>([]);
const renameForm = reactive({
new_name: '',
old_path: '',
});
const createDirForm = reactive({
dir_name: "",
});
function handleBreadcrumbClick(item: { path: string }) {
currentPath.value = item.path;
const renameForm = reactive({
new_name: "",
old_path: "",
});
function handleBreadcrumbClick(item: { path: string }) {
currentPath.value = item.path;
updateBreadcrumb();
refreshList();
}
function updateBreadcrumb() {
if (currentPath.value === "/") {
breadcrumbList.value = [{ name: "资源根目录", path: "/" }];
return;
}
const parts = currentPath.value.split("/").filter((part) => part !== "");
breadcrumbList.value = [
{ name: "资源根目录", path: "/" },
...parts.map((part, index) => ({
name: part,
path: parts.slice(0, index + 1).join("/"),
})),
];
}
function handleFileNameClick(row: ResourceItem) {
if (row.is_dir) {
if (currentPath.value === "/") {
currentPath.value = row.name;
} else {
currentPath.value = currentPath.value + "/" + row.name;
}
updateBreadcrumb();
refreshList();
} else {
handleFilePreview(row);
}
}
function handleFilePreview(file: ResourceItem) {
let previewUrl = file.file_url;
if (previewUrl && !previewUrl.startsWith("http")) {
previewUrl = `${window.location.origin}${previewUrl}`;
}
function updateBreadcrumb() {
if (currentPath.value === '/') {
breadcrumbList.value = [{ name: '资源根目录', path: '/' }];
return;
}
window.open(previewUrl, "_blank");
}
const parts = currentPath.value.split('/').filter((part) => part !== '');
function handleSelectionChange(selection: ResourceItem[]) {
selectedItems.value = selection;
}
breadcrumbList.value = [
{ name: '资源根目录', path: '/' },
...parts.map((part, index) => ({
name: part,
path: parts.slice(0, index + 1).join('/'),
})),
];
function handleUpload() {
uploadDialogVisible.value = true;
uploadFileList.value = [];
}
function handleUploadChange(_file: unknown, fileList: unknown[]) {
uploadFileList.value = fileList as any[];
}
async function handleUploadConfirm() {
if (uploadFileList.value.length === 0) {
ElMessage.warning("请选择要上传的文件");
return;
}
function handleFileNameClick(row: ResourceItem) {
if (row.is_dir) {
if (currentPath.value === '/') {
currentPath.value = row.name;
} else {
currentPath.value = currentPath.value + '/' + row.name;
}
updateBreadcrumb();
refreshList();
} else {
handleFilePreview(row);
}
}
try {
uploading.value = true;
const formData = new FormData();
uploadFileList.value.forEach((file: { raw?: Blob }) => {
const raw = file.raw;
if (raw) formData.append("file", raw);
});
function handleFilePreview(file: ResourceItem) {
let previewUrl = file.file_url;
const targetPath = currentPath.value === "/" ? "" : currentPath.value;
formData.append("target_path", targetPath);
if (previewUrl && !previewUrl.startsWith('http')) {
previewUrl = `${window.location.origin}${previewUrl}`;
}
window.open(previewUrl, '_blank');
}
function handleSelectionChange(selection: ResourceItem[]) {
selectedItems.value = selection;
}
function handleUpload() {
uploadDialogVisible.value = true;
uploadFileList.value = [];
}
function handleUploadChange(_file: unknown, fileList: unknown[]) {
uploadFileList.value = fileList as any[];
}
async function handleUploadConfirm() {
if (uploadFileList.value.length === 0) {
ElMessage.warning('请选择要上传的文件');
return;
}
try {
uploading.value = true;
const formData = new FormData();
uploadFileList.value.forEach((file: { raw?: Blob }) => {
const raw = file.raw;
if (raw) formData.append('file', raw);
});
const targetPath = currentPath.value === '/' ? '' : currentPath.value;
formData.append('target_path', targetPath);
await ResourceAPI.uploadFile(formData);
uploadDialogVisible.value = false;
refreshList();
} catch (error) {
console.error('Upload error:', error);
} finally {
uploading.value = false;
}
}
function handleUploadClose() {
await ResourceAPI.uploadFile(formData);
uploadDialogVisible.value = false;
uploadFileList.value = [];
}
function handleCreateDir() {
createDirForm.dir_name = '';
createDirDialogVisible.value = true;
}
async function handleCreateDirConfirm() {
if (!createDirForm.dir_name.trim()) {
ElMessage.warning('请输入文件夹名称');
return;
}
try {
const parentPath = currentPath.value === '/' ? '' : currentPath.value;
await ResourceAPI.createDirectory({
parent_path: parentPath,
dir_name: createDirForm.dir_name.trim(),
});
createDirDialogVisible.value = false;
refreshList();
} catch (error) {
console.error('Create directory error:', error);
}
}
function onShowHiddenChange() {
refreshList();
} catch (error) {
console.error("Upload error:", error);
} finally {
uploading.value = false;
}
}
function handleUploadClose() {
uploadDialogVisible.value = false;
uploadFileList.value = [];
}
function handleCreateDir() {
createDirForm.dir_name = "";
createDirDialogVisible.value = true;
}
async function handleCreateDirConfirm() {
if (!createDirForm.dir_name.trim()) {
ElMessage.warning("请输入文件夹名称");
return;
}
async function handleDownload(item: ResourceItem) {
try {
const response = await ResourceAPI.downloadFile(item.file_url);
const blob = response.data;
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = item.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Download error:', error);
try {
const parentPath = currentPath.value === "/" ? "" : currentPath.value;
await ResourceAPI.createDirectory({
parent_path: parentPath,
dir_name: createDirForm.dir_name.trim(),
});
createDirDialogVisible.value = false;
refreshList();
} catch (error) {
console.error("Create directory error:", error);
}
}
function onShowHiddenChange() {
refreshList();
}
async function handleDownload(item: ResourceItem) {
try {
const response = await ResourceAPI.downloadFile(item.file_url);
const blob = response.data;
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = item.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Download error:", error);
}
}
function handleRename(item: ResourceItem) {
renameForm.old_path = item.file_url;
renameForm.new_name = item.name;
renameDialogVisible.value = true;
}
async function handleRenameConfirm() {
if (!renameForm.new_name.trim()) {
ElMessage.warning("请输入新名称");
return;
}
try {
await ResourceAPI.renameResource({
old_path: renameForm.old_path,
new_name: renameForm.new_name.trim(),
});
renameDialogVisible.value = false;
refreshList();
} catch (error) {
console.error("Rename error:", error);
}
}
async function handleDelete(item: ResourceItem) {
try {
await ElMessageBox.confirm(`确定要删除 ${item.name} 吗?`, "确认删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
await ResourceAPI.deleteResource([item.file_url]);
refreshList();
} catch (error) {
if (error !== "cancel") {
console.error("Delete error:", error);
}
}
}
function handleRename(item: ResourceItem) {
renameForm.old_path = item.file_url;
renameForm.new_name = item.name;
renameDialogVisible.value = true;
async function handleBatchDelete() {
if (selectedItems.value.length === 0) {
ElMessage.warning("请选择要删除的文件");
return;
}
async function handleRenameConfirm() {
if (!renameForm.new_name.trim()) {
ElMessage.warning('请输入新名称');
return;
}
try {
await ResourceAPI.renameResource({
old_path: renameForm.old_path,
new_name: renameForm.new_name.trim(),
});
renameDialogVisible.value = false;
refreshList();
} catch (error) {
console.error('Rename error:', error);
}
}
async function handleDelete(item: ResourceItem) {
try {
await ElMessageBox.confirm(`确定要删除 ${item.name} 吗?`, '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await ResourceAPI.deleteResource([item.file_url]);
refreshList();
} catch (error) {
if (error !== 'cancel') {
console.error('Delete error:', error);
try {
await ElMessageBox.confirm(
`确定要删除选中的 ${selectedItems.value.length} 个文件吗?`,
"确认删除",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
);
const paths = selectedItems.value.map((item) => item.file_url);
await ResourceAPI.deleteResource(paths);
refreshList();
} catch (error) {
if (error !== "cancel") {
console.error("Batch delete error:", error);
}
}
}
async function handleBatchDelete() {
if (selectedItems.value.length === 0) {
ElMessage.warning('请选择要删除的文件');
return;
}
function formatFileSize(size?: number | null) {
if (!size || size === null) return "-";
const units = ["B", "KB", "MB", "GB", "TB"];
let unitIndex = 0;
let fileSize = size;
try {
await ElMessageBox.confirm(
`确定要删除选中的 ${selectedItems.value.length} 个文件吗?`,
'确认删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
const paths = selectedItems.value.map((item) => item.file_url);
await ResourceAPI.deleteResource(paths);
refreshList();
} catch (error) {
if (error !== 'cancel') {
console.error('Batch delete error:', error);
}
}
while (fileSize >= 1024 && unitIndex < units.length - 1) {
fileSize /= 1024;
unitIndex++;
}
function formatFileSize(size?: number | null) {
if (!size || size === null) return '-';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let unitIndex = 0;
let fileSize = size;
while (fileSize >= 1024 && unitIndex < units.length - 1) {
fileSize /= 1024;
unitIndex++;
}
return `${fileSize.toFixed(1)} ${units[unitIndex]}`;
}
return `${fileSize.toFixed(1)} ${units[unitIndex]}`;
}
</script>
<style lang="scss" scoped>
.card-header {
.card-header {
display: flex;
align-items: center;
}
.data-table__content {
.file-name {
display: flex;
gap: 8px;
align-items: center;
}
.data-table__content {
.file-name {
display: flex;
gap: 8px;
align-items: center;
.file-name-clickable {
color: var(--el-color-primary);
cursor: pointer;
&:hover {
color: var(--el-color-primary-light-3);
text-decoration: underline;
}
}
}
}
:deep(.el-breadcrumb__item) {
&.is-link {
.file-name-clickable {
color: var(--el-color-primary);
cursor: pointer;
&:hover {
color: var(--el-color-primary-light-3);
text-decoration: underline;
}
}
}
}
:deep(.el-breadcrumb__item) {
&.is-link {
color: var(--el-color-primary);
cursor: pointer;
&:hover {
color: var(--el-color-primary-light-3);
}
}
}
</style>
@@ -60,13 +60,13 @@
</el-tooltip>
<el-descriptions :column="1" border>
<el-descriptions-item label="用户使用率">
{{ (server.cpu?.used || 0).toFixed(1) + '%' }}
{{ (server.cpu?.used || 0).toFixed(1) + "%" }}
</el-descriptions-item>
<el-descriptions-item label="系统使用率">
{{ (server.cpu?.sys || 0).toFixed(1) + '%' }}
{{ (server.cpu?.sys || 0).toFixed(1) + "%" }}
</el-descriptions-item>
<el-descriptions-item label="当前空闲率">
{{ (server.cpu?.free || 0).toFixed(1) + '%' }}
{{ (server.cpu?.free || 0).toFixed(1) + "%" }}
</el-descriptions-item>
</el-descriptions>
</el-card>
@@ -168,16 +168,16 @@
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="服务器名称">
{{ server.sys?.computer_name || '-' }}
{{ server.sys?.computer_name || "-" }}
</el-descriptions-item>
<el-descriptions-item label="操作系统">
{{ server.sys?.os_name || '-' }}
{{ server.sys?.os_name || "-" }}
</el-descriptions-item>
<el-descriptions-item label="服务器IP">
{{ server.sys?.computer_ip || '-' }}
{{ server.sys?.computer_ip || "-" }}
</el-descriptions-item>
<el-descriptions-item label="系统架构">
{{ server.sys?.os_arch || '-' }}
{{ server.sys?.os_arch || "-" }}
</el-descriptions-item>
</el-descriptions>
</el-card>
@@ -196,22 +196,22 @@
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="Python名称">
{{ server.py?.name || '-' }}
{{ server.py?.name || "-" }}
</el-descriptions-item>
<el-descriptions-item label="Python版本">
{{ server.py?.version || '-' }}
{{ server.py?.version || "-" }}
</el-descriptions-item>
<el-descriptions-item label="启动时间">
{{ server.py?.start_time || '-' }}
{{ server.py?.start_time || "-" }}
</el-descriptions-item>
<el-descriptions-item label="运行时长">
{{ server.py?.run_time || '-' }}
{{ server.py?.run_time || "-" }}
</el-descriptions-item>
<el-descriptions-item label="安装路径">
{{ server.py?.home || '-' }}
{{ server.py?.home || "-" }}
</el-descriptions-item>
<el-descriptions-item label="项目路径">
{{ server.sys?.user_dir || '-' }}
{{ server.sys?.user_dir || "-" }}
</el-descriptions-item>
</el-descriptions>
</el-card>
@@ -261,58 +261,58 @@
</template>
<script lang="ts" setup>
import ServerAPI, { type ServerInfo } from '@/api/module_monitor/server';
import ServerAPI, { type ServerInfo } from "@/api/module_monitor/server";
const loading = ref(false);
const server = ref<ServerInfo>({
cpu: {
cpu_num: 0,
used: 0,
sys: 0,
free: 0,
},
mem: {
total: '',
used: '',
free: '',
usage: 0,
},
sys: {
computer_name: '',
os_name: '',
computer_ip: '',
os_arch: '',
user_dir: '',
},
py: {
name: '',
version: '',
start_time: '',
run_time: '',
home: '',
memory_total: '',
memory_used: '',
memory_free: '',
memory_usage: 0,
},
disks: [],
});
const loading = ref(false);
const server = ref<ServerInfo>({
cpu: {
cpu_num: 0,
used: 0,
sys: 0,
free: 0,
},
mem: {
total: "",
used: "",
free: "",
usage: 0,
},
sys: {
computer_name: "",
os_name: "",
computer_ip: "",
os_arch: "",
user_dir: "",
},
py: {
name: "",
version: "",
start_time: "",
run_time: "",
home: "",
memory_total: "",
memory_used: "",
memory_free: "",
memory_usage: 0,
},
disks: [],
});
async function getList() {
loading.value = true;
try {
const response = await ServerAPI.getServer();
server.value = response.data.data;
} catch (error) {
console.error('获取服务器信息失败:', error);
} finally {
loading.value = false;
}
async function getList() {
loading.value = true;
try {
const response = await ServerAPI.getServer();
server.value = response.data.data;
} catch (error) {
console.error("获取服务器信息失败:", error);
} finally {
loading.value = false;
}
}
onMounted(() => {
getList();
});
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped></style>