feat(electron-vue-template):重构认证与设备管理模块

- 统一token存取逻辑,封装getToken/setToken/removeToken方法
-优化设备ID获取逻辑,调整API路径
- 完善设备管理接口类型定义,增强类型安全
- 调整SSE连接逻辑,使用统一配置管理- 重构HTTP客户端,集中管理后端服务配置
- 更新认证相关API接口,完善请求/响应类型
- 优化设备列表展示逻辑,移除冗余字段
- 调整图片代理路径,统一API前缀
- 完善用户反馈列表展示功能,增强交互体验
- 移除冗余的错误处理逻辑,简化代码结构
This commit is contained in:
2025-10-16 10:37:00 +08:00
parent 6f04658265
commit 132299c4b7
37 changed files with 2193 additions and 682 deletions

View File

@@ -0,0 +1,57 @@
import request from '@/utils/request'
// 查询反馈列表
export function listFeedback(query) {
return request({
url: '/monitor/feedback/list',
method: 'get',
params: query
})
}
// 查询反馈详细
export function getFeedback(id) {
return request({
url: '/monitor/feedback/' + id,
method: 'get'
})
}
// 更新反馈状态
export function updateFeedbackStatus(id, data) {
return request({
url: '/monitor/feedback/status/' + id,
method: 'put',
data: data
})
}
// 删除反馈
export function delFeedback(id) {
return request({
url: '/monitor/feedback/' + id,
method: 'delete'
})
}
// 获取反馈统计信息
export function getFeedbackStatistics() {
return request({
url: '/monitor/feedback/statistics',
method: 'get'
})
}
// 获取反馈日志内容(用于在线查看)
export function getFeedbackLogContent(id) {
return request({
url: '/monitor/feedback/log/content/' + id,
method: 'get'
})
}
// 下载反馈日志文件
export function downloadFeedbackLog(id) {
return process.env.VUE_APP_BASE_API + '/monitor/feedback/log/download/' + id
}

View File

