diff --git a/.eslintrc.js b/.eslintrc.js index 028cfe19..1410f964 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,8 +15,8 @@ module.exports = { eqeqeq: 0, // 关闭必须使用全等 'no-extend-native': 0, 'vue/no-parsing-error': 0, // 关闭此项避免在{{}}中使用>、<号导致报错的问题 - 'vue/no-use-v-if-with-v-for': 0, // vue2暂时关闭v-if和v-for写在一起的错误提示,到vue3后要遵守 'no-useless-escape': 0, - 'no-eval': 0 + 'no-eval': 0, + 'no-trailing-spaces': 0 } } diff --git a/README.md b/README.md index 4c33c21a..2356879c 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ https://www.lodashjs.com/ 多个单词时,应该以高阶的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾 eg:`SearchButtonClear.vue` 反例:`ClearSearchButton.vue` - **文件夹** -使用小写,单词间使用连字符`-`连接 -eg:`right-box` +使用小写,单词间使用连字符`-`连接,或使用驼峰格式 +eg:`right-box`、`rightBox` - **VUE组件文件** @@ -99,4 +99,4 @@ eg: `el.style.border = 'xxx'` - **布局** -避免使用float,视情况使用position,建议使用flex和grid \ No newline at end of file +避免使用float,视情况使用position,建议使用flex和grid diff --git a/src/assets/css/common.scss b/src/assets/css/common.scss index 7d9afa68..d8f2c8c7 100644 --- a/src/assets/css/common.scss +++ b/src/assets/css/common.scss @@ -1,3 +1,5 @@ +/* 不含主题变量的通用css */ + [v-cloak] { display: none !important; } @@ -14,3 +16,8 @@ body { cursor: default !important; overflow: hidden; } +.temp-dom { + visibility: hidden; + font-size: 14px; + position: fixed; +} diff --git a/src/assets/css/common/index.scss b/src/assets/css/common/index.scss index 5ed92762..f42f1269 100644 --- a/src/assets/css/common/index.scss +++ b/src/assets/css/common/index.scss @@ -1,2 +1,12 @@ -@import './rightBoxCommon'; -@import './tableCommon'; +@import 'right-box-common'; +@import 'table-common'; + +/* 通用css */ +.panel-chart__no-data { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + color: #999; +} diff --git a/src/assets/css/common/rightBoxCommon.scss b/src/assets/css/common/right-box-common.scss similarity index 100% rename from src/assets/css/common/rightBoxCommon.scss rename to src/assets/css/common/right-box-common.scss diff --git a/src/assets/css/common/tableCommon.scss b/src/assets/css/common/table-common.scss similarity index 100% rename from src/assets/css/common/tableCommon.scss rename to src/assets/css/common/table-common.scss diff --git a/src/assets/css/components/components/advancedSearch/advancedSearch.scss b/src/assets/css/components/components/advancedSearch/advanced-search.scss similarity index 100% rename from src/assets/css/components/components/advancedSearch/advancedSearch.scss rename to src/assets/css/components/components/advancedSearch/advanced-search.scss diff --git a/src/assets/css/components/components/charts/panel.scss b/src/assets/css/components/components/charts/panel.scss index 7adea929..b5d9ed9a 100644 --- a/src/assets/css/components/components/charts/panel.scss +++ b/src/assets/css/components/components/charts/panel.scss @@ -728,6 +728,7 @@ .header__operation-btn { margin-left: 12px; cursor: pointer; + color: #999; } .ip-detail-as { color: #999; diff --git a/src/assets/css/components/components/common/TimeRange/dateTimeRange.scss b/src/assets/css/components/components/common/TimeRange/date-time-range.scss similarity index 100% rename from src/assets/css/components/components/common/TimeRange/dateTimeRange.scss rename to src/assets/css/components/components/common/TimeRange/date-time-range.scss diff --git a/src/assets/css/components/components/common/TimeRange/timeRefresh.scss b/src/assets/css/components/components/common/TimeRange/time-refresh.scss similarity index 100% rename from src/assets/css/components/components/common/TimeRange/timeRefresh.scss rename to src/assets/css/components/components/common/TimeRange/time-refresh.scss diff --git a/src/assets/css/components/components/rightBox/settings/chartBox.scss b/src/assets/css/components/components/rightBox/settings/chart-box.scss similarity index 100% rename from src/assets/css/components/components/rightBox/settings/chartBox.scss rename to src/assets/css/components/components/rightBox/settings/chart-box.scss diff --git a/src/assets/css/components/components/setting/galaxyProxyDebug.scss b/src/assets/css/components/components/setting/galaxy-proxy-debug.scss similarity index 100% rename from src/assets/css/components/components/setting/galaxyProxyDebug.scss rename to src/assets/css/components/components/setting/galaxy-proxy-debug.scss diff --git a/src/assets/css/components/components/table/settings/galaxyProxyTable.scss b/src/assets/css/components/components/table/settings/galaxy-proxy-table.scss similarity index 100% rename from src/assets/css/components/components/table/settings/galaxyProxyTable.scss rename to src/assets/css/components/components/table/settings/galaxy-proxy-table.scss diff --git a/src/assets/css/components/index.scss b/src/assets/css/components/index.scss index ce73b892..a59ef8b2 100644 --- a/src/assets/css/components/index.scss +++ b/src/assets/css/components/index.scss @@ -1,20 +1,22 @@ -@import './components/advancedSearch/advancedSearch'; +@import 'components/advancedSearch/advanced-search'; @import './components/charts/panel'; -@import './components/common/TimeRange/dateTimeRange'; -@import './components/common/TimeRange/timeRefresh'; +@import 'components/common/TimeRange/date-time-range'; +@import 'components/common/TimeRange/time-refresh'; @import './components/common/pagination'; // @import './components/entities/entities'; @import './components/layout/layout'; -@import './components/rightBox/settings/chartBox'; -@import './components/setting/galaxyProxyDebug'; -@import './components/table/settings/galaxyProxyTable'; +@import 'components/rightBox/settings/chart-box'; +@import 'components/setting/galaxy-proxy-debug'; +@import 'components/table/settings/galaxy-proxy-table'; @import './components/table/common'; @import './views/charts/chart'; -@import './views/entityExplorer/entityExplorer'; -@import './views/entityExplorer/search/explorerSearch'; -@import './views/entityExplorer/entityFilter'; -@import './views/entityExplorer/entityDetail'; -@import './views/entityExplorer/entityList/entityList'; +@import 'views/entityExplorer/entity-explorer'; +@import 'views/entityExplorer/search/explorer-search'; +@import 'views/entityExplorer/entity-filter'; +@import 'views/entityExplorer/entity-detail'; +@import 'views/entityExplorer/entityList/entity-list'; @import './views/entityExplorer/entityList/card'; @import './views/entityExplorer/entityList/row'; -@import './views/entityExplorer/entityList/detailOverview'; +@import 'views/entityExplorer/entityList/detail-overview'; +@import './views/charts/panel'; +//@import '../chart'; diff --git a/src/assets/css/components/views/charts/chart.scss b/src/assets/css/components/views/charts/chart.scss index 0ab4d78e..2debd81d 100644 --- a/src/assets/css/components/views/charts/chart.scss +++ b/src/assets/css/components/views/charts/chart.scss @@ -375,12 +375,11 @@ } .chart__loading { position: absolute; - height: calc(100% - 47px); - top: 47px; + height: 100%; width: 100%; background-color: #fefefe; z-index: 1; - opacity: .9; + opacity: .8; i { position: absolute; @@ -390,3 +389,12 @@ color: #aaa; } } + +.map-back { + position: absolute; + right: 12px; + top: 15px; + color: $--color-primary; + cursor: pointer; + z-index: 2; +} diff --git a/src/assets/css/components/views/charts/panel.scss b/src/assets/css/components/views/charts/panel.scss new file mode 100644 index 00000000..4261cd0f --- /dev/null +++ b/src/assets/css/components/views/charts/panel.scss @@ -0,0 +1,636 @@ +.cn-panel2 { + position: relative; + + .panel__time { + position: absolute; + right: 10px; + top: 10px; + z-index: 1; + display: flex; + + &>div { + margin-left: 10px; + } + } + .chart-list { + &>.vue-grid-layout>.vue-grid-item { + &>.panel-chart { + border: 1px solid $--chart-box-border-color; + background-color: #FFFFFF; + box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02); + height: 100%; + display: flex; + position: relative; + flex-direction: column; + + &.panel-chart--title-chart { + border: none; + background-color: transparent; + box-shadow: none; + } + .chart-header { + display: flex; + justify-content:space-between; + align-items:center; + padding: 10px 20px 10px 18px; + height: 47px; + + font-size: 16px; + color: $--color-text-primary; + transition: all 0.2s; + + &.chart-header--float { + position: absolute; + width: 100%; + z-index: 100; + box-sizing: border-box; + height: 10px; + opacity: 0; + transition: all linear .2s; + + &:hover { + height: 47px; + opacity: 1; + } + } + &.chart-header--title-chart { + font-size: 20px; + color: #333; + padding: 0; + } + /*&:hover { + background-color: $--chart-title-hover-background-color; + + .chart-header__tools { + .chart-header__tool .tool__icon { + visibility: visible; + } + } + }*/ + .chart-header__title { + max-width: calc(100% - 100px); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + .chart-header__tools { + display: flex; + justify-content: space-between; + align-items: center; + + .chart-header__tool { + margin-left: 20px; + cursor: pointer; + + .tool__icon { + // visibility: hidden; + font-size: 14px; + color: $--color-text-primary; + } + .nz-chart-dropdown { + position: absolute; + top: 44px; + right: 0; + left: unset; + transform-origin: center top; + z-index: 1000; + width: 180px; + li { + padding-left: 15px !important; + padding-right: 0 !important; + width: calc(100% - 15px); + text-align: left; + i { + margin-right: 10px; + } + &:hover i { + color: $--color-primary; + } + } + } + } + } + .chart-header-error{ + position: absolute; + left: 0; + top: -1px; + } + } + .chart-screen-header.list-page{ + background: $--background-color-empty; + } + .chart-screen-header { + display: flex; + justify-content:space-between; + align-items:center; + padding: 0 20px 0 20px; + height: 39px; + font-size: 14px; + line-height: 40px; + color: $--color-text-primary; + transition: all 0.2s; + width: 100%; + box-sizing: border-box; + margin-top: 15px; + &.chart-header--float { + position: absolute; + width: 100%; + z-index: 100; + box-sizing: border-box; + height: 10px; + opacity: 0; + transition: all linear .2s; + + &:hover { + height: 39px; + opacity: 1; + } + } + .chart-header__title { + max-width: calc(100% - 100px); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + font-size: 18px; + color: #333; + font-weight: 700; + } + .chart-header__tools { + display: flex; + justify-content: space-between; + align-items: center; + + .chart-header__tool { + margin-left: 20px; + cursor: pointer; + + .tool__icon { + visibility: hidden; + font-size: 14px; + color: $--color-text-primary; + } + .nz-chart-dropdown { + position: absolute; + top: 44px; + right: 0; + left: unset; + transform-origin: center top; + z-index: 1000; + width: 180px; + li { + padding-left: 15px !important; + padding-right: 0 !important; + width: calc(100% - 15px); + text-align: left; + i { + margin-right: 10px; + } + &:hover i { + color: $--color-primary; + } + } + } + } + } + .chart-header-error{ + position: absolute; + left: 0; + top: -1px; + } + } + &>.cn-chart { + position: relative; + border-radius: 2px; + height: 100%; + width: 100%; + + .chart-drawing { + height: 100%; + width: 100%; + } + } + &>.cn-chart__whois>.cn-chart__body { + overflow: auto; + } + &>.cn-chart__echarts, &>.cn-chart__table, &>.cn-chart__map, &>.cn-chart__group, &>.cn-chart__block, &>.cn-chart__whois, &>.cn-chart__dns-record, &>.cn-chart__app-basic { + display: flex; + flex-direction: column; + .cn-chart__header { + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + padding: 10px 20px 10px 18px; + height: 47px; + + .cn-chart__title { + font-size: 16px; + color: #333333; + font-weight: bold; + } + .header__operations { + color: #999; + } + } + .cn-chart__body { + flex: auto; + display: flex; + .el-descriptions { + padding-top: 30px; + } + &>.el-descriptions { + flex: 0 0 350px; + padding: 30px 36px; + } + .chart-location { + display: flex; + flex: 1; + flex-direction: column; + padding: 0 20px 20px 0; + } + .el-descriptions__content { + color: #3976CB; + } + } + } + &>.cn-chart__block { + &>.cn-chart__header { + height: 60px; + border-bottom: none !important; + } + &>.cn-chart__body { + display: grid !important; + grid-template-columns: repeat(30, 1fr); + grid-auto-flow: row; + grid-gap: 10px; + padding: 0 20px; + &>.cn-chart { + border: 1px solid #E7EAED; + } + /* detail页面block下的五连图的标题样式改变 */ + .cn-chart__group .cn-chart__echarts { + .cn-chart__header { + border-bottom: none !important; + + .header__title { + font-size: 14px !important; + color: #3976CB !important; + } + } + } + } + } + .cn-chart__group { + .cn-chart__header { + border-bottom: 1px solid $--content-right-background-color; + } + &>.cn-chart__body { + display: grid !important; + grid-gap: 10px; + padding: 0 20px; + .cn-chart { + border: none; + box-shadow: none; + } + } + } + &>.cn-chart__title { + display: flex; + align-items: center; + font-size: 20px; + padding-left: 10px; + color: #333; + background-color: transparent; + box-shadow: none; + border: none; + } + &>.cn-chart__tabs { + padding: 10px 25px 10px 15px; + + .el-tabs__nav-wrap::after { + height: 1px; + } + &>.el-tabs__header { + margin-bottom: 10px; + } + &>.el-tabs__content { + height: calc(100% - 40px); + } + } + &>.cn-chart__table { + .cn-chart__header { + border-bottom: 1px solid $--content-right-background-color; + .header__operations { + display: flex; + justify-content: end; + align-items: center; + + .header__operation.header__operation--table { + display: flex; + align-items: center; + height: 22px; + margin-left: 10px; + color: $--color-primary; + border: 1px solid $--color-primary; + border-radius: $--border-radius-primary; + + .option__button { + display: flex; + align-items: center; + height: 100%; + padding: 0 5px; + cursor: pointer; + background-color: white; + transition: all linear .2s; + } + .option__button:hover { + background-color: #EFF2F5; + } + .option__button.icon-group-item:first-of-type:not(:last-of-type) { + padding: 0 5px 0 0; + } + .option__button.icon-group-item:last-of-type:not(:first-of-type) { + padding: 0 0 0 5px; + } + .option__select { + .el-input__inner { + width: 80px; + padding-right: 20px; + border: none; + height: 100%; + line-height: 20px; + color: $--color-primary; + } + .el-input__prefix > div { + font-weight: normal; + line-height: 19px; + color: $--color-primary; + } + .el-input__suffix { + display: flex; + .el-input__suffix-inner { + line-height: 14px; + .el-select__caret { + line-height: 14px; + width: 16px; + color: $--color-primary; + } + } + } + } + .option__select.select-column { + .el-input__inner { + width: 86px; + padding-left: 8px; + } + } + .icon-group-divide { + height: 14px; + width: 1px; + background-color: $--color-primary; + } + i { + font-size: 12px; + } + } + } + } + .cn-chart__body { + flex: auto; + overflow-y: auto; + + .el-table { + padding: 0 10px; + + &:before { + height: 0; + } + thead { + color: #333; + } + th.is-leaf, td { + border-bottom: none; + } + th { + padding-bottom: 5px; + } + td { + padding: 4px 0; + color: #333; + } + } + } + } + &>.cn-chart__echarts { + .cn-chart__header { + border-bottom: 1px solid $--content-right-background-color; + .header__operations { + display: flex; + justify-content: end; + align-items: center; + + .header__operation.header__operation--echarts { + display: flex; + align-items: center; + height: 22px; + margin-left: 10px; + color: $--color-primary; + border: 1px solid $--color-primary; + border-radius: $--border-radius-primary; + + .option__button { + display: flex; + align-items: center; + height: 100%; + padding: 0 5px; + cursor: pointer; + background-color: white; + transition: all linear .2s; + } + .option__button:hover { + background-color: #EFF2F5; + } + .option__button.icon-group-item:first-of-type:not(:last-of-type) { + padding: 0 5px 0 0; + } + .option__button.icon-group-item:last-of-type:not(:first-of-type) { + padding: 0 0 0 5px; + } + .option__select { + .el-input__inner { + width: 120px; + padding-right: 20px; + border: none; + height: 100%; + line-height: 20px; + color: $--color-primary; + } + .el-input__prefix > div { + font-weight: normal; + line-height: 19px; + color: $--color-primary; + } + .el-input__suffix { + display: flex; + .el-input__suffix-inner { + line-height: 14px; + .el-select__caret { + line-height: 14px; + width: 16px; + color: $--color-primary; + } + } + } + } + .option__select.select-column { + .el-input__inner { + width: 86px; + padding-left: 8px; + } + } + .icon-group-divide { + height: 14px; + width: 1px; + background-color: $--color-primary; + } + i { + font-size: 12px; + } + } + } + } + .cn-chart__body { + overflow: hidden auto; + + .el-table { + padding: 0 10px; + + &:before { + height: 0; + } + thead { + color: #333; + } + th.is-leaf, td { + border-bottom: none; + } + th { + padding-bottom: 5px; + } + td { + padding: 4px 0; + color: #333; + } + } + } + .cn-chart__body.pie-with-table { + flex-basis: 40%; + } + .cn-chart__footer.pie-with-table { + flex-basis: 60%; + padding: 10px 30px 30px; + } + } + .pie-table { + font-size: 14px; + color: #333333; + font-weight: 500; + + .el-table__header-wrapper { + .cell { + color: #333; + } + } + .el-table__expanded-cell[class*=cell] { + padding: 0; + } + + .expand-table .el-table__body .el-table__row:last-of-type td { + border: none; + } + .expand-table { + font-weight: 400; + color: #606266; + + .el-table__body-wrapper { + height: auto !important; + } + } + } + .chart__legend { + width: calc(100% - 40px); + border: 1px solid #E7EAED; + color: #5f6368; + margin: auto; + margin-bottom: 15px; + + .chart__table-top { + width: 100%; + height: 30px; + border-bottom: #E7EAED 1px solid; + display: flex; + + div { + font-size: 13px; + line-height: 28px; + color: $--color-primary; + } + } + .chart__table-below { + height: 240px; + width: 100%; + font-size: 13px; + } + .table-below-box { + width: 100%; + display: flex; + align-items: center; + line-height: 24px; + } + .table-below-box:hover { + background-color: #f9f9f9; + border: 0; + color: #383838; + } + .table__below-color { + width: 27px; + height: 7px; + flex-shrink: 0; + padding-left: 10px; + + div { + height: 100%; + width: 100%; + border-radius: 24%; + } + } + .table__below-title { + padding: 0 6px; + flex-shrink: 1; + flex-grow: 1; + overflow: hidden; + min-width: 200px; + text-overflow: ellipsis; + white-space: nowrap; + } + .table__below-statistics { + width: 80px; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .table-below-box:not(.chart__table-top) { + cursor: pointer; + } + .table-below-box.table-below-box--inactivated { + color: #ccc; + .table__below-color div { + background-color: #ccc !important; + } + } + } + } + } + } +} diff --git a/src/assets/css/components/views/entityExplorer/entityDetail.scss b/src/assets/css/components/views/entityExplorer/entity-detail.scss similarity index 100% rename from src/assets/css/components/views/entityExplorer/entityDetail.scss rename to src/assets/css/components/views/entityExplorer/entity-detail.scss diff --git a/src/assets/css/components/views/entityExplorer/entityExplorer.scss b/src/assets/css/components/views/entityExplorer/entity-explorer.scss similarity index 100% rename from src/assets/css/components/views/entityExplorer/entityExplorer.scss rename to src/assets/css/components/views/entityExplorer/entity-explorer.scss diff --git a/src/assets/css/components/views/entityExplorer/entityFilter.scss b/src/assets/css/components/views/entityExplorer/entity-filter.scss similarity index 100% rename from src/assets/css/components/views/entityExplorer/entityFilter.scss rename to src/assets/css/components/views/entityExplorer/entity-filter.scss diff --git a/src/assets/css/components/views/entityExplorer/entityList/detailOverview.scss b/src/assets/css/components/views/entityExplorer/entityList/detail-overview.scss similarity index 100% rename from src/assets/css/components/views/entityExplorer/entityList/detailOverview.scss rename to src/assets/css/components/views/entityExplorer/entityList/detail-overview.scss diff --git a/src/assets/css/components/views/entityExplorer/entityList/entityList.scss b/src/assets/css/components/views/entityExplorer/entityList/entity-list.scss similarity index 100% rename from src/assets/css/components/views/entityExplorer/entityList/entityList.scss rename to src/assets/css/components/views/entityExplorer/entityList/entity-list.scss diff --git a/src/assets/css/components/views/entityExplorer/search/explorerSearch.scss b/src/assets/css/components/views/entityExplorer/search/explorer-search.scss similarity index 100% rename from src/assets/css/components/views/entityExplorer/search/explorerSearch.scss rename to src/assets/css/components/views/entityExplorer/search/explorer-search.scss diff --git a/src/assets/css/theme.scss b/src/assets/css/theme.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/assets/css/themes/THEME_README.md b/src/assets/css/themes/THEME_README.md index 2fd9984d..bf66b293 100644 --- a/src/assets/css/themes/THEME_README.md +++ b/src/assets/css/themes/THEME_README.md @@ -9,3 +9,5 @@ ### 源码改动 - theme-chalk/src/common/var.scss 首行增加$--theme属性 - theme-chalk/src/mixins/mixins.scss 改动较多,如需了解请比对文件差异 +- // $arrow-selector: #{& + '__arrow'}; + 改为 $arrow-selector: '.el-popper__arrow'; diff --git a/src/assets/css/themes/src/mixins/mixins.scss b/src/assets/css/themes/src/mixins/mixins.scss index e5c65133..59c6f537 100644 --- a/src/assets/css/themes/src/mixins/mixins.scss +++ b/src/assets/css/themes/src/mixins/mixins.scss @@ -140,7 +140,7 @@ } @at-root { - #{renderThemeClass($selector)}##{&}#{$specSelector}.#{$block+$element-separator+$element+$modifierCombo} { + #{renderThemeClass($selector)}#{&}#{$specSelector}.#{$block+$element-separator+$element+$modifierCombo} { @content; } } @@ -164,9 +164,8 @@ } @mixin when($state) { - $selector: &; @at-root { - #{renderThemeClass($selector)}&.#{$state-prefix + $state} { + &.#{$state-prefix + $state} { @content; } } diff --git a/src/assets/css/themes/src/popper.scss b/src/assets/css/themes/src/popper.scss index e3c85eb2..636ae97f 100644 --- a/src/assets/css/themes/src/popper.scss +++ b/src/assets/css/themes/src/popper.scss @@ -12,7 +12,8 @@ word-wrap: break-word; visibility: visible; - $arrow-selector: #{& + '__arrow'}; + // $arrow-selector: #{& + '__arrow'}; + $arrow-selector: '.el-popper__arrow'; @include when(dark) { color: $--color-white; diff --git a/src/assets/css/themes/theme-dark.scss b/src/assets/css/themes/theme-dark.scss index 8b35893c..172e2268 100644 --- a/src/assets/css/themes/theme-dark.scss +++ b/src/assets/css/themes/theme-dark.scss @@ -51,4 +51,12 @@ $--chart-single-value-icon-background-color: #E8F6FF; $--content-right-background-color: #EFF2F5; //右侧背景色 +// 空白背景色 +$--background-color-empty: #fffffe; +$--background-color-1: #EFEFEF; +// 普通字色(覆盖element-ui内置变量) +$--color-text-regular: #666665; +$--border-color-light: #E7EAED; +$--chart-box-border-color: $--border-color-light; +$--chart-title-hover-background-color: #323238; @import './common'; diff --git a/src/assets/css/themes/theme-light.scss b/src/assets/css/themes/theme-light.scss index 8b35893c..f7af0885 100644 --- a/src/assets/css/themes/theme-light.scss +++ b/src/assets/css/themes/theme-light.scss @@ -51,4 +51,12 @@ $--chart-single-value-icon-background-color: #E8F6FF; $--content-right-background-color: #EFF2F5; //右侧背景色 +// 空白背景色 +$--background-color-empty: #fffffe; +$--background-color-1: #EFEFEF; +// 普通字色(覆盖element-ui内置变量) +$--color-text-regular: #666665; +$--border-color-light: #E7EAED; +$--chart-box-border-color: $--border-color-light; +$--chart-title-hover-background-color: $--background-color-1; @import './common'; diff --git a/src/components/common/Loading.vue b/src/components/common/Loading.vue new file mode 100644 index 00000000..ccf98d85 --- /dev/null +++ b/src/components/common/Loading.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/components/layout/Home.vue b/src/components/layout/Home.vue index f9f0392a..c7ad5c6f 100644 --- a/src/components/layout/Home.vue +++ b/src/components/layout/Home.vue @@ -5,6 +5,8 @@ + + diff --git a/src/components/rightBox/settings/ChartBox.vue b/src/components/rightBox/settings/ChartBox.vue index df858e1d..fafc71df 100644 --- a/src/components/rightBox/settings/ChartBox.vue +++ b/src/components/rightBox/settings/ChartBox.vue @@ -131,7 +131,7 @@ diff --git a/src/views/charts/ChartHeader.vue b/src/views/charts/ChartHeader.vue new file mode 100644 index 00000000..91d31c32 --- /dev/null +++ b/src/views/charts/ChartHeader.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/views/charts/Panel.vue b/src/views/charts/Panel.vue index 410b4793..a0cc3757 100644 --- a/src/views/charts/Panel.vue +++ b/src/views/charts/Panel.vue @@ -1,13 +1,17 @@ @@ -58,7 +62,7 @@ import { ref } from 'vue' import { panelTypeAndRouteMapping } from '@/utils/constants' import { api, getPanelList, getChartList } from '@/utils/api' import { getNowTime } from '@/utils/date-util' -import Chart from './Chart' +import PanelChartList from './PanelChartList' import DateTimeRange from '@/components/common/TimeRange/DateTimeRange' import TimeRefresh from '@/components/common/TimeRange/TimeRefresh' @@ -70,13 +74,14 @@ export default { typeName: String }, components: { - Chart, DateTimeRange, - TimeRefresh + TimeRefresh, + PanelChartList }, data () { return { chartList: [], // 普通panel的chart + panelLock: true, // entity详情的chart detailTabs: [], detailChartList: [], diff --git a/src/views/charts/PanelChart.vue b/src/views/charts/PanelChart.vue new file mode 100644 index 00000000..0e5c0dc0 --- /dev/null +++ b/src/views/charts/PanelChart.vue @@ -0,0 +1,309 @@ + + + diff --git a/src/views/charts/PanelChartList.vue b/src/views/charts/PanelChartList.vue new file mode 100644 index 00000000..ce0c05af --- /dev/null +++ b/src/views/charts/PanelChartList.vue @@ -0,0 +1,343 @@ + + + diff --git a/src/views/charts/charts/ChartMap.vue b/src/views/charts/charts/ChartMap.vue new file mode 100644 index 00000000..ee742b59 --- /dev/null +++ b/src/views/charts/charts/ChartMap.vue @@ -0,0 +1,263 @@ + + + diff --git a/src/views/charts/charts/ChartNoData.vue b/src/views/charts/charts/ChartNoData.vue new file mode 100644 index 00000000..1396f5f0 --- /dev/null +++ b/src/views/charts/charts/ChartNoData.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/views/charts/charts/ChartTabs.vue b/src/views/charts/charts/ChartTabs.vue new file mode 100644 index 00000000..f7b2dcf9 --- /dev/null +++ b/src/views/charts/charts/ChartTabs.vue @@ -0,0 +1,68 @@ + + + diff --git a/src/views/charts/charts/chart-options.js b/src/views/charts/charts/chart-options.js new file mode 100644 index 00000000..460c4e6d --- /dev/null +++ b/src/views/charts/charts/chart-options.js @@ -0,0 +1,828 @@ +/** + * @author 陈劲松 + * @date 2021/6/16 + * @description chart option和一些工具 +*/ +import { format } from 'echarts' +import { unitTypes } from '@/utils/constants' +import unitConvert from '@/utils/unit-convert' +import _ from 'lodash' +export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666', + '#73BFDE', '#3BA172', '#FC8452', '#9960B4', + '#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC', + '#5888BC', '#63B6AC', '#EDC6B2', '#D5746B'] +export const chartBarColor = ['#0F8AB2', '#57CBAC'] +export function getChartColor (index) { + return chartColor[index % chartColor.length] +} +export function getCharBartColor (index) { + return chartBarColor[index % chartBarColor.length] +} +const line = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axiosFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time' + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: function (value, index, a, b) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + animation: false, + grid: { + left: 55, + bottom: 30, + top: 100, + right: 25 + }, + color: chartColor, + legend: { + tooltip: { + show: true, + formatter: '{a}' + }, + show: true, + right: 23, + top: 8, + padding: 2, + orient: 'horizontal', + icon: 'circle', + itemGap: 10, + itemWidth: 10, + textStyle: { + padding: [0, 0, 0, 2], + fontSize: 14 + }, + formatter: tooLongFormatter + }, + axisLabel: { + fontSize: 14 + }, + series: [ + { + name: '', + type: 'line', + smooth: false, + symbol: 'none', + data: [] + } + ] +} +const lineWithStatistics = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axiosFormatter, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time' + }, + animation: false, + yAxis: { + type: 'value', + axisLabel: { + formatter: function (value, index) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + color: chartColor, + grid: { + left: 55, + bottom: 30, + top: 20, + right: 20 + }, + legend: { + show: false + }, + axisLabel: { + fontSize: 14 + }, + series: [ + { + name: '', + type: 'line', + smooth: false, + symbol: 'none', + data: [] + } + ] +} +const lineStack = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axiosFormatter, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time' + }, + color: chartColor, + yAxis: { + type: 'value', + axisLabel: { + formatter: function (value, index) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + grid: { + left: 55, + bottom: 45, + top: 10, + right: 180 + }, + legend: { + show: true, + right: 30, + top: 'middle', + orient: 'vertical', + icon: 'circle', + itemGap: 20, + itemWidth: 10, + formatter: tooLongFormatter, + textStyle: { + padding: [0, 0, 0, 5], + fontSize: 14 + } + }, + axisLabel: { + fontSize: 14 + }, + series: [ + { + name: '', + type: 'line', + stack: 'value', + areaStyle: {}, + symbol: 'none', + data: [] + } + ] +} +const pieWithTable = { + tooltip: { + appendToBody: true + }, + color: chartColor, + animation: false, + legend: { + orient: 'vertical', + type: 'plain', + left: '60%', + top: 'middle', + icon: 'circle', + itemWidth: 10, // 设置宽度 + itemHeight: 10, // 设置高度 + itemGap: 20, + formatter: tooLongFormatter, + tooltip: { + show: true + } + }, + series: [ + { + type: 'pie', + selectedMode: 'single', + radius: ['42%', '65%'], + center: ['30%', '50%'], + data: [], + label: { + formatter: '{d}%' + }, + tooltip: { + formatter: function (param, index, callback) { + return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}` + } + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + } + ] +} +const ipHostedDomain = { + color: chartColor, + animation: false, + tooltip: { + show: true + }, + legend: { + orient: 'vertical', + type: 'plain', + right: '8%', + top: 'middle', + icon: 'circle', + itemWidth: 10, // 设置宽度 + itemHeight: 10, // 设置高度 + itemGap: 20, + tooltip: { + show: true + } + }, + series: [ + { + type: 'pie', + selectedMode: 'single', + radius: ['42%', '65%'], + center: ['36%', '50%'], + data: [], + label: { + formatter: '{d}%' + }, + tooltip: { + formatter: function (param, index, callback) { + return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}` + } + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + } + ] +} +const singleValueLine = { + tooltip: { + show: true, + enterable: true, + showContent: true, + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + } + }, + xAxis: { + type: 'time', + show: false + }, + yAxis: { + type: 'value', + show: false + }, + animation: false, + grid: { + left: 0, + bottom: 2, + top: 5, + right: 0 + }, + color: chartColor, + legend: { + show: false + }, + series: [ + { + type: 'line', + legendHoverLink: false, + itemStyle: { + normal: { + color: '#81C9FF', + lineStyle: { + width: 2 + } + } + }, + data: [], + showSymbol: false, + areaStyle: { color: '#C9EAFF' } + } + ] +} +export const entityListLineOption = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axiosFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time', + show: false + }, + yAxis: { + type: 'value', + show: false + }, + animation: false, + grid: { + left: 0, + bottom: 2, + top: 5, + right: 0 + }, + color: chartColor, + legend: { + show: false + }, + series: [ + { + type: 'line', + legendHoverLink: false, + itemStyle: { + normal: { + lineStyle: { + width: 2 + } + } + }, + data: [], + showSymbol: false + } + ] +} +const relationShip = { + grid: { + left: 0, + bottom: 50, + top: 80, + right: 0 + }, + series: [ + { + type: 'graph', + layout: 'force', + symbolSize: 40, + roam: true, + force: { + repulsion: 350 + }, + draggable: true, + label: { show: true }, + edgeSymbol: ['none', 'arrow'], + edgeSymbolSize: 7, + data: [], + links: [] + } + ] +} +const sankey = { + tooltip: { + trigger: 'item', + triggerOn: 'mousemove' + }, + series: [ + { + type: 'sankey', + data: [], + links: [], + right: '5%', + top: 50, + bottom: 100, + levels: [ + { + depth: 0, + itemStyle: { + color: '#47D49C' + }, + lineStyle: { + color: '#999' + } + }, { + depth: 1, + itemStyle: { + color: '#A69BF5' + }, + lineStyle: { + color: '#999' + } + }, { + depth: 2, + itemStyle: { + color: '#73A0FA' + }, + lineStyle: { + color: '#999' + } + } + ] + } + ] +} +const ipOpenPortBar = { + xAxis: { + type: 'category', + axisTick: { show: false }, + axisLine: { show: false } + }, + grid: { + top: 30, + left: 60, + right: 50, + bottom: 50 + }, + yAxis: { + type: 'value', + show: false + }, + series: [{ + barWidth: 38, + data: [], + type: 'bar', + label: { show: true, position: 'top' }, + barCategoryGap: '10%' + }] +} +const categoryBar = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: categoryVerticalFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'category', + axisTick: { show: false }, + axisLine: { show: false } + }, + grid: { + top: 20, + left: 10, + right: 25, + bottom: 20, + containLabel: true + }, + yAxis: { + type: 'value', + axisTick: { show: false }, + axisLine: { show: false } + }, + color: chartColor, + series: [{ + barWidth: 15, + data: [], + type: 'bar', + label: { show: false }, + barCategoryGap: '10%', + itemStyle: { + color: function (params) { + return getCharBartColor([params.dataIndex]) + } + } + }] +} + +const timeBar = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: timeVerticalFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time', + axisTick: { show: false }, + axisLine: { show: false }, + axisLabel: { + interval: 0, + // rotate: -40, //设置日期显示样式(倾斜度) + formatter: function (value) { // 在这里写你需要的时间格式 + const t_date = new Date(value) + return [t_date.getMonth() + 1, t_date.getDate()].join('/') + ' ' + [t_date.getHours(), t_date.getMinutes()].join(':') + } + } + }, + grid: { + top: 20, + left: 25, + right: 25, + bottom: 20, + containLabel: true + }, + yAxis: { + type: 'value', + axisTick: { show: false }, + axisLine: { show: false }, + axisLabel: { + formatter: function (value, index, a, b) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + color: chartColor, + series: [{ + barWidth: 15, + data: [], + type: 'bar', + label: { show: false }, + barCategoryGap: '10%', + itemStyle: { + color: function (params) { + return getCharBartColor([params.dataIndex]) + } + } + }] +} +const typeOptionMappings = [ + { value: 11, option: line }, // 常规折线图 + { value: 12, option: lineWithStatistics }, // 带统计表格的折线图 + { value: 13, option: lineStack }, // 折线堆叠图 + { value: 22, option: ipOpenPortBar }, // ip详情--开放端口的柱状图 + { value: 23, option: timeBar }, // 矿机所属单位 + { value: 24, option: categoryBar }, // 挖矿事件统计 + { value: 31, option: pieWithTable }, // 常规折线图 + { value: 33, option: ipHostedDomain }, // ip详情--托管域名 + { value: 34, option: ipHostedDomain }, // app详情--相关域名 + { value: 42, option: relationShip }, // 关系图 + { value: 43, option: sankey }, // 桑基图 + { value: 52, option: singleValueLine } +] +const typeCategory = { + MAP: 'map', + TABLE: 'table', + ECHARTS: 'echarts', + TITLE: 'title', + SINGLE: 'singleValue', + TABS: 'tabs' +} +export function getTypeCategory (type) { + if (isMap(type)) { + return typeCategory.MAP + } else if (isEcharts(type)) { + return typeCategory.ECHARTS + } else if (isTable(type)) { + return typeCategory.TABLE + } else if (isSingleValue(type)) { + return typeCategory.SINGLE + } else if (isTitle(type)) { + return typeCategory.TITLE + } else if (isTabs(type)) { + return typeCategory.TABS + } +} +/* 柱状图:挖矿事件统计(time类型柱状图) */ +export function isEchartsTimeBar (type) { + return type == 23 +} +/* 柱状图:矿机所属单位(category类型柱状图) */ +export function isEchartsCategoryBar (type) { + return type == 24 +} +/* 饼图柱状图等 */ +export function isEcharts (type) { + return type >= 11 && type <= 50 +} +/* 地图 */ +export function isMap (type) { + return type >= 1 && type <= 10 +} +/* 连线地图 */ +export function isMapLine (type) { + return type === 1 +} +/* 色块地图 */ +export function isMapBlock (type) { + return type === 2 +} +/* 带统计的折线图 */ +export function isEchartsWithStatistics (type) { + return type === 12 +} +/* 关系图 */ +export function isRelationShip (type) { + return type === 42 +} +/* 桑基图 */ +export function isSankey (type) { + return type === 43 +} +/* 单值 */ +export function isSingleValue (type) { + return type >= 51 && type <= 60 +} +/* 带折线图的单值 */ +export function isSingleValueWithEcharts (type) { + return type === 52 +} +/* 带折线图的单值 */ +export function isSingleValueWithEchartsTemp (type) { + return type === 55 +} +/* 带Table的饼图 */ +export function isEchartsWithTable (type) { + return type === 31 +} +/* table */ +export function isTable (type) { + return type >= 61 && type <= 70 +} +/* table */ +export function isActiveIpTable (type) { + return type == 63 +} +/* title */ +export function isTitle (type) { + return type === 93 +} +/* tabs */ +export function isTabs (type) { + return type === 91 +} +/* IP实体基本信息 */ +export function isIpBasicInfo (type) { + return type === 4 +} +/* IP实体开放端口 */ +export function isIpOpenPort (type) { + return type === 22 +} +/* IP实体托管域名 */ +export function isIpHostedDomain (type) { + return type === 33 +} +/* APP实体相关域名 */ +export function isAppRelatedDomain (type) { + return type === 34 +} +/* APP实体基本信息 */ +export function isAppBasicInfo (type) { + return type === 82 +} +/* DOMAIN实体Whois */ +export function isDomainWhois (type) { + return type === 83 +} +/* DOMAIN实体DNS记录 */ +export function isDomainDnsRecord (type) { + return type === 84 +} +/* 近期挖矿事件 */ +export function isCryptocurrencyEventList (type) { + return type === 85 +} +/* 组 */ +export function isGroup (type) { + return type === 94 +} +/* 实体详情块 */ +export function isBlock (type) { + return type === 95 +} +export function getOption (type) { + const mapping = typeOptionMappings.find(m => m.value === type) + return mapping && mapping.option ? _.cloneDeep(mapping.option) : null +} +export const layoutConstant = { + HEADER: 'header', + FOOTER: 'footer' +} +export function getLayout (type) { + const layout = [] + if (!isSingleValue(type) && !isTitle(type)) { + layout.push(layoutConstant.HEADER) + } + if (type === 12 || type === 31) { + layout.push(layoutConstant.FOOTER) + } + return layout +} + +function tooLongFormatter (name) { + return format.truncateText(name, 110, '12') +} +function axiosFormatter (params) { + let str = '
' + params.forEach((item, i) => { + const tData = item.data[0] + if (i === 0) { + str += '
' + str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss') + str += '
' + } + str += '
' + str += item.marker + str += ` + ${item.seriesName} + ` + str += ` + ${unitConvert(item.data[1], item.data[2]).join(' ')} + ` + str += '
' + }) + str += '
' + return str +} + +export function timeVerticalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + const tData = item.data[0] + if (i === 0) { + str += '
' + str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss') + str += '
' + } + str += '
' + str += item.marker + str += ` + ${item.seriesName} + ` + str += ` + ${unitConvert(item.data[1], item.data[2]).join(' ')} + ` + str += '
' + }) + str += '
' + return str +} + +export function timeHorizontalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + const tData = item.data[1] + if (i === 0) { + str += '
' + str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss') + str += '
' + } + str += '
' + str += item.marker + str += ` + ${item.seriesName} + ` + str += ` + ${unitConvert(item.data[0], item.data[2]).join(' ')} + ` + str += '
' + }) + str += '
' + return str +} +export function categoryHorizontalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + str += '
' + str += item.data[1] + ': ' + item.data[0] + str += '
' + }) + str += '
' + return str +} +export function categoryVerticalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + str += '
' + str += item.data[0] + ': ' + item.data[1] + str += '
' + }) + str += '
' + return str +} diff --git a/src/views/charts/charts/options/bar.js b/src/views/charts/charts/options/bar.js new file mode 100644 index 00000000..0f9198ea --- /dev/null +++ b/src/views/charts/charts/options/bar.js @@ -0,0 +1,135 @@ +import unitConvert from '@/utils/unit-convert' +import { unitTypes } from '@/utils/constants' +import { + categoryVerticalFormatter, + chartColor, + getCharBartColor, + timeVerticalFormatter +} from '@/views/charts/charts/tools' + +export const ipOpenPortBar = { + xAxis: { + type: 'category', + axisTick: { show: false }, + axisLine: { show: false } + }, + grid: { + top: 30, + left: 60, + right: 50, + bottom: 50 + }, + yAxis: { + type: 'value', + show: false + }, + series: [{ + barWidth: 38, + data: [], + type: 'bar', + label: { show: true, position: 'top' }, + barCategoryGap: '10%' + }] +} +export const categoryBar = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: categoryVerticalFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'category', + axisTick: { show: false }, + axisLine: { show: false } + }, + grid: { + top: 20, + left: 10, + right: 25, + bottom: 20, + containLabel: true + }, + yAxis: { + type: 'value', + axisTick: { show: false }, + axisLine: { show: false } + }, + color: chartColor, + series: [{ + barWidth: 15, + data: [], + type: 'bar', + label: { show: false }, + barCategoryGap: '10%', + itemStyle: { + color: function (params) { + return getCharBartColor([params.dataIndex]) + } + } + }] +} +export const timeBar = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: timeVerticalFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time', + axisTick: { show: false }, + axisLine: { show: false }, + axisLabel: { + interval: 0, + // rotate: -40, //设置日期显示样式(倾斜度) + formatter: function (value) { // 在这里写你需要的时间格式 + const tDate = new Date(value) + return [tDate.getMonth() + 1, tDate.getDate()].join('/') + ' ' + [tDate.getHours(), tDate.getMinutes()].join(':') + } + } + }, + grid: { + top: 20, + left: 25, + right: 25, + bottom: 20, + containLabel: true + }, + yAxis: { + type: 'value', + axisTick: { show: false }, + axisLine: { show: false }, + axisLabel: { + formatter: function (value, index, a, b) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + color: chartColor, + series: [{ + barWidth: 15, + data: [], + type: 'bar', + label: { show: false }, + barCategoryGap: '10%', + itemStyle: { + color: function (params) { + return getCharBartColor([params.dataIndex]) + } + } + }] +} diff --git a/src/views/charts/charts/options/graph.js b/src/views/charts/charts/options/graph.js new file mode 100644 index 00000000..43eddd9e --- /dev/null +++ b/src/views/charts/charts/options/graph.js @@ -0,0 +1,25 @@ +export const relationShip = { + grid: { + left: 0, + bottom: 50, + top: 80, + right: 0 + }, + series: [ + { + type: 'graph', + layout: 'force', + symbolSize: 40, + roam: true, + force: { + repulsion: 350 + }, + draggable: true, + label: { show: true }, + edgeSymbol: ['none', 'arrow'], + edgeSymbolSize: 7, + data: [], + links: [] + } + ] +} diff --git a/src/views/charts/charts/options/line.js b/src/views/charts/charts/options/line.js new file mode 100644 index 00000000..300cea2c --- /dev/null +++ b/src/views/charts/charts/options/line.js @@ -0,0 +1,274 @@ +import unitConvert from '@/utils/unit-convert' +import { unitTypes } from '@/utils/constants' +import { chartColor } from '@/views/charts/charts/chart-options' +import { axisFormatter, tooLongFormatter } from '../tools' + +export const line = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axisFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time' + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: function (value, index, a, b) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + animation: false, + grid: { + left: 55, + bottom: 30, + top: 100, + right: 25 + }, + color: chartColor, + legend: { + tooltip: { + show: true, + formatter: '{a}' + }, + show: true, + right: 23, + top: 8, + padding: 2, + orient: 'horizontal', + icon: 'circle', + itemGap: 10, + itemWidth: 10, + textStyle: { + padding: [0, 0, 0, 2], + fontSize: 14 + }, + formatter: tooLongFormatter + }, + axisLabel: { + fontSize: 14 + }, + series: [ + { + name: '', + type: 'line', + smooth: false, + symbol: 'none', + data: [] + } + ] +} +export const lineWithStatistics = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axisFormatter, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time' + }, + animation: false, + yAxis: { + type: 'value', + axisLabel: { + formatter: function (value, index) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + color: chartColor, + grid: { + left: 55, + bottom: 30, + top: 20, + right: 20 + }, + legend: { + show: false + }, + axisLabel: { + fontSize: 14 + }, + series: [ + { + name: '', + type: 'line', + smooth: false, + symbol: 'none', + data: [] + } + ] +} +export const lineStack = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axisFormatter, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time' + }, + color: chartColor, + yAxis: { + type: 'value', + axisLabel: { + formatter: function (value, index) { + return unitConvert(value, unitTypes.number).join(' ') + } + }, + minInterval: 1 + }, + grid: { + left: 55, + bottom: 45, + top: 10, + right: 180 + }, + legend: { + show: true, + right: 30, + top: 'middle', + orient: 'vertical', + icon: 'circle', + itemGap: 20, + itemWidth: 10, + formatter: tooLongFormatter, + textStyle: { + padding: [0, 0, 0, 5], + fontSize: 14 + } + }, + axisLabel: { + fontSize: 14 + }, + series: [ + { + name: '', + type: 'line', + stack: 'value', + areaStyle: {}, + symbol: 'none', + data: [] + } + ] +} +export const singleValueLine = { + tooltip: { + show: true, + enterable: true, + showContent: true, + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + } + }, + xAxis: { + type: 'time', + show: false + }, + yAxis: { + type: 'value', + show: false + }, + animation: false, + grid: { + left: 0, + bottom: 2, + top: 5, + right: 0 + }, + color: chartColor, + legend: { + show: false + }, + series: [ + { + type: 'line', + legendHoverLink: false, + itemStyle: { + normal: { + color: '#81C9FF', + lineStyle: { + width: 2 + } + } + }, + data: [], + showSymbol: false, + areaStyle: { color: '#C9EAFF' } + } + ] +} +export const entityListLine = { + tooltip: { + appendToBody: true, + trigger: 'axis', + textStyle: { + width: '20px', + overflow: 'truncate' + }, + formatter: axisFormatter, + show: true, + className: 'nz-chart-tooltip', + extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important' + }, + xAxis: { + type: 'time', + show: false + }, + yAxis: { + type: 'value', + show: false + }, + animation: false, + grid: { + left: 0, + bottom: 2, + top: 5, + right: 0 + }, + color: chartColor, + legend: { + show: false + }, + series: [ + { + type: 'line', + legendHoverLink: false, + itemStyle: { + normal: { + lineStyle: { + width: 2 + } + } + }, + data: [], + showSymbol: false + } + ] +} diff --git a/src/views/charts/charts/options/pie.js b/src/views/charts/charts/options/pie.js new file mode 100644 index 00000000..b2648335 --- /dev/null +++ b/src/views/charts/charts/options/pie.js @@ -0,0 +1,93 @@ +import unitConvert from '@/utils/unit-convert' +import { chartColor } from '@/views/charts/charts/chart-options' +import { tooLongFormatter } from '../tools' + +export const pieWithTable = { + tooltip: { + appendToBody: true + }, + color: chartColor, + animation: false, + legend: { + orient: 'vertical', + type: 'plain', + left: '60%', + top: 'middle', + icon: 'circle', + itemWidth: 10, // 设置宽度 + itemHeight: 10, // 设置高度 + itemGap: 20, + formatter: tooLongFormatter, + tooltip: { + show: true + } + }, + series: [ + { + type: 'pie', + selectedMode: 'single', + radius: ['42%', '65%'], + center: ['30%', '50%'], + data: [], + label: { + formatter: '{d}%' + }, + tooltip: { + formatter: function (param, index, callback) { + return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}` + } + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + } + ] +} +export const ipHostedDomain = { + color: chartColor, + animation: false, + tooltip: { + show: true + }, + legend: { + orient: 'vertical', + type: 'plain', + right: '8%', + top: 'middle', + icon: 'circle', + itemWidth: 10, // 设置宽度 + itemHeight: 10, // 设置高度 + itemGap: 20, + tooltip: { + show: true + } + }, + series: [ + { + type: 'pie', + selectedMode: 'single', + radius: ['42%', '65%'], + center: ['36%', '50%'], + data: [], + label: { + formatter: '{d}%' + }, + tooltip: { + formatter: function (param, index, callback) { + return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}` + } + }, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)' + } + } + } + ] +} diff --git a/src/views/charts/charts/options/sankey.js b/src/views/charts/charts/options/sankey.js new file mode 100644 index 00000000..e4856ffe --- /dev/null +++ b/src/views/charts/charts/options/sankey.js @@ -0,0 +1,43 @@ +export const sankey = { + tooltip: { + trigger: 'item', + triggerOn: 'mousemove' + }, + series: [ + { + type: 'sankey', + data: [], + links: [], + right: '5%', + top: 50, + bottom: 100, + levels: [ + { + depth: 0, + itemStyle: { + color: '#47D49C' + }, + lineStyle: { + color: '#999' + } + }, { + depth: 1, + itemStyle: { + color: '#A69BF5' + }, + lineStyle: { + color: '#999' + } + }, { + depth: 2, + itemStyle: { + color: '#73A0FA' + }, + lineStyle: { + color: '#999' + } + } + ] + } + ] +} diff --git a/src/views/charts/charts/tools.js b/src/views/charts/charts/tools.js new file mode 100644 index 00000000..3eb4c239 --- /dev/null +++ b/src/views/charts/charts/tools.js @@ -0,0 +1,337 @@ +import unitConvert from '@/utils/unit-convert' +import { format } from 'echarts' +import _ from 'lodash' +import { line, lineWithStatistics, lineStack, singleValueLine } from './options/line' +import { ipOpenPortBar, timeBar, categoryBar } from './options/bar' +import { pieWithTable, ipHostedDomain } from './options/pie' +import { relationShip } from './options/graph' +import { sankey } from './options/sankey' + +export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666', + '#73BFDE', '#3BA172', '#FC8452', '#9960B4', + '#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC', + '#5888BC', '#63B6AC', '#EDC6B2', '#D5746B'] +export const chartBarColor = ['#0F8AB2', '#57CBAC'] +export function getChartColor (index) { + return chartColor[index % chartColor.length] +} +export function getCharBartColor (index) { + return chartBarColor[index % chartBarColor.length] +} + +const typeOptionMappings = [ + { value: 11, option: line }, // 常规折线图 + { value: 12, option: lineWithStatistics }, // 带统计表格的折线图 + { value: 13, option: lineStack }, // 折线堆叠图 + { value: 22, option: ipOpenPortBar }, // ip详情--开放端口的柱状图 + { value: 23, option: timeBar }, // 矿机所属单位 + { value: 24, option: categoryBar }, // 挖矿事件统计 + { value: 31, option: pieWithTable }, // 常规折线图 + { value: 33, option: ipHostedDomain }, // ip详情--托管域名 + { value: 34, option: ipHostedDomain }, // app详情--相关域名 + { value: 42, option: relationShip }, // 关系图 + { value: 43, option: sankey }, // 桑基图 + { value: 52, option: singleValueLine } // 单值图中的折线图 +] +export function getOption (type) { + const mapping = typeOptionMappings.find(m => m.value === type) + return mapping && mapping.option ? _.cloneDeep(mapping.option) : null +} + +/* 柱状图:挖矿事件统计(time类型柱状图) */ +export function isEchartsTimeBar (type) { + return type === 23 +} +/* 柱状图:矿机所属单位(category类型柱状图) */ +export function isEchartsCategoryBar (type) { + return type === 24 +} +/* 饼图柱状图等 */ +export function isEcharts (type) { + return type >= 11 && type <= 50 +} +/* 地图 */ +export function isMap (type) { + return type >= 1 && type <= 10 +} +/* 连线地图 */ +export function isMapLine (type) { + return type === 1 +} +/* 色块地图 */ +export function isMapBlock (type) { + return type === 2 +} +/* 带统计的折线图 */ +export function isEchartsWithStatistics (type) { + return type === 12 +} +/* 关系图 */ +export function isRelationShip (type) { + return type === 42 +} +/* 桑基图 */ +export function isSankey (type) { + return type === 43 +} +/* 单值 */ +export function isSingleValue (type) { + return type >= 51 && type <= 60 +} +/* 带折线图的单值 */ +export function isSingleValueWithEcharts (type) { + return type === 52 +} +/* 带折线图的单值 */ +export function isSingleValueWithEchartsTemp (type) { + return type === 55 +} +/* 带Table的饼图 */ +export function isEchartsWithTable (type) { + return type === 31 +} +/* table */ +export function isTable (type) { + return type >= 61 && type <= 70 +} +/* table */ +export function isActiveIpTable (type) { + return type === 63 +} +/* title */ +export function isTitle (type) { + return type === 93 +} +/* tabs */ +export function isTabs (type) { + return type === 91 +} +/* IP实体基本信息 */ +export function isIpBasicInfo (type) { + return type === 4 +} +/* IP实体开放端口 */ +export function isIpOpenPort (type) { + return type === 22 +} +/* IP实体托管域名 */ +export function isIpHostedDomain (type) { + return type === 33 +} +/* APP实体相关域名 */ +export function isAppRelatedDomain (type) { + return type === 34 +} +/* APP实体基本信息 */ +export function isAppBasicInfo (type) { + return type === 82 +} +/* DOMAIN实体Whois */ +export function isDomainWhois (type) { + return type === 83 +} +/* DOMAIN实体DNS记录 */ +export function isDomainDnsRecord (type) { + return type === 84 +} +/* 近期挖矿事件 */ +export function isCryptocurrencyEventList (type) { + return type === 85 +} +/* 组 */ +export function isGroup (type) { + return type === 94 +} +/* 实体详情块 */ +export function isBlock (type) { + return type === 95 +} + + +/* 根据type获取图表分类 */ +const typeCategory = { + MAP: 'map', + TABLE: 'table', + ECHARTS: 'echarts', + TITLE: 'title', + SINGLE: 'singleValue', + TABS: 'tabs' +} +export function getTypeCategory (type) { + if (isMap(type)) { + return typeCategory.MAP + } else if (isEcharts(type)) { + return typeCategory.ECHARTS + } else if (isTable(type)) { + return typeCategory.TABLE + } else if (isSingleValue(type)) { + return typeCategory.SINGLE + } else if (isTitle(type)) { + return typeCategory.TITLE + } else if (isTabs(type)) { + return typeCategory.TABS + } +} + + +/* 根据type获取布局 */ +export const layoutConstant = { + HEADER: 'header', + FOOTER: 'footer' +} +export function getLayout (type) { + const layout = [] + if (!isSingleValue(type) && !isTitle(type)) { + layout.push(layoutConstant.HEADER) + } + if (type === 12 || type === 31) { + layout.push(layoutConstant.FOOTER) + } + return layout +} + + +export function getGroupHeight (arr) { + if (arr.length) { + let lastItem = [] + let maxY = arr[0].y + arr.forEach((children, index) => { + if (maxY === children.y) { + lastItem.push(children) + } else if (maxY < children.y) { + maxY = children.y + lastItem = [children] + } + }) + let maxHeight = 0 + lastItem.forEach(last => { + if (maxHeight < last.height) { + maxHeight = last.height + } + }) + if (maxY < 0) { + maxY = 0 + } + return maxHeight + maxY + } else { + return 1 + } +} +export function getLayoutPosition (arr) { + if (arr.length) { + let lastItem = [] + let maxY = 0 + arr.forEach((children, index) => { + if (maxY === children.y) { + lastItem.push(children) + } else if (maxY < children.y) { + maxY = children.y + lastItem = [children] + } + }) + let maxX = 0 + lastItem.forEach(last => { + if (maxX < last.x + last.span) { + maxX = last.x + last.span + } + }) + return { + x: maxX, + y: maxY + } + } else { + return { + x: 0, + y: 0 + } + } +} +export function axisFormatter (params) { + let str = '
' + params.forEach((item, i) => { + const tData = item.data[0] + if (i === 0) { + str += '
' + str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss') + str += '
' + } + str += '
' + str += item.marker + str += ` + ${item.seriesName} + ` + str += ` + ${unitConvert(item.data[1], item.data[2]).join(' ')} + ` + str += '
' + }) + str += '
' + return str +} +export function tooLongFormatter (name) { + return format.truncateText(name, 110, '12') +} +export function timeHorizontalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + const tData = item.data[1] + if (i === 0) { + str += '
' + str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss') + str += '
' + } + str += '
' + str += item.marker + str += ` + ${item.seriesName} + ` + str += ` + ${unitConvert(item.data[0], item.data[2]).join(' ')} + ` + str += '
' + }) + str += '
' + return str +} +export function categoryHorizontalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + str += '
' + str += item.data[1] + ': ' + item.data[0] + str += '
' + }) + str += '
' + return str +} +export function categoryVerticalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + str += '
' + str += item.data[0] + ': ' + item.data[1] + str += '
' + }) + str += '
' + return str +} +export function timeVerticalFormatter (params) { + let str = '
' + params.forEach((item, i) => { + const tData = item.data[0] + if (i === 0) { + str += '
' + str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss') + str += '
' + } + str += '
' + str += item.marker + str += ` + ${item.seriesName} + ` + str += ` + ${unitConvert(item.data[1], item.data[2]).join(' ')} + ` + str += '
' + }) + str += '
' + return str +} diff --git a/src/views/settings/Chart.vue b/src/views/settings/Chart.vue index bc9c6aa5..df0afb7e 100644 --- a/src/views/settings/Chart.vue +++ b/src/views/settings/Chart.vue @@ -53,7 +53,7 @@