diff --git a/nezha-fronted/package-lock.json b/nezha-fronted/package-lock.json index 72d7a2fd9..733cf7072 100644 --- a/nezha-fronted/package-lock.json +++ b/nezha-fronted/package-lock.json @@ -3986,6 +3986,16 @@ "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", "dev": true }, + "clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -6121,6 +6131,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -8428,6 +8443,14 @@ "minimatch": "~3.0.2" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "requires": { + "delegate": "^3.1.2" + } + }, "got": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", @@ -16336,6 +16359,11 @@ } } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -17807,6 +17835,11 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tinycolor2": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", @@ -18486,6 +18519,14 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz", "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==" }, + "vue-clipboard2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/vue-clipboard2/-/vue-clipboard2-0.3.3.tgz", + "integrity": "sha512-aNWXIL2DKgJyY/1OOeITwAQz1fHaCIGvUFHf9h8UcoQBG5a74MkdhS/xqoYe7DNZdQmZRL+TAdIbtUs9OyVjbw==", + "requires": { + "clipboard": "^2.0.0" + } + }, "vue-color": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.1.tgz", diff --git a/nezha-fronted/src/assets/css/common.scss b/nezha-fronted/src/assets/css/common.scss index 166462773..206e19ee5 100644 --- a/nezha-fronted/src/assets/css/common.scss +++ b/nezha-fronted/src/assets/css/common.scss @@ -476,3 +476,8 @@ textarea { .ql-tooltip { background: $--background-color-empty; } + + +.chart-time-series.hide{ + display: none !important; +} diff --git a/nezha-fronted/src/assets/css/common/tableCommon.scss b/nezha-fronted/src/assets/css/common/tableCommon.scss index 7d70ffce7..deb815e7b 100644 --- a/nezha-fronted/src/assets/css/common/tableCommon.scss +++ b/nezha-fronted/src/assets/css/common/tableCommon.scss @@ -88,12 +88,15 @@ background-color: $--background-color-empty; transition:all .2s; color: $--button-icon-color; - i { font-size: 14px; color: $--button-icon-color; } } + .top-tool-btn.active{ + background-color: $--button-icon-active-background-color; + border: 1px solid $--button-icon-active-border-color !important; + } .top-tool-btn.top-tool-btn--text { padding: 0 8px; width: unset; diff --git a/nezha-fronted/src/assets/css/components/common/rightBox/panelBox.scss b/nezha-fronted/src/assets/css/components/common/rightBox/panelBox.scss new file mode 100644 index 000000000..86d720ff9 --- /dev/null +++ b/nezha-fronted/src/assets/css/components/common/rightBox/panelBox.scss @@ -0,0 +1,217 @@ +.right-box-panel{ + .item-receivers{ + .el-select.el-select--small{ + width: 100%; + } + .item-receivers-text{ + color: $--color-text-secondary; + } + } + .el-input--suffix.el-date-editor--datetime .el-input__inner{ + padding-left: 15px; + } + .form-items--half-width-group{ + .item-receivers{ + width: 100%; + .el-select.el-select--small{ + width: 100%; + } + } + .form-item--half-width{ + .el-date-editor.el-date-editor--datetime{ + width: 100%; + .el-input__inner{ + height: 32px; + } + } + } + .check-month_box{ + .el-checkbox-group{ + display: flex; + justify-content: start; + align-items: center; + flex-wrap: wrap; + .el-checkbox-button{ + display: flex; + justify-content: center; + align-items: center; + width: calc(100% / 7); + margin-right: -2px; + margin-bottom: -1px; + border: 1px solid $--border-color-light; + .el-checkbox-button__inner{ + border: 0px; + border-left: 0px !important; + border-bottom: 0px !important; + display: block; + width: 100%; + height: 100%; + background-color: $--background-color-empty; + border-radius: 0; + } + } + } + + .el-checkbox-button.is-checked .el-checkbox-button__inner { + color: #fff; + border-radius: 0; + background-color: $--color-primary !important; + } + } + .range-time{ + width: 100%; + .el-form-item__content{ + height: 41px; + .el-radio-group{ + height: 100%; + border-left: 1px solid $--border-color-light; + .el-radio-button__inner{ + height: 100%; + line-height: 1.5; + font-size: 14px; + color: $--color-text-primary; + } + } + .el-checkbox-group{ + height: 100%; + .el-checkbox-button{ + margin-right: -2px; + .el-checkbox-button__inner{ + background-color: $--background-color-empty; + border:1px solid $--border-color-light; + } + } + } + } + .el-radio-group{ + display: flex; + border-left: 0; + .el-radio-button{ + flex: 1; + } + } + + .el-checkbox-button.is-checked .el-checkbox-button__inner { + color: #fff; + border-radius: 0; + background-color: $--color-primary !important; + border: 1px solid $--color-primary !important; + // margin-right: -1px; + } + } + .form-tabs{ + width: 100%; + .el-tabs__nav{ + display: flex; + .el-tabs__item{ + flex: 1; + } + .el-checkbox-button.is-checked .el-checkbox-button__inner { + color: #fff; + border-radius: 0; + background-color: $--color-primary !important; + border: 1px solid $--color-primary !important; + // margin-right: -1px; + } + } + } + .form-day-week{ + width: 100%; + .el-input-group__append{ + border: 1px solid $--border-color-light; + background-color: $--right-box-sub-title-background-color; + } + } + } + .variable-box{ + margin-bottom: 16px; + .variable-title{ + border: 1px solid $--border-color-light; + border-radius: 2px; + position: relative; + display: flex; + padding: 0; + padding-left: 5px; + line-height: 30px; + justify-content: space-between; + margin-bottom: 10px; + box-sizing: border-box; + height: 32px; + .variable-title-left{ + width: 450px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + .nz-icon-arrow-down{ + display: inline-block; + transition: transform .3s; + cursor: pointer; + } + .nz-icon-arrow-down.is-active{ + transform: rotate(-90deg ); + } + &>span{ + font-size: 14px; + color: $--color-text-regular; + } + } + .nz-icon{ + cursor: pointer; + color: $--color-text-primary; + } + .nz-icon-mimabukejian{ + font-size: 18px; + } + } + .variable-title{ + margin-bottom: 0; + margin-top: 10px; + } + .add-variable{ + border: 1px solid $--border-color-light; + border-radius: 2px; + text-align: center; + margin-bottom: 10px; + padding: 3px 10px; + color: $--color-text-regular; + font-size: 14px; + cursor: pointer; + margin-top: 16px; + } + } + + } + +.el-form-item__content{ + text-align: unset !important; + .el-checkbox-button{ + width: calc(100% / 7) !important; + .el-checkbox-button__inner{ + width: 100%; + padding: 11px 3px; + } + + } + .el-checkbox-button.is-focus .el-checkbox-button__inner{ + border-color: $--border-color-base; + } + + .el-tabs__item.is-top.is-active{ + color: #fff !important; + background-color: $--color-primary !important; + } + .el-radio-button.el-radio-button--small.is-active{ + .el-radio-button__inner{ + color: #fff !important; + } + } +} + +.multiTip{ + max-width:200px; +} +.allOptionTip{ + max-width:208px; +} + + \ No newline at end of file diff --git a/nezha-fronted/src/assets/css/components/index.scss b/nezha-fronted/src/assets/css/components/index.scss index 1f13dcb64..17add18bb 100644 --- a/nezha-fronted/src/assets/css/components/index.scss +++ b/nezha-fronted/src/assets/css/components/index.scss @@ -51,6 +51,7 @@ @import './common/rightBox/chartRightBox/chartRightBox.scss'; @import './common/rightBox/mibBox.scss'; @import './common/rightBox/assetBactchEditBox.scss'; +@import './common/rightBox/panelBox.scss'; @import './common/table/alert/alertMessageTable.scss'; @import './common/table/alert/alertRuleTable.scss'; @import './common/table/alert/alertSilenceTable.scss'; diff --git a/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue b/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue index 7b83c7dfc..30e8b5f0c 100644 --- a/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue +++ b/nezha-fronted/src/components/chart/chart/chartTimeSeries.vue @@ -26,7 +26,7 @@ import { formatScientificNotation } from '@/components/common/js/tools' import chartDataFormat from '@/components/chart/chartDataFormat' import { randomcolor } from '@/components/common/js/radomcolor/randomcolor' import { chartLegendPlacement } from '@/components/common/js/constants' -import { getChart, setChart } from '@/components/common/js/common' +import { getChart, setChart, chartCache } from '@/components/common/js/common' import { initColor } from '@/components/chart/chart/tools' import lodash from 'lodash' @@ -69,6 +69,36 @@ export default { } } }, + watch: { + // 监听当前鼠标所在的图表id变化 + '$store.state.panel.currentMousemove': { + handler (n) { + // 判断是否是当前鼠标所在的图表 + if (n === this.chartId) { + let option = {} + for (const key in chartCache) { + if (!chartCache[key] || chartCache[key].group !== 'timeSeriesGroup') { + continue + } + if (chartCache[key] === getChart(this.chartId)) { + option = { + tooltip: { + className: 'chart-time-series' + } + } + } else { + option = { + tooltip: { + className: 'chart-time-series hide' + } + } + } + chartCache[key].setOption(option) + } + } + } + } + }, methods: { initChart (chartOptions = this.chartOption) { try { @@ -122,6 +152,22 @@ export default { } myChart.setOption(chartOption) this.isInit && setChart(this.chartId, myChart) // 缓存;不使用vue的data是为避免整个chart被监听导致卡顿 + + this.$store.commit('setCurrentMousemove', 0) + if (this.isInit) { + // timeSeries类型图表设置group 用于多表联动 + myChart.group = 'timeSeriesGroup' + myChart.getZr().on('mousemove', params => { + if (this.$store.state.panel.isConnect !== 'crosshair') { + return false + } + // crosshair 模式才会执行 + if (this.$store.state.panel.currentMousemove !== this.chartId) { + this.$store.commit('setCurrentMousemove', this.chartId) + } + }) + } + this.isInit = false }, 200) }, @@ -191,7 +237,7 @@ export default { return { minTime, maxTime, minValue, maxValue, copies, unit, dot } }, xAxisLabelFormatter (minTime, maxTime) { - let self = this + const self = this return function (val, index) { const value = val * 1000 let offset = localStorage.getItem('nz-sys-timezone') diff --git a/nezha-fronted/src/components/chart/chart/legend.vue b/nezha-fronted/src/components/chart/chart/legend.vue index 39aecf264..d9aade502 100644 --- a/nezha-fronted/src/components/chart/chart/legend.vue +++ b/nezha-fronted/src/components/chart/chart/legend.vue @@ -44,6 +44,8 @@ import lodash from 'lodash' import { getChart } from '@/components/common/js/common' import chartDataFormat from '@/components/chart/chartDataFormat' import { statisticsList } from '@/components/common/js/constants' +import * as chart from 'echarts' +import { isTimeSeries } from './tools' export default { name: 'chartLegend', props: { @@ -65,6 +67,10 @@ export default { computed: { isStatistics () { return !lodash.isEmpty(this.chartInfo.param.legend.values) + }, + // timeSeries类型图表联动 + isConnect () { + return this.$store.state.panel.isConnect } }, methods: { @@ -94,6 +100,10 @@ export default { } if (echarts) { + // 判断timeSeries类型图表 先取消多表联动 + if (isTimeSeries(this.chartInfo.type) && (this.isConnect && this.isConnect !== 'none')) { + chart.disconnect('timeSeriesGroup') + } if (!hasGrey) { // 1.除当前legend外全置灰 echarts.dispatchAction({ type: 'legendInverseSelect' @@ -116,6 +126,10 @@ export default { }) this.$set(this.isGrey, index, !this.isGrey[index]) } + // 判断timeSeries类型图表 建立多表联动 + if (isTimeSeries(this.chartInfo.type) && (this.isConnect && this.isConnect !== 'none')) { + chart.connect('timeSeriesGroup') + } if (this.chartInfo.type !== 'pie' && this.chartInfo.type !== 'bar' && this.chartInfo.type !== 'treemap') { // 处理点击后的 Y轴 const chartInfo = this.chartInfo diff --git a/nezha-fronted/src/components/chart/chart/options/chartTimeSeries.js b/nezha-fronted/src/components/chart/chart/options/chartTimeSeries.js index fc47e9bf8..3fcf01ce5 100644 --- a/nezha-fronted/src/components/chart/chart/options/chartTimeSeries.js +++ b/nezha-fronted/src/components/chart/chart/options/chartTimeSeries.js @@ -32,14 +32,20 @@ export const chartTimeSeriesLineOption = { } }, tooltip: { + show: true, trigger: 'axis', confine: false, extraCssText: 'z-index:99999999;', z: 9, animation: false, appendToBody: true, - className: 'chart-time-series' - // formatter: 动态生成 + className: 'chart-time-series', + axisPointer: { + type: 'cross', // 十字准星指示器,表示启用两个正交的轴的 axisPointer + label: { + show: false + } + } }, color: initColor(), grid: { @@ -61,9 +67,9 @@ export const chartTimeSeriesLineOption = { fontSize: 10 // formatter: 动态生成 }, - axisPointer: { // y轴上显示指针对应的值 - show: true - }, + // axisPointer: { // y轴上显示指针对应的值 + // show: true + // }, splitLine: { show: true, lineStyle: { diff --git a/nezha-fronted/src/components/common/js/common.js b/nezha-fronted/src/components/common/js/common.js index 5ccc57a0b..91099ac62 100644 --- a/nezha-fronted/src/components/common/js/common.js +++ b/nezha-fronted/src/components/common/js/common.js @@ -16,7 +16,7 @@ export function getUUID () { return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()) } -const chartCache = {} +export const chartCache = {} export function getChart (key) { return chartCache[`chart${key}`] diff --git a/nezha-fronted/src/components/common/rightBox/chart/publicConfig.js b/nezha-fronted/src/components/common/rightBox/chart/publicConfig.js index 05553f0a0..6d2b9667b 100644 --- a/nezha-fronted/src/components/common/rightBox/chart/publicConfig.js +++ b/nezha-fronted/src/components/common/rightBox/chart/publicConfig.js @@ -362,7 +362,7 @@ export default { showInput (index, flag) { this.expressionsShow[index].hideInput = flag }, - transformNumToLetter (num) { // 相当于26进制 获取id + transformNumToLetter (num) { // 相当于26进制 获取idaddExpression const self = this let letter = '' const loopNum = parseInt(num / 26) diff --git a/nezha-fronted/src/components/common/rightBox/panelBox.vue b/nezha-fronted/src/components/common/rightBox/panelBox.vue index 721426ec2..b0d1661d8 100644 --- a/nezha-fronted/src/components/common/rightBox/panelBox.vue +++ b/nezha-fronted/src/components/common/rightBox/panelBox.vue @@ -14,6 +14,261 @@ + + + + + + + + + + + {{$t('config.assetLabel.default')}} + + + {{$t('dashboard.panel.crosshair')}} + + + {{$t('dashboard.panel.shareTooltip')}} + + +
{{$t('dashboard.panel.chartTooltipText')}}
+
+ + +
+
+ {{$t('dashboard.panel.variables')}} +
+ +
+ + + {{item.name}} + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {{$t('dashboard.panel.multiTip')}} + + {{$t('dashboard.panel.multi')}} + + + +
+ + + + +
+ + +
+ + {{$t('dashboard.panel.allOptionTip')}} + + {{$t('dashboard.panel.allOption')}} + + + +
+ + + + +
+
+
+
+
{{$t('dashboard.panel.addVariable')}}
+
+ + +
+
+ {{$t('dashboard.panel.report')}} + +
+
+ +
+ + + + + + + + + + + {{ $t("dashboard.panel.prevDay") }} + {{ $t("dashboard.panel.prevWeek") }} + {{ $t("dashboard.panel.prevMonth") }} + + + + + + + + + + + + + + + + + + + + {{item}} + + + + + {{ item }} + + + + + + {{item.name}} + + + +
+
+
+
+ @@ -32,6 +287,8 @@ diff --git a/nezha-fronted/src/store/panel.js b/nezha-fronted/src/store/panel.js index 2fe6a2cc1..b3c167f31 100644 --- a/nezha-fronted/src/store/panel.js +++ b/nezha-fronted/src/store/panel.js @@ -20,7 +20,11 @@ const panel = { topologyChartInfo: {}, // 查看模式 mode: '', - addCabinetBoxParams: { dcid: '', cabinetBoxShow: false } + addCabinetBoxParams: { dcid: '', cabinetBoxShow: false }, + // timeSeries类型图表联动 + isConnect: 'none', + // 当前鼠标所在的图表id + currentMousemove: 0 }, mutations: { setShowRightBox (state, flag) { @@ -77,6 +81,14 @@ const panel = { // 设置查看模式 setMode (state, mode) { state.mode = mode + }, + // 设置timeSeries类型图表联动 + setConnect (state, value) { + state.isConnect = value + }, + // 设置当前鼠标所在的图表id (timeSeries类型图表联动) + setCurrentMousemove (state, value) { + state.currentMousemove = value } }, getters: {