@@ -56,17 +56,17 @@
</el-card>
</el-col>
<el-col :span="6">
<el-card class="monitor-card">
<el-card class="monitor-card clickable-card" @click.native="showFeedbackList">
<template v-slot:header>
<div class="card-header">
<i class="el-icon-s-data"></i>
<span>离线设备</span>
<i class="el-icon-chat-line-square"></i>
<span>用户反馈</span>
</div>
</template>
<div class="card-body">
<count-to :start-val="0" :end-val="statisticsData.totalCount - statisticsData.onlineCount" :duration="2500" class="card-value" />
<count-to :start-val="0" :end-val="feedbackStats.pendingCount" :duration="2500" class="card-value" />
<div class="card-footer">
<span>离线设备数</span>
<span>待处理: {{ feedbackStats.pendingCount }} / 今日: {{ feedbackStats.todayCount }}</span>
</div>
</div>
</el-card>
@@ -116,18 +116,18 @@
</div>
</template>
<el-table :data="clientList" style="width: 100%" v-loading="loading">
<el-table-column prop="clientId" label="设备ID" width="240" show-overflow-tooltip></el-table-column>
<el-table-column prop="username" label="账号" width="120"></el-table-column>
<el-table-column prop="hostname" label="设备名称" width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="osName" label="操作系统" width="120"></el-table-column>
<el-table-column prop="ipAddress" label="IP地址" width="150"></el-table-column>
<el-table-column prop="lastActiveTime" label="最后活跃时间" width="180"></el-table-column>
<el-table-column prop="online" label="状态" width="100">
<el-table-column prop="clientId" label="设备ID" min-width="240" show-overflow-tooltip></el-table-column>
<el-table-column prop="username" label="账号" min-width="120"></el-table-column>
<el-table-column prop="hostname" label="设备名称" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="osName" label="操作系统" min-width="120"></el-table-column>
<el-table-column prop="ipAddress" label="IP地址" min-width="150"></el-table-column>
<el-table-column prop="lastActiveTime" label="最后活跃时间" min-width="180"></el-table-column>
<el-table-column prop="online" label="状态" min-width="100">
<template v-slot="scope">
<el-tag :type="scope.row.online === '1' ? 'success' : 'info'">{{ scope.row.online === '1' ? '在线' : '离线' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<el-table-column label="操作" min-width="120">
<template v-slot="scope">
<el-button size="mini" type="text" @click="viewClientData(scope.row)">详情</el-button>
</template>
@@ -241,6 +241,111 @@
</div>
</div>
</el-dialog>
<!-- 用户反馈列表弹窗 -->
<el-dialog title="用户反馈列表" :visible.sync="feedbackDialogVisible" width="80%">
<el-table :data="feedbackList" style="width: 100%" v-loading="feedbackLoading">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="username" label="用户名" width="120"></el-table-column>
<el-table-column prop="deviceId" label="设备ID" width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="feedbackContent" label="反馈内容" min-width="250" show-overflow-tooltip></el-table-column>
<el-table-column prop="logDate" label="日志日期" width="120">
<template slot-scope="scope">
<span>{{ scope.row.logDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="getFeedbackStatusType(scope.row.status)" size="small">
{{ getFeedbackStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="提交时间" width="180"></el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="viewFeedbackDetail(scope.row)">详情</el-button>
<el-button size="mini" type="text" @click="viewFeedbackLog(scope.row)" v-if="scope.row.logFilePath">
查看日志
</el-button>
<el-button size="mini" type="text" @click="handleFeedbackStatus(scope.row, 'processing')"
v-if="scope.row.status === 'pending'">
处理中
</el-button>
<el-button size="mini" type="text" @click="handleFeedbackStatus(scope.row, 'completed')"
v-if="scope.row.status !== 'completed'">
完成
</el-button>
<el-button size="mini" type="text" style="color: #F56C6C;" @click="deleteFeedback(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
background
@size-change="handleFeedbackSizeChange"
@current-change="handleFeedbackCurrentChange"
:current-page="feedbackQueryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="feedbackQueryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="feedbackTotal">
</el-pagination>
</div>
</el-dialog>
<!-- 反馈详情弹窗 -->
<el-dialog title="反馈详情" :visible.sync="feedbackDetailDialogVisible" width="60%">
<el-descriptions :column="2" border>
<el-descriptions-item label="反馈ID">{{ currentFeedback.id }}</el-descriptions-item>
<el-descriptions-item label="用户名">{{ currentFeedback.username }}</el-descriptions-item>
<el-descriptions-item label="设备ID" :span="2">{{ currentFeedback.deviceId }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getFeedbackStatusType(currentFeedback.status)" size="small">
{{ getFeedbackStatusText(currentFeedback.status) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="日志日期">
{{ currentFeedback.logDate || '未附带日志' }}
</el-descriptions-item>
<el-descriptions-item label="提交时间" :span="2">{{ currentFeedback.createTime }}</el-descriptions-item>
<el-descriptions-item label="反馈内容" :span="2">
<div style="white-space: pre-wrap; word-wrap: break-word;">{{ currentFeedback.feedbackContent }}</div>
</el-descriptions-item>
<el-descriptions-item label="处理备注" :span="2" v-if="currentFeedback.remark">
<div style="white-space: pre-wrap; word-wrap: break-word;">{{ currentFeedback.remark }}</div>
</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button @click="feedbackDetailDialogVisible = false">关闭</el-button>
<el-button type="primary" @click="viewFeedbackLog(currentFeedback)" v-if="currentFeedback.logFilePath">
查看日志
</el-button>
<el-button @click="downloadFeedbackLogFile(currentFeedback)" v-if="currentFeedback.logFilePath">
下载日志
</el-button>
</div>
</el-dialog>
<!-- 反馈日志查看弹窗 -->
<el-dialog :title="'反馈日志: ' + currentFeedbackLog.username" :visible.sync="feedbackLogDialogVisible" width="80%" top="5vh">
<div class="log-container">
<div class="log-header">
<el-button type="primary" size="small" @click="loadFeedbackLog(currentFeedbackLog.id)" :loading="feedbackLogLoading">
<i class="el-icon-refresh"></i> 刷新
</el-button>
<el-button type="success" size="small" @click="downloadFeedbackLogFile(currentFeedbackLog)">
<i class="el-icon-download"></i> 下载日志
</el-button>
<span class="log-info">日志日期: {{ currentFeedbackLog.logDate || '-' }}</span>
</div>
<div class="log-content" v-loading="feedbackLogLoading">
<pre class="log-text">{{ feedbackLogContent }}</pre>
</div>
</div>
</el-dialog>
</div>
</template>
@@ -250,6 +355,7 @@ import LineChart from '@/components/Charts/LineChart'
import PieChart from '@/components/Charts/PieChart'
import { listInfo, listError, listData } from '@/api/monitor/client'
import { getVersionInfo } from '@/api/monitor/version'
import { listFeedback, getFeedback, updateFeedbackStatus, delFeedback, getFeedbackLogContent, downloadFeedbackLog } from '@/api/monitor/feedback'
import request from '@/utils/request'
export default {
@@ -321,7 +427,27 @@ export default {
logLoading: false,
currentLogClient: {},
logContent: '',
lastLogUpdate: ''
lastLogUpdate: '',
// 用户反馈
feedbackDialogVisible: false,
feedbackLoading: false,
feedbackList: [],
feedbackQueryParams: {
pageNum: 1,
pageSize: 10
},
feedbackTotal: 0,
feedbackDetailDialogVisible: false,
currentFeedback: {},
feedbackStats: {
pendingCount: 0,
todayCount: 0
},
// 反馈日志查看
feedbackLogDialogVisible: false,
feedbackLogLoading: false,
feedbackLogContent: '',
currentFeedbackLog: {}
}
},
created() {
@@ -329,6 +455,7 @@ export default {
this.getOnlineClientTrend()
this.getDataTypeDistribution()
this.getClientList()
this.getFeedbackStatistics()
},
methods: {
// 获取统计数据
@@ -558,6 +685,186 @@ export default {
console.error('获取版本信息失败:', error)
this.$message.error('获取下载链接失败: ' + (error.message || '网络错误'))
})
},
// 获取反馈统计信息
getFeedbackStatistics() {
request.get('/monitor/feedback/statistics').then(res => {
if (res.code === 200) {
this.feedbackStats = {
pendingCount: res.data.pendingCount || 0,
todayCount: res.data.todayCount || 0
}
}
}).catch(error => {
console.error('获取反馈统计失败:', error)
})
},
// 显示反馈列表
showFeedbackList() {
this.feedbackDialogVisible = true
this.getFeedbackList()
},
// 获取反馈列表
getFeedbackList() {
this.feedbackLoading = true
listFeedback(this.feedbackQueryParams).then(res => {
this.feedbackLoading = false
if (res.code === 200) {
this.feedbackList = res.rows || []
this.feedbackTotal = res.total || 0
}
}).catch(() => {
this.feedbackLoading = false
})
},
// 查看反馈详情
viewFeedbackDetail(row) {
this.currentFeedback = {...row}
this.feedbackDetailDialogVisible = true
},
// 查看反馈日志
viewFeedbackLog(row) {
if (!row.logFilePath) {
this.$message.warning('该反馈未附带日志文件')
return
}
this.currentFeedbackLog = {...row}
this.feedbackLogDialogVisible = true
this.loadFeedbackLog(row.id)
},
// 加载反馈日志内容
loadFeedbackLog(id) {
this.feedbackLogLoading = true
this.feedbackLogContent = ''
getFeedbackLogContent(id).then(res => {
this.feedbackLogLoading = false
if (res.code === 200) {
this.feedbackLogContent = res.data.content || '日志内容为空'
} else {
this.$message.error(res.msg || '获取日志失败')
this.feedbackLogContent = '获取日志失败'
}
}).catch(error => {
this.feedbackLogLoading = false
this.$message.error('获取日志失败: ' + (error.message || '未知错误'))
this.feedbackLogContent = '获取日志失败: ' + (error.message || '未知错误')
})
},
// 下载反馈日志
downloadFeedbackLogFile(row) {
if (!row.logFilePath) {
this.$message.warning('该反馈未附带日志文件')
return
}
// 使用request下载文件带token
const url = '/monitor/feedback/log/download/' + row.id
const loading = this.$loading({
lock: true,
text: '正在下载日志文件...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
request({
url: url,
method: 'get',
responseType: 'blob'
}).then(response => {
const blob = new Blob([response])
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = `feedback_${row.id}_log.txt`
link.click()
window.URL.revokeObjectURL(link.href)
loading.close()
this.$message.success('下载成功')
}).catch(error => {
loading.close()
console.error('下载失败:', error)
this.$message.error('下载失败: ' + (error.message || '请稍后重试'))
})
},
// 更新反馈状态
handleFeedbackStatus(row, status) {
this.$prompt('请输入处理备注(可选)', '更新状态', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea'
}).then(({ value }) => {
updateFeedbackStatus(row.id, {
status: status,
remark: value || ''
}).then(res => {
if (res.code === 200) {
this.$message.success('状态更新成功')
this.getFeedbackList()
this.getFeedbackStatistics()
} else {
this.$message.error(res.msg || '更新失败')
}
}).catch(error => {
this.$message.error('更新失败: ' + (error.message || '未知错误'))
})
}).catch(() => {})
},
// 删除反馈
deleteFeedback(row) {
this.$confirm('确定要删除该反馈吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delFeedback(row.id).then(res => {
if (res.code === 200) {
this.$message.success('删除成功')
this.getFeedbackList()
this.getFeedbackStatistics()
} else {
this.$message.error(res.msg || '删除失败')
}
}).catch(error => {
this.$message.error('删除失败: ' + (error.message || '未知错误'))
})
}).catch(() => {})
},
// 反馈分页
handleFeedbackSizeChange(val) {
this.feedbackQueryParams.pageSize = val
this.getFeedbackList()
},
handleFeedbackCurrentChange(val) {
this.feedbackQueryParams.pageNum = val
this.getFeedbackList()
},
// 获取状态标签类型
getFeedbackStatusType(status) {
const typeMap = {
'pending': 'warning',
'processing': 'primary',
'completed': 'success'
}
return typeMap[status] || 'info'
},
// 获取状态文本
getFeedbackStatusText(status) {
const textMap = {
'pending': '待处理',
'processing': '处理中',
'completed': '已完成'
}
return textMap[status] || status
}
}
}