feat: 全面重构前端UI及完善后端功能
前端重构: - 重构Layout为左侧导航+顶栏的现代管理后台布局 - 重构设备管理页面(Devices.vue):左侧分组面板+右侧设备列表 - 重构设备详情(DeviceDetail.vue):集成硬件资产/软件资产/变更记录标签页 - 移除独立资产管理页面,功能合并至设备详情 - 重构Dashboard/登录/设置/告警/水印/上网管控等页面样式 - 新增全局CSS变量和统一样式系统 - 添加分组管理UI:新建/重命名/删除分组,移动设备到分组 后端完善: - 新增分组CRUD API(groups.rs):创建/重命名/删除分组,设备分组移动 - 客户端硬件采集:完善GPU/主板/序列号/磁盘信息采集(Windows PowerShell) - 客户端软件采集:通过Windows注册表读取已安装软件列表 - 新增SoftwareAssetReport消息类型(0x09)及处理链路 - 数据库新增upsert_software方法处理软件资产存储 - 服务端推送软件资产配置给新注册设备 - 修复密码修改功能,添加旧密码验证
This commit is contained in:
@@ -1,55 +1,82 @@
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<div class="page-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header><span class="card-title">系统信息</span></template>
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="系统版本">v{{ version }}</el-descriptions-item>
|
||||
<el-descriptions-item label="数据库">{{ dbInfo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="在线终端">{{ health.connected_clients }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
<div class="csm-card">
|
||||
<div class="csm-card-header">系统信息</div>
|
||||
<div class="csm-card-body">
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">系统版本</span>
|
||||
<span class="info-value">v{{ version }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">数据库</span>
|
||||
<span class="info-value">{{ dbInfo }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">在线终端</span>
|
||||
<span class="info-value">{{ health.connected_clients }} 台</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" style="margin-top: 20px">
|
||||
<template #header><span class="card-title">修改密码</span></template>
|
||||
<el-form :model="pwdForm" label-width="100px" size="small">
|
||||
<el-form-item label="当前密码">
|
||||
<el-input v-model="pwdForm.oldPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input v-model="pwdForm.newPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码">
|
||||
<el-input v-model="pwdForm.confirmPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="changePassword">修改密码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="csm-card" style="margin-top: 20px">
|
||||
<div class="csm-card-header">修改密码</div>
|
||||
<div class="csm-card-body">
|
||||
<el-form :model="pwdForm" label-width="100px" size="default">
|
||||
<el-form-item label="当前密码">
|
||||
<el-input v-model="pwdForm.oldPassword" type="password" show-password placeholder="输入当前密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码">
|
||||
<el-input v-model="pwdForm.newPassword" type="password" show-password placeholder="输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码">
|
||||
<el-input v-model="pwdForm.confirmPassword" type="password" show-password placeholder="再次输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="pwdLoading" @click="changePassword">修改密码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header><span class="card-title">数据维护</span></template>
|
||||
<el-form label-width="100px" size="small">
|
||||
<el-form-item label="历史数据">
|
||||
<el-button @click="showRetentionInfo">查看保留策略</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库">
|
||||
<el-button type="warning" @click="manualCleanup">手动清理</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="csm-card">
|
||||
<div class="csm-card-header">数据维护</div>
|
||||
<div class="csm-card-body">
|
||||
<div class="maintenance-item">
|
||||
<div>
|
||||
<div style="font-weight:500">历史数据保留</div>
|
||||
<div style="font-size:12px;color:var(--csm-text-tertiary);margin-top:4px">配置数据保留策略,自动清理过期数据</div>
|
||||
</div>
|
||||
<el-button @click="showRetentionInfo">查看策略</el-button>
|
||||
</div>
|
||||
<el-divider style="margin:12px 0" />
|
||||
<div class="maintenance-item">
|
||||
<div>
|
||||
<div style="font-weight:500">手动清理</div>
|
||||
<div style="font-size:12px;color:var(--csm-text-tertiary);margin-top:4px">立即清理过期的历史数据</div>
|
||||
</div>
|
||||
<el-button type="warning" plain @click="manualCleanup">执行清理</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" style="margin-top: 20px">
|
||||
<template #header><span class="card-title">当前用户</span></template>
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="用户名">{{ user.username }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ user.role }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
<div class="csm-card" style="margin-top: 20px">
|
||||
<div class="csm-card-header">当前用户</div>
|
||||
<div class="csm-card-body">
|
||||
<div class="user-card">
|
||||
<div class="user-avatar-large">{{ user.username.charAt(0).toUpperCase() }}</div>
|
||||
<div class="user-detail">
|
||||
<div style="font-weight:600;font-size:16px">{{ user.username }}</div>
|
||||
<el-tag size="small" type="warning" effect="light" style="margin-top:4px">{{ user.role }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -66,9 +93,9 @@ const health = reactive({ connected_clients: 0, db_size_bytes: 0 })
|
||||
const user = reactive({ username: 'admin', role: 'admin' })
|
||||
|
||||
const pwdForm = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||
const pwdLoading = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// Decode username from JWT token
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
@@ -83,21 +110,39 @@ onMounted(() => {
|
||||
if (data.version) version.value = data.version
|
||||
health.connected_clients = data.connected_clients || 0
|
||||
const bytes = data.db_size_bytes || 0
|
||||
dbInfo.value = `SQLite (WAL mode) - ${(bytes / 1024 / 1024).toFixed(2)} MB`
|
||||
dbInfo.value = `SQLite (WAL) - ${(bytes / 1024 / 1024).toFixed(2)} MB`
|
||||
})
|
||||
.catch(() => { /* ignore */ })
|
||||
})
|
||||
|
||||
function changePassword() {
|
||||
async function changePassword() {
|
||||
if (!pwdForm.oldPassword) {
|
||||
ElMessage.error('请输入当前密码')
|
||||
return
|
||||
}
|
||||
if (pwdForm.newPassword.length < 6) {
|
||||
ElMessage.error('新密码至少6位')
|
||||
return
|
||||
}
|
||||
if (pwdForm.newPassword !== pwdForm.confirmPassword) {
|
||||
ElMessage.error('两次输入的密码不一致')
|
||||
return
|
||||
}
|
||||
if (pwdForm.newPassword.length < 6) {
|
||||
ElMessage.error('密码至少6位')
|
||||
return
|
||||
pwdLoading.value = true
|
||||
try {
|
||||
await api.put('/api/auth/change-password', {
|
||||
old_password: pwdForm.oldPassword,
|
||||
new_password: pwdForm.newPassword,
|
||||
})
|
||||
ElMessage.success('密码修改成功')
|
||||
pwdForm.oldPassword = ''
|
||||
pwdForm.newPassword = ''
|
||||
pwdForm.confirmPassword = ''
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.message || '密码修改失败')
|
||||
} finally {
|
||||
pwdLoading.value = false
|
||||
}
|
||||
ElMessage.success('密码修改功能待实现')
|
||||
}
|
||||
|
||||
function showRetentionInfo() {
|
||||
@@ -110,6 +155,69 @@ function manualCleanup() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings-page { padding: 20px; }
|
||||
.card-title { font-weight: 600; font-size: 15px; }
|
||||
.csm-card-header {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: var(--csm-text-primary);
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--csm-border-color);
|
||||
}
|
||||
|
||||
.csm-card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 13px;
|
||||
color: var(--csm-text-secondary);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--csm-text-primary);
|
||||
}
|
||||
|
||||
.maintenance-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.user-avatar-large {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: var(--csm-primary);
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user