feat: 新增机器状态查看&机器状态等

This commit is contained in:
meilin.huang
2022-04-27 10:59:02 +08:00
parent 6fe892ca9f
commit ce78b2caee
41 changed files with 1240 additions and 2470 deletions

View File

@@ -0,0 +1,176 @@
{
"seriesCnt": "4",
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#008acd",
"subtitleColor": "#aaaaaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eeeeee",
"color": [
"#2ec7c9",
"#b6a2de",
"#5ab1ef",
"#ffb980",
"#d87a80",
"#8d98b3",
"#e5cf0d",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#c05050",
"#59678c",
"#c9ab00",
"#7eb00a",
"#6f5553",
"#c14089"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#5ab1ef",
"#e0ffff"
],
"legendTextColor": "#333333",
"kColor": "#d87a80",
"kColor0": "#2ec7c9",
"kBorderColor": "#d87a80",
"kBorderColor0": "#2ec7c9",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 3,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": true,
"graphLineWidth": 1,
"graphLineColor": "#aaaaaa",
"mapLabelColor": "#d87a80",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#eeeeee",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#dddddd",
"mapAreaColorE": "rgba(254,153,78,1)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#eeeeee",
"axisTickShow": true,
"axisTickColor": "#eeeeee",
"axisLabelShow": true,
"axisLabelColor": "#eeeeee",
"splitLineShow": true,
"splitLineColor": [
"#aaaaaa"
],
"splitAreaShow": false,
"splitAreaColor": [
"#eeeeee"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#eee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#2ec7c9",
"toolboxEmphasisColor": "#18a4a6",
"tooltipAxisColor": "#008acd",
"tooltipAxisWidth": "1",
"timelineLineColor": "#008acd",
"timelineLineWidth": 1,
"timelineItemColor": "#008acd",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#2ec7c9",
"timelineCheckBorderColor": "#2ec7c9",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#008acd",
"timelineControlBorderColor": "#008acd",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#008acd",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "#efefff",
"datazoomFillColor": "rgba(182,162,222,0.2)",
"datazoomHandleColor": "#008acd",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}

View File

@@ -0,0 +1,7 @@
import * as echarts from 'echarts'
export default function(dom: any, theme: any = null, option: any) {
let chart = echarts.init(dom, theme);
chart.setOption(option);
return chart;
}

View File

@@ -1,32 +1,53 @@
import type { App } from 'vue';
import { store } from '@/store/index.ts';
import { judementSameArr } from '@/common/utils/arrayOperation.ts';
import { auth, auths, authAll } from './authFunction'
// 用户权限指令
export function authDirective(app: App) {
// 单个权限验证v-auth="xxx"
app.directive('auth', {
mounted(el, binding) {
if (!store.state.userInfos.userInfos.permissions.some((v: any) => v === binding.value)) el.parentNode.removeChild(el);
if (!auth(binding.value)) {
parseNoAuth(el, binding);
};
},
});
// 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
app.directive('auths', {
mounted(el, binding) {
let flag = false;
store.state.userInfos.userInfos.permissions.map((val: any) => {
binding.value.map((v: any) => {
if (val === v) flag = true;
});
});
if (!flag) el.parentNode.removeChild(el);
if (!auths(binding.value)) {
parseNoAuth(el, binding);
}
},
});
// 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
app.directive('auth-all', {
mounted(el, binding) {
const flag = judementSameArr(binding.value, store.state.userInfos.userInfos.permissions);
if (!flag) el.parentNode.removeChild(el);
if (!authAll(binding.value)) {
parseNoAuth(el, binding);
};
},
});
}
/**
* 处理没有权限场景
*
* @param el 元素
* @param binding 绑定至
*/
const parseNoAuth = (el: any, binding: any) => {
const { arg } = binding;
// 如果是禁用模式,则将元素禁用
if (arg == 'disabled') {
el.setAttribute('disabled', true);
el.classList.add('is-disabled');
el.addEventListener('click', disableClickFn, true);
} else {
// 移除该元素
el.parentNode.removeChild(el);
}
}
const disableClickFn = (event: any) => {
event && event.stopImmediatePropagation();
}

View File

@@ -1,64 +0,0 @@
<template>
<div class="active-plate-main">
<ul class="active-list">
<li class="item" v-for="item in infoList" :key="item.title">
<p class="num" :style="{color:item.color}">{{item.count}}</p>
<p class="desc">{{item.title}}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'activePlate',
components: {},
props: {
// 需要展示的数据集合
infoList: {
type: Array,
require: true,
},
},
}
</script>
<style lang="scss">
.active-plate-main {
width: 100%;
height: 130px;
.active-list {
display: flex;
list-style: none;
padding-top: 15px;
.item {
position: relative;
flex: 1;
text-align: center;
.num {
font-size: 42px;
font-weight: bold;
font-family: sans-serif;
}
.desc {
font-size: 16px;
}
&::after {
position: absolute;
top: 18px;
right: 0;
content: '';
display: block;
width: 1px;
height: 56px;
background: #e7eef0;
}
&:nth-last-of-type(1) {
&::after {
background: none;
}
}
}
}
}
</style>

View File

@@ -1,39 +0,0 @@
<template>
<div class="base-chart" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
option: Object,
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(this.option)
on(window, 'resize', this.resize)
})
},
},
}
</script>
<style>
.base-chart {
width: 100%;
height: 360px;
padding: 28px;
background: #fff;
}
</style>

View File

@@ -1,43 +0,0 @@
<template>
<div class="card-main">
<div class="title">
{{title}}
<span>{{desc}}</span>
</div>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: '标题'
},
desc: {
type: String,
default: '描述'
}
}
}
</script>
<style lang='scss'>
.card-main {
border-radius: 8px;
background: #fff;
margin-bottom: 20px;
padding-bottom: 10px;
}
.title {
color: #060606;
font-size: 16px;
padding: 20px 32px;
span {
padding-left: 17px;
font-size: 12px;
color: #dededf;
}
}
</style>

View File

@@ -1,138 +0,0 @@
<template>
<div class="bar-main" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
value: Object,
text: String,
subtext: String
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
const xAxisData = Object.keys(this.value)
const seriesData = Object.values(this.value)
const option = {
grid: {
left: '1%',
right: '1%',
top: '2%',
bottom: '1%',
containLabel: true
},
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{c}人',
// position: ['30%', '90%'],
position: 'top',
backgroundColor: '#FAFBFE',
textStyle: {
fontSize: 14,
color: '#6d6d6d'
}
},
xAxis: {
// show: false,
type: 'category',
data: xAxisData,
splitLine: {
show: false
}
},
yAxis: [
{
// show: false,
type: 'value',
splitLine: {
show: true,
lineStyle: {
// 设置刻度线粗度(粗的宽度)
width: 1,
// 颜色数组,数组数量要比刻度线数量大才能不循环使用
color: [
'rgba(0, 0, 0, 0)',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee'
]
}
}
}
],
series: [
{
data: seriesData,
type: 'bar',
barWidth: 36,
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#f2f5ff' },
{ offset: 1, color: '#fff' }
])
}
},
itemStyle: {
normal: {
barBorderRadius: [50],
color: new echarts.graphic.LinearGradient(
0,
1,
0,
0,
[
{
offset: 0,
color: '#3AA1FF' // 0% 处的颜色
},
{
offset: 1,
color: '#36CBCB' // 100% 处的颜色
}
],
false
)
}
}
}
]
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
}
}
}
</script>
<style>
.bar-main {
width: 100%;
height: 360px;
padding: 28px;
background: #fff;
}
</style>

View File

@@ -1,92 +0,0 @@
<template>
<div class="line-main" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
value: Array,
title: String,
subtext: String,
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
const dateList = this.value.map(function (item) {
return item[0]
})
const valueList = this.value.map(function (item) {
return item[1]
})
const option = {
// Make gradient line here
visualMap: [
{
show: false,
type: 'continuous',
seriesIndex: 0,
min: 0,
max: 400,
}
],
title: [
{
left: 'center',
text: this.title,
}
],
tooltip: {
trigger: 'axis',
},
xAxis: [
{
data: dateList,
}
],
yAxis: [
{
splitLine: { show: false },
},
],
grid: [
{
},
],
series: [
{
type: 'line',
showSymbol: false,
data: valueList,
},
],
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
},
},
}
</script>
<style>
.line-main {
width: 100%;
height: 360px;
padding: 28px;
background: #fff;
}
</style>

View File

@@ -1,104 +0,0 @@
<template>
<div class="funnel-main" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
value: Array,
text: String,
subtext: String
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
const legend = this.value.map(_ => _.name)
const option = {
grid: {
left: '1%',
right: '1%',
top: '2%',
bottom: '1%',
containLabel: true
},
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
tooltip: {
show: false,
trigger: 'item',
formatter: '{c} ({d}%)',
// position: ['30%', '90%'],
position: 'right',
backgroundColor: 'transparent',
textStyle: {
fontSize: 14,
color: '#666'
}
},
legend: {
orient: 'vertical',
left: 'right',
bottom: 0,
// data: legend,
backgroundColor: 'transparent',
icon: 'circle'
},
series: [
{
name: '访问来源',
type: 'funnel',
radius: ['50%', '65%'],
avoidLabelOverlap: false,
label: {
normal: {
show: false,
position: 'right',
formatter: '{c} ({d}%)'
}
},
// labelLine: {
// normal: {
// show: false
// }
// },
data: [
{ value: 400, name: '交易完成' },
{ value: 300, name: '支付订单' },
{ value: 200, name: '生成订单' },
{ value: 100, name: '放入购物车' },
{ value: 100, name: '浏览网站' }
]
}
]
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
}
}
}
</script>
<style>
.funnel-main {
width: 100%;
height: 295px;
padding: 28px;
background: #fff;
}
</style>

View File

@@ -1,89 +0,0 @@
<template>
<div class="gauge-main" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
value: Object,
text: String,
subtext: String
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
const option = {
grid: {
left: 0,
right: 0,
top: 0,
bottom: 0
// containLabel: true
},
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {},
series: [
{
name: '业务指标',
startAngle: 195,
endAngle: -15,
axisLine: {
show: true,
lineStyle: {
color: [
[0.6, '#4ECB73'],
[0.8, '#FBD437'],
[1, '#F47F92']
],
width: 16
}
},
pointer: {
length: '80%',
width: 3,
color: 'auto'
},
axisTick: {
show: false
},
splitLine: { show: false },
type: 'gauge',
detail: {
formatter: '{value}%',
textStyle: {
color: '#595959',
fontSize: 32
}
},
data: [{ value: 10 }]
}
]
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
}
}
}
</script>
<style>
.gauge-main {
width: 100%;
height: 360px;
background: #fff;
}
</style>

View File

@@ -1,121 +0,0 @@
<template>
<div class="line-main" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
value: Object,
text: String,
subtext: String
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
const xAxisData = Object.keys(this.value)
const seriesData = Object.values(this.value)
const option = {
grid: {
left: '1%',
right: '1%',
top: '2%',
bottom: '1%',
containLabel: true
},
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{c}人',
// position: ['30%', '90%'],
position: 'top',
backgroundColor: '#387DE1',
textStyle: {
fontSize: 18,
color: '#fff'
}
},
xAxis: {
// show: false,
type: 'category',
data: xAxisData,
splitLine: {
show: false
}
},
yAxis: [
{
// show: false,
type: 'value',
splitLine: {
show: true,
lineStyle: {
// 设置刻度线粗度(粗的宽度)
width: 1,
// 颜色数组,数组数量要比刻度线数量大才能不循环使用
color: [
'rgba(0, 0, 0, 0)',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee',
'#eee'
]
}
}
}
],
series: [
{
data: seriesData,
type: 'line',
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#f2f5ff' },
{ offset: 1, color: '#fff' }
])
}
},
lineStyle: {
normal: {
width: 5,
color: '#36CBCB'
}
}
}
]
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
}
}
}
</script>
<style>
.line-main {
width: 100%;
height: 360px;
padding: 28px;
background: #fff;
}
</style>

View File

@@ -1,111 +0,0 @@
<template>
<div class="pie-main" id="box" ref="dom"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from './onoff'
echarts.registerTheme('tdTheme', tdTheme)
export default {
props: {
value: Array,
text: String,
subtext: String,
},
watch: {
value: {
handler: function (val, oldval) {
this.value = val
this.initChart()
},
deep: true, //对象内部的属性监听,也叫深度监听
},
},
mounted() {
this.initChart()
},
methods: {
resize() {
this.dom.resize()
},
initChart() {
this.$nextTick(() => {
const legend = this.value.map((_) => _.name)
const option = {
title: {
text: this.text,
subtext: this.subtext,
x: 'center',
},
position: {
top: 40,
},
tooltip: {
trigger: 'item',
formatter: '{c} ({d}%)',
// position: ['30%', '90%'],
position: function (point, params, dom, rect, size) {
console.log(size)
const leftWidth = size.viewSize[0] / 2 - size.contentSize[0] / 2
console.log(leftWidth)
return { left: leftWidth, bottom: 0 }
},
backgroundColor: 'transparent',
textStyle: {
fontSize: 24,
color: '#666',
},
},
legend: {
// orient: 'vertical',
top: 0,
data: legend,
backgroundColor: 'transparent',
icon: 'circle',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: ['45%', '60%'],
center: ['50%', '52%'],
avoidLabelOverlap: false,
label: {
normal: {
show: false,
position: 'center',
},
emphasis: {
show: true,
textStyle: {
fontSize: '24',
},
},
},
labelLine: {
normal: {
show: false,
},
},
data: this.value,
},
],
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
},
},
}
</script>
<style>
.pie-main {
width: 100%;
height: 360px;
padding: 28px;
background: #fff;
}
</style>

View File

@@ -1,37 +0,0 @@
/**
* @description 绑定事件 on(element, event, handler)
*/
export const on = (function () {
if (document.addEventListener != null) {
return function (element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function (element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
/**
* @description 解绑事件 off(element, event, handler)
*/
export const off = (function () {
if (document.removeEventListener != null) {
return function (element, event, handler) {
if (element && event) {
element.removeEventListener(event, handler, false);
}
};
} else {
return function (element, event, handler) {
if (element && event) {
element.detachEvent('on' + event, handler);
}
};
}
})();

View File

@@ -1,490 +0,0 @@
{
"color": [
"#2d8cf0",
"#19be6b",
"#ff9900",
"#E46CBB",
"#9A66E4",
"#ed3f14"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#516b91"
},
"subtextStyle": {
"color": "#93b7e3"
}
},
"line": {
"itemStyle": {
"normal": {
"borderWidth": "2"
}
},
"lineStyle": {
"normal": {
"width": "2"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"radar": {
"itemStyle": {
"normal": {
"borderWidth": "2"
}
},
"lineStyle": {
"normal": {
"width": "2"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"bar": {
"itemStyle": {
"normal": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
},
"emphasis": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
}
},
"pie": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"scatter": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"boxplot": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"parallel": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"sankey": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"funnel": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"gauge": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"candlestick": {
"itemStyle": {
"normal": {
"color": "#edafda",
"color0": "transparent",
"borderColor": "#d680bc",
"borderColor0": "#8fd3e8",
"borderWidth": "2"
}
}
},
"graph": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"lineStyle": {
"normal": {
"width": 1,
"color": "#aaa"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true,
"color": [
"#2d8cf0",
"#19be6b",
"#f5ae4a",
"#9189d5",
"#56cae2",
"#cbb0e3"
],
"label": {
"normal": {
"textStyle": {
"color": "#eee"
}
}
}
},
"map": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(165,231,240,1)",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(81,107,145)"
}
}
}
},
"geo": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(165,231,240,1)",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(81,107,145)"
}
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"normal": {
"borderColor": "#999"
},
"emphasis": {
"borderColor": "#666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#ccc",
"width": 1
},
"crossStyle": {
"color": "#ccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#8fd3e8",
"width": 1
},
"itemStyle": {
"normal": {
"color": "#8fd3e8",
"borderWidth": 1
},
"emphasis": {
"color": "#8fd3e8"
}
},
"controlStyle": {
"normal": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
},
"emphasis": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
}
},
"checkpointStyle": {
"color": "#8fd3e8",
"borderColor": "rgba(138,124,168,0.37)"
},
"label": {
"normal": {
"textStyle": {
"color": "#8fd3e8"
}
},
"emphasis": {
"textStyle": {
"color": "#8fd3e8"
}
}
}
},
"visualMap": {
"color": [
"#516b91",
"#59c4e6",
"#a5e7f0"
]
},
"dataZoom": {
"backgroundColor": "rgba(0,0,0,0)",
"dataBackgroundColor": "rgba(255,255,255,0.3)",
"fillerColor": "rgba(167,183,204,0.4)",
"handleColor": "#a7b7cc",
"handleSize": "100%",
"textStyle": {
"color": "#333"
}
},
"markPoint": {
"label": {
"normal": {
"textStyle": {
"color": "#eee"
}
},
"emphasis": {
"textStyle": {
"color": "#eee"
}
}
}
}
}

View File

@@ -2,7 +2,7 @@
<div class="home-container">
<el-row :gutter="15">
<el-col :sm="6" class="mb15">
<div class="home-card-item home-card-first">
<div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
<div class="flex-margin flex">
<img :src="getUserInfos.photo" />
<div class="home-card-first-right ml15">
@@ -14,81 +14,15 @@
</div>
</el-col>
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList" :key="k">
<div class="home-card-item home-card-item-box" :style="{ background: v.color }">
<div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
<div class="home-card-item-flex">
<div class="home-card-item-title pb3">{{ v.title }}</div>
<div class="home-card-item-title-num pb6" :id="v.id"></div>
<!-- <div class="home-card-item-tip pb3">{{ v.tip }}</div>
<div class="home-card-item-tip-num" :id="`tipNum${k + 1}`"></div> -->
</div>
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
</div>
</el-col>
</el-row>
<!-- <el-row :gutter="15">
<el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16" class="mb15">
<el-card shadow="hover" header="商品销售情况">
<div style="height: 200px" ref="homeLaboratoryRef"></div>
</el-card>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8">
<el-card shadow="hover" header="环境监测">
<div class="home-monitor">
<div class="flex-warp">
<div class="flex-warp-item" v-for="(v, k) in environmentList" :key="k">
<div class="flex-warp-item-box">
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
<span class="pl5">{{ v.label }}</span>
<div class="mt10">{{ v.value }}</div>
</div>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="15">
<el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16" class="home-warning-media">
<el-card shadow="hover" header="布局配置" class="home-warning-card">
<el-table :data="tableData.data" style="width: 100%" stripe>
<el-table-column prop="date" label="时间"></el-table-column>
<el-table-column prop="name" label="实验室名称"></el-table-column>
<el-table-column prop="address" label="报警内容"></el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8" class="home-dynamic-media">
<el-card shadow="hover" header="动态信息">
<div class="home-dynamic">
<el-scrollbar>
<div class="home-dynamic-item" v-for="(v, k) in activitiesList" :key="k">
<div class="home-dynamic-item-left">
<div class="home-dynamic-item-left-time1 mb5">{{ v.time1 }}</div>
<div class="home-dynamic-item-left-time2">{{ v.time2 }}</div>
</div>
<div class="home-dynamic-item-line">
<i class="iconfont icon-fangkuang"></i>
</div>
<div class="home-dynamic-item-right">
<div class="home-dynamic-item-right-title mb5">
<i class="el-icon-s-comment"></i>
<span>{{ v.title }}</span>
</div>
<div class="home-dynamic-item-right-label">{{ v.label }}</div>
</div>
</div>
</el-scrollbar>
</div>
</el-card>
</el-col>
</el-row>
<el-row>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mt15">
<el-card shadow="hover" header="履约超时预警">
<div style="height: 200px" ref="homeOvertimeRef"></div>
</el-card>
</el-col>
</el-row> -->
</div>
</template>
@@ -99,35 +33,36 @@ import { useStore } from '@/store/index.ts';
import { CountUp } from 'countup.js';
import { formatAxis } from '@/common/utils/formatTime.ts';
import { indexApi } from './api';
import { topCardItemList, environmentList, activitiesList } from './mock.ts';
import { useRouter } from 'vue-router';
export default {
name: 'Home',
name: 'HomePage',
setup() {
// const { proxy } = getCurrentInstance() as any;
const router = useRouter();
const store = useStore();
const state = reactive({
topCardItemList,
environmentList,
activitiesList,
tableData: {
data: [
{
date: '2016-05-02',
name: '1号实验室',
address: '烟感2.1%OBS/M',
},
{
date: '2016-05-04',
name: '2号实验室',
address: '温度30℃',
},
{
date: '2016-05-01',
name: '3号实验室',
address: '湿度57%RH',
},
],
},
topCardItemList: [
{
title: '项目数',
id: 'projectNum',
color: '#FEBB50',
},
{
title: 'Linux机器数',
id: 'machineNum',
color: '#F95959',
},
{
title: '数据库总数',
id: 'dbNum',
color: '#8595F4',
},
{
title: 'redis总数',
id: 'redisNum',
color: '#1abc9c',
},
],
});
// 当前时间提示语
@@ -137,7 +72,7 @@ export default {
// 初始化数字滚动
const initNumCountUp = async () => {
const res: any = await indexApi.getIndexCount.request()
const res: any = await indexApi.getIndexCount.request();
nextTick(() => {
new CountUp('projectNum', res.projectNum).start();
new CountUp('machineNum', res.machineNum).start();
@@ -146,104 +81,31 @@ export default {
});
};
// // 实验室使用情况
// const initHomeLaboratory = () => {
// const myChart = echarts.init(proxy.$refs.homeLaboratoryRef);
// const option = {
// grid: {
// top: 50,
// right: 20,
// bottom: 30,
// left: 30,
// },
// tooltip: {
// trigger: 'axis',
// },
// legend: {
// data: ['预购队列', '最新成交价'],
// right: 13,
// },
// xAxis: {
// data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
// },
// yAxis: [
// {
// type: 'value',
// name: '价格',
// },
// ],
// series: [
// {
// name: '预购队列',
// type: 'bar',
// data: [5, 20, 36, 10, 10, 20],
// },
// {
// name: '最新成交价',
// type: 'line',
// data: [15, 20, 16, 20, 30, 8],
// },
// ],
// };
// myChart.setOption(option);
// window.addEventListener('resize', () => {
// myChart.resize();
// });
// };
// // 履约超时预警
// const initHomeOvertime = () => {
// const myChart = echarts.init(proxy.$refs.homeOvertimeRef);
// const option = {
// grid: {
// top: 50,
// right: 20,
// bottom: 30,
// left: 30,
// },
// tooltip: {
// trigger: 'axis',
// },
// legend: {
// data: ['订单数量', '超时数量', '在线数量', '预警数量'],
// right: 13,
// },
// xAxis: {
// data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
// },
// yAxis: [
// {
// type: 'value',
// name: '数量',
// },
// ],
// series: [
// {
// name: '订单数量',
// type: 'bar',
// data: [5, 20, 36, 10, 10, 20, 11, 13, 10, 9, 17, 19],
// },
// {
// name: '超时数量',
// type: 'bar',
// data: [15, 12, 26, 15, 11, 16, 31, 13, 5, 16, 13, 15],
// },
// {
// name: '在线数量',
// type: 'line',
// data: [15, 20, 16, 20, 30, 8, 16, 19, 12, 18, 19, 14],
// },
// {
// name: '预警数量',
// type: 'line',
// data: [10, 10, 13, 12, 15, 18, 19, 10, 12, 15, 11, 17],
// },
// ],
// };
// myChart.setOption(option);
// window.addEventListener('resize', () => {
// myChart.resize();
// });
// };
const toPage = (item: any) => {
switch (item.id) {
case 'personal': {
router.push('/personal');
break;
}
case 'projectNum': {
router.push('/ops/projects');
break;
}
case 'machineNum': {
router.push('/ops/machines');
break;
}
case 'dbNum': {
router.push('/ops/dbms/dbs');
break;
}
case 'redisNum': {
router.push('/ops/redis/manage');
break;
}
}
};
// 页面加载时
onMounted(() => {
initNumCountUp();
@@ -255,9 +117,11 @@ export default {
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
return {
getUserInfos,
currentTime,
toPage,
...toRefs(state),
};
},
@@ -273,6 +137,7 @@ export default {
background: gray;
border-radius: 4px;
transition: all ease 0.3s;
cursor: pointer;
&:hover {
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
transition: all ease 0.3s;

View File

@@ -1,93 +0,0 @@
// 最顶部 card
export const topCardItemList = [
{
title: '项目数',
id: 'projectNum',
num: '123',
tip: '通过人数',
tipNum: '911',
color: '#FEBB50',
iconColor: '#FDC566',
icon: 'el-icon-histogram',
},
{
title: 'Linux机器数',
id: 'machineNum',
num: '123',
tip: '在场人数',
tipNum: '911',
color: '#F95959',
iconColor: '#F86C6B',
icon: 'iconfont icon-jinridaiban',
},
{
title: '数据库总数',
id: "dbNum",
num: '123',
tip: '使用中',
tipNum: '611',
color: '#8595F4',
iconColor: '#92A1F4',
icon: 'iconfont icon-AIshiyanshi',
},
{
title: 'redis总数',
id: 'redisNum',
num: '123',
tip: '通过人数',
tipNum: '911',
color: '#1abc9c',
iconColor: '#FDC566',
icon: 'iconfont icon-shenqingkaiban',
},
];
// 环境监测
export const environmentList = [
{
icon: 'iconfont icon-yangan',
label: '烟感',
value: '2.1%OBS/M',
iconColor: '#F72B3F',
},
{
icon: 'iconfont icon-wendu',
label: '温度',
value: '30℃',
iconColor: '#91BFF8',
},
{
icon: 'iconfont icon-shidu',
label: '湿度',
value: '57%RH',
iconColor: '#88D565',
},
{
icon: 'iconfont icon-zaosheng',
label: '噪声',
value: '57DB',
iconColor: '#FBD4A0',
},
];
// 动态信息
export const activitiesList = [
{
time1: '今天',
time2: '12:20:30',
title: '更名',
label: '正式更名为 vue-next-admin',
},
{
time1: '02-17',
time2: '12:20:30',
title: '页面',
label: '完成对首页的开发',
},
{
time1: '02-14',
time2: '12:20:30',
title: '页面',
label: '新增个人中心',
},
];

View File

@@ -62,7 +62,7 @@ import { formatAxis } from '@/common/utils/formatTime.ts';
import openApi from '@/common/openApi';
import { letterAvatar } from '@/common/utils/string';
export default defineComponent({
name: 'Account',
name: 'AccountLogin',
setup() {
const store = useStore();
const route = useRoute();

View File

@@ -33,7 +33,7 @@
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
export default defineComponent({
name: 'login',
name: 'MobileLogin',
setup() {
const state = reactive({
ruleForm: {

View File

@@ -33,12 +33,11 @@
<script lang="ts">
import { toRefs, reactive, computed } from 'vue';
import Account from '@/views/login/component/Account.vue';
import Mobile from '@/views/login/component/mobile.vue';
import Account from '@/views/login/component/AccountLogin.vue';
import { useStore } from '@/store/index.ts';
export default {
name: 'login',
components: { Account, Mobile },
name: 'LoginPage',
components: { Account },
setup() {
const store = useStore();
const state = reactive({

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :show-close="false" :before-close="cancel" width="35%">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="35%">
<el-form :model="form" ref="dbForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :show-close="false" :before-close="cancel" width="35%">
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" :before-close="cancel" width="35%">
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px" >
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>

View File

@@ -15,14 +15,28 @@
<el-button v-auth="'machine:del'" :disabled="currentId == null" @click="deleteMachine(currentId)" type="danger" icon="delete"
>删除</el-button
>
<el-button v-auth="'machine:file'" type="success" icon="files" :disabled="currentId == null" @click="fileManage(currentData)" plain
<el-button
v-auth="'machine:file'"
type="success"
icon="files"
:disabled="currentId == null || currentData.status == -1"
@click="fileManage(currentData)"
plain
>文件</el-button
>
<div style="float: right">
<el-select v-model="params.projectId" placeholder="请选择项目" @clear="search" filterable clearable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"> </el-option>
</el-select>
<el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search" plain clearable></el-input>
<el-input
class="ml5"
placeholder="请输入名称"
style="width: 150px"
v-model="params.name"
@clear="search"
plain
clearable
></el-input>
<el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search" plain clearable></el-input>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
@@ -37,30 +51,66 @@
</template>
</el-table-column>
<el-table-column prop="name" label="名称" min-width="130" show-overflow-tooltip></el-table-column>
<el-table-column prop="ip" label="ip:port" min-width="130">
<el-table-column prop="ip" label="ip:port" min-width="140">
<template #default="scope">
{{ `${scope.row.ip}:${scope.row.port}` }}
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary" :underline="false">{{
`${scope.row.ip}:${scope.row.port}`
}}</el-link>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="75"></el-table-column>
<el-table-column prop="status" label="状态" min-width="60">
<template #default="scope">
<el-switch
v-auth:disabled="'machine:update'"
:width="47"
v-model="scope.row.status"
:active-value="1"
:inactive-value="-1"
active-color="#13ce66"
inactive-color="#ff4949"
inline-prompt
active-text="启用"
inactive-text="停用"
@change="changeStatus(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="90"></el-table-column>
<el-table-column prop="projectName" label="项目" min-width="120"></el-table-column>
<el-table-column prop="ip" label="hasCli" width="70">
<template #default="scope">
{{ `${scope.row.hasCli ? '是' : '否'}` }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="165">
<el-table-column prop="createTime" label="创建时间" min-width="165">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="creator" label="创建者" min-width="60"></el-table-column>
<el-table-column label="操作" min-width="260" fixed="right">
<el-table-column prop="creator" label="创建者" min-width="80"></el-table-column>
<el-table-column label="操作" min-width="280" fixed="right">
<template #default="scope">
<el-button type="success" @click="serviceManager(scope.row)" plain size="small">脚本</el-button>
<el-button v-auth="'machine:terminal'" type="primary" @click="showTerminal(scope.row)" plain size="small">终端</el-button>
<el-button @click="showProcess(scope.row)" plain size="small">进程</el-button>
<el-button :disabled="!scope.row.hasCli" type="danger" @click="closeCli(scope.row)" plain size="small">关闭连接</el-button>
<el-button :disabled="scope.row.status == -1" type="success" @click="serviceManager(scope.row)" plain size="small"
>脚本</el-button
>
<el-button
v-auth="'machine:terminal'"
:disabled="scope.row.status == -1"
type="primary"
@click="showTerminal(scope.row)"
plain
size="small"
>终端</el-button
>
<el-button @click="showProcess(scope.row)" :disabled="scope.row.status == -1" plain size="small">进程</el-button>
<el-button
:disabled="!scope.row.hasCli || scope.row.status == -1"
type="danger"
@click="closeCli(scope.row)"
plain
size="small"
>关闭连接</el-button
>
</template>
</el-table-column>
</el-table>
@@ -84,15 +134,17 @@
@valChange="submitSuccess"
></machine-edit>
<!-- <el-dialog @close="closeMonitor" title="监控信息" v-model="monitorDialog.visible" width="60%">
<monitor ref="monitorDialogRef" :machineId="monitorDialog.machineId" />
</el-dialog> -->
<process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
<machine-stats
v-model:visible="machineStatsDialog.visible"
:machineId="machineStatsDialog.machineId"
:title="machineStatsDialog.title"
></machine-stats>
</div>
</template>
@@ -100,13 +152,13 @@
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
// import Monitor from './Monitor.vue';
import { machineApi } from './api';
import { projectApi } from '../project/api.ts';
import ServiceManage from './ServiceManage.vue';
import FileManage from './FileManage.vue';
import MachineEdit from './MachineEdit.vue';
import ProcessList from './ProcessList.vue';
import MachineStats from './MachineStats.vue';
export default defineComponent({
name: 'MachineList',
@@ -115,11 +167,13 @@ export default defineComponent({
ProcessList,
FileManage,
MachineEdit,
MachineStats,
},
setup() {
const router = useRouter();
const state = reactive({
projects: [],
stats: '',
params: {
pageNum: 1,
pageSize: 10,
@@ -152,8 +206,10 @@ export default defineComponent({
machineId: 0,
title: '',
},
monitorDialog: {
machineStatsDialog: {
visible: false,
stats: null,
title: '',
machineId: 0,
},
machineEditDialog: {
@@ -176,22 +232,6 @@ export default defineComponent({
state.currentData = item;
};
// const monitor = (id: number) => {
// state.monitorDialog.machineId = id;
// state.monitorDialog.visible = true;
// // 如果重复打开同一个则开启定时任务
// const md: any = monitorDialogRef;
// if (md) {
// md.startInterval();
// }
// };
// const closeMonitor = () => {
// // 关闭窗口,取消定时任务
// const md: any = monitorDialogRef;
// md.cancelInterval();
// };
const showTerminal = (row: any) => {
const { href } = router.resolve({
path: `/machine/terminal`,
@@ -244,6 +284,22 @@ export default defineComponent({
state.serviceDialog.title = `${row.name} => ${row.ip}`;
};
/**
* 调整机器状态
*/
const changeStatus = async (row: any) => {
await machineApi.changeStatus.request({ id: row.id, status: row.status });
};
/**
* 显示机器状态统计信息
*/
const showMachineStats = async (machine: any) => {
state.machineStatsDialog.machineId = machine.id;
state.machineStatsDialog.title = `机器状态: ${machine.name} => ${machine.ip}`;
state.machineStatsDialog.visible = true;
};
const submitSuccess = () => {
state.currentId = null;
state.currentData = null;
@@ -281,7 +337,9 @@ export default defineComponent({
deleteMachine,
closeCli,
serviceManager,
showMachineStats,
showProcess,
changeStatus,
submitSuccess,
fileManage,
search,

View File

@@ -0,0 +1,322 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="true" :destroy-on-close="true" :before-close="cancel" width="980px">
<el-row :gutter="20">
<el-col :lg="10" :md="10">
<el-descriptions size="small" title="基础信息" :column="2" border>
<template #extra>
<el-link @click="onRefresh" icon="refresh" :underline="false" type="success"></el-link>
</template>
<el-descriptions-item label="主机名">
{{ stats.Hostname }}
</el-descriptions-item>
<el-descriptions-item label="运行时间">
{{ stats.Uptime }}
</el-descriptions-item>
<el-descriptions-item label="总任务">
{{ stats.TotalProcs }}
</el-descriptions-item>
<el-descriptions-item label="运行中任务">
{{ stats.RunningProcs }}
</el-descriptions-item>
<el-descriptions-item label="负载"> {{ stats.Load1 }} {{ stats.Load5 }} {{ stats.Load10 }} </el-descriptions-item>
</el-descriptions>
</el-col>
<el-col :lg="7" :md="7">
<div class="card-item-chart" ref="memRef"></div>
</el-col>
<el-col :lg="7" :md="7">
<div class="card-item-chart" ref="cpuRef"></div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :lg="8" :md="8">
<span style="font-size: 16px; font-weight: 700">磁盘</span>
<el-table :data="stats.FSInfos" stripe max-height="250" style="width: 100%" border>
<el-table-column prop="MountPoint" label="挂载点" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="Used" label="可使用" min-width="70" show-overflow-tooltip>
<template #default="scope">
{{ formatByteSize(scope.row.Free) }}
</template>
</el-table-column>
<el-table-column prop="Used" label="已使用" min-width="70" show-overflow-tooltip>
<template #default="scope">
{{ formatByteSize(scope.row.Used) }}
</template>
</el-table-column>
</el-table>
</el-col>
<el-col :lg="16" :md="16">
<span style="font-size: 16px; font-weight: 700">网卡</span>
<el-table :data="netInter" stripe max-height="250" style="width: 100%" border>
<el-table-column prop="name" label="网卡" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="IPv4" label="IPv4" min-width="130" show-overflow-tooltip></el-table-column>
<el-table-column prop="IPv6" label="IPv6" min-width="130" show-overflow-tooltip></el-table-column>
<el-table-column prop="Rx" label="接收(rx)" min-width="110" show-overflow-tooltip>
<template #default="scope">
{{ formatByteSize(scope.row.Rx) }}
</template>
</el-table-column>
<el-table-column prop="Tx" label="发送(tx)" min-width="110" show-overflow-tooltip>
<template #default="scope">
{{ formatByteSize(scope.row.Tx) }}
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-dialog>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref, nextTick } from 'vue';
import useEcharts from '@/common/echarts/useEcharts.ts';
import tdTheme from '@/common/echarts/theme.json';
import { formatByteSize } from '@/common/utils/format';
import { machineApi } from './api';
export default defineComponent({
name: 'MachineStats',
components: {},
props: {
visible: {
type: Boolean,
},
stats: {
type: Object,
},
machineId: {
type: Number,
},
title: {
type: String,
},
},
setup(props: any, { emit }) {
const cpuRef: any = ref();
const memRef: any = ref();
let cpuChart: any = null;
let memChart: any = null;
const state = reactive({
dialogVisible: false,
charts: [] as any,
stats: {} as any,
netInter: [] as any,
});
watch(props, async (newValue) => {
const visible = newValue.visible;
if (visible) {
await setStats();
}
state.dialogVisible = visible;
if (visible) {
initCharts();
}
});
const setStats = async () => {
state.stats = await machineApi.stats.request({ id: props.machineId });
};
const onRefresh = async () => {
await setStats();
initCharts();
};
const initMemStats = () => {
const data = [
{ name: '可用内存', value: state.stats.MemAvailable },
{
name: '已用内存',
value: state.stats.MemTotal - state.stats.MemAvailable,
},
];
const option = {
title: {
text: '内存',
x: 'left',
textStyle: { fontSize: 15 },
},
tooltip: {
trigger: 'item',
valueFormatter: formatByteSize,
},
legend: {
top: '15%',
orient: 'vertical',
left: 'left',
textStyle: { fontSize: 12 },
},
series: [
{
name: '内存',
type: 'pie',
radius: ['30%', '60%'], // 饼图内圈和外圈大小
center: ['60%', '50%'], // 饼图位置0: 左右1: 上下
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '15',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: data,
},
],
};
if (memChart) {
memChart.setOption(option, true);
return;
}
const chart: any = useEcharts(memRef.value, tdTheme, option);
memChart = chart;
state.charts.push(chart);
};
const initCpuStats = () => {
const cpu = state.stats.CPU;
const data = [
{ name: 'Idle', value: cpu.Idle },
{
name: 'Iowait',
value: cpu.Iowait,
},
{
name: 'System',
value: cpu.System,
},
{
name: 'User',
value: cpu.User,
},
];
const option = {
title: {
text: 'CPU使用率',
x: 'left',
textStyle: { fontSize: 15 },
},
tooltip: {
trigger: 'item',
valueFormatter: (value: any) => value + '%',
},
legend: {
top: '15%',
orient: 'vertical',
left: 'left',
textStyle: { fontSize: 12 },
},
series: [
{
name: 'CPU',
type: 'pie',
radius: ['30%', '60%'], // 饼图内圈和外圈大小
center: ['60%', '50%'], // 饼图位置0: 左右1: 上下
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '15',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: data,
},
],
};
if (cpuChart) {
cpuChart.setOption(option, true);
return;
}
const chart: any = useEcharts(cpuRef.value, tdTheme, option);
cpuChart = chart;
state.charts.push(chart);
};
const initCharts = () => {
nextTick(() => {
initMemStats();
initCpuStats();
});
parseNetInter();
initEchartsResize();
};
const initEchartResizeFun = () => {
nextTick(() => {
for (let i = 0; i < state.charts.length; i++) {
setTimeout(() => {
state.charts[i].resize();
}, i * 1000);
}
});
};
const initEchartsResize = () => {
window.addEventListener('resize', initEchartResizeFun);
};
const parseNetInter = () => {
state.netInter = [];
const netInter = state.stats.NetIntf;
const keys = Object.keys(netInter);
const values = Object.values(netInter);
for (let i = 0; i < values.length; i++) {
// 需要使用属性拷贝否则会再次触发watch
let value: any = values[i];
// 将网卡名称赋值新属性值name
value.name = keys[i];
state.netInter.push(value);
}
};
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
cpuChart = null;
memChart = null;
}, 200);
};
return {
...toRefs(state),
cpuRef,
memRef,
cancel,
formatByteSize,
onRefresh,
};
},
});
</script>
<style lang="scss">
.card-item-chart {
height: 200px;
width: 100%;
}
</style>

View File

@@ -1,208 +0,0 @@
<template>
<div>
<el-row>
<el-col>
<HomeCard desc="Base info" title="基础信息">
<ActivePlate :infoList="infoCardData" />
</HomeCard>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :lg="6" :md="24">
<HomeCard desc="Task info" title="任务">
<ChartPie v-model:value="taskData" />
</HomeCard>
</el-col>
<el-col :lg="6" :md="24">
<HomeCard desc="Mem info" title="内存">
<ChartPie v-model:value="memData" />
</HomeCard>
</el-col>
<el-col :lg="6" :md="24">
<HomeCard desc="Swap info" title="CPU">
<ChartPie v-model:value="cpuData" />
</HomeCard>
</el-col>
</el-row>
<!-- <el-row :gutter="20">
<el-col :lg="18" :md="24">
<HomeCard desc="User active" title="每周用户活跃量">
<ChartLine :value="lineData" />
</HomeCard>
</el-col>
</el-row>-->
<el-row :gutter="20">
<el-col :lg="12" :md="24">
<ChartContinuou :value="this.data" title="内存" />
</el-col>
<el-col :lg="12" :md="24">
<ChartContinuou :value="this.data" title="CPU" />
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :lg="12" :md="24">
<HomeCard desc="load info" title="负载情况">
<BaseChart :option="this.loadChartOption" />
</HomeCard>
</el-col>
<el-col :lg="12" :md="24">
<ChartContinuou :value="this.data" title="磁盘IO" />
</el-col>
</el-row>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent, onBeforeUnmount, onMounted } from 'vue';
import ActivePlate from '@/components/chart/ActivePlate.vue';
import HomeCard from '@/components/chart/Card.vue';
import ChartPie from '@/components/chart/ChartPie.vue';
import ChartLine from '@/components/chart/ChartLine.vue';
import ChartGauge from '@/components/chart/ChartGauge.vue';
import ChartBar from '@/components/chart/ChartBar.vue';
import ChartFunnel from '@/components/chart/ChartFunnel.vue';
import ChartContinuou from '@/components/chart/ChartContinuou.vue';
import BaseChart from '@/components/chart/BaseChart.vue';
import { machineApi } from './api';
export default defineComponent({
name: 'Monitor',
components: {
HomeCard,
ActivePlate,
ChartPie,
ChartFunnel,
ChartLine,
ChartGauge,
ChartBar,
ChartContinuou,
BaseChart,
},
props: {
machineId: {
type: Number,
},
},
setup(props: any) {
let timer = 0;
const state = reactive({
infoCardData: [
{
title: 'total task',
icon: 'md-person-add',
count: 0,
color: '#11A0F8',
},
{ title: '总内存', icon: 'md-locate', count: '', color: '#FFBB44 ' },
{
title: '可用内存',
icon: 'md-help-circle',
count: '',
color: '#7ACE4C',
},
{ title: '空闲交换空间', icon: 'md-share', count: 657, color: '#11A0F8' },
{
title: '使用中交换空间',
icon: 'md-chatbubbles',
count: 12,
color: '#91AFC8',
},
],
taskData: [
{ value: 0, name: '运行中', color: '#3AA1FFB' },
{ value: 0, name: '睡眠中', color: '#36CBCB' },
{ value: 0, name: '结束', color: '#4ECB73' },
{ value: 0, name: '僵尸', color: '#F47F92' },
],
memData: [
{ value: 0, name: '空闲', color: '#3AA1FFB' },
{ value: 0, name: '使用中', color: '#36CBCB' },
{ value: 0, name: '缓存', color: '#4ECB73' },
],
swapData: [
{ value: 0, name: '空闲', color: '#3AA1FFB' },
{ value: 0, name: '使用中', color: '#36CBCB' },
],
cpuData: [
{ value: 0, name: '用户空间', color: '#3AA1FFB' },
{ value: 0, name: '内核空间', color: '#36CBCB' },
{ value: 0, name: '改变优先级', color: '#4ECB73' },
{ value: 0, name: '空闲率', color: '#4ECB73' },
{ value: 0, name: '等待IO', color: '#4ECB73' },
{ value: 0, name: '硬中断', color: '#4ECB73' },
{ value: 0, name: '软中断', color: '#4ECB73' },
{ value: 0, name: '虚拟机', color: '#4ECB73' },
],
});
watch(props, (newValue, oldValue) => {
if (newValue.machineId) {
intervalGetTop();
}
});
onMounted(() => {
intervalGetTop();
});
onBeforeUnmount(() => {
cancelInterval();
});
const cancelInterval = () => {
clearInterval(timer);
timer = 0;
};
const startInterval = () => {
if (!timer) {
timer = setInterval(getTop, 3000) as any;
}
};
const intervalGetTop = () => {
getTop();
startInterval();
};
const getTop = async () => {
const topInfo = await machineApi.top.request({ id: props.machineId });
state.infoCardData[0].count = topInfo.totalTask;
state.infoCardData[1].count = Math.round(topInfo.totalMem / 1024) + 'M';
state.infoCardData[2].count = Math.round(topInfo.availMem / 1024) + 'M';
state.infoCardData[3].count = Math.round(topInfo.freeSwap / 1024) + 'M';
state.infoCardData[4].count = Math.round(topInfo.usedSwap / 1024) + 'M';
state.taskData[0].value = topInfo.runningTask;
state.taskData[1].value = topInfo.sleepingTask;
state.taskData[2].value = topInfo.stoppedTask;
state.taskData[3].value = topInfo.zombieTask;
state.memData[0].value = Math.round(topInfo.freeMem / 1024);
state.memData[1].value = Math.round(topInfo.usedMem / 1024);
state.memData[2].value = Math.round(topInfo.cacheMem / 1024);
state.cpuData[0].value = topInfo.cpuUs;
state.cpuData[1].value = topInfo.cpuSy;
state.cpuData[2].value = topInfo.cpuNi;
state.cpuData[3].value = topInfo.cpuId;
state.cpuData[4].value = topInfo.cpuWa;
state.cpuData[5].value = topInfo.cpuHi;
state.cpuData[6].value = topInfo.cpuSi;
state.cpuData[7].value = topInfo.cpuSt;
};
},
});
</script>
<style lang="scss">
.count-style {
font-size: 50px;
}
</style>

View File

@@ -11,6 +11,8 @@ export const machineApi = {
closeCli: Api.create("/machines/{id}/close-cli", 'delete'),
// 保存按钮
saveMachine: Api.create("/machines", 'post'),
// 调整状态
changeStatus: Api.create("/machines/{id}/{status}", 'put'),
// 删除机器
del: Api.create("/machines/{id}", 'delete'),
scripts: Api.create("/machines/{machineId}/scripts", 'get'),

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :show-close="false" :before-close="cancel" width="35%">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="35%">
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px">
<el-form-item prop="projectId" label="项目:" required>
<el-select style="width: 100%" v-model="form.projectId" placeholder="请选择项目" @change="changeProject" filterable>

View File

@@ -1,6 +1,6 @@
<template>
<div class="role-dialog">
<el-dialog :title="title" v-model="_visible" :show-close="false" :before-close="cancel" width="500px">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px">
<el-form :model="form" label-width="90px">
<el-form-item prop="name" label="角色名称:" required>
<el-input v-model="form.name" auto-complete="off"></el-input>
@@ -41,7 +41,7 @@ export default defineComponent({
},
setup(props: any, { emit }) {
const state = reactive({
_visible: false,
dvisible: false,
form: {
id: null,
name: '',
@@ -52,7 +52,7 @@ export default defineComponent({
});
watch(props, (newValue) => {
state._visible = newValue.visible;
state.dvisible = newValue.visible;
if (newValue.data) {
state.form = { ...newValue.data };
} else {