first commit
This commit is contained in:
551
src/pages/admin/statistics/AdminStatisticsPage.vue
Normal file
551
src/pages/admin/statistics/AdminStatisticsPage.vue
Normal file
@@ -0,0 +1,551 @@
|
||||
<template>
|
||||
<div class="admin-statistics-page">
|
||||
<div class="page-header">
|
||||
<h1>统计管理</h1>
|
||||
<p>管理员专用统计管理界面</p>
|
||||
</div>
|
||||
|
||||
<!-- 仪表板管理 -->
|
||||
<el-card class="dashboard-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>仪表板管理</span>
|
||||
<el-button type="primary" @click="showCreateDialog = true">
|
||||
<el-icon><Plus /></el-icon>
|
||||
创建仪表板
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<div class="filter-section">
|
||||
<el-form :inline="true" :model="filterForm" class="filter-form">
|
||||
<el-form-item label="用户角色">
|
||||
<el-select v-model="filterForm.user_role" placeholder="选择角色" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="管理员" value="admin" />
|
||||
<el-option label="普通用户" value="user" />
|
||||
<el-option label="经理" value="manager" />
|
||||
<el-option label="分析师" value="analyst" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问级别">
|
||||
<el-select v-model="filterForm.access_level" placeholder="选择级别" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="私有" value="private" />
|
||||
<el-option label="公开" value="public" />
|
||||
<el-option label="共享" value="shared" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filterForm.is_active" placeholder="选择状态" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="激活" :value="true" />
|
||||
<el-option label="停用" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认">
|
||||
<el-select v-model="filterForm.is_default" placeholder="是否默认" clearable>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadDashboards">
|
||||
<el-icon><SearchIcon /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetFilter">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 仪表板列表 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="dashboards"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="name" label="仪表板名称" min-width="150" />
|
||||
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="user_role" label="用户角色" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getRoleTagType(row.user_role)">
|
||||
{{ getRoleName(row.user_role) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="access_level" label="访问级别" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getAccessLevelTagType(row.access_level)">
|
||||
{{ getAccessLevelName(row.access_level) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_default" label="默认" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_default ? 'success' : 'info'">
|
||||
{{ row.is_default ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="is_active" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_active ? 'success' : 'danger'">
|
||||
{{ row.is_active ? '激活' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="refresh_interval" label="刷新间隔(秒)" width="120" />
|
||||
<el-table-column prop="created_at" label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="viewDashboard(row)">
|
||||
<el-icon><ViewIcon /></el-icon>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button size="small" type="primary" @click="editDashboard(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteDashboard(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 创建/编辑仪表板对话框 -->
|
||||
<el-dialog
|
||||
v-model="showCreateDialog"
|
||||
:title="editingDashboard ? '编辑仪表板' : '创建仪表板'"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="dashboardFormRef"
|
||||
:model="dashboardForm"
|
||||
:rules="dashboardRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="仪表板名称" prop="name">
|
||||
<el-input v-model="dashboardForm.name" placeholder="请输入仪表板名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input
|
||||
v-model="dashboardForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入仪表板描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户角色" prop="user_role">
|
||||
<el-select v-model="dashboardForm.user_role" placeholder="选择用户角色">
|
||||
<el-option label="管理员" value="admin" />
|
||||
<el-option label="普通用户" value="user" />
|
||||
<el-option label="经理" value="manager" />
|
||||
<el-option label="分析师" value="analyst" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问级别" prop="access_level">
|
||||
<el-select v-model="dashboardForm.access_level" placeholder="选择访问级别">
|
||||
<el-option label="私有" value="private" />
|
||||
<el-option label="公开" value="public" />
|
||||
<el-option label="共享" value="shared" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="刷新间隔" prop="refresh_interval">
|
||||
<el-input-number
|
||||
v-model="dashboardForm.refresh_interval"
|
||||
:min="30"
|
||||
:max="3600"
|
||||
:step="30"
|
||||
placeholder="刷新间隔(秒)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否默认">
|
||||
<el-switch v-model="dashboardForm.is_default" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否激活">
|
||||
<el-switch v-model="dashboardForm.is_active" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showCreateDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="creating" @click="saveDashboard">
|
||||
{{ editingDashboard ? '更新' : '创建' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
adminCreateDashboard,
|
||||
adminDeleteDashboard,
|
||||
adminGetDashboards,
|
||||
adminUpdateDashboard
|
||||
} from '@/api/statistics'
|
||||
import {
|
||||
Delete,
|
||||
Edit,
|
||||
Plus,
|
||||
Refresh,
|
||||
Search as SearchIcon,
|
||||
View as ViewIcon
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
export default {
|
||||
name: 'AdminStatisticsPage',
|
||||
components: {
|
||||
Plus,
|
||||
SearchIcon,
|
||||
Refresh,
|
||||
ViewIcon,
|
||||
Edit,
|
||||
Delete
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const dashboards = ref([])
|
||||
const showCreateDialog = ref(false)
|
||||
const creating = ref(false)
|
||||
const editingDashboard = ref(null)
|
||||
const dashboardFormRef = ref()
|
||||
|
||||
// 筛选表单
|
||||
const filterForm = reactive({
|
||||
user_role: '',
|
||||
access_level: '',
|
||||
is_active: '',
|
||||
is_default: ''
|
||||
})
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 仪表板表单
|
||||
const dashboardForm = reactive({
|
||||
name: '',
|
||||
description: '',
|
||||
user_role: 'user',
|
||||
access_level: 'private',
|
||||
refresh_interval: 300,
|
||||
is_default: false,
|
||||
is_active: true
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const dashboardRules = {
|
||||
name: [{ required: true, message: '请输入仪表板名称', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '请输入仪表板描述', trigger: 'blur' }],
|
||||
user_role: [{ required: true, message: '请选择用户角色', trigger: 'change' }],
|
||||
access_level: [{ required: true, message: '请选择访问级别', trigger: 'change' }],
|
||||
refresh_interval: [{ required: true, message: '请输入刷新间隔', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 加载仪表板列表
|
||||
const loadDashboards = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.pageSize,
|
||||
...filterForm
|
||||
}
|
||||
|
||||
// 移除空值参数
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === '' || params[key] === null || params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await adminGetDashboards(params)
|
||||
|
||||
if (response.success) {
|
||||
dashboards.value = response.data.items || []
|
||||
pagination.total = response.data.total || 0
|
||||
} else {
|
||||
ElMessage.error(response.message || '获取仪表板列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取仪表板列表失败:', error)
|
||||
ElMessage.error('获取仪表板列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置筛选条件
|
||||
const resetFilter = () => {
|
||||
Object.keys(filterForm).forEach(key => {
|
||||
filterForm[key] = ''
|
||||
})
|
||||
pagination.page = 1
|
||||
loadDashboards()
|
||||
}
|
||||
|
||||
// 查看仪表板
|
||||
const viewDashboard = (dashboard) => {
|
||||
// 跳转到仪表板详情页面
|
||||
router.push(`/statistics/dashboard/${dashboard.id}`)
|
||||
}
|
||||
|
||||
// 编辑仪表板
|
||||
const editDashboard = (dashboard) => {
|
||||
editingDashboard.value = dashboard
|
||||
Object.assign(dashboardForm, {
|
||||
name: dashboard.name,
|
||||
description: dashboard.description,
|
||||
user_role: dashboard.user_role,
|
||||
access_level: dashboard.access_level,
|
||||
refresh_interval: dashboard.refresh_interval,
|
||||
is_default: dashboard.is_default,
|
||||
is_active: dashboard.is_active
|
||||
})
|
||||
showCreateDialog.value = true
|
||||
}
|
||||
|
||||
// 删除仪表板
|
||||
const deleteDashboard = async (dashboard) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除仪表板"${dashboard.name}"吗?`,
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
const response = await adminDeleteDashboard(dashboard.id)
|
||||
if (response.success) {
|
||||
ElMessage.success('删除成功')
|
||||
loadDashboards()
|
||||
} else {
|
||||
ElMessage.error(response.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除仪表板失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存仪表板
|
||||
const saveDashboard = async () => {
|
||||
try {
|
||||
await dashboardFormRef.value.validate()
|
||||
creating.value = true
|
||||
|
||||
const data = { ...dashboardForm }
|
||||
let response
|
||||
|
||||
if (editingDashboard.value) {
|
||||
response = await adminUpdateDashboard(editingDashboard.value.id, data)
|
||||
} else {
|
||||
response = await adminCreateDashboard(data)
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
ElMessage.success(editingDashboard.value ? '更新成功' : '创建成功')
|
||||
showCreateDialog.value = false
|
||||
resetForm()
|
||||
loadDashboards()
|
||||
} else {
|
||||
ElMessage.error(response.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存仪表板失败:', error)
|
||||
ElMessage.error('操作失败')
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
editingDashboard.value = null
|
||||
Object.assign(dashboardForm, {
|
||||
name: '',
|
||||
description: '',
|
||||
user_role: 'user',
|
||||
access_level: 'private',
|
||||
refresh_interval: 300,
|
||||
is_default: false,
|
||||
is_active: true
|
||||
})
|
||||
dashboardFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const handleSizeChange = (val) => {
|
||||
pagination.pageSize = val
|
||||
pagination.page = 1
|
||||
loadDashboards()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
pagination.page = val
|
||||
loadDashboards()
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
const getRoleName = (role) => {
|
||||
const roleNames = {
|
||||
admin: '管理员',
|
||||
user: '普通用户',
|
||||
manager: '经理',
|
||||
analyst: '分析师'
|
||||
}
|
||||
return roleNames[role] || role
|
||||
}
|
||||
|
||||
const getRoleTagType = (role) => {
|
||||
const tagTypes = {
|
||||
admin: 'danger',
|
||||
user: 'primary',
|
||||
manager: 'warning',
|
||||
analyst: 'success'
|
||||
}
|
||||
return tagTypes[role] || 'info'
|
||||
}
|
||||
|
||||
const getAccessLevelName = (level) => {
|
||||
const levelNames = {
|
||||
private: '私有',
|
||||
public: '公开',
|
||||
shared: '共享'
|
||||
}
|
||||
return levelNames[level] || level
|
||||
}
|
||||
|
||||
const getAccessLevelTagType = (level) => {
|
||||
const tagTypes = {
|
||||
private: 'info',
|
||||
public: 'success',
|
||||
shared: 'warning'
|
||||
}
|
||||
return tagTypes[level] || 'info'
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) return '-'
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadDashboards()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
dashboards,
|
||||
showCreateDialog,
|
||||
creating,
|
||||
editingDashboard,
|
||||
dashboardFormRef,
|
||||
filterForm,
|
||||
pagination,
|
||||
dashboardForm,
|
||||
dashboardRules,
|
||||
loadDashboards,
|
||||
resetFilter,
|
||||
viewDashboard,
|
||||
editDashboard,
|
||||
deleteDashboard,
|
||||
saveDashboard,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
getRoleName,
|
||||
getRoleTagType,
|
||||
getAccessLevelName,
|
||||
getAccessLevelTagType,
|
||||
formatDate
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-statistics-page {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
591
src/pages/admin/statistics/DashboardManagement.vue
Normal file
591
src/pages/admin/statistics/DashboardManagement.vue
Normal file
@@ -0,0 +1,591 @@
|
||||
<template>
|
||||
<div class="dashboard-management">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>仪表板管理</h1>
|
||||
<p>管理系统统计仪表板</p>
|
||||
</div>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<el-card class="filter-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>筛选条件</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :inline="true" :model="filterForm" class="filter-form">
|
||||
<el-form-item label="仪表板名称">
|
||||
<el-input
|
||||
v-model="filterForm.name"
|
||||
placeholder="请输入仪表板名称"
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户角色">
|
||||
<el-select v-model="filterForm.user_role" placeholder="请选择用户角色" clearable>
|
||||
<el-option label="管理员" value="admin"></el-option>
|
||||
<el-option label="用户" value="user"></el-option>
|
||||
<el-option label="经理" value="manager"></el-option>
|
||||
<el-option label="分析师" value="analyst"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问级别">
|
||||
<el-select v-model="filterForm.access_level" placeholder="请选择访问级别" clearable>
|
||||
<el-option label="私有" value="private"></el-option>
|
||||
<el-option label="公开" value="public"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter">查询</el-button>
|
||||
<el-button @click="resetFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 仪表板列表 -->
|
||||
<el-card class="dashboards-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>仪表板列表</span>
|
||||
<div class="header-actions">
|
||||
<span class="total-count">共 {{ total }} 条记录</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showCreateDialog = true"
|
||||
>
|
||||
创建新仪表板
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="dashboards"
|
||||
v-loading="loading"
|
||||
style="width: 100%"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column prop="name" label="仪表板名称" min-width="200" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
<div class="dashboard-name">
|
||||
<span class="name">{{ scope.row.name }}</span>
|
||||
<div class="description" v-if="scope.row.description">
|
||||
{{ scope.row.description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="user_role" label="用户角色" width="120">
|
||||
<template v-slot="scope">
|
||||
<el-tag :type="getRoleTagType(scope.row.user_role)">
|
||||
{{ getRoleText(scope.row.user_role) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="access_level" label="访问级别" width="120">
|
||||
<template v-slot="scope">
|
||||
<el-tag :type="scope.row.access_level === 'public' ? 'success' : 'info'">
|
||||
{{ scope.row.access_level === 'public' ? '公开' : '私有' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="refresh_interval" label="刷新间隔" width="120">
|
||||
<template v-slot="scope">
|
||||
{{ scope.row.refresh_interval }}秒
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="is_active" label="状态" width="100">
|
||||
<template v-slot="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.is_active"
|
||||
@change="toggleDashboardStatus(scope.row)"
|
||||
:loading="scope.row.updating"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="is_default" label="默认" width="80">
|
||||
<template v-slot="scope">
|
||||
<el-tag :type="scope.row.is_default ? 'success' : 'info'">
|
||||
{{ scope.row.is_default ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
{{ formatDate(scope.row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updated_at" label="更新时间" width="180" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
{{ formatDate(scope.row.updated_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template v-slot="scope">
|
||||
<el-button @click="editDashboard(scope.row)" type="text" size="small">编辑</el-button>
|
||||
<el-button @click="previewDashboard(scope.row)" type="text" size="small">预览</el-button>
|
||||
<el-button @click="deleteDashboard(scope.row)" type="text" size="small" style="color: #F56C6C">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pagination.page"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pagination.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
background
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 创建/编辑仪表板对话框 -->
|
||||
<el-dialog
|
||||
:title="isEdit ? '编辑仪表板' : '创建新仪表板'"
|
||||
v-model="showCreateDialog"
|
||||
width="600px"
|
||||
@close="resetForm"
|
||||
>
|
||||
<el-form :model="form" :rules="formRules" ref="form" label-width="100px">
|
||||
<el-form-item label="仪表板名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入仪表板名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入仪表板描述"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户角色" prop="user_role">
|
||||
<el-select v-model="form.user_role" placeholder="请选择用户角色">
|
||||
<el-option label="管理员" value="admin"></el-option>
|
||||
<el-option label="用户" value="user"></el-option>
|
||||
<el-option label="经理" value="manager"></el-option>
|
||||
<el-option label="分析师" value="analyst"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问级别" prop="access_level">
|
||||
<el-select v-model="form.access_level" placeholder="请选择访问级别">
|
||||
<el-option label="私有" value="private"></el-option>
|
||||
<el-option label="公开" value="public"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="刷新间隔" prop="refresh_interval">
|
||||
<el-input-number
|
||||
v-model="form.refresh_interval"
|
||||
:min="60"
|
||||
:max="3600"
|
||||
:step="60"
|
||||
></el-input-number>
|
||||
<span class="form-tip">秒</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="布局配置" prop="layout">
|
||||
<el-input
|
||||
v-model="form.layout"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入布局配置JSON"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="组件配置" prop="widgets">
|
||||
<el-input
|
||||
v-model="form.widgets"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入组件配置JSON"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设置配置" prop="settings">
|
||||
<el-input
|
||||
v-model="form.settings"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入设置配置JSON"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="is_active">
|
||||
<el-switch v-model="form.is_active"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="设为默认" prop="is_default">
|
||||
<el-switch v-model="form.is_default"></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showCreateDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveDashboard" :loading="saving">
|
||||
{{ isEdit ? '更新' : '创建' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 仪表板预览对话框 -->
|
||||
<el-dialog
|
||||
:title="previewDashboard?.name || '仪表板预览'"
|
||||
v-model="previewDialogVisible"
|
||||
fullscreen
|
||||
>
|
||||
<StatisticsDashboard
|
||||
v-if="previewDashboard"
|
||||
:dashboard-id="previewDashboard.id"
|
||||
:user-role="previewDashboard.user_role"
|
||||
:auto-refresh="true"
|
||||
:refresh-interval="previewDashboard.refresh_interval * 1000"
|
||||
/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { adminCreateDashboard, adminDeleteDashboard, adminGetDashboard, adminGetDashboards, adminUpdateDashboard } from '@/api/statistics'
|
||||
import StatisticsDashboard from '@/components/statistics/StatisticsDashboard.vue'
|
||||
|
||||
export default {
|
||||
name: 'DashboardManagement',
|
||||
components: {
|
||||
StatisticsDashboard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
dashboards: [],
|
||||
total: 0,
|
||||
filterForm: {
|
||||
name: '',
|
||||
user_role: '',
|
||||
access_level: ''
|
||||
},
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
sortField: '',
|
||||
sortOrder: '',
|
||||
showCreateDialog: false,
|
||||
isEdit: false,
|
||||
saving: false,
|
||||
form: {
|
||||
id: null,
|
||||
name: '',
|
||||
description: '',
|
||||
user_role: 'user',
|
||||
access_level: 'private',
|
||||
refresh_interval: 300,
|
||||
layout: JSON.stringify({ columns: 3, rows: 4 }),
|
||||
widgets: JSON.stringify([]),
|
||||
settings: JSON.stringify({ theme: 'light', auto_refresh: true }),
|
||||
is_active: true,
|
||||
is_default: false
|
||||
},
|
||||
formRules: {
|
||||
name: [{ required: true, message: '请输入仪表板名称', trigger: 'blur' }],
|
||||
description: [{ required: true, message: '请输入仪表板描述', trigger: 'blur' }],
|
||||
user_role: [{ required: true, message: '请选择用户角色', trigger: 'change' }],
|
||||
access_level: [{ required: true, message: '请选择访问级别', trigger: 'change' }]
|
||||
},
|
||||
previewDialogVisible: false,
|
||||
previewDashboard: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadDashboards()
|
||||
},
|
||||
methods: {
|
||||
async loadDashboards() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
...this.filterForm,
|
||||
page: this.pagination.page,
|
||||
limit: this.pagination.pageSize,
|
||||
sort_field: this.sortField,
|
||||
sort_order: this.sortOrder
|
||||
}
|
||||
|
||||
const response = await adminGetDashboards(params)
|
||||
|
||||
if (response.success) {
|
||||
this.dashboards = response.data.items || []
|
||||
this.pagination.total = response.data.total || 0
|
||||
this.total = this.pagination.total
|
||||
} else {
|
||||
this.$message.error(response.message || '获取仪表板列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取仪表板列表失败:', error)
|
||||
this.$message.error('获取仪表板列表失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async editDashboard(dashboard) {
|
||||
try {
|
||||
const response = await adminGetDashboard(dashboard.id)
|
||||
|
||||
if (response.success) {
|
||||
this.form = { ...response.data }
|
||||
this.isEdit = true
|
||||
this.showCreateDialog = true
|
||||
} else {
|
||||
this.$message.error(response.message || '获取仪表板详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取仪表板详情失败:', error)
|
||||
this.$message.error('获取仪表板详情失败')
|
||||
}
|
||||
},
|
||||
|
||||
async saveDashboard() {
|
||||
try {
|
||||
await this.$refs.form.validate()
|
||||
this.saving = true
|
||||
|
||||
const data = {
|
||||
...this.form,
|
||||
created_by: this.$store.getters.userId
|
||||
}
|
||||
|
||||
const response = this.isEdit
|
||||
? await adminUpdateDashboard(this.form.id, data)
|
||||
: await adminCreateDashboard(data)
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success(this.isEdit ? '仪表板更新成功' : '仪表板创建成功')
|
||||
this.showCreateDialog = false
|
||||
this.loadDashboards()
|
||||
} else {
|
||||
this.$message.error(response.message || (this.isEdit ? '仪表板更新失败' : '仪表板创建失败'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存仪表板失败:', error)
|
||||
this.$message.error('保存仪表板失败')
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
|
||||
async toggleDashboardStatus(dashboard) {
|
||||
try {
|
||||
dashboard.updating = true
|
||||
const response = await adminUpdateDashboard(dashboard.id, {
|
||||
is_active: dashboard.is_active
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success('状态更新成功')
|
||||
} else {
|
||||
this.$message.error(response.message || '状态更新失败')
|
||||
dashboard.is_active = !dashboard.is_active // 回滚状态
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新仪表板状态失败:', error)
|
||||
this.$message.error('更新仪表板状态失败')
|
||||
dashboard.is_active = !dashboard.is_active // 回滚状态
|
||||
} finally {
|
||||
dashboard.updating = false
|
||||
}
|
||||
},
|
||||
|
||||
previewDashboard(dashboard) {
|
||||
this.previewDashboard = dashboard
|
||||
this.previewDialogVisible = true
|
||||
},
|
||||
|
||||
async deleteDashboard(dashboard) {
|
||||
try {
|
||||
await this.$confirm(`确定要删除仪表板 "${dashboard.name}" 吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const response = await adminDeleteDashboard(dashboard.id)
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success('仪表板删除成功')
|
||||
this.loadDashboards()
|
||||
} else {
|
||||
this.$message.error(response.message || '删除仪表板失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除仪表板失败:', error)
|
||||
this.$message.error('删除仪表板失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleFilter() {
|
||||
this.pagination.page = 1
|
||||
this.loadDashboards()
|
||||
},
|
||||
|
||||
resetFilter() {
|
||||
this.filterForm = {
|
||||
name: '',
|
||||
user_role: '',
|
||||
access_level: ''
|
||||
}
|
||||
this.pagination.page = 1
|
||||
this.loadDashboards()
|
||||
},
|
||||
|
||||
handleSortChange({ prop, order }) {
|
||||
this.sortField = prop
|
||||
this.sortOrder = order === 'ascending' ? 'asc' : 'desc'
|
||||
this.loadDashboards()
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.pagination.pageSize = val
|
||||
this.loadDashboards()
|
||||
},
|
||||
|
||||
handleCurrentChange(val) {
|
||||
this.pagination.page = val
|
||||
this.loadDashboards()
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.form = {
|
||||
id: null,
|
||||
name: '',
|
||||
description: '',
|
||||
user_role: 'user',
|
||||
access_level: 'private',
|
||||
refresh_interval: 300,
|
||||
layout: JSON.stringify({ columns: 3, rows: 4 }),
|
||||
widgets: JSON.stringify([]),
|
||||
settings: JSON.stringify({ theme: 'light', auto_refresh: true }),
|
||||
is_active: true,
|
||||
is_default: false
|
||||
}
|
||||
this.isEdit = false
|
||||
this.$refs.form?.resetFields()
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-'
|
||||
return new Date(dateString).toLocaleString('zh-CN')
|
||||
},
|
||||
|
||||
getRoleTagType(role) {
|
||||
const typeMap = {
|
||||
admin: 'danger',
|
||||
manager: 'warning',
|
||||
analyst: 'info',
|
||||
user: 'success'
|
||||
}
|
||||
return typeMap[role] || 'info'
|
||||
},
|
||||
|
||||
getRoleText(role) {
|
||||
const textMap = {
|
||||
admin: '管理员',
|
||||
manager: '经理',
|
||||
analyst: '分析师',
|
||||
user: '用户'
|
||||
}
|
||||
return textMap[role] || role
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-management {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dashboards-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dashboard-name .name {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.dashboard-name .description {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
margin-left: 8px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
558
src/pages/admin/statistics/MetricsManagement.vue
Normal file
558
src/pages/admin/statistics/MetricsManagement.vue
Normal file
@@ -0,0 +1,558 @@
|
||||
<template>
|
||||
<div class="metrics-management">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>指标管理</h1>
|
||||
<p>管理系统统计指标</p>
|
||||
</div>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<el-card class="filter-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>筛选条件</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :inline="true" :model="filterForm" class="filter-form">
|
||||
<el-form-item label="指标名称">
|
||||
<el-input
|
||||
v-model="filterForm.name"
|
||||
placeholder="请输入指标名称"
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="指标类型">
|
||||
<el-select v-model="filterForm.metric_type" placeholder="请选择指标类型" clearable>
|
||||
<el-option label="API调用" value="api_calls"></el-option>
|
||||
<el-option label="用户统计" value="users"></el-option>
|
||||
<el-option label="财务统计" value="finance"></el-option>
|
||||
<el-option label="产品统计" value="products"></el-option>
|
||||
<el-option label="认证统计" value="certification"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filterForm.is_active" placeholder="请选择状态" clearable>
|
||||
<el-option label="活跃" :value="true"></el-option>
|
||||
<el-option label="禁用" :value="false"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter">查询</el-button>
|
||||
<el-button @click="resetFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 指标列表 -->
|
||||
<el-card class="metrics-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>指标列表</span>
|
||||
<div class="header-actions">
|
||||
<span class="total-count">共 {{ total }} 条记录</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showCreateDialog = true"
|
||||
>
|
||||
创建新指标
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="metrics"
|
||||
v-loading="loading"
|
||||
style="width: 100%"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column prop="name" label="指标名称" min-width="200" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
<div class="metric-name">
|
||||
<span class="name">{{ scope.row.name }}</span>
|
||||
<div class="description" v-if="scope.row.description">
|
||||
{{ scope.row.description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="metric_type" label="指标类型" width="150">
|
||||
<template v-slot="scope">
|
||||
<el-tag :type="getMetricTypeTagType(scope.row.metric_type)">
|
||||
{{ getMetricTypeText(scope.row.metric_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="value" label="当前值" width="120" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
{{ scope.row.value ? scope.row.value.toLocaleString() : '0' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="unit" label="单位" width="80">
|
||||
<template v-slot="scope">
|
||||
{{ scope.row.unit || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="aggregation_type" label="聚合类型" width="120">
|
||||
<template v-slot="scope">
|
||||
{{ getAggregationTypeText(scope.row.aggregation_type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="is_active" label="状态" width="100">
|
||||
<template v-slot="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.is_active"
|
||||
@change="toggleMetricStatus(scope.row)"
|
||||
:loading="scope.row.updating"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
{{ formatDate(scope.row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="updated_at" label="更新时间" width="180" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
{{ formatDate(scope.row.updated_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template v-slot="scope">
|
||||
<el-button @click="editMetric(scope.row)" type="text" size="small">编辑</el-button>
|
||||
<el-button @click="deleteMetric(scope.row)" type="text" size="small" style="color: #F56C6C">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pagination.page"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pagination.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
background
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 创建/编辑指标对话框 -->
|
||||
<el-dialog
|
||||
:title="isEdit ? '编辑指标' : '创建新指标'"
|
||||
v-model="showCreateDialog"
|
||||
width="600px"
|
||||
@close="resetForm"
|
||||
>
|
||||
<el-form :model="form" :rules="formRules" ref="form" label-width="100px">
|
||||
<el-form-item label="指标名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入指标名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="指标类型" prop="metric_type">
|
||||
<el-select v-model="form.metric_type" placeholder="请选择指标类型">
|
||||
<el-option label="API调用" value="api_calls"></el-option>
|
||||
<el-option label="用户统计" value="users"></el-option>
|
||||
<el-option label="财务统计" value="finance"></el-option>
|
||||
<el-option label="产品统计" value="products"></el-option>
|
||||
<el-option label="认证统计" value="certification"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="指标描述" prop="description">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入指标描述"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="单位" prop="unit">
|
||||
<el-input v-model="form.unit" placeholder="请输入单位"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="初始值" prop="value">
|
||||
<el-input-number
|
||||
v-model="form.value"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入初始值"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="聚合类型" prop="aggregation_type">
|
||||
<el-select v-model="form.aggregation_type" placeholder="请选择聚合类型">
|
||||
<el-option label="求和" value="sum"></el-option>
|
||||
<el-option label="平均值" value="avg"></el-option>
|
||||
<el-option label="最大值" value="max"></el-option>
|
||||
<el-option label="最小值" value="min"></el-option>
|
||||
<el-option label="计数" value="count"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间粒度" prop="time_granularity">
|
||||
<el-select v-model="form.time_granularity" placeholder="请选择时间粒度">
|
||||
<el-option label="分钟" value="minute"></el-option>
|
||||
<el-option label="小时" value="hour"></el-option>
|
||||
<el-option label="天" value="day"></el-option>
|
||||
<el-option label="周" value="week"></el-option>
|
||||
<el-option label="月" value="month"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="is_active">
|
||||
<el-switch v-model="form.is_active"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="设为默认" prop="is_default">
|
||||
<el-switch v-model="form.is_default"></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showCreateDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveMetric" :loading="saving">
|
||||
{{ isEdit ? '更新' : '创建' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { adminCreateMetric, adminDeleteMetric, adminGetMetric, adminGetMetrics, adminUpdateMetric } from '@/api/statistics'
|
||||
|
||||
export default {
|
||||
name: 'MetricsManagement',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
metrics: [],
|
||||
total: 0,
|
||||
filterForm: {
|
||||
name: '',
|
||||
metric_type: '',
|
||||
is_active: null
|
||||
},
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
sortField: '',
|
||||
sortOrder: '',
|
||||
showCreateDialog: false,
|
||||
isEdit: false,
|
||||
saving: false,
|
||||
form: {
|
||||
id: null,
|
||||
name: '',
|
||||
metric_type: '',
|
||||
description: '',
|
||||
unit: '',
|
||||
value: 0,
|
||||
aggregation_type: 'sum',
|
||||
time_granularity: 'hour',
|
||||
is_active: true,
|
||||
is_default: false
|
||||
},
|
||||
formRules: {
|
||||
name: [{ required: true, message: '请输入指标名称', trigger: 'blur' }],
|
||||
metric_type: [{ required: true, message: '请选择指标类型', trigger: 'change' }],
|
||||
description: [{ required: true, message: '请输入指标描述', trigger: 'blur' }],
|
||||
aggregation_type: [{ required: true, message: '请选择聚合类型', trigger: 'change' }],
|
||||
time_granularity: [{ required: true, message: '请选择时间粒度', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadMetrics()
|
||||
},
|
||||
methods: {
|
||||
async loadMetrics() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
...this.filterForm,
|
||||
page: this.pagination.page,
|
||||
limit: this.pagination.pageSize,
|
||||
sort_field: this.sortField,
|
||||
sort_order: this.sortOrder
|
||||
}
|
||||
|
||||
const response = await adminGetMetrics(params)
|
||||
|
||||
if (response.success) {
|
||||
this.metrics = response.data.items || []
|
||||
this.pagination.total = response.data.total || 0
|
||||
this.total = this.pagination.total
|
||||
} else {
|
||||
this.$message.error(response.message || '获取指标列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取指标列表失败:', error)
|
||||
this.$message.error('获取指标列表失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async editMetric(metric) {
|
||||
try {
|
||||
const response = await adminGetMetric(metric.id)
|
||||
|
||||
if (response.success) {
|
||||
this.form = { ...response.data }
|
||||
this.isEdit = true
|
||||
this.showCreateDialog = true
|
||||
} else {
|
||||
this.$message.error(response.message || '获取指标详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取指标详情失败:', error)
|
||||
this.$message.error('获取指标详情失败')
|
||||
}
|
||||
},
|
||||
|
||||
async saveMetric() {
|
||||
try {
|
||||
await this.$refs.form.validate()
|
||||
this.saving = true
|
||||
|
||||
const data = {
|
||||
...this.form,
|
||||
created_by: this.$store.getters.userId
|
||||
}
|
||||
|
||||
const response = this.isEdit
|
||||
? await adminUpdateMetric(this.form.id, data)
|
||||
: await adminCreateMetric(data)
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success(this.isEdit ? '指标更新成功' : '指标创建成功')
|
||||
this.showCreateDialog = false
|
||||
this.loadMetrics()
|
||||
} else {
|
||||
this.$message.error(response.message || (this.isEdit ? '指标更新失败' : '指标创建失败'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存指标失败:', error)
|
||||
this.$message.error('保存指标失败')
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
|
||||
async toggleMetricStatus(metric) {
|
||||
try {
|
||||
metric.updating = true
|
||||
const response = await adminUpdateMetric(metric.id, {
|
||||
is_active: metric.is_active
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success('状态更新成功')
|
||||
} else {
|
||||
this.$message.error(response.message || '状态更新失败')
|
||||
metric.is_active = !metric.is_active // 回滚状态
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新指标状态失败:', error)
|
||||
this.$message.error('更新指标状态失败')
|
||||
metric.is_active = !metric.is_active // 回滚状态
|
||||
} finally {
|
||||
metric.updating = false
|
||||
}
|
||||
},
|
||||
|
||||
async deleteMetric(metric) {
|
||||
try {
|
||||
await this.$confirm(`确定要删除指标 "${metric.name}" 吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const response = await adminDeleteMetric(metric.id)
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success('指标删除成功')
|
||||
this.loadMetrics()
|
||||
} else {
|
||||
this.$message.error(response.message || '删除指标失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除指标失败:', error)
|
||||
this.$message.error('删除指标失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleFilter() {
|
||||
this.pagination.page = 1
|
||||
this.loadMetrics()
|
||||
},
|
||||
|
||||
resetFilter() {
|
||||
this.filterForm = {
|
||||
name: '',
|
||||
metric_type: '',
|
||||
is_active: null
|
||||
}
|
||||
this.pagination.page = 1
|
||||
this.loadMetrics()
|
||||
},
|
||||
|
||||
handleSortChange({ prop, order }) {
|
||||
this.sortField = prop
|
||||
this.sortOrder = order === 'ascending' ? 'asc' : 'desc'
|
||||
this.loadMetrics()
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.pagination.pageSize = val
|
||||
this.loadMetrics()
|
||||
},
|
||||
|
||||
handleCurrentChange(val) {
|
||||
this.pagination.page = val
|
||||
this.loadMetrics()
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.form = {
|
||||
id: null,
|
||||
name: '',
|
||||
metric_type: '',
|
||||
description: '',
|
||||
unit: '',
|
||||
value: 0,
|
||||
aggregation_type: 'sum',
|
||||
time_granularity: 'hour',
|
||||
is_active: true,
|
||||
is_default: false
|
||||
}
|
||||
this.isEdit = false
|
||||
this.$refs.form?.resetFields()
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-'
|
||||
return new Date(dateString).toLocaleString('zh-CN')
|
||||
},
|
||||
|
||||
getMetricTypeTagType(type) {
|
||||
const typeMap = {
|
||||
api_calls: 'primary',
|
||||
users: 'success',
|
||||
finance: 'warning',
|
||||
products: 'info',
|
||||
certification: 'danger'
|
||||
}
|
||||
return typeMap[type] || 'info'
|
||||
},
|
||||
|
||||
getMetricTypeText(type) {
|
||||
const textMap = {
|
||||
api_calls: 'API调用',
|
||||
users: '用户统计',
|
||||
finance: '财务统计',
|
||||
products: '产品统计',
|
||||
certification: '认证统计'
|
||||
}
|
||||
return textMap[type] || type
|
||||
},
|
||||
|
||||
getAggregationTypeText(type) {
|
||||
const textMap = {
|
||||
sum: '求和',
|
||||
avg: '平均值',
|
||||
max: '最大值',
|
||||
min: '最小值',
|
||||
count: '计数'
|
||||
}
|
||||
return textMap[type] || type
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metrics-management {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.metrics-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.metric-name .name {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.metric-name .description {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
660
src/pages/admin/statistics/ReportsManagement.vue
Normal file
660
src/pages/admin/statistics/ReportsManagement.vue
Normal file
@@ -0,0 +1,660 @@
|
||||
<template>
|
||||
<div class="reports-management">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>报告管理</h1>
|
||||
<p>管理系统统计报告</p>
|
||||
</div>
|
||||
|
||||
<!-- 筛选区域 -->
|
||||
<el-card class="filter-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>筛选条件</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :inline="true" :model="filterForm" class="filter-form">
|
||||
<el-form-item label="报告名称">
|
||||
<el-input
|
||||
v-model="filterForm.name"
|
||||
placeholder="请输入报告名称"
|
||||
clearable
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="报告类型">
|
||||
<el-select v-model="filterForm.report_type" placeholder="请选择报告类型" clearable>
|
||||
<el-option label="用户统计" value="user_statistics"></el-option>
|
||||
<el-option label="API调用" value="api_calls"></el-option>
|
||||
<el-option label="财务报告" value="finance_report"></el-option>
|
||||
<el-option label="产品统计" value="product_statistics"></el-option>
|
||||
<el-option label="认证统计" value="certification_statistics"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filterForm.status" placeholder="请选择状态" clearable>
|
||||
<el-option label="已完成" value="completed"></el-option>
|
||||
<el-option label="生成中" value="generating"></el-option>
|
||||
<el-option label="已过期" value="expired"></el-option>
|
||||
<el-option label="生成失败" value="failed"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleFilter">查询</el-button>
|
||||
<el-button @click="resetFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 报告列表 -->
|
||||
<el-card class="reports-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>报告列表</span>
|
||||
<div class="header-actions">
|
||||
<span class="total-count">共 {{ total }} 条记录</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showGenerateDialog = true"
|
||||
>
|
||||
生成新报告
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
:data="reports"
|
||||
v-loading="loading"
|
||||
style="width: 100%"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column prop="name" label="报告名称" min-width="200" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
<div class="report-name">
|
||||
<span class="name">{{ scope.row.name }}</span>
|
||||
<div class="description" v-if="scope.row.description">
|
||||
{{ scope.row.description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="report_type" label="报告类型" width="150">
|
||||
<template v-slot="scope">
|
||||
<el-tag :type="getReportTypeTagType(scope.row.report_type)">
|
||||
{{ getReportTypeText(scope.row.report_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template v-slot="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="period" label="统计周期" width="120">
|
||||
<template v-slot="scope">
|
||||
{{ getPeriodText(scope.row.period) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="generated_by" label="生成人" width="120">
|
||||
<template v-slot="scope">
|
||||
{{ scope.row.generated_by || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="created_at" label="创建时间" width="180" sortable="custom">
|
||||
<template v-slot="scope">
|
||||
{{ formatDate(scope.row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="expires_at" label="过期时间" width="180">
|
||||
<template v-slot="scope">
|
||||
<span v-if="scope.row.expires_at">{{ formatDate(scope.row.expires_at) }}</span>
|
||||
<span v-else class="text-muted">永不过期</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="250" fixed="right">
|
||||
<template v-slot="scope">
|
||||
<el-button
|
||||
@click="viewReport(scope.row)"
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="scope.row.status !== 'completed'"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="downloadReport(scope.row)"
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="scope.row.status !== 'completed'"
|
||||
>
|
||||
下载
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="regenerateReport(scope.row)"
|
||||
type="text"
|
||||
size="small"
|
||||
v-if="scope.row.status === 'failed'"
|
||||
>
|
||||
重新生成
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="deleteReport(scope.row)"
|
||||
type="text"
|
||||
size="small"
|
||||
style="color: #F56C6C"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pagination.page"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pagination.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
background
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 生成报告对话框 -->
|
||||
<el-dialog
|
||||
title="生成报告"
|
||||
v-model="showGenerateDialog"
|
||||
width="600px"
|
||||
@close="resetGenerateForm"
|
||||
>
|
||||
<el-form :model="generateForm" :rules="generateRules" ref="generateForm" label-width="100px">
|
||||
<el-form-item label="报告名称" prop="name">
|
||||
<el-input v-model="generateForm.name" placeholder="请输入报告名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="报告类型" prop="report_type">
|
||||
<el-select v-model="generateForm.report_type" placeholder="请选择报告类型">
|
||||
<el-option label="用户统计" value="user_statistics"></el-option>
|
||||
<el-option label="API调用" value="api_calls"></el-option>
|
||||
<el-option label="财务报告" value="finance_report"></el-option>
|
||||
<el-option label="产品统计" value="product_statistics"></el-option>
|
||||
<el-option label="认证统计" value="certification_statistics"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input
|
||||
v-model="generateForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入报告描述"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="统计周期" prop="period">
|
||||
<el-select v-model="generateForm.period" placeholder="请选择统计周期">
|
||||
<el-option label="日" value="daily"></el-option>
|
||||
<el-option label="周" value="weekly"></el-option>
|
||||
<el-option label="月" value="monthly"></el-option>
|
||||
<el-option label="季度" value="quarterly"></el-option>
|
||||
<el-option label="年" value="yearly"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围" prop="dateRange">
|
||||
<el-date-picker
|
||||
v-model="generateForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="报告格式" prop="format">
|
||||
<el-checkbox-group v-model="generateForm.format">
|
||||
<el-checkbox label="pdf">PDF</el-checkbox>
|
||||
<el-checkbox label="excel">Excel</el-checkbox>
|
||||
<el-checkbox label="csv">CSV</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级" prop="priority">
|
||||
<el-select v-model="generateForm.priority" placeholder="请选择优先级">
|
||||
<el-option label="低" value="low"></el-option>
|
||||
<el-option label="中" value="medium"></el-option>
|
||||
<el-option label="高" value="high"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="过期时间" prop="expires_at">
|
||||
<el-date-picker
|
||||
v-model="generateForm.expires_at"
|
||||
type="datetime"
|
||||
placeholder="选择过期时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template v-slot:footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showGenerateDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="generateReport" :loading="generating">
|
||||
生成报告
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 报告详情对话框 -->
|
||||
<el-dialog
|
||||
title="报告详情"
|
||||
v-model="showDetailDialog"
|
||||
width="800px"
|
||||
>
|
||||
<div v-if="selectedReport">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="报告名称">{{ selectedReport.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报告类型">{{ getReportTypeText(selectedReport.report_type) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusTagType(selectedReport.status)">
|
||||
{{ getStatusText(selectedReport.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="统计周期">{{ getPeriodText(selectedReport.period) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ formatDate(selectedReport.created_at) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="过期时间">
|
||||
{{ selectedReport.expires_at ? formatDate(selectedReport.expires_at) : '永不过期' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="描述" :span="2">{{ selectedReport.description || '无' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div v-if="selectedReport.status === 'completed'" class="report-content">
|
||||
<h4>报告内容</h4>
|
||||
<div class="report-data" v-html="selectedReport.content"></div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedReport.status === 'failed'" class="error-content">
|
||||
<h4>错误信息</h4>
|
||||
<el-alert
|
||||
:title="selectedReport.error_message || '生成失败'"
|
||||
type="error"
|
||||
:closable="false"
|
||||
></el-alert>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { adminDeleteReport, adminGetReport, adminGetReports } from '@/api/statistics'
|
||||
|
||||
export default {
|
||||
name: 'ReportsManagement',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
reports: [],
|
||||
total: 0,
|
||||
filterForm: {
|
||||
name: '',
|
||||
report_type: '',
|
||||
status: ''
|
||||
},
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
},
|
||||
sortField: '',
|
||||
sortOrder: '',
|
||||
showGenerateDialog: false,
|
||||
generating: false,
|
||||
generateForm: {
|
||||
name: '',
|
||||
report_type: '',
|
||||
description: '',
|
||||
period: 'monthly',
|
||||
dateRange: [],
|
||||
format: ['pdf'],
|
||||
priority: 'medium',
|
||||
expires_at: ''
|
||||
},
|
||||
generateRules: {
|
||||
name: [{ required: true, message: '请输入报告名称', trigger: 'blur' }],
|
||||
report_type: [{ required: true, message: '请选择报告类型', trigger: 'change' }],
|
||||
description: [{ required: true, message: '请输入报告描述', trigger: 'blur' }],
|
||||
period: [{ required: true, message: '请选择统计周期', trigger: 'change' }],
|
||||
dateRange: [{ required: true, message: '请选择时间范围', trigger: 'change' }]
|
||||
},
|
||||
showDetailDialog: false,
|
||||
selectedReport: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadReports()
|
||||
},
|
||||
methods: {
|
||||
async loadReports() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
...this.filterForm,
|
||||
page: this.pagination.page,
|
||||
limit: this.pagination.pageSize,
|
||||
sort_field: this.sortField,
|
||||
sort_order: this.sortOrder
|
||||
}
|
||||
|
||||
const response = await adminGetReports(params)
|
||||
|
||||
if (response.success) {
|
||||
this.reports = response.data.items || []
|
||||
this.pagination.total = response.data.total || 0
|
||||
this.total = this.pagination.total
|
||||
} else {
|
||||
this.$message.error(response.message || '获取报告列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取报告列表失败:', error)
|
||||
this.$message.error('获取报告列表失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async generateReport() {
|
||||
try {
|
||||
await this.$refs.generateForm.validate()
|
||||
this.generating = true
|
||||
|
||||
// TODO: 实现报告生成功能 - 后端暂无此接口
|
||||
this.$message.info('报告生成功能暂未实现')
|
||||
this.showGenerateDialog = false
|
||||
} catch (error) {
|
||||
console.error('生成报告失败:', error)
|
||||
this.$message.error('生成报告失败')
|
||||
} finally {
|
||||
this.generating = false
|
||||
}
|
||||
},
|
||||
|
||||
async viewReport(report) {
|
||||
try {
|
||||
const response = await adminGetReport(report.id)
|
||||
|
||||
if (response.success) {
|
||||
this.selectedReport = response.data
|
||||
this.showDetailDialog = true
|
||||
} else {
|
||||
this.$message.error(response.message || '获取报告详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取报告详情失败:', error)
|
||||
this.$message.error('获取报告详情失败')
|
||||
}
|
||||
},
|
||||
|
||||
downloadReport(report) {
|
||||
// TODO: 实现报告下载逻辑
|
||||
this.$message.info('下载功能开发中')
|
||||
},
|
||||
|
||||
async regenerateReport(report) {
|
||||
try {
|
||||
await this.$confirm('确定要重新生成这个报告吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
// TODO: 实现报告重新生成功能 - 后端暂无此接口
|
||||
this.$message.info('报告重新生成功能暂未实现')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('重新生成报告失败:', error)
|
||||
this.$message.error('重新生成报告失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async deleteReport(report) {
|
||||
try {
|
||||
await this.$confirm(`确定要删除报告 "${report.name}" 吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const response = await adminDeleteReport(report.id)
|
||||
|
||||
if (response.success) {
|
||||
this.$message.success('报告删除成功')
|
||||
this.loadReports()
|
||||
} else {
|
||||
this.$message.error(response.message || '删除报告失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除报告失败:', error)
|
||||
this.$message.error('删除报告失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleFilter() {
|
||||
this.pagination.page = 1
|
||||
this.loadReports()
|
||||
},
|
||||
|
||||
resetFilter() {
|
||||
this.filterForm = {
|
||||
name: '',
|
||||
report_type: '',
|
||||
status: ''
|
||||
}
|
||||
this.pagination.page = 1
|
||||
this.loadReports()
|
||||
},
|
||||
|
||||
handleSortChange({ prop, order }) {
|
||||
this.sortField = prop
|
||||
this.sortOrder = order === 'ascending' ? 'asc' : 'desc'
|
||||
this.loadReports()
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.pagination.pageSize = val
|
||||
this.loadReports()
|
||||
},
|
||||
|
||||
handleCurrentChange(val) {
|
||||
this.pagination.page = val
|
||||
this.loadReports()
|
||||
},
|
||||
|
||||
resetGenerateForm() {
|
||||
this.generateForm = {
|
||||
name: '',
|
||||
report_type: '',
|
||||
description: '',
|
||||
period: 'monthly',
|
||||
dateRange: [],
|
||||
format: ['pdf'],
|
||||
priority: 'medium',
|
||||
expires_at: ''
|
||||
}
|
||||
this.$refs.generateForm?.resetFields()
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-'
|
||||
return new Date(dateString).toLocaleString('zh-CN')
|
||||
},
|
||||
|
||||
getReportTypeTagType(type) {
|
||||
const typeMap = {
|
||||
user_statistics: 'primary',
|
||||
api_calls: 'success',
|
||||
finance_report: 'warning',
|
||||
product_statistics: 'info',
|
||||
certification_statistics: 'danger'
|
||||
}
|
||||
return typeMap[type] || 'info'
|
||||
},
|
||||
|
||||
getReportTypeText(type) {
|
||||
const textMap = {
|
||||
user_statistics: '用户统计',
|
||||
api_calls: 'API调用',
|
||||
finance_report: '财务报告',
|
||||
product_statistics: '产品统计',
|
||||
certification_statistics: '认证统计'
|
||||
}
|
||||
return textMap[type] || type
|
||||
},
|
||||
|
||||
getStatusTagType(status) {
|
||||
const typeMap = {
|
||||
completed: 'success',
|
||||
generating: 'info',
|
||||
expired: 'warning',
|
||||
failed: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
},
|
||||
|
||||
getStatusText(status) {
|
||||
const textMap = {
|
||||
completed: '已完成',
|
||||
generating: '生成中',
|
||||
expired: '已过期',
|
||||
failed: '生成失败'
|
||||
}
|
||||
return textMap[status] || status
|
||||
},
|
||||
|
||||
getPeriodText(period) {
|
||||
const textMap = {
|
||||
daily: '日',
|
||||
weekly: '周',
|
||||
monthly: '月',
|
||||
quarterly: '季度',
|
||||
yearly: '年'
|
||||
}
|
||||
return textMap[period] || period
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.reports-management {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.reports-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-name .name {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.report-name .description {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #C0C4CC;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.report-content h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.report-data {
|
||||
background-color: #f5f7fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.error-content h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
413
src/pages/admin/statistics/StatisticsSettings.vue
Normal file
413
src/pages/admin/statistics/StatisticsSettings.vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<template>
|
||||
<div class="statistics-settings">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<h1>统计设置</h1>
|
||||
<p>配置统计系统参数</p>
|
||||
</div>
|
||||
|
||||
<!-- 设置内容 -->
|
||||
<el-card class="settings-card">
|
||||
<template v-slot:header>
|
||||
<div class="card-header">
|
||||
<span>统计设置</span>
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-refresh"
|
||||
@click="loadSettings"
|
||||
>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="activeSettingTab" type="border-card">
|
||||
<!-- 基础设置 -->
|
||||
<el-tab-pane label="基础设置" name="general">
|
||||
<template v-slot:label>
|
||||
<el-icon><Setting /></el-icon> 基础设置
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-form :model="generalSettings" label-width="150px">
|
||||
<el-form-item label="数据保留天数">
|
||||
<el-input-number
|
||||
v-model="generalSettings.data_retention_days"
|
||||
:min="30"
|
||||
:max="3650"
|
||||
></el-input-number>
|
||||
<span class="form-tip">天</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认仪表板ID">
|
||||
<el-input
|
||||
v-model="generalSettings.default_dashboard_id"
|
||||
placeholder="请输入默认仪表板ID"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用实时统计">
|
||||
<el-switch v-model="generalSettings.enable_realtime_statistics"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="统计精度">
|
||||
<el-select v-model="generalSettings.precision" placeholder="请选择统计精度">
|
||||
<el-option label="分钟级" value="minute"></el-option>
|
||||
<el-option label="小时级" value="hour"></el-option>
|
||||
<el-option label="天级" value="day"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveGeneralSettings">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 聚合设置 -->
|
||||
<el-tab-pane label="聚合设置" name="aggregation">
|
||||
<template v-slot:label>
|
||||
<el-icon><Timer /></el-icon> 聚合设置
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-form :model="aggregationSettings" label-width="150px">
|
||||
<el-form-item label="小时聚合间隔">
|
||||
<el-input-number
|
||||
v-model="aggregationSettings.hourly_interval_minutes"
|
||||
:min="1"
|
||||
:max="60"
|
||||
></el-input-number>
|
||||
<span class="form-tip">分钟</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="日聚合时间">
|
||||
<el-time-picker
|
||||
v-model="aggregationSettings.daily_aggregation_time"
|
||||
placeholder="选择时间"
|
||||
value-format="HH:mm"
|
||||
></el-time-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用自动聚合">
|
||||
<el-switch v-model="aggregationSettings.enable_auto_aggregation"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="聚合线程数">
|
||||
<el-input-number
|
||||
v-model="aggregationSettings.aggregation_threads"
|
||||
:min="1"
|
||||
:max="10"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveAggregationSettings">保存</el-button>
|
||||
<el-button @click="triggerManualAggregation">手动触发聚合</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 告警设置 -->
|
||||
<el-tab-pane label="告警设置" name="alerts">
|
||||
<template v-slot:label>
|
||||
<el-icon><Bell /></el-icon> 告警设置
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-form :model="alertSettings" label-width="150px">
|
||||
<el-form-item label="启用异常告警">
|
||||
<el-switch v-model="alertSettings.enable_anomaly_alerts"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警阈值 (%)">
|
||||
<el-input-number
|
||||
v-model="alertSettings.alert_threshold_percent"
|
||||
:min="1"
|
||||
:max="100"
|
||||
></el-input-number>
|
||||
<span class="form-tip">%</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警接收邮箱">
|
||||
<el-input
|
||||
v-model="alertSettings.alert_recipients_email"
|
||||
placeholder="多个邮箱用逗号分隔"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="告警频率限制">
|
||||
<el-input-number
|
||||
v-model="alertSettings.alert_frequency_limit"
|
||||
:min="1"
|
||||
:max="60"
|
||||
></el-input-number>
|
||||
<span class="form-tip">分钟</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveAlertSettings">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 性能设置 -->
|
||||
<el-tab-pane label="性能设置" name="performance">
|
||||
<template v-slot:label>
|
||||
<el-icon><Cpu /></el-icon> 性能设置
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-form :model="performanceSettings" label-width="150px">
|
||||
<el-form-item label="缓存过期时间">
|
||||
<el-input-number
|
||||
v-model="performanceSettings.cache_expiration_seconds"
|
||||
:min="60"
|
||||
:max="86400"
|
||||
></el-input-number>
|
||||
<span class="form-tip">秒</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用数据压缩">
|
||||
<el-switch v-model="performanceSettings.enable_data_compression"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="查询超时时间">
|
||||
<el-input-number
|
||||
v-model="performanceSettings.query_timeout_seconds"
|
||||
:min="5"
|
||||
:max="300"
|
||||
></el-input-number>
|
||||
<span class="form-tip">秒</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="批量处理大小">
|
||||
<el-input-number
|
||||
v-model="performanceSettings.batch_size"
|
||||
:min="100"
|
||||
:max="10000"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="savePerformanceSettings">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 系统状态 -->
|
||||
<el-tab-pane label="系统状态" name="status">
|
||||
<template v-slot:label>
|
||||
<el-icon><Monitor /></el-icon> 系统状态
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="系统状态">
|
||||
<el-tag :type="systemStatus.healthy ? 'success' : 'danger'">
|
||||
{{ systemStatus.healthy ? '正常' : '异常' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据总量">
|
||||
{{ systemStatus.total_records || 0 }} 条
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="活跃指标">
|
||||
{{ systemStatus.active_metrics || 0 }} 个
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="仪表板数量">
|
||||
{{ systemStatus.dashboard_count || 0 }} 个
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后聚合时间">
|
||||
{{ systemStatus.last_aggregation_time || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="缓存命中率">
|
||||
{{ systemStatus.cache_hit_rate || 0 }}%
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="status-actions">
|
||||
<el-button @click="refreshSystemStatus">刷新状态</el-button>
|
||||
<el-button @click="clearCache">清理缓存</el-button>
|
||||
<el-button @click="rebuildIndex">重建索引</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { adminGetSystemStatistics, adminTriggerAggregation } from '@/api/statistics'
|
||||
import { Bell, Cpu, Monitor, Setting, Timer } from '@element-plus/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'StatisticsSettings',
|
||||
components: {
|
||||
Setting,
|
||||
Timer,
|
||||
Bell,
|
||||
Cpu,
|
||||
Monitor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeSettingTab: 'general',
|
||||
generalSettings: {
|
||||
data_retention_days: 365,
|
||||
default_dashboard_id: '',
|
||||
enable_realtime_statistics: true,
|
||||
precision: 'hour'
|
||||
},
|
||||
aggregationSettings: {
|
||||
hourly_interval_minutes: 60,
|
||||
daily_aggregation_time: '02:00',
|
||||
enable_auto_aggregation: true,
|
||||
aggregation_threads: 4
|
||||
},
|
||||
alertSettings: {
|
||||
enable_anomaly_alerts: true,
|
||||
alert_threshold_percent: 10,
|
||||
alert_recipients_email: '',
|
||||
alert_frequency_limit: 15
|
||||
},
|
||||
performanceSettings: {
|
||||
cache_expiration_seconds: 3600,
|
||||
enable_data_compression: true,
|
||||
query_timeout_seconds: 30,
|
||||
batch_size: 1000
|
||||
},
|
||||
systemStatus: {
|
||||
healthy: true,
|
||||
total_records: 0,
|
||||
active_metrics: 0,
|
||||
dashboard_count: 0,
|
||||
last_aggregation_time: '',
|
||||
cache_hit_rate: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadSettings()
|
||||
this.refreshSystemStatus()
|
||||
},
|
||||
methods: {
|
||||
async loadSettings() {
|
||||
try {
|
||||
const response = await adminGetSystemStatistics({ period: 'overall' })
|
||||
if (response.success && response.data) {
|
||||
// 更新系统状态
|
||||
this.systemStatus = {
|
||||
...this.systemStatus,
|
||||
...response.data
|
||||
}
|
||||
}
|
||||
this.$message.success('统计设置加载完成')
|
||||
} catch (error) {
|
||||
console.error('加载统计设置失败:', error)
|
||||
this.$message.error('加载统计设置失败')
|
||||
}
|
||||
},
|
||||
|
||||
saveGeneralSettings() {
|
||||
this.$message.success('基础设置已保存')
|
||||
console.log('保存基础设置:', this.generalSettings)
|
||||
// TODO: 调用后端API保存设置
|
||||
},
|
||||
|
||||
saveAggregationSettings() {
|
||||
this.$message.success('聚合设置已保存')
|
||||
console.log('保存聚合设置:', this.aggregationSettings)
|
||||
// TODO: 调用后端API保存设置
|
||||
},
|
||||
|
||||
async triggerManualAggregation() {
|
||||
try {
|
||||
const response = await adminTriggerAggregation({ period: 'daily', force: true })
|
||||
if (response.success) {
|
||||
this.$message.success('手动触发聚合成功')
|
||||
} else {
|
||||
this.$message.error(response.message || '手动触发聚合失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('手动触发聚合失败:', error)
|
||||
this.$message.error('手动触发聚合失败')
|
||||
}
|
||||
},
|
||||
|
||||
saveAlertSettings() {
|
||||
this.$message.success('告警设置已保存')
|
||||
console.log('保存告警设置:', this.alertSettings)
|
||||
// TODO: 调用后端API保存设置
|
||||
},
|
||||
|
||||
savePerformanceSettings() {
|
||||
this.$message.success('性能设置已保存')
|
||||
console.log('保存性能设置:', this.performanceSettings)
|
||||
// TODO: 调用后端API保存设置
|
||||
},
|
||||
|
||||
async refreshSystemStatus() {
|
||||
try {
|
||||
const response = await adminGetSystemStatistics({ period: 'overall' })
|
||||
if (response.success && response.data) {
|
||||
this.systemStatus = {
|
||||
...this.systemStatus,
|
||||
...response.data
|
||||
}
|
||||
this.$message.success('系统状态已刷新')
|
||||
} else {
|
||||
this.$message.error(response.message || '获取系统状态失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统状态失败:', error)
|
||||
this.$message.error('获取系统状态失败')
|
||||
}
|
||||
},
|
||||
|
||||
clearCache() {
|
||||
this.$message.info('缓存清理功能开发中')
|
||||
// TODO: 实现缓存清理功能
|
||||
},
|
||||
|
||||
rebuildIndex() {
|
||||
this.$message.info('索引重建功能开发中')
|
||||
// TODO: 实现索引重建功能
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.statistics-settings {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab-pane-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
margin-left: 8px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-actions {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-actions .el-button {
|
||||
margin: 0 8px;
|
||||
}
|
||||
</style>
|
||||
1325
src/pages/admin/statistics/SystemStatisticsPage.vue
Normal file
1325
src/pages/admin/statistics/SystemStatisticsPage.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user