feat: 首页根据设计图修改
|
@ -16,6 +16,15 @@ export default [
|
|||
},
|
||||
component: () => import('views/configs/setting_dashboard/index.vue')
|
||||
},
|
||||
// 旧主页
|
||||
{
|
||||
path: '/Oldresource_dashboard',
|
||||
meta: {
|
||||
title: '资源概览',
|
||||
noTag: true
|
||||
},
|
||||
component: () => import('views/configs/setting_dashboard/indexOld.vue')
|
||||
},
|
||||
{
|
||||
name: 'ProfileMessage',
|
||||
path: '/message',
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
import { request } from '@cmp/cmp-element'
|
||||
import { wrapperParams } from 'utils'
|
||||
|
||||
// 指标列表
|
||||
export function getMetrics(params) {
|
||||
return request.get('/cms/v1/metrics', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
// 设置监控IP
|
||||
export function setIps(params) {
|
||||
return request.post(`/cms/v1/vms/${params.id}/ips`, wrapperParams(params))
|
||||
}
|
||||
export function geIps(id) {
|
||||
return request.get(`/cms/v1/vms/${id}/ips`)
|
||||
}
|
||||
// 分发策略
|
||||
// 列表
|
||||
export function getDistributions(params) {
|
||||
return request.get('/cms/v1/distributions', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 新增
|
||||
export function createDistri(params) {
|
||||
return request.post('/cms/v1/distributions', wrapperParams(params))
|
||||
}
|
||||
// 修改
|
||||
export function modifyDistri(params) {
|
||||
return request.put(`/cms/v1/distributions/${params.id}`, wrapperParams(params))
|
||||
}
|
||||
// 删除
|
||||
export function removeDistri(id) {
|
||||
return request.delete(`/cms/v1/distributions/${id}`)
|
||||
}
|
||||
// 详情
|
||||
export function getDistriDetail(id) {
|
||||
return request.get(`/cms/v1/distributions/${id}`)
|
||||
}
|
||||
// 告警模板
|
||||
const tempUrl = '/cms/v1/templates'
|
||||
// 列表
|
||||
export function getTempList(params) {
|
||||
return request.get(tempUrl, {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 新增
|
||||
export function createTemp(params) {
|
||||
return request.post(tempUrl, wrapperParams(params))
|
||||
}
|
||||
// 修改
|
||||
export function modifyTemp(params) {
|
||||
return request.put(`${tempUrl}/${params.id}`, wrapperParams(params))
|
||||
}
|
||||
// 删除
|
||||
export function removeTemp(id) {
|
||||
return request.delete(`${tempUrl}/${id}`)
|
||||
}
|
||||
// 批量删除
|
||||
export function batchRemoveTemp(params) {
|
||||
return request.delete(tempUrl, {
|
||||
data: params
|
||||
})
|
||||
}
|
||||
// 详情
|
||||
export function getTempDetail(id) {
|
||||
return request.get(`${tempUrl}/${id}`)
|
||||
}
|
||||
// 告警列表
|
||||
export function getAlarmList(params) {
|
||||
return request.get('/cms/v1/alarms', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
export function getAlarmDetail(id) {
|
||||
return request.get(`/cms/v1/alarms/${id}`)
|
||||
}
|
||||
// 告警确认
|
||||
export function alarmConfirm(params) {
|
||||
return request.patch('/cms/v1/alarms', {
|
||||
action: 'confirm',
|
||||
...wrapperParams(params)
|
||||
})
|
||||
}
|
||||
// 告警解决
|
||||
export function alarmSolve(params) {
|
||||
return request.patch('/cms/v1/alarms', {
|
||||
action: 'solve',
|
||||
...wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function getAlarmChart(params) {
|
||||
return request.get('/cms/v1/alarms/chart', {
|
||||
params
|
||||
})
|
||||
}
|
||||
// vcenter主机资源概览
|
||||
export function getVcHostOverview(id) {
|
||||
return request.get(`/cms/v1/hosts/${id}`, {
|
||||
params: wrapperParams({ type: 'VMWARE' })
|
||||
})
|
||||
}
|
||||
// vcenter云主机资源概览
|
||||
export function getVmOverview(id) {
|
||||
return request.get(`/cms/v1/vms/${id}`)
|
||||
}
|
||||
// 云主机资源概览仪表盘
|
||||
export function getHostDashboard(params) {
|
||||
return request.get('/cms/v1/prometheus', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
// 主机云主机图表
|
||||
export function getCharts(params) {
|
||||
return request.get('/cms/v1/charts', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
// openstack主机详情
|
||||
export function getOpenstackHost(id) {
|
||||
return request.get(`/cms/v1/hosts/${id}`, {
|
||||
params: wrapperParams({ type: 'OPENSTACK' })
|
||||
})
|
||||
}
|
||||
// 主机CPU
|
||||
export function getHostCpu(id) {
|
||||
return request.get(`/cms/v1/hosts/${id}/metrics`, {
|
||||
params: wrapperParams({ type: 'cpu' })
|
||||
})
|
||||
}
|
||||
export function getHostMem(id) {
|
||||
return request.get(`/cms/v1/hosts/${id}/metrics`, {
|
||||
params: wrapperParams({ type: 'mem' })
|
||||
})
|
||||
}
|
||||
export function getHostDisk(id) {
|
||||
return request.get(`/cms/v1/hosts/${id}/metrics`, {
|
||||
params: wrapperParams({ type: 'disk' })
|
||||
})
|
||||
}
|
||||
// 告警策略主机列表
|
||||
export function getPolicyHosts(params) {
|
||||
return request.get('/cms/v1/hosts', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 云主机列表
|
||||
export function getVms(params) {
|
||||
return request.get('/cms/v1/vms', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getDataStore(params) {
|
||||
return request.get('/cms/v1/datastores', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 开启监控
|
||||
export function openMonitor(params) {
|
||||
return request.patch('/cms/v1/vendors', {
|
||||
action: 'open',
|
||||
...wrapperParams(params)
|
||||
})
|
||||
}
|
||||
// 关闭监控
|
||||
export function closeMonitor(params) {
|
||||
return request.patch('/cms/v1/vendors', {
|
||||
action: 'close',
|
||||
...wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function getRatio(params) {
|
||||
return request.get('/cms/v1/vendors/ratio', {
|
||||
params
|
||||
})
|
||||
}
|
||||
export function ratioOk(params) {
|
||||
return request.post('/cms/v1/vendors/ratio', wrapperParams(params))
|
||||
}
|
||||
// hmc主机分区列表
|
||||
export function getServers(params) {
|
||||
return request.get('/cms/v1/hmc/servers', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
export function getPartitions(params) {
|
||||
return request.get('/cms/v1/hmc/partitions', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 运维认证
|
||||
export function getAuthentications(vendorId) {
|
||||
return request.get(`/cms/v1/vendors/${vendorId}/authentications`)
|
||||
}
|
||||
export function authenticationsOk(params) {
|
||||
return request.post(`/cms/v1/vendors/${params.vendorId}/authentications`, wrapperParams(params))
|
||||
}
|
||||
export function getFusionHost(params) {
|
||||
return request.get('/cms/v1/fusioncloud/hosts', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
export function getFusionHostDetail(id, type) {
|
||||
return request.get(`/cmp/plugins/${type}/v1/hosts/${id}`)
|
||||
}
|
||||
// 安装agent
|
||||
export function installTaskExporter(params) {
|
||||
return request.post('/cms/v1/agent', wrapperParams(params))
|
||||
}
|
||||
export function getOpenstackVm(type, id) {
|
||||
return request.get(`/cmp/plugins/${type}/v1/vms/${id}`)
|
||||
}
|
||||
|
||||
export function getSecurityGroup(type, params) {
|
||||
return request.get(`/cmp/plugins/${type}/v1/vms/${params.id}/sgroups`, {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
export function getUsage(params) {
|
||||
return request.get('/cms/v1/prometheus', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function getPoolDatas(params) {
|
||||
return request.get('/cms/v1/prometheus/filter', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
|
||||
export function getServices(params) {
|
||||
return request.get('/cms/v1/services', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 资源利用率TOP5
|
||||
export function getResTops(params) {
|
||||
return request.get('/cms/v1/tops', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
|
||||
// IPMI设置
|
||||
export function getHosts(id) {
|
||||
return request.get(`/cms/v1/hosts/ipmi/${id}`)
|
||||
}
|
||||
|
||||
export function patchHosts(url, params) {
|
||||
return request.patch(url, { ...wrapperParams(params) })
|
||||
}
|
||||
// 数据源配置
|
||||
export function configDataSource(vendorId, params) {
|
||||
return request.post(`/cms/v1/vendors/${vendorId}/monitor`, wrapperParams(params))
|
||||
}
|
||||
export function getfilters(params) {
|
||||
return request.get('/cms/v1/prometheus/filter', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
// 告警策略
|
||||
export function getRuleGroup(params) {
|
||||
return request.get('/cms/v1/rulegroups', {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function createRuleGroup(params) {
|
||||
return request.post('/cms/v1/rulegroups', wrapperParams(params))
|
||||
}
|
||||
export function modifyRuleGroup(params) {
|
||||
return request.put(`/cms/v1/rulegroups/${params.id}`, wrapperParams(params))
|
||||
}
|
||||
export function removeRuleGroup(id) {
|
||||
return request.delete(`/cms/v1/rulegroups/${id}`)
|
||||
}
|
||||
export function batchRemoveRuleGroup(params) {
|
||||
return request.delete('/cms/v1/rulegroups', {
|
||||
data: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function getRuleGroupDetail(id) {
|
||||
return request.get(`/cms/v1/rulegroups/${id}`)
|
||||
}
|
||||
export function ruleGroupEnable(params) {
|
||||
return request.patch('/cms/v1/rulegroups/enable', { ...wrapperParams(params) })
|
||||
}
|
||||
export function rulegroupsBinding(params) {
|
||||
return request.patch('/cms/v1/rulegroups/binding', { ...wrapperParams(params) })
|
||||
}
|
||||
export function rulegroupsUnBinding(params) {
|
||||
return request.delete('/cms/v1/rulegroups/binding', {
|
||||
data: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function getRuleGroupBind(id) {
|
||||
return request.get(`/cms/v1/rulegroups/${id}/resources`)
|
||||
}
|
||||
export function modifyAlarmStatus(params) {
|
||||
return request.post('/cms/v1/alarmstatus', wrapperParams(params))
|
||||
}
|
||||
export function deleteAlarmStatus(params) {
|
||||
return request.delete('/cms/v1/alarmstatus', {
|
||||
data: wrapperParams(params)
|
||||
})
|
||||
}
|
||||
export function getVolumeByType(type1, params, type, projectName) {
|
||||
return request.get(`/cmp/plugins/${type1}/v1/vendors/type/${type}/${projectName}/volumes`, {
|
||||
params
|
||||
})
|
||||
}
|
||||
// smart主机列表
|
||||
export function getSmartHosts(params) {
|
||||
return request.get('/cms/v1/smartx/hosts', {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
// 华为云
|
||||
export function getHuaweiResources(type, params) {
|
||||
return request.get(`/cms/v1/hcso/${type}`, {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
export function getStretch(vendorId) {
|
||||
return request.get(`/cms/v1/hcso/as/${vendorId}`)
|
||||
}
|
||||
|
||||
export function getMysqlRds(params) {
|
||||
return request.get('/cms/v1/apsarastack/rds/mysql', { params })
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { request } from '@cmp/cmp-element'
|
||||
|
||||
const newsUrl = '/scms/v1/memorabilia'
|
||||
|
||||
export function mockHttp(params) {
|
||||
return request.get(newsUrl, { params })
|
||||
}
|
|
@ -1,91 +1,45 @@
|
|||
<template>
|
||||
<el-container class="setting-wrapper" :class="{ full: !isSetting }">
|
||||
<el-header class="setting-header" v-if="isSetting">
|
||||
<span>自定义设置</span>
|
||||
<div>
|
||||
<cb-link @click="reset()">恢复默认设置</cb-link>
|
||||
<!-- <el-button>取消</el-button> -->
|
||||
<el-button type="primary" @click="savePanels" :loading="loading">保存</el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-container class="setting-wrapper">
|
||||
<el-container class="setting-container">
|
||||
<el-aside width="200px" class="setting-aside" v-if="isSetting">
|
||||
<div class="aside-tool">
|
||||
<span>隐藏已添加模块</span>
|
||||
<el-switch class="pull-right" v-model="hideSelectedModule"></el-switch>
|
||||
</div>
|
||||
<el-scrollbar class="pool-scroll">
|
||||
<draggable :list="poolList" v-bind="{ sort: false, group: { name: 'field', pull: 'clone', put: false } }" @end="drop">
|
||||
<div class="pool-item" :class="forbidPoolCodes.includes(item.code) && 'forbid'" v-for="item in poolList" :key="item.id" :index="item.id" v-show="!(hideSelectedModule && forbidPoolCodes.includes(item.code))">
|
||||
<span>{{ item.name }}</span>
|
||||
<i class="icon" :class="forbidPoolCodes.includes(item.code) ? 'el-icon-success' : 'el-icon-plus'"></i>
|
||||
</div>
|
||||
</draggable>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
<el-main class="setting-main" id="setting-container">
|
||||
<el-scrollbar style="height: 100%">
|
||||
<div v-if="!isSetting" class="top-header">
|
||||
<span>您好,{{ userData.name }} 欢迎您!</span>
|
||||
<el-button class="pull-right" type="text" @click="goPage('/config/dashboard-setting')">首页设置</el-button>
|
||||
</div>
|
||||
<grid-layout v-if="layoutData && layoutData.length" :layout="layoutData" :col-num="12" :row-height="16" :is-draggable="isSetting" :is-resizable="isSetting" :vertical-compact="true" :margin="[16, 16]" :use-css-transforms="true">
|
||||
<grid-item class="grid-item" v-for="(item, key) in layoutData" :key="item.i" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" @resize="resizeEvent" @resized="resizeEvent">
|
||||
<div class="view-card">
|
||||
<div class="card-title">
|
||||
<span>{{ item.config.title }}</span>
|
||||
<div class="card-operate" v-if="isSetting">
|
||||
<el-dropdown>
|
||||
<span class="operate-icon"><i class="el-icon-setting"></i></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="handleEdit(item)">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="handleDelete(key)">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" v-if="item.ready">
|
||||
<cg-loop-charts
|
||||
v-if="item.config.type == 'LoopCharts'"
|
||||
:setting="getChartConfig(item)"
|
||||
ref="chartRef"
|
||||
:id="item.i"
|
||||
:data="item.data"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:theme="item.config.title"
|
||||
:unit="item.config.unit"
|
||||
:type="['TodayAlarmCount'].includes(item.config.code) ? 'half' : ''"
|
||||
></cg-loop-charts>
|
||||
<cg-line-charts v-else-if="item.config.type == 'LineCharts'" ref="chartRef" :id="item.i" :data="item.data" width="100%" height="100%" :unit="item.config.unit" :setting="getChartConfig(item)"></cg-line-charts>
|
||||
<cg-bar-reverse-charts v-else-if="item.config.type == 'BarReverseCharts'" ref="chartRef" :id="item.i" :data="item.data" width="100%" height="100%" :unit="item.config.unit" :setting="topSetting"></cg-bar-reverse-charts>
|
||||
<component v-else :is="getComponent(item.config)" :item-data="item" :is-setting="isSetting"></component>
|
||||
</div>
|
||||
</div>
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
<el-dialog title="卡片配置" :close-on-click-modal="false" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form label-width="90px">
|
||||
<template v-for="item in configData">
|
||||
<el-form-item :label="item.name + ':'" v-if="item.type === 'TEXT'" :key="item.name">
|
||||
<el-input v-model="item.defaultValue"></el-input>
|
||||
</el-form-item>
|
||||
<select-vendor :itemData="item" :config-data="configData" v-else-if="item.type === 'SELECTVENDOR'" :key="item.name + 1"> </select-vendor>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="ghost" @click.native="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click.native="settingPanel">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<cb-empty class="empty" v-if="layoutData && layoutData.length === 0">
|
||||
<div class="text-center">
|
||||
<div class="m-b-xs">暂无数据</div>
|
||||
<div>请按照所纳管的平台进入<router-link class="detail-href" :to="{ name: 'SettingDashboard' }">管理中心→系统配置→首页配置</router-link>中配置相关数据</div>
|
||||
</div>
|
||||
</cb-empty>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<!-- 待办工单 -->
|
||||
<TaskList class="m-b-md" height="918px" />
|
||||
<!-- 平台容量统计 -->
|
||||
<PlatformCapacity class="m-b-md" height="242px" />
|
||||
<!-- 告警处理 -->
|
||||
<AlarmHandling class="m-b-md" height="412px" />
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<UserInfo class="m-b-md" />
|
||||
<!-- 数据展示 -->
|
||||
<StatisticsDisplay class="m-b-md" height="314px" />
|
||||
<!-- 工单统计 -->
|
||||
<OrderStatistics class="m-b-md" height="226px" />
|
||||
<!-- 公告 -->
|
||||
<NoticeList class="m-b-md" height="378px" />
|
||||
<!-- 单日告警统计 -->
|
||||
<DailyAlarmStatistics class="m-b-md" height="412px" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<!-- 应用 CPUTop5 -->
|
||||
<CPUTop5 class="m-b-md" height="320px" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- 虚拟机 CPUTop5 -->
|
||||
<VMCPUTop5 class="m-b-md" height="320px" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<!-- 告警列表 -->
|
||||
<WarningList />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
@ -93,350 +47,36 @@
|
|||
</template>
|
||||
<script>
|
||||
import { computed, nextTick, ref, unref, watch } from 'vue'
|
||||
import VueGridLayout from 'vue-grid-layout'
|
||||
import draggable from 'vuedraggable'
|
||||
import CommonOperation from './CommonOperation.vue'
|
||||
import AccessControl from './AccessControl.vue'
|
||||
import DataCenterOverview from './DataCenterOverview.vue'
|
||||
import CountCard from './CountCard.vue'
|
||||
import DataView from './DataView.vue'
|
||||
import ResUsed from './ResUsed.vue'
|
||||
import AlarmCount from './AlarmCount.vue'
|
||||
import TaskHistory from './TaskHistory.vue'
|
||||
import SelectVendor from './SelectVendor.vue'
|
||||
import { getPanel, getPool, savePanel, getConfig, resetPanel } from 'services/system/portal'
|
||||
import { request } from '@cmp/cmp-element'
|
||||
import { wrapperParams } from 'utils'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import { getCardData } from './utils'
|
||||
import { topSetting } from './data'
|
||||
import { useRouter, useRoute, useStore, useInstance } from '@cmp/cmp-core'
|
||||
const GridLayout = VueGridLayout.GridLayout
|
||||
const GridItem = VueGridLayout.GridItem
|
||||
const colorMap = ['#1890FF', '#F84540', '#18BE6A', '#696BD8', '#FE9900', '#01b3eb']
|
||||
const color = ['#E03B3B', '#F09C2B', '#049BD3', '#1E54DE']
|
||||
import UserInfo from './new_dashboard_component/UserInfo.vue'
|
||||
import TaskList from './new_dashboard_component/TaskList.vue'
|
||||
import StatisticsDisplay from './new_dashboard_component/StatisticsDisplay.vue'
|
||||
import OrderStatistics from './new_dashboard_component/OrderStatistics.vue'
|
||||
import PlatformCapacity from './new_dashboard_component/PlatformCapacity.vue'
|
||||
import NoticeList from './new_dashboard_component/NoticeList.vue'
|
||||
import AlarmHandling from './new_dashboard_component/AlarmHandling.vue'
|
||||
import DailyAlarmStatistics from './new_dashboard_component/DailyAlarmStatistics.vue'
|
||||
import CPUTop5 from './new_dashboard_component/CPUTop5.vue'
|
||||
import VMCPUTop5 from './new_dashboard_component/VMCPUTop5.vue'
|
||||
import WarningList from './new_dashboard_component/WarningList.vue'
|
||||
export default {
|
||||
components: {
|
||||
GridLayout,
|
||||
GridItem,
|
||||
draggable,
|
||||
CommonOperation,
|
||||
AccessControl,
|
||||
DataCenterOverview,
|
||||
CountCard,
|
||||
DataView,
|
||||
ResUsed,
|
||||
AlarmCount,
|
||||
TaskHistory,
|
||||
SelectVendor
|
||||
UserInfo,
|
||||
TaskList,
|
||||
StatisticsDisplay,
|
||||
OrderStatistics,
|
||||
PlatformCapacity,
|
||||
NoticeList,
|
||||
AlarmHandling,
|
||||
DailyAlarmStatistics,
|
||||
CPUTop5,
|
||||
VMCPUTop5,
|
||||
WarningList
|
||||
},
|
||||
setup(props, context) {
|
||||
const loading = ref(false)
|
||||
// 是否可配置
|
||||
const isSetting = ref(true)
|
||||
const route = useRoute()
|
||||
const ins = useInstance()
|
||||
function init() {
|
||||
getPanelList()
|
||||
if (ins.$route.path === '/resource_dashboard') {
|
||||
// 展示界面
|
||||
isSetting.value = false
|
||||
} else {
|
||||
getPoolList()
|
||||
isSetting.value = true
|
||||
}
|
||||
}
|
||||
init()
|
||||
watch(
|
||||
() => ins.$route.path,
|
||||
() => {
|
||||
init()
|
||||
}
|
||||
)
|
||||
// 卡片池处理
|
||||
const poolList = ref([])
|
||||
const hideSelectedModule = ref(false)
|
||||
const poolMap = computed(() => {
|
||||
const map = {}
|
||||
unref(poolList).forEach(item => {
|
||||
map[item.id] = item
|
||||
})
|
||||
return map
|
||||
})
|
||||
async function getPoolList() {
|
||||
const res = await getPool({ module: 'COS' })
|
||||
if (res.success) {
|
||||
poolList.value = res.data
|
||||
}
|
||||
}
|
||||
// 获取内容面板
|
||||
const layoutData = ref([])
|
||||
const forbidPoolCodes = computed(() => {
|
||||
return unref(layoutData)
|
||||
.map(item => item.config.code)
|
||||
.filter(item => {
|
||||
return !['PM', 'VM', 'DATAVIEW'].includes(item)
|
||||
})
|
||||
})
|
||||
// 获取组件数据
|
||||
function getData(url, params, item) {
|
||||
request
|
||||
.get(url, {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
item.data = data.data
|
||||
}
|
||||
})
|
||||
}
|
||||
function handleListData(item) {
|
||||
const config = (item.config = JSON.parse(item.config))
|
||||
const location = item.location.split(',')
|
||||
item.x = Number(location[0])
|
||||
item.y = Number(location[1])
|
||||
item.w = Number(location[2])
|
||||
item.h = Number(location[3])
|
||||
item.i = item.id.toString()
|
||||
item.data = {}
|
||||
item.ready = false
|
||||
setTimeout(() => {
|
||||
item.ready = true
|
||||
})
|
||||
getCardData(item)
|
||||
config.url && getData(config.url, config.params, item)
|
||||
return item
|
||||
}
|
||||
async function getPanelList() {
|
||||
loading.value = true
|
||||
const data = await getPanel({ module: 'COS' })
|
||||
loading.value = false
|
||||
if (data.success) {
|
||||
const result = []
|
||||
data.data.forEach(item => {
|
||||
result.push(handleListData(item))
|
||||
})
|
||||
layoutData.value = result
|
||||
}
|
||||
}
|
||||
// 新建panel
|
||||
function drop(e) {
|
||||
const id = e.item.getAttribute('index')
|
||||
const poolItem = poolMap.value[id]
|
||||
if (forbidPoolCodes.value.includes(poolItem.code)) return
|
||||
// 处理生成坐标
|
||||
const colWidth = document.getElementById('setting-container').offsetWidth / 12
|
||||
let x = Math.round((e.originalEvent.pageX - 300) / colWidth)
|
||||
let y = Math.round((e.originalEvent.pageY - 92) / 80)
|
||||
unref(layoutData).forEach(item => {
|
||||
const xMax = item.x + item.w
|
||||
const yMax = item.y + item.h
|
||||
if (x >= item.x && x <= xMax && y >= item.y && y <= yMax) {
|
||||
x = item.x
|
||||
y = item.y
|
||||
}
|
||||
})
|
||||
|
||||
handleCreate(x, y, poolItem)
|
||||
}
|
||||
async function handleCreate(x, y, data) {
|
||||
const { width, height, url, id, name, type, code, moreConfig } = data
|
||||
if (x + width > 12) x = 12 - width // 数据处理防止坐标溢出
|
||||
const obj = {
|
||||
x: x,
|
||||
y: y,
|
||||
w: width,
|
||||
h: height,
|
||||
ready: false,
|
||||
i: new Date().getTime().toString(),
|
||||
config: {
|
||||
url,
|
||||
id,
|
||||
title: name,
|
||||
type,
|
||||
moreConfig,
|
||||
code
|
||||
},
|
||||
data: {}
|
||||
}
|
||||
const params = { code: data.code }
|
||||
data.properties.forEach(item => {
|
||||
const { code, defaultValue } = item
|
||||
defaultValue && (obj.config[code] = defaultValue)
|
||||
if (item.paramsd) params[code] = defaultValue
|
||||
// if (item.code === 'color') {
|
||||
// const color = []
|
||||
// JSON.parse(item.config).forEach((cell) => {
|
||||
// color.push(cell.value)
|
||||
// })
|
||||
// obj.config.color = color
|
||||
// }
|
||||
})
|
||||
obj.config.params = params
|
||||
getCardData(obj)
|
||||
data.url && getData(data.url, params, obj)
|
||||
layoutData.value.unshift(obj)
|
||||
await nextTick()
|
||||
obj.ready = true
|
||||
}
|
||||
function getComponent(config) {
|
||||
const map = {
|
||||
// PM: 'ServerStatusOverview',
|
||||
// VM: 'ServerStatusOverview'
|
||||
}
|
||||
const component = map[config.code] || config.type
|
||||
const copComponentList = ['TaskCount', 'InspectCategoryCount', 'InspectHistory', 'Top5']
|
||||
if (!copComponentList.includes(component)) return component
|
||||
}
|
||||
// 保存配置
|
||||
async function savePanels() {
|
||||
const handleConfig = function () {
|
||||
const config = []
|
||||
layoutData.value.forEach(item => {
|
||||
const obj = {
|
||||
id: item.id,
|
||||
module: 'CMC',
|
||||
location: [item.x, item.y, item.w, item.h].join(','),
|
||||
config: item.config
|
||||
}
|
||||
config.push(obj)
|
||||
})
|
||||
return config
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await savePanel({
|
||||
module: 'COS',
|
||||
config: handleConfig()
|
||||
})
|
||||
if (res.success) {
|
||||
Message.success(res.message)
|
||||
goPage('/resource_dashboard')
|
||||
}
|
||||
} catch (error) {}
|
||||
loading.value = false
|
||||
}
|
||||
// 恢复默认设置
|
||||
async function reset() {
|
||||
MessageBox.confirm('您确定要恢复默认设置吗?', '提示', {
|
||||
confirmButtonClass: 'el-button--danger',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await resetPanel('COS')
|
||||
if (res.success) {
|
||||
Message.success(res.message)
|
||||
goPage('/resource_dashboard')
|
||||
}
|
||||
})
|
||||
.catch(e => {})
|
||||
}
|
||||
const store = useStore()
|
||||
const userData = computed(() => store.getters.userData || {})
|
||||
// 编辑删除卡片
|
||||
const currentPanel = ref({})
|
||||
const configData = ref({})
|
||||
function handleDelete(index) {
|
||||
layoutData.value.splice(index, 1)
|
||||
}
|
||||
const dialogVisible = ref(false)
|
||||
async function handleEdit(item) {
|
||||
currentPanel.value = item
|
||||
const config = item.config
|
||||
configData.value = [
|
||||
{
|
||||
code: 'title',
|
||||
defaultValue: config.title,
|
||||
name: '卡片标题',
|
||||
paramsd: false,
|
||||
type: 'TEXT'
|
||||
}
|
||||
]
|
||||
// 是否需要额外配置
|
||||
if (config.moreConfig) {
|
||||
const res = await getConfig({
|
||||
poolId: config.id
|
||||
})
|
||||
if (res.success) {
|
||||
configData.value = res.data
|
||||
}
|
||||
}
|
||||
unref(configData).forEach(item => {
|
||||
item.defaultValue = config[item.code]
|
||||
if (item.config) item.config = JSON.parse(item.config)
|
||||
if (item.code === 'color' && config.color) {
|
||||
item.config.forEach((cell, key) => {
|
||||
cell.value = config.color[key]
|
||||
})
|
||||
}
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 对组件进行设置
|
||||
function settingPanel() {
|
||||
const config = currentPanel.value.config
|
||||
const params = { code: config.code }
|
||||
unref(configData).forEach(item => {
|
||||
config[item.code] = item.defaultValue
|
||||
// 是否传参
|
||||
if (item.paramsd) params[item.code] = item.defaultValue
|
||||
if (item.code === 'color') {
|
||||
config.color = []
|
||||
item.config.forEach(cell => {
|
||||
config.color.push(cell.value)
|
||||
})
|
||||
}
|
||||
})
|
||||
dialogVisible.value = false
|
||||
config.params = params
|
||||
config.url && getData(config.url, params, currentPanel.value)
|
||||
}
|
||||
const chartRef = ref([])
|
||||
// 改变大小
|
||||
function resizeEvent(i) {
|
||||
const chartCell = unref(chartRef).find(item => item.id === i)
|
||||
if (chartCell) chartCell.chart.resize()
|
||||
}
|
||||
function goPage(path) {
|
||||
const router = useRouter()
|
||||
router.push(path)
|
||||
// router.push({
|
||||
// name: 'Redirect',
|
||||
// query: {
|
||||
// path
|
||||
// }
|
||||
// })
|
||||
}
|
||||
function getChartConfig(item) {
|
||||
const { chartConfig } = item.config
|
||||
return typeof chartConfig === 'string' ? JSON.parse(chartConfig) : chartConfig
|
||||
}
|
||||
return {
|
||||
topSetting,
|
||||
loading,
|
||||
userData,
|
||||
poolList,
|
||||
hideSelectedModule,
|
||||
layoutData,
|
||||
forbidPoolCodes,
|
||||
drop,
|
||||
getComponent,
|
||||
isSetting,
|
||||
savePanels,
|
||||
reset,
|
||||
getChartConfig,
|
||||
// 卡片操作
|
||||
dialogVisible,
|
||||
currentPanel,
|
||||
configData,
|
||||
handleDelete,
|
||||
handleEdit,
|
||||
settingPanel,
|
||||
chartRef,
|
||||
resizeEvent,
|
||||
color,
|
||||
goPage
|
||||
}
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -445,108 +85,77 @@ export default {
|
|||
height: calc(100vh - 50px);
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
&.full {
|
||||
margin: 0 -16px;
|
||||
}
|
||||
font-size: 14px !important;
|
||||
.setting-container {
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
.setting-header {
|
||||
background: #fff;
|
||||
height: 50px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e4e4e4;
|
||||
& > span {
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
::v-deep {
|
||||
.el-table--group,
|
||||
.el-table--border {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.setting-aside {
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
.aside-tool {
|
||||
margin-bottom: 18px;
|
||||
.el-table th.is-leaf,
|
||||
.el-table td {
|
||||
border: none;
|
||||
}
|
||||
.pool-scroll {
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
.pool-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
cursor: move;
|
||||
border: 1px solid #e6e6e6;
|
||||
margin-bottom: 10px;
|
||||
&.forbid {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
& > span {
|
||||
flex: 1;
|
||||
}
|
||||
.icon {
|
||||
color: #1e54de;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.view-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.card-title {
|
||||
font-weight: bold;
|
||||
color: #393b3e;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card-body {
|
||||
height: calc(100% - 40px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.card-operate {
|
||||
|
||||
.el-table::before,
|
||||
.el-table--group::after,
|
||||
.el-table--border::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -32.5px;
|
||||
top: -32.5px;
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
border-radius: 50%;
|
||||
background: rgba(30, 84, 222, 0.25);
|
||||
.operate-icon {
|
||||
color: #fff;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
.table-container .table-header {
|
||||
background: linear-gradient(180deg, #f0f3ff 0%, #fafbff 100%);
|
||||
color: #393b3e;
|
||||
}
|
||||
// 按钮
|
||||
.el-button--primary.is-plain {
|
||||
color: rgba(72, 144, 253, 1);
|
||||
background: rgba(72, 144, 253, 0.1);
|
||||
border: none;
|
||||
}
|
||||
.el-button--primary.is-plain:hover,
|
||||
.el-button--primary.is-plain:focus {
|
||||
background: rgba(72, 144, 253, 1);
|
||||
border-color: rgba(72, 144, 253, 1);
|
||||
color: #ffffff;
|
||||
}
|
||||
.el-button--text {
|
||||
color: #4890fd;
|
||||
}
|
||||
.el-button--text:hover,
|
||||
.el-button--text:focus {
|
||||
color: #4b76dd;
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// 表格
|
||||
.pagination-container {
|
||||
position: relative;
|
||||
.el-pagination__total {
|
||||
position: absolute;
|
||||
bottom: -24px;
|
||||
left: -18px;
|
||||
cursor: pointer;
|
||||
left: 0;
|
||||
}
|
||||
.el-pagination__jump {
|
||||
display: none;
|
||||
}
|
||||
.el-pagination__sizes {
|
||||
margin-right: 0;
|
||||
}
|
||||
.el-pagination.is-background .el-pager li:not(.disabled).active {
|
||||
background: rgba(0, 144, 255, 0.1);
|
||||
color: #1890ff;
|
||||
}
|
||||
.el-pagination.is-background .btn-prev,
|
||||
.el-pagination.is-background .btn-next,
|
||||
.el-pagination.is-background .el-pager li {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.grid-item {
|
||||
touch-action: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.setting-main {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
.top-header {
|
||||
padding: 17px 17px 0;
|
||||
}
|
||||
}
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,552 @@
|
|||
<template>
|
||||
<el-container class="setting-wrapper" :class="{ full: !isSetting }">
|
||||
<el-header class="setting-header" v-if="isSetting">
|
||||
<span>自定义设置</span>
|
||||
<div>
|
||||
<cb-link @click="reset()">恢复默认设置</cb-link>
|
||||
<!-- <el-button>取消</el-button> -->
|
||||
<el-button type="primary" @click="savePanels" :loading="loading">保存</el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-container class="setting-container">
|
||||
<el-aside width="200px" class="setting-aside" v-if="isSetting">
|
||||
<div class="aside-tool">
|
||||
<span>隐藏已添加模块</span>
|
||||
<el-switch class="pull-right" v-model="hideSelectedModule"></el-switch>
|
||||
</div>
|
||||
<el-scrollbar class="pool-scroll">
|
||||
<draggable :list="poolList" v-bind="{ sort: false, group: { name: 'field', pull: 'clone', put: false } }" @end="drop">
|
||||
<div class="pool-item" :class="forbidPoolCodes.includes(item.code) && 'forbid'" v-for="item in poolList" :key="item.id" :index="item.id" v-show="!(hideSelectedModule && forbidPoolCodes.includes(item.code))">
|
||||
<span>{{ item.name }}</span>
|
||||
<i class="icon" :class="forbidPoolCodes.includes(item.code) ? 'el-icon-success' : 'el-icon-plus'"></i>
|
||||
</div>
|
||||
</draggable>
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
<el-main class="setting-main" id="setting-container">
|
||||
<el-scrollbar style="height: 100%">
|
||||
<div v-if="!isSetting" class="top-header">
|
||||
<span>您好,{{ userData.name }} 欢迎您!</span>
|
||||
<el-button class="pull-right" type="text" @click="goPage('/config/dashboard-setting')">首页设置</el-button>
|
||||
</div>
|
||||
<grid-layout v-if="layoutData && layoutData.length" :layout="layoutData" :col-num="12" :row-height="16" :is-draggable="isSetting" :is-resizable="isSetting" :vertical-compact="true" :margin="[16, 16]" :use-css-transforms="true">
|
||||
<grid-item class="grid-item" v-for="(item, key) in layoutData" :key="item.i" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" @resize="resizeEvent" @resized="resizeEvent">
|
||||
<div class="view-card">
|
||||
<div class="card-title">
|
||||
<span>{{ item.config.title }}</span>
|
||||
<div class="card-operate" v-if="isSetting">
|
||||
<el-dropdown>
|
||||
<span class="operate-icon"><i class="el-icon-setting"></i></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="handleEdit(item)">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="handleDelete(key)">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" v-if="item.ready">
|
||||
<cg-loop-charts
|
||||
v-if="item.config.type == 'LoopCharts'"
|
||||
:setting="getChartConfig(item)"
|
||||
ref="chartRef"
|
||||
:id="item.i"
|
||||
:data="item.data"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:theme="item.config.title"
|
||||
:unit="item.config.unit"
|
||||
:type="['TodayAlarmCount'].includes(item.config.code) ? 'half' : ''"
|
||||
></cg-loop-charts>
|
||||
<cg-line-charts v-else-if="item.config.type == 'LineCharts'" ref="chartRef" :id="item.i" :data="item.data" width="100%" height="100%" :unit="item.config.unit" :setting="getChartConfig(item)"></cg-line-charts>
|
||||
<cg-bar-reverse-charts v-else-if="item.config.type == 'BarReverseCharts'" ref="chartRef" :id="item.i" :data="item.data" width="100%" height="100%" :unit="item.config.unit" :setting="topSetting"></cg-bar-reverse-charts>
|
||||
<component v-else :is="getComponent(item.config)" :item-data="item" :is-setting="isSetting"></component>
|
||||
</div>
|
||||
</div>
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
<el-dialog title="卡片配置" :close-on-click-modal="false" :visible.sync="dialogVisible" width="800px">
|
||||
<el-form label-width="90px">
|
||||
<template v-for="item in configData">
|
||||
<el-form-item :label="item.name + ':'" v-if="item.type === 'TEXT'" :key="item.name">
|
||||
<el-input v-model="item.defaultValue"></el-input>
|
||||
</el-form-item>
|
||||
<select-vendor :itemData="item" :config-data="configData" v-else-if="item.type === 'SELECTVENDOR'" :key="item.name + 1"> </select-vendor>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="ghost" @click.native="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click.native="settingPanel">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<cb-empty class="empty" v-if="layoutData && layoutData.length === 0">
|
||||
<div class="text-center">
|
||||
<div class="m-b-xs">暂无数据</div>
|
||||
<div>请按照所纳管的平台进入<router-link class="detail-href" :to="{ name: 'SettingDashboard' }">管理中心→系统配置→首页配置</router-link>中配置相关数据</div>
|
||||
</div>
|
||||
</cb-empty>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
<script>
|
||||
import { computed, nextTick, ref, unref, watch } from 'vue'
|
||||
import VueGridLayout from 'vue-grid-layout'
|
||||
import draggable from 'vuedraggable'
|
||||
import CommonOperation from './CommonOperation.vue'
|
||||
import AccessControl from './AccessControl.vue'
|
||||
import DataCenterOverview from './DataCenterOverview.vue'
|
||||
import CountCard from './CountCard.vue'
|
||||
import DataView from './DataView.vue'
|
||||
import ResUsed from './ResUsed.vue'
|
||||
import AlarmCount from './AlarmCount.vue'
|
||||
import TaskHistory from './TaskHistory.vue'
|
||||
import SelectVendor from './SelectVendor.vue'
|
||||
import { getPanel, getPool, savePanel, getConfig, resetPanel } from 'services/system/portal'
|
||||
import { request } from '@cmp/cmp-element'
|
||||
import { wrapperParams } from 'utils'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import { getCardData } from './utils'
|
||||
import { topSetting } from './data'
|
||||
import { useRouter, useRoute, useStore, useInstance } from '@cmp/cmp-core'
|
||||
const GridLayout = VueGridLayout.GridLayout
|
||||
const GridItem = VueGridLayout.GridItem
|
||||
const colorMap = ['#1890FF', '#F84540', '#18BE6A', '#696BD8', '#FE9900', '#01b3eb']
|
||||
const color = ['#E03B3B', '#F09C2B', '#049BD3', '#1E54DE']
|
||||
export default {
|
||||
components: {
|
||||
GridLayout,
|
||||
GridItem,
|
||||
draggable,
|
||||
CommonOperation,
|
||||
AccessControl,
|
||||
DataCenterOverview,
|
||||
CountCard,
|
||||
DataView,
|
||||
ResUsed,
|
||||
AlarmCount,
|
||||
TaskHistory,
|
||||
SelectVendor
|
||||
},
|
||||
setup(props, context) {
|
||||
const loading = ref(false)
|
||||
// 是否可配置
|
||||
const isSetting = ref(true)
|
||||
const route = useRoute()
|
||||
const ins = useInstance()
|
||||
function init() {
|
||||
getPanelList()
|
||||
if (ins.$route.path === '/resource_dashboard') {
|
||||
// 展示界面
|
||||
isSetting.value = false
|
||||
} else {
|
||||
getPoolList()
|
||||
isSetting.value = true
|
||||
}
|
||||
}
|
||||
init()
|
||||
watch(
|
||||
() => ins.$route.path,
|
||||
() => {
|
||||
init()
|
||||
}
|
||||
)
|
||||
// 卡片池处理
|
||||
const poolList = ref([])
|
||||
const hideSelectedModule = ref(false)
|
||||
const poolMap = computed(() => {
|
||||
const map = {}
|
||||
unref(poolList).forEach(item => {
|
||||
map[item.id] = item
|
||||
})
|
||||
return map
|
||||
})
|
||||
async function getPoolList() {
|
||||
const res = await getPool({ module: 'COS' })
|
||||
if (res.success) {
|
||||
poolList.value = res.data
|
||||
}
|
||||
}
|
||||
// 获取内容面板
|
||||
const layoutData = ref([])
|
||||
const forbidPoolCodes = computed(() => {
|
||||
return unref(layoutData)
|
||||
.map(item => item.config.code)
|
||||
.filter(item => {
|
||||
return !['PM', 'VM', 'DATAVIEW'].includes(item)
|
||||
})
|
||||
})
|
||||
// 获取组件数据
|
||||
function getData(url, params, item) {
|
||||
request
|
||||
.get(url, {
|
||||
params: wrapperParams(params)
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
item.data = data.data
|
||||
}
|
||||
})
|
||||
}
|
||||
function handleListData(item) {
|
||||
const config = (item.config = JSON.parse(item.config))
|
||||
const location = item.location.split(',')
|
||||
item.x = Number(location[0])
|
||||
item.y = Number(location[1])
|
||||
item.w = Number(location[2])
|
||||
item.h = Number(location[3])
|
||||
item.i = item.id.toString()
|
||||
item.data = {}
|
||||
item.ready = false
|
||||
setTimeout(() => {
|
||||
item.ready = true
|
||||
})
|
||||
getCardData(item)
|
||||
config.url && getData(config.url, config.params, item)
|
||||
return item
|
||||
}
|
||||
async function getPanelList() {
|
||||
loading.value = true
|
||||
const data = await getPanel({ module: 'COS' })
|
||||
loading.value = false
|
||||
if (data.success) {
|
||||
const result = []
|
||||
data.data.forEach(item => {
|
||||
result.push(handleListData(item))
|
||||
})
|
||||
layoutData.value = result
|
||||
}
|
||||
}
|
||||
// 新建panel
|
||||
function drop(e) {
|
||||
const id = e.item.getAttribute('index')
|
||||
const poolItem = poolMap.value[id]
|
||||
if (forbidPoolCodes.value.includes(poolItem.code)) return
|
||||
// 处理生成坐标
|
||||
const colWidth = document.getElementById('setting-container').offsetWidth / 12
|
||||
let x = Math.round((e.originalEvent.pageX - 300) / colWidth)
|
||||
let y = Math.round((e.originalEvent.pageY - 92) / 80)
|
||||
unref(layoutData).forEach(item => {
|
||||
const xMax = item.x + item.w
|
||||
const yMax = item.y + item.h
|
||||
if (x >= item.x && x <= xMax && y >= item.y && y <= yMax) {
|
||||
x = item.x
|
||||
y = item.y
|
||||
}
|
||||
})
|
||||
|
||||
handleCreate(x, y, poolItem)
|
||||
}
|
||||
async function handleCreate(x, y, data) {
|
||||
const { width, height, url, id, name, type, code, moreConfig } = data
|
||||
if (x + width > 12) x = 12 - width // 数据处理防止坐标溢出
|
||||
const obj = {
|
||||
x: x,
|
||||
y: y,
|
||||
w: width,
|
||||
h: height,
|
||||
ready: false,
|
||||
i: new Date().getTime().toString(),
|
||||
config: {
|
||||
url,
|
||||
id,
|
||||
title: name,
|
||||
type,
|
||||
moreConfig,
|
||||
code
|
||||
},
|
||||
data: {}
|
||||
}
|
||||
const params = { code: data.code }
|
||||
data.properties.forEach(item => {
|
||||
const { code, defaultValue } = item
|
||||
defaultValue && (obj.config[code] = defaultValue)
|
||||
if (item.paramsd) params[code] = defaultValue
|
||||
// if (item.code === 'color') {
|
||||
// const color = []
|
||||
// JSON.parse(item.config).forEach((cell) => {
|
||||
// color.push(cell.value)
|
||||
// })
|
||||
// obj.config.color = color
|
||||
// }
|
||||
})
|
||||
obj.config.params = params
|
||||
getCardData(obj)
|
||||
data.url && getData(data.url, params, obj)
|
||||
layoutData.value.unshift(obj)
|
||||
await nextTick()
|
||||
obj.ready = true
|
||||
}
|
||||
function getComponent(config) {
|
||||
const map = {
|
||||
// PM: 'ServerStatusOverview',
|
||||
// VM: 'ServerStatusOverview'
|
||||
}
|
||||
const component = map[config.code] || config.type
|
||||
const copComponentList = ['TaskCount', 'InspectCategoryCount', 'InspectHistory', 'Top5']
|
||||
if (!copComponentList.includes(component)) return component
|
||||
}
|
||||
// 保存配置
|
||||
async function savePanels() {
|
||||
const handleConfig = function () {
|
||||
const config = []
|
||||
layoutData.value.forEach(item => {
|
||||
const obj = {
|
||||
id: item.id,
|
||||
module: 'CMC',
|
||||
location: [item.x, item.y, item.w, item.h].join(','),
|
||||
config: item.config
|
||||
}
|
||||
config.push(obj)
|
||||
})
|
||||
return config
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await savePanel({
|
||||
module: 'COS',
|
||||
config: handleConfig()
|
||||
})
|
||||
if (res.success) {
|
||||
Message.success(res.message)
|
||||
goPage('/resource_dashboard')
|
||||
}
|
||||
} catch (error) {}
|
||||
loading.value = false
|
||||
}
|
||||
// 恢复默认设置
|
||||
async function reset() {
|
||||
MessageBox.confirm('您确定要恢复默认设置吗?', '提示', {
|
||||
confirmButtonClass: 'el-button--danger',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await resetPanel('COS')
|
||||
if (res.success) {
|
||||
Message.success(res.message)
|
||||
goPage('/resource_dashboard')
|
||||
}
|
||||
})
|
||||
.catch(e => {})
|
||||
}
|
||||
const store = useStore()
|
||||
const userData = computed(() => store.getters.userData || {})
|
||||
// 编辑删除卡片
|
||||
const currentPanel = ref({})
|
||||
const configData = ref({})
|
||||
function handleDelete(index) {
|
||||
layoutData.value.splice(index, 1)
|
||||
}
|
||||
const dialogVisible = ref(false)
|
||||
async function handleEdit(item) {
|
||||
currentPanel.value = item
|
||||
const config = item.config
|
||||
configData.value = [
|
||||
{
|
||||
code: 'title',
|
||||
defaultValue: config.title,
|
||||
name: '卡片标题',
|
||||
paramsd: false,
|
||||
type: 'TEXT'
|
||||
}
|
||||
]
|
||||
// 是否需要额外配置
|
||||
if (config.moreConfig) {
|
||||
const res = await getConfig({
|
||||
poolId: config.id
|
||||
})
|
||||
if (res.success) {
|
||||
configData.value = res.data
|
||||
}
|
||||
}
|
||||
unref(configData).forEach(item => {
|
||||
item.defaultValue = config[item.code]
|
||||
if (item.config) item.config = JSON.parse(item.config)
|
||||
if (item.code === 'color' && config.color) {
|
||||
item.config.forEach((cell, key) => {
|
||||
cell.value = config.color[key]
|
||||
})
|
||||
}
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
// 对组件进行设置
|
||||
function settingPanel() {
|
||||
const config = currentPanel.value.config
|
||||
const params = { code: config.code }
|
||||
unref(configData).forEach(item => {
|
||||
config[item.code] = item.defaultValue
|
||||
// 是否传参
|
||||
if (item.paramsd) params[item.code] = item.defaultValue
|
||||
if (item.code === 'color') {
|
||||
config.color = []
|
||||
item.config.forEach(cell => {
|
||||
config.color.push(cell.value)
|
||||
})
|
||||
}
|
||||
})
|
||||
dialogVisible.value = false
|
||||
config.params = params
|
||||
config.url && getData(config.url, params, currentPanel.value)
|
||||
}
|
||||
const chartRef = ref([])
|
||||
// 改变大小
|
||||
function resizeEvent(i) {
|
||||
const chartCell = unref(chartRef).find(item => item.id === i)
|
||||
if (chartCell) chartCell.chart.resize()
|
||||
}
|
||||
function goPage(path) {
|
||||
const router = useRouter()
|
||||
router.push(path)
|
||||
// router.push({
|
||||
// name: 'Redirect',
|
||||
// query: {
|
||||
// path
|
||||
// }
|
||||
// })
|
||||
}
|
||||
function getChartConfig(item) {
|
||||
const { chartConfig } = item.config
|
||||
return typeof chartConfig === 'string' ? JSON.parse(chartConfig) : chartConfig
|
||||
}
|
||||
return {
|
||||
topSetting,
|
||||
loading,
|
||||
userData,
|
||||
poolList,
|
||||
hideSelectedModule,
|
||||
layoutData,
|
||||
forbidPoolCodes,
|
||||
drop,
|
||||
getComponent,
|
||||
isSetting,
|
||||
savePanels,
|
||||
reset,
|
||||
getChartConfig,
|
||||
// 卡片操作
|
||||
dialogVisible,
|
||||
currentPanel,
|
||||
configData,
|
||||
handleDelete,
|
||||
handleEdit,
|
||||
settingPanel,
|
||||
chartRef,
|
||||
resizeEvent,
|
||||
color,
|
||||
goPage
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.setting-wrapper {
|
||||
height: calc(100vh - 50px);
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
&.full {
|
||||
margin: 0 -16px;
|
||||
}
|
||||
.setting-container {
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
.setting-header {
|
||||
background: #fff;
|
||||
height: 50px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e4e4e4;
|
||||
& > span {
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.setting-aside {
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
.aside-tool {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.pool-scroll {
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
.pool-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
cursor: move;
|
||||
border: 1px solid #e6e6e6;
|
||||
margin-bottom: 10px;
|
||||
&.forbid {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
& > span {
|
||||
flex: 1;
|
||||
}
|
||||
.icon {
|
||||
color: #1e54de;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.view-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.card-title {
|
||||
font-weight: bold;
|
||||
color: #393b3e;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card-body {
|
||||
height: calc(100% - 40px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.card-operate {
|
||||
position: absolute;
|
||||
right: -32.5px;
|
||||
top: -32.5px;
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
border-radius: 50%;
|
||||
background: rgba(30, 84, 222, 0.25);
|
||||
.operate-icon {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
bottom: -24px;
|
||||
left: -18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.grid-item {
|
||||
touch-action: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.setting-main {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
.top-header {
|
||||
padding: 17px 17px 0;
|
||||
}
|
||||
}
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<ItemCard title="告警处理" v-bind="$attrs">
|
||||
<el-col :span="8">
|
||||
<div class="cell" v-for="(item, index) in countData" :key="item.name">
|
||||
<div class="mark" :style="{ background: colorMap[index] }"></div>
|
||||
<span class="title">{{ item.name }}</span>
|
||||
<span class="value">{{ item.value }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="16" class="full-height">
|
||||
<span class="chart-title">近七日告警趋势统计</span>
|
||||
<div style="height: calc(100% - 50px)">
|
||||
<LineCharts v-if="lineData" :data="lineData" width="100%" height="100%" :setting="chartSetting"></LineCharts>
|
||||
</div>
|
||||
</el-col>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import LineCharts from './echarts/LineCharts.vue'
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import { getAlarmChart } from 'services/monitor/index'
|
||||
import * as echarts from 'echarts/core'
|
||||
export const colorMap = ['rgba(255, 0, 0, 1)', 'rgba(245, 167, 45, 1)', 'rgba(18, 185, 242, 1)', 'rgba(24, 144, 255, 1)']
|
||||
const chartSetting = {
|
||||
color: colorMap,
|
||||
legend: {
|
||||
right: 0,
|
||||
textStyle: {
|
||||
color: 'rgba(140, 140, 140, 1)'
|
||||
}
|
||||
}
|
||||
}
|
||||
export default {
|
||||
components: { ItemCard, LineCharts },
|
||||
setup(props, context) {
|
||||
const countData = ref([
|
||||
{
|
||||
name: '紧急告警',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: '重要告警',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: '次要告警',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: '提示告警',
|
||||
value: 0
|
||||
}
|
||||
])
|
||||
;(async function () {
|
||||
const res = await getAlarmChart({ action: 'pieChart' })
|
||||
countData.value = res.data
|
||||
})()
|
||||
const lineData = ref({
|
||||
keys: [],
|
||||
values: []
|
||||
})
|
||||
;(async function () {
|
||||
const end = new Date().setHours(0, 0, 0, 0) / 1000
|
||||
// 一天是86400秒
|
||||
const res = await getAlarmChart({ action: 'barChart', start: end - 86400 * 7, end })
|
||||
res.data.values = res.data.values.map((item, index) => {
|
||||
item.series = {
|
||||
areaStyle: {
|
||||
opacity: 0.1,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: colorMap[index]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(255,255,255, 0)'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
lineData.value = res.data
|
||||
})()
|
||||
return {
|
||||
colorMap,
|
||||
chartSetting,
|
||||
countData,
|
||||
lineData
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cell {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
margin-left: 8px;
|
||||
height: 56px;
|
||||
background: linear-gradient(180deg, rgba(240, 243, 255, 0.5) 0%, rgba(250, 251, 255, 0.5) 100%);
|
||||
border-radius: 4px;
|
||||
.title {
|
||||
margin-left: 23px;
|
||||
}
|
||||
.value {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.mark {
|
||||
margin-left: 10px;
|
||||
width: 4px;
|
||||
height: 36px;
|
||||
background: #ff0000;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.chart-title {
|
||||
margin-left: 15px;
|
||||
font-weight: 600;
|
||||
color: #092943;
|
||||
}
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<ItemCard title="应用CPU TOP5" v-bind="$attrs">
|
||||
<BarReverseCharts width="100%" height="100%" :data="barData" unit="%" v-if="barData" :setting="chartSetting"></BarReverseCharts>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import BarReverseCharts from './echarts/BarReverseCharts.vue'
|
||||
import { getResTops } from 'services/monitor/index'
|
||||
const chartSetting = {
|
||||
barColor: ['rgba(255, 204, 77, 1)', 'rgba(59, 228, 218, 1)', 'rgba(93, 148, 255, 1)', 'rgba(93, 148, 255, 1)', 'rgba(93, 148, 255, 1)']
|
||||
}
|
||||
export default {
|
||||
components: { ItemCard, BarReverseCharts },
|
||||
data() {
|
||||
return {
|
||||
barData: null,
|
||||
chartSetting
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
getResTops({
|
||||
vendorId: 1,
|
||||
type: 'vmMem',
|
||||
limit: 5
|
||||
}).then(data => {
|
||||
this.barData = data.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<ItemCard title="单日告警统计" v-bind="$attrs">
|
||||
<LoopCharts class="full-height" id="alarm1" v-if="todayAlarmData" :data="todayAlarmData" width="100%" type="half" :setting="chartSetting"></LoopCharts>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import { getAlarmChart } from 'services/monitor/index.js'
|
||||
import LoopCharts from './echarts/LoopCharts.vue'
|
||||
import { colorMap } from './AlarmHandling.vue'
|
||||
const chartSetting = {
|
||||
color: colorMap
|
||||
}
|
||||
export default {
|
||||
components: { ItemCard, LoopCharts },
|
||||
data() {
|
||||
return {
|
||||
todayAlarmData: null,
|
||||
chartSetting
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this, this.getTodayAlarm()
|
||||
},
|
||||
methods: {
|
||||
getTodayAlarm() {
|
||||
getAlarmChart({
|
||||
action: 'pieChart',
|
||||
time: 'TODAY'
|
||||
}).then(data => {
|
||||
if (data.success) {
|
||||
this.todayAlarmData = data.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<div class="view-card" :style="{ height }">
|
||||
<div class="card-title">
|
||||
<span>{{ title }}</span>
|
||||
<div class="card-operate">
|
||||
<slot name="operate"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.view-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.card-title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
color: #393b3e;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.card-body {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
.card-operate {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<ItemCard title="公告列表" v-bind="$attrs">
|
||||
<cb-table
|
||||
:data="tableList"
|
||||
:params="params"
|
||||
:get-list="getItemList"
|
||||
:total="total"
|
||||
:otherProps="{
|
||||
border: false
|
||||
}"
|
||||
>
|
||||
<el-table-column show-overflow-tooltip label="标题" prop="title">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="flex">
|
||||
<img :src="imgMap[row.type]" alt="" />
|
||||
<span>{{ row.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="内容" prop="content"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="时间" prop="date"></el-table-column>
|
||||
<span slot="pagination"></span>
|
||||
</cb-table>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import { mockHttp } from 'services/system/dashboard'
|
||||
import 红 from './images/通知-红.png'
|
||||
import 黄 from './images/通知-黄.png'
|
||||
import 蓝 from './images/通知-蓝.png'
|
||||
|
||||
export default {
|
||||
components: { ItemCard },
|
||||
data() {
|
||||
return {
|
||||
imgMap: {
|
||||
红,
|
||||
黄,
|
||||
蓝
|
||||
},
|
||||
tableList: [
|
||||
{
|
||||
title: '测试标题',
|
||||
date: '2020-01-01',
|
||||
content: '测试内容',
|
||||
type: '红'
|
||||
}
|
||||
],
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getItemList() {
|
||||
mockHttp(this.params).then(data => {
|
||||
if (data.success) {
|
||||
this.tableList = data.data.rows
|
||||
this.total = data.data.total
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<ItemCard title="工单统计" v-bind="$attrs">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8" v-for="item in numList" :key="item.label">
|
||||
<div class="num-item" :style="{ backgroundColor: item.background, backgroundImage: `url(${item.bgImg})` }">
|
||||
<div class="value">{{ item.value }}</div>
|
||||
<div class="title">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import 待办工单 from './images/icon-待办工单.png'
|
||||
import 已办工单 from './images/icon-已办工单.png'
|
||||
import 总数 from './images/icon-总数.png'
|
||||
|
||||
export default {
|
||||
components: { ItemCard },
|
||||
data() {
|
||||
return {
|
||||
numList: [
|
||||
{ label: '待办工单', value: 36, bgImg: 待办工单, background: 'rgba(252, 182, 98, 0.1)' },
|
||||
{ label: '已办工单', value: 20, bgImg: 已办工单, background: 'rgba(70, 216, 171, 0.1)' },
|
||||
{ label: '总数', value: 20, bgImg: 总数, background: 'rgba(93, 148, 255, 0.1)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep {
|
||||
.num-item {
|
||||
color: #fff;
|
||||
height: 110px;
|
||||
padding: 30px 0 0 25px;
|
||||
border-radius: 5px;
|
||||
background-position: right bottom;
|
||||
background-repeat: no-repeat;
|
||||
.value {
|
||||
color: #000;
|
||||
font-size: 34px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.title {
|
||||
color: #8c8da3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<ItemCard title="平台容量统计" v-bind="$attrs">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6" v-for="item in numList" :key="item.label">
|
||||
<div class="num-item" :style="{ backgroundColor: item.background, backgroundImage: `url(${item.bgImg})` }">
|
||||
<div class="title">{{ item.label }}</div>
|
||||
<div class="value">{{ item.value }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import 服务器 from './images/服务器(台).png'
|
||||
import 虚拟机 from './images/虚拟机(台).png'
|
||||
import 网络设备 from './images/网络设备(台).png'
|
||||
import 安全设备 from './images/安全设备(台).png'
|
||||
|
||||
export default {
|
||||
components: { ItemCard },
|
||||
data() {
|
||||
return {
|
||||
numList: [
|
||||
{ label: '服务器(台)', value: 36, bgImg: 服务器, background: 'rgba(231, 242, 252, 1)' },
|
||||
{ label: '虚拟机(台)', value: 20, bgImg: 虚拟机, background: 'rgba(250, 243, 233, 1)' },
|
||||
{ label: '网络设备(台)', value: 20, bgImg: 网络设备, background: 'rgba(227, 245, 252, 1)' },
|
||||
{ label: '安全设备(台)', value: 20, bgImg: 安全设备, background: 'rgba(235, 246, 239, 1)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep {
|
||||
.num-item {
|
||||
color: #fff;
|
||||
height: 110px;
|
||||
padding: 30px 0 0;
|
||||
padding-left: 40%;
|
||||
border-radius: 5px;
|
||||
background-position: 10% 40%;
|
||||
background-size: 56px;
|
||||
background-repeat: no-repeat;
|
||||
.value {
|
||||
color: #000;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.title {
|
||||
color: #8c8da3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<ItemCard title="数据展示" v-bind="$attrs">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" v-for="item in numList" :key="item.label">
|
||||
<div class="num-item" :style="{ backgroundImage: `url(${item.bgImg})` }">
|
||||
<div class="value">{{ item.value }}</div>
|
||||
<div>{{ item.label }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" type="flex" justify="space-around">
|
||||
<el-col :span="7" v-for="item in statistics" :key="item.label">
|
||||
<div class="statistic-item">
|
||||
<div class="value">{{ item.value }}</div>
|
||||
<div class="title">{{ item.label }}</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import 当前用户授权应用数 from './images/icon-当前用户授权应用数.png'
|
||||
import 数据权限申请数量 from './images/icon-数据权限申请数量.png'
|
||||
|
||||
export default {
|
||||
components: { ItemCard },
|
||||
data() {
|
||||
return {
|
||||
numList: [
|
||||
{ label: '当前用户授权应用数', value: 36, bgImg: 当前用户授权应用数 },
|
||||
{ label: '数据权限申请数量', value: 20, bgImg: 数据权限申请数量 }
|
||||
],
|
||||
statistics: [
|
||||
{ label: '数据调度任务量', value: 36 },
|
||||
{ label: '运维任务申请表', value: 17 },
|
||||
{ label: '日志审计任务量', value: 20 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep {
|
||||
.num-item {
|
||||
color: #fff;
|
||||
height: 95px;
|
||||
padding: 30px 0 0 25px;
|
||||
background: #5d94ff;
|
||||
border-radius: 5px;
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
.value {
|
||||
font-size: 34px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.statistic-item {
|
||||
margin-top: 25px;
|
||||
.value {
|
||||
font-size: 30px;
|
||||
color: #002c46;
|
||||
font-weight: 600;
|
||||
}
|
||||
.title {
|
||||
color: #8c8da3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<ItemCard title="待办工单" v-bind="$attrs">
|
||||
<el-button slot="operate" type="primary" plain>查看更多</el-button>
|
||||
<cb-table :data="tableList" :params="params" :get-list="getItemList" :total="total">
|
||||
<el-table-column type="index" width="50" label="序号"> </el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="标题" prop="name">
|
||||
<template slot-scope="scope">
|
||||
<span class="name">{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="状态" prop="status"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="发起人" prop="progress"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="时间" prop="time"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="操作" width="60px">
|
||||
<template slot-scope="scope">
|
||||
<cb-link @click="handleOperation(scope.row)">审批</cb-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</cb-table>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import { mockHttp } from 'services/system/dashboard'
|
||||
|
||||
export default {
|
||||
components: { ItemCard },
|
||||
data() {
|
||||
return {
|
||||
tableList: [
|
||||
{
|
||||
name: '测试标题',
|
||||
status: '待处理',
|
||||
progress: '张三',
|
||||
time: '2020-01-01'
|
||||
}
|
||||
],
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getItemList() {
|
||||
mockHttp(this.params).then(data => {
|
||||
if (data.success) {
|
||||
this.tableList = data.data.rows
|
||||
this.total = data.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
handleOperation(row) {
|
||||
console.log('操作任务', row)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.name {
|
||||
font-weight: 600;
|
||||
color: #344767;
|
||||
}
|
||||
v-deep {
|
||||
.el-table--small {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<div class="user-info">
|
||||
<div class="user-info__avatar">
|
||||
<img :src="userData.portrait" alt="用户头像" />
|
||||
</div>
|
||||
<div class="user-info__details">
|
||||
<div class="user-info__greeting">{{ greetingMessage }}</div>
|
||||
<div>
|
||||
<div class="user-info__role" v-for="role in userRole" :key="role">{{ role }}</div>
|
||||
</div>
|
||||
<div class="user-info__login-time">登录时间:{{ loginTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useRouter, useRoute, useStore, useInstance } from '@cmp/cmp-core'
|
||||
import { computed, nextTick, ref, unref, watch } from 'vue'
|
||||
export default {
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const userData = computed(() => store.getters.userData || {})
|
||||
const greetingMessage = computed(() => (userData.value.name ? userData.value.name + ', 您好' : '--'))
|
||||
const userRole = computed(() => userData.value.roleNames || [])
|
||||
const loginTime = computed(() => userData.value.lastLoginDate)
|
||||
return {
|
||||
userData,
|
||||
greetingMessage,
|
||||
userRole,
|
||||
loginTime
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-info {
|
||||
background-image: url('./images/个人信息bg.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 40px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
height: 204px;
|
||||
}
|
||||
|
||||
.user-info__avatar {
|
||||
margin-right: 30px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.user-info__avatar img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.user-info__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-info__greeting {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.user-info__role {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
margin-bottom: 8px;
|
||||
line-height: 25px;
|
||||
margin-right: 5px;
|
||||
padding: 0 10px;
|
||||
height: 25px;
|
||||
background: #5d94ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-info__login-time {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<ItemCard title="虚拟机CPU利用率 TOP5" v-bind="$attrs">
|
||||
<BarReverseCharts width="100%" height="100%" :data="barData" unit="%" v-if="barData" :setting="chartSetting"></BarReverseCharts>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import BarReverseCharts from './echarts/BarReverseCharts.vue'
|
||||
import { getResTops } from 'services/monitor/index'
|
||||
const chartSetting = {
|
||||
barColor: ['rgba(255, 204, 77, 1)', 'rgba(59, 228, 218, 1)', 'rgba(93, 148, 255, 1)', 'rgba(93, 148, 255, 1)', 'rgba(93, 148, 255, 1)']
|
||||
}
|
||||
export default {
|
||||
components: { ItemCard, BarReverseCharts },
|
||||
data() {
|
||||
return {
|
||||
barData: null,
|
||||
chartSetting
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
getResTops({
|
||||
vendorId: 1,
|
||||
type: 'hostCpu',
|
||||
limit: 5
|
||||
}).then(data => {
|
||||
this.barData = data.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<ItemCard title="告警列表" v-bind="$attrs">
|
||||
<cb-table :data="tableList" :params="params" :get-list="getItemList" :total="total">
|
||||
<el-table-column type="index" width="50" label="序号"> </el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="标题" prop="name"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="级别" prop="level"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="宫颈类型" prop="type"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="告警来源" prop="time"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="时间" prop="time"></el-table-column>
|
||||
<el-table-column show-overflow-tooltip label="操作" width="50">
|
||||
<template slot-scope="scope">
|
||||
<cb-link @click="handleWarningOperation(scope.row)">查看工单</cb-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</cb-table>
|
||||
</ItemCard>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ItemCard from './ItemCard.vue'
|
||||
import { mockHttp } from 'services/system/dashboard'
|
||||
|
||||
export default {
|
||||
components: { ItemCard },
|
||||
data() {
|
||||
return {
|
||||
tableList: [],
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getItemList() {
|
||||
mockHttp(this.params).then(data => {
|
||||
if (data.success) {
|
||||
this.tableList = data.data.rows
|
||||
this.total = data.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
handleWarningOperation(row) {
|
||||
console.log('处理告警', row)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,272 @@
|
|||
<template>
|
||||
<div :style="{ height, width }" class="chart-container">
|
||||
<div class="chart" :class="{ hide: isNoData }" :id="id" style="width: 100%; height: 100%"></div>
|
||||
<cb-empty class="chart-no-data" v-show="isNoData"></cb-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
import useEchart, { commonProps } from '@cmp/cmp-echarts/hooks/useChart'
|
||||
import { merge } from 'lodash-es'
|
||||
import * as echarts from 'echarts/core'
|
||||
import 排名1 from '../images/排名 1.png'
|
||||
import 排名2 from '../images/排名 2.png'
|
||||
import 排名3 from '../images/排名 3.png'
|
||||
import 排名4 from '../images/排名 4.png'
|
||||
import 排名5 from '../images/排名 5.png'
|
||||
const commonLinerColor = [
|
||||
['#8699FF', '#4B66FF'],
|
||||
['#23A3B0', '#1BB879'],
|
||||
['#888B79', '#DC931F']
|
||||
]
|
||||
function getLinerColor(startColor, endColor) {
|
||||
// 只有一种颜色返回单色
|
||||
if (!endColor) return startColor
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: startColor
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: endColor
|
||||
}
|
||||
])
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
...commonProps
|
||||
},
|
||||
setup(props) {
|
||||
// 数据处理
|
||||
function handleData(data) {
|
||||
const { legendLength = 60, series: seriesConfig = {}, colorMap = {} } = props.setting
|
||||
const legends = []
|
||||
const series = []
|
||||
const { values = [], keys = [] } = data
|
||||
values.forEach(item => {
|
||||
if (!item.data) return
|
||||
const { name } = item
|
||||
const resName = name.substring(0, legendLength)
|
||||
legends.push(name)
|
||||
const data = item.data.map((cell, index) => {
|
||||
return {
|
||||
value: cell,
|
||||
itemStyle: {
|
||||
color: props.setting.barColor[index]
|
||||
}
|
||||
}
|
||||
})
|
||||
series.push({
|
||||
name: resName,
|
||||
type: 'bar',
|
||||
smooth: true,
|
||||
barMaxWidth: 11,
|
||||
showBackground: true,
|
||||
backgroundStyle: {
|
||||
color: 'rgba(245, 247, 250, 1)',
|
||||
borderRadius: 50
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 50
|
||||
},
|
||||
...seriesConfig,
|
||||
data
|
||||
})
|
||||
})
|
||||
return {
|
||||
legends,
|
||||
series,
|
||||
keys
|
||||
}
|
||||
}
|
||||
function updateChart() {
|
||||
var _props$data
|
||||
if (!((_props$data = props.data) !== null && _props$data !== void 0 && _props$data.values)) return
|
||||
const { linerColor = commonLinerColor, color: scolor, legend = {}, xAxis = {}, yAxis = {}, grid = {} } = props.setting
|
||||
const { legends, series, keys } = handleData(props.data)
|
||||
const color =
|
||||
scolor ||
|
||||
(linerColor &&
|
||||
linerColor.map(item => {
|
||||
return getLinerColor(item[0], item[1])
|
||||
}))
|
||||
const options = {
|
||||
color: color || commonColor,
|
||||
legend: {
|
||||
show: false,
|
||||
data: legends,
|
||||
...legend
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
left: '-150',
|
||||
right: '2%',
|
||||
bottom: 1,
|
||||
containLabel: true,
|
||||
...grid
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
position: 'bottom',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
...xAxis
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
inverse: true,
|
||||
// ...yAxis,
|
||||
data: keys,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
position: 'left',
|
||||
axisLabel: {
|
||||
show: true,
|
||||
margin: 150,
|
||||
textStyle: {
|
||||
align: 'left'
|
||||
},
|
||||
color: 'rgba(52, 71, 103, 1)',
|
||||
formatter: function (value, index) {
|
||||
return `{rang${index + 1}|}` + ' ' + value
|
||||
},
|
||||
rich: {
|
||||
rang1: {
|
||||
width: 24,
|
||||
height: 28,
|
||||
backgroundColor: {
|
||||
image: 排名1
|
||||
}
|
||||
},
|
||||
rang2: {
|
||||
width: 24,
|
||||
height: 28,
|
||||
backgroundColor: {
|
||||
image: 排名2
|
||||
}
|
||||
},
|
||||
rang3: {
|
||||
width: 24,
|
||||
height: 28,
|
||||
backgroundColor: {
|
||||
image: 排名3
|
||||
}
|
||||
},
|
||||
rang4: {
|
||||
width: 24,
|
||||
height: 28,
|
||||
backgroundColor: {
|
||||
image: 排名4
|
||||
}
|
||||
},
|
||||
rang5: {
|
||||
width: 24,
|
||||
height: 28,
|
||||
backgroundColor: {
|
||||
image: 排名5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
...yAxis
|
||||
},
|
||||
|
||||
{
|
||||
type: 'category',
|
||||
inverse: true,
|
||||
// ...yAxis,
|
||||
data: keys,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
formatter: function (value, index) {
|
||||
return props.data.values[0].data[index] + '%'
|
||||
},
|
||||
margin: 40,
|
||||
width: 10,
|
||||
fontSize: 14,
|
||||
fontWeight: 600
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
...yAxis
|
||||
}
|
||||
],
|
||||
series
|
||||
}
|
||||
chart.value && chart.value.setOption(merge(options, props.options), true)
|
||||
}
|
||||
const { chart } = useEchart(props, updateChart)
|
||||
const isNoData = computed(() => !props.data?.keys?.length)
|
||||
return {
|
||||
updateChart,
|
||||
chart,
|
||||
isNoData
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.chart-no-data {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<div :style="{ height, width }" class="chart-container">
|
||||
<div class="chart" :class="{ hide: isNoData }" :id="id" style="width: 100%; height: 100%"></div>
|
||||
<cb-empty class="chart-no-data" v-show="isNoData"></cb-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
import useEchart, { commonProps } from '@cmp/cmp-echarts/hooks/useChart'
|
||||
import { merge } from 'lodash-es'
|
||||
export default {
|
||||
props: {
|
||||
...commonProps
|
||||
},
|
||||
setup(props, context) {
|
||||
const commonYAxis = {
|
||||
type: 'value',
|
||||
nameGap: 5,
|
||||
min: 0,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: ['#ccc'],
|
||||
type: 'solid'
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: ['#d9d9d9']
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#333'
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
|
||||
const commonGrid = {
|
||||
left: 20,
|
||||
right: 1,
|
||||
top: 30,
|
||||
bottom: 1,
|
||||
containLabel: true
|
||||
}
|
||||
|
||||
// 数据处理
|
||||
function handleData(data) {
|
||||
const { legendLength = 60 } = props.setting
|
||||
const legends = []
|
||||
const series = []
|
||||
const { values = [], keys = [] } = data
|
||||
values.forEach(item => {
|
||||
const { name, data, series: seriesConfig = {} } = item
|
||||
const resName = name?.substring(0, legendLength)
|
||||
legends.push(name)
|
||||
series.push({
|
||||
name: resName,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
...seriesConfig,
|
||||
data
|
||||
})
|
||||
})
|
||||
return {
|
||||
legends,
|
||||
series,
|
||||
keys
|
||||
}
|
||||
}
|
||||
function updateChart() {
|
||||
if (!props.data?.values) return
|
||||
const { legends, series, keys } = handleData(props.data)
|
||||
const { legend = {}, grid = {}, xAxis = {}, yAxis = {}, color = [] } = props.setting
|
||||
const options = {
|
||||
color,
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
data: legends,
|
||||
...legend
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
confine: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
type: 'category',
|
||||
data: keys.map(item => {
|
||||
return item.replace(' ', '\n')
|
||||
}),
|
||||
axisLabel: {
|
||||
color: 'rgba(140, 140, 140, 1)'
|
||||
},
|
||||
...xAxis
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
...commonYAxis,
|
||||
name: props.unit,
|
||||
|
||||
axisLabel: {
|
||||
color: 'rgba(140, 140, 140, 1)'
|
||||
},
|
||||
...yAxis
|
||||
}
|
||||
],
|
||||
grid: {
|
||||
...commonGrid,
|
||||
...grid
|
||||
},
|
||||
series
|
||||
}
|
||||
chart.value && chart.value.setOption(merge(options, props.options), true)
|
||||
}
|
||||
const { chart } = useEchart(props, updateChart)
|
||||
const isNoData = computed(() => !props.data?.keys?.length)
|
||||
return {
|
||||
chart,
|
||||
updateChart,
|
||||
isNoData
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.chart-no-data {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<div :style="{ height, width }" class="chart-container">
|
||||
<div class="chart" :class="{ hide: isNoData }" :id="id" style="width: 100%; height: 100%"></div>
|
||||
<cb-empty class="chart-no-data" v-show="isNoData"></cb-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
import useEchart, { commonProps } from '@cmp/cmp-echarts/hooks/useChart'
|
||||
import { merge } from 'lodash-es'
|
||||
export default {
|
||||
props: {
|
||||
...commonProps,
|
||||
theme: {
|
||||
type: String,
|
||||
default: '数据统计'
|
||||
},
|
||||
type: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isNoData() {
|
||||
var _this$data
|
||||
return !((_this$data = this.data) !== null && _this$data !== void 0 && _this$data.length)
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
function getLegendOptions({ countMap, legend }) {
|
||||
return {
|
||||
icon: 'circle',
|
||||
top: '80%',
|
||||
left: 0,
|
||||
type: 'scroll',
|
||||
formatter: name => {
|
||||
const { legendLength = 10 } = props.setting
|
||||
const resultName = `${name.substr(0, legendLength)}${name.length > legendLength ? '...' : ''}`
|
||||
return `{count|${countMap[name]}${props.unit || ''}}\n{name|${resultName}}`
|
||||
},
|
||||
textStyle: {
|
||||
rich: {
|
||||
name: {
|
||||
color: 'rgba(140, 141, 163, 1)',
|
||||
width: 'auto'
|
||||
},
|
||||
count: {
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: 50,
|
||||
color: '#000',
|
||||
width: 70,
|
||||
fontWeight: 800,
|
||||
fontSize: '24px'
|
||||
}
|
||||
}
|
||||
},
|
||||
...legend
|
||||
}
|
||||
}
|
||||
// 数据处理
|
||||
function handleData(data) {
|
||||
let total = 0
|
||||
const countMap = {}
|
||||
// 将图例分为左右两份
|
||||
const leftLegends = []
|
||||
data.forEach((item, index) => {
|
||||
const { name, value } = item
|
||||
leftLegends.push(name)
|
||||
|
||||
total += value / 1
|
||||
countMap[name] = value >= 0 ? value : 0
|
||||
})
|
||||
const { fixed = 0 } = props.setting
|
||||
return {
|
||||
total: total.toFixed(fixed) / 1,
|
||||
leftLegends,
|
||||
countMap
|
||||
}
|
||||
}
|
||||
function getSeries(total) {
|
||||
if (props.type === 'half') {
|
||||
const data = [...props.data]
|
||||
if (total === 0) {
|
||||
data.push({
|
||||
name: '',
|
||||
value: 1,
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
itemStyle: {
|
||||
color: 'rgba(53, 114, 248, 1)',
|
||||
borderColor: '#ecf0fc',
|
||||
borderWidth: 15,
|
||||
borderRadius: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
data.push({
|
||||
name: '',
|
||||
value: total || 1,
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
color: 'transparent'
|
||||
}
|
||||
})
|
||||
return {
|
||||
radius: props.setting.radius || ['80%', '100%'],
|
||||
center: props.setting.center || ['50%', '55%'],
|
||||
data
|
||||
}
|
||||
}
|
||||
return {
|
||||
radius: props.setting.radius || ['45%', '60%'],
|
||||
center: props.setting.center || ['50%', '32%'],
|
||||
data: props.data
|
||||
}
|
||||
}
|
||||
function updateChart() {
|
||||
var _props$data
|
||||
if (!((_props$data = props.data) !== null && _props$data !== void 0 && _props$data.length)) return
|
||||
const { leftLegends, total, countMap } = handleData(props.data)
|
||||
const { legend = {}, color = [], series = {} } = props.setting
|
||||
const legendOptions = getLegendOptions({
|
||||
countMap,
|
||||
legend
|
||||
})
|
||||
const options = {
|
||||
color,
|
||||
title: {
|
||||
text: `{name|总数}\n{value|${total}}`,
|
||||
top: props.type === 'half' ? '30%' : '25%',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
rich: {
|
||||
name: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 'normal',
|
||||
color: '#002C46'
|
||||
},
|
||||
value: {
|
||||
fontSize: '26px',
|
||||
lineHeight: 50,
|
||||
fontWeight: 800,
|
||||
color: '#002C46'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: [
|
||||
{
|
||||
...legendOptions,
|
||||
data: leftLegends,
|
||||
left: '10%'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: function (params) {
|
||||
const str = params.seriesName + '</br>' + params.name + ':' + (params.data.value >= 0 ? params.data.value : 0) + (props.unit ? props.unit : '') + '(' + (params.percent >= 0 ? params.percent : 0) + '%)'
|
||||
return str
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: props.theme,
|
||||
type: 'pie',
|
||||
startAngle: props.type === 'half' ? 180 : 90,
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
formatter: '{b} {c}',
|
||||
...series.label
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 5,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1
|
||||
},
|
||||
...series,
|
||||
...getSeries(total)
|
||||
}
|
||||
]
|
||||
}
|
||||
chart.value && chart.value.setOption(merge(options, props.options), true)
|
||||
}
|
||||
const { chart } = useEchart(props, updateChart)
|
||||
return {
|
||||
updateChart,
|
||||
chart
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.chart-no-data {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 547 B |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 881 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 669 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 493 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 574 B |
After Width: | Height: | Size: 620 B |
After Width: | Height: | Size: 654 B |
After Width: | Height: | Size: 640 B |
After Width: | Height: | Size: 550 B |
After Width: | Height: | Size: 581 B |
After Width: | Height: | Size: 515 B |
After Width: | Height: | Size: 650 B |