| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085 |
- <template>
- <a-card size="small" class="explorer-monitor-line" :style="monitorLineCardStyle">
- <div slot="title" v-if="title">
- <a-row type="flex">
- <a-col>
- <a class="font-weight-bold h-100 d-block" style="margin-right: 6px;" @click="toggleShowTableLegend">
- <a-icon type="line-chart" style="font-size: 14px;" v-if="showTableLegend" />
- <a-icon type="credit-card" style="font-size: 14px;" v-if="!showTableLegend" />
- </a>
- </a-col>
- <a-col :span="21">
- {{ title }}
- </a-col>
- </a-row>
- </div>
- <div slot="extra" v-if="showTableExport && !isTemplate">
- <a-button v-if="showTableExport && curPager.total" type="link" :title="$t('monitor.full_export')" @click="exportTable">
- {{ $t('table.action.export') }}
- </a-button>
- <slot name="extra" />
- </div>
- <loader v-if="loading" :loading="true" />
- <template v-else>
- <div class="d-flex">
- <uchart :data="uChartData" :options="uChartOptions" :otherCursorMovePoint="otherCursorMovePoint" />
- <div v-if="alertHandlerShow && lineChartOptionsC.dataset.length" class="alert-handler-wrapper position-relative">
- <div class="position-absolute clearfix d-flex align-items-center" :style="{ top: `${topStyleRange[1]}px` }">
- <div class="alert-handler-line" />
- <div class="alert-handler"> {{ formatThreshold }}</div>
- </div>
- </div>
- </div>
- <vxe-grid
- v-if="tableData && tableData.length && showTable && showTableLegend"
- max-height="500"
- size="mini"
- border
- row-id="raw_name"
- ref="tableRef"
- highlight-hover-row
- class="mt-3"
- :columns="columns"
- :data="tableData"
- :row-style="getRowStyle"
- resizable
- :sort-config="sortConfig"
- @cell-click="cellClick"
- @sort-change="sortChange" />
- <div class="vxe-grid--pager-wrapper" v-if="showTableLegend">
- <div class="vxe-pager size--mini">
- <div class="vxe-pager--wrapper">
- <span class="vxe-pager--total" v-if="!pager || (pager && pager.total < 11)">{{ total }}</span>
- <vxe-pager
- v-else
- size="mini"
- @page-change="pageChange"
- :page-sizes="getPageSizes"
- :current-page.sync="curPager.page"
- :page-size.sync="curPager.limit"
- :total="curPager.total" />
- </div>
- </div>
- </div>
- </template>
- </a-card>
- </template>
- <script>
- import * as R from 'ramda'
- import _ from 'lodash'
- import XLSX from 'xlsx'
- import { metric_zh, tableColumnMaps } from '@Monitor/constants'
- import { ColorHash } from '@/utils/colorHash'
- import { transformUnit } from '@/utils/utils'
- import { getChartTooltipLabel } from '@Monitor/utils'
- const MAX_COLUMNS = 10
- export default {
- name: 'ExplorerMonitorLine',
- components: {
- },
- props: {
- isTemplate: {
- type: Boolean,
- default: false,
- },
- unit: {
- type: Object,
- default: () => ({}),
- },
- series: {
- type: Array,
- required: true,
- },
- reducedResult: {
- type: Object,
- default: () => ({}),
- },
- reducedResultOrder: {
- type: String,
- default: '',
- },
- pager: {
- type: Object,
- required: false,
- },
- timeFormatStr: {
- type: String,
- default: 'YYYY-MM-DD HH:mm',
- },
- lineChartOptions: {
- type: Object,
- default: () => ({
- legend: {
- show: false,
- selectedMode: 'multiple',
- selected: {},
- },
- }),
- },
- metricInfo: {
- type: Object,
- default: () => ({}),
- },
- loading: {
- type: Boolean,
- default: false,
- },
- description: {
- type: Object,
- },
- threshold: {
- },
- showTableExport: {
- type: Boolean,
- default: false,
- },
- showTableLegend: {
- type: Boolean,
- default: true,
- },
- monitorLineCardStyle: {
- type: Object,
- default: () => ({}),
- },
- otherCursorMovePoint: {
- type: Array,
- default: () => {
- return [-10, -10]
- },
- },
- },
- data () {
- return {
- chartInstanceOption: {
- series: [],
- },
- lineChartOptionsC: {},
- chartInstance: null,
- seriesOldClickName: null,
- highlight: {
- index: null,
- color: '',
- },
- highlights: [],
- colors: [],
- yMax: 0,
- alertHandlerShow: false,
- topStyleRange: [220, 20],
- curPager: Object.assign({}, this.pager || { seriesIndex: 0, total: 0, limit: 10, page: 0 }),
- }
- },
- computed: {
- fixedSeries () {
- if (this.series.length) {
- const cols = this.series[0].columns.filter(col => col !== 'time')
- if (cols.length > 1) {
- const series = []
- cols.forEach((col, idx) => {
- this.series.forEach(item => {
- const seriesItem = {
- columns: [col, 'time'],
- name: col,
- raw_name: col,
- points: item.points.map(point => [point[idx], point[point.length - 1]]),
- }
- series.push(seriesItem)
- })
- })
- return series
- } else {
- return this.series
- }
- }
- return this.series
- },
- groupBy () {
- const groupBy = _.get(this.metricInfo, 'model.group_by')
- if (groupBy && groupBy.length && groupBy[0].type !== 'time' && groupBy[0].type !== 'fill') {
- return groupBy
- }
- return null
- },
- resultReducer () {
- return _.get(this.metricInfo, 'result_reducer')
- },
- isSelectFunction () {
- const select = _.get(this.metricInfo, 'model.select') || []
- let ret = ''
- select.map(item => {
- if (R.is(Array, item)) {
- item.map(l => {
- if (['mean', 'sum', 'max', 'min'].includes(l.type)) {
- ret = l.type
- }
- })
- } else if (R.is(Object, item)) {
- if (['mean', 'sum', 'max', 'min'].includes(item.type)) {
- ret = item.type
- }
- }
- })
- return ret
- },
- showTable () {
- if (this.isTemplate) {
- return false
- }
- if (!this.groupBy && !this.resultReducer && this.isSelectFunction && this.columns.length < 2) {
- return false
- }
- return true
- },
- tableData () {
- return this.getTableData(this.fixedSeries, this.reducedResult)
- },
- total () {
- const total = this.tableData.length || 0
- return this.$t('monitor_metric_78', [total])
- },
- columns () {
- const that = this
- const columns = [
- {
- field: 'color',
- width: 50,
- slots: {
- default: ({ row, rowIndex }) => {
- if (this.highlights.some(item => item.index === rowIndex)) {
- return [<icon type="checkbox-fill" style={{ fontSize: '20px', color: (that.colors.length && that.colors[rowIndex]) || that.colorHash.hex(`${rowIndex * 1000}`), cursor: 'pointer', transform: 'translateY(3px)' }}></icon>]
- }
- return [<icon type="checkbox-empty" style="font-size:20px;cursor:pointer;transform:translateY(3px)"></icon>]
- },
- header: ({ column }, h) => {
- let type = 'checkbox-empty'
- if (this.isAllSelected) {
- type = 'checkbox-fill'
- } else if (this.highlights.length) {
- type = 'checkbox-some'
- }
- return [
- this.$createElement('icon', {
- props: {
- type,
- },
- style: {
- fontSize: '20px',
- cursor: 'pointer',
- transform: 'translateY(3px)',
- color: 'var(--antd-wave-shadow-color)',
- },
- on: {
- click: this.checkAllClick,
- },
- }),
- ]
- },
- },
- },
- ]
- if (this.tableData && this.tableData.length) {
- R.forEachObjIndexed((value, key) => {
- const isColumn = !R.isNil(this.tableData[0][key])
- if (isColumn) {
- const measurement = _.get(this.metricInfo, 'model.measurement')
- if ((this.description && this.description.metric_res_type === 'guest') || measurement.startsWith('vm_')) {
- if (value.field.startsWith('host')) {
- return
- }
- }
- columns.push({
- ...value,
- formatter: ({ cellValue }) => {
- if (cellValue === 'value') {
- const display_name = this.description.display_name
- const metric = this.metricInfo.model.measurement + '_' + this.description.name
- let label = metric_zh[display_name]
- if (label) {
- label += '/' + metric
- } else {
- label = metric
- }
- return `${this.description.label || label}${this.isSelectFunction ? `(${this.isSelectFunction.toUpperCase()})` : ''}` || cellValue
- }
- return cellValue
- },
- slots: {
- default: ({ row, rowIndex }) => {
- const cellValue = row[value.field]
- let val = cellValue
- if (cellValue === 'value') {
- const display_name = this.description.display_name
- const metric = this.metricInfo.model.measurement + '_' + this.description.name
- let label = metric_zh[display_name]
- if (label) {
- label += '/' + metric
- } else {
- label = metric
- }
- val = `${this.description.label || label}${this.isSelectFunction ? `(${this.isSelectFunction.toUpperCase()})` : ''}` || cellValue
- }
- return [<span>{val}</span>]
- },
- },
- })
- }
- }, tableColumnMaps)
- }
- const groupByFields = (this.groupBy || []).map(item => _.get(item, 'params[0]'))
- if (this.groupBy && groupByFields.length) {
- groupByFields.forEach(groupByField => {
- if (!columns.find(val => val.field === groupByField)) {
- const title = this.$te(`dictionary.${groupByField}`) ? this.$t(`dictionary.${groupByField}`) : groupByField
- columns.push({
- field: groupByField,
- title,
- formatter: ({ row }) => row[groupByField] || '-',
- slots: {
- default: ({ row, rowIndex }) => {
- const val = row[groupByField] || '-'
- return [<span>{val}</span>]
- },
- },
- })
- }
- })
- }
- if (this.reducedResult && this.reducedResult.reducer) {
- const { reducer = {} } = this.reducedResult
- const title = reducer.type === 'percentile' ? `P${reducer.params && reducer.params[0]}` : reducer.type === 'avg' ? 'MEAN' : reducer.type.toUpperCase()
- columns.push({
- field: 'result',
- title,
- formatter: ({ cellValue }) => {
- const unit = _.get(this.description, 'description.unit') || _.get(this.description, 'unit')
- const val = transformUnit(cellValue, unit)
- return val.text
- },
- slots: {
- default: ({ row, rowIndex }) => {
- const cellValue = row.result
- const unit = _.get(this.description, 'description.unit') || _.get(this.description, 'unit')
- const val = transformUnit(cellValue, unit)
- return [<span>{val.text}</span>]
- },
- },
- sortable: true,
- })
- }
- if (this.tableData && this.tableData.length && this.tableData.some(item => {
- return item.raw_name && item.raw_name.startsWith('{')
- })) {
- columns.push({
- field: 'raw_name',
- title: this.$t('cloudenv.text_237'),
- slots: {
- default: ({ row, rowIndex }) => {
- const val = row.raw_name || ''
- return [<span>{val}</span>]
- },
- },
- formatter: ({ row }) => {
- return row.raw_name || ''
- },
- })
- }
- // 只有raw_name 一列可以展示
- if (columns.length === 1) {
- columns.push({
- field: 'raw_name',
- title: this.$t('monitor.monitor_metric'),
- slots: {
- default: ({ row, rowIndex }) => {
- const val = (row.raw_name || '').replace('unknown-0-', '')
- return [<span>{val}</span>]
- },
- },
- formatter: ({ row }) => {
- return (row.raw_name || '').replace('unknown-0-', '')
- },
- })
- }
- return columns.slice(0, MAX_COLUMNS)
- },
- formatThreshold () {
- if (!this.threshold) return '0'
- const unit = _.get(this.description, 'description.unit') || _.get(this.description, 'unit')
- let formatStr = '0'
- if (unit === '%') { // 比如用户输入 2.22%,这里传入为 '0.00'
- const sLen = this.threshold.length
- const sArr = []
- for (let i = 0; i < sLen; i++) {
- const s = this.threshold[i]
- const v = /\d/.test(s) ? '0' : s
- sArr.push(v)
- }
- formatStr = sArr.join('')
- }
- const ret = transformUnit(this.threshold, unit, 1000, formatStr)
- return `${ret.value}${ret.unit}`
- },
- title () {
- let title = ''
- if (this.fixedSeries.length && this.description) {
- title = this.description.title || ''
- if (this.description.metric_res_type && this.description.metric_res_type === 'host') {
- if (this.$te(`dictionary.${this.description.metric_res_type}`)) {
- const hostPrefix = this.$t(`dictionary.${this.description.metric_res_type}`)
- if (!title.startsWith(hostPrefix)) {
- title = hostPrefix + title
- }
- }
- }
- }
- return title
- },
- sortConfig () {
- if (this.reducedResultOrder && this.columns.some(item => item.field === 'result')) {
- return {
- defaultSort: {
- field: 'result',
- order: this.reducedResultOrder,
- },
- }
- }
- return {}
- },
- getPageSizes () {
- const ret = [10, 20, 50, 100, 200]
- if (this.curPager.Total > ret[ret.length - 1]) {
- ret.push(this.curPager.Total)
- }
- return ret
- },
- isAllSelected () {
- return this.highlights.length === this.tableData.length
- },
- uChartData () {
- const ret = []
- if (this.fixedSeries.length) {
- const time = this.fixedSeries[0].points.map(item => item[item.length - 1])
- ret.push(time.map(item => item / 1000))
- this.fixedSeries.forEach((item, i) => {
- const row = []
- time.forEach(t => {
- const target = item.points.filter(p => p[1] === t)
- if (target.length) {
- row.push(target[0][0])
- } else {
- row.push(null)
- }
- })
- if (this.highlights.some(item => item.index === i)) {
- ret.push(row)
- }
- })
- }
- return ret
- },
- uChartOptions () {
- let unit = _.get(this.description, 'description.unit') || _.get(this.description, 'unit')
- // 检查数据是否为空(只有时间轴,没有数据系列)
- const isEmptyData = !this.uChartData || this.uChartData.length <= 1
- let yTicks
- let finalYMin
- let finalYMax
- if (isEmptyData) {
- // 如果数据为空,不显示刻度线,设置默认范围
- yTicks = []
- finalYMin = 0
- finalYMax = 100
- } else {
- // 检查单位是否为100%且数据都在0-100之间
- const isPercent100 = (unit === '%' || unit === '100%') && this.isDataInRange0To100(this.uChartData)
- if (isPercent100) {
- // 如果单位是100%且数据都在0-100之间,使用固定的刻度线
- yTicks = [0, 20, 40, 60, 80, 100]
- finalYMin = 0
- finalYMax = 100
- } else {
- // 检查数据是否都是整数
- const isInteger = this.isAllIntegers(this.uChartData)
- // 计算 Y 轴范围
- const minRange = isInteger ? 1 : 0.01
- const [yMin, yMax] = this.calculateYAxisRange(this.uChartData, minRange)
- // 生成 Y 轴刻度(会自动在数据范围外增加刻度线)
- yTicks = this.generateYTicks(yMin, yMax, 1, 10, isInteger)
- // 使用刻度线的最小值和最大值作为 Y 轴范围
- finalYMin = yTicks.length > 0 ? yTicks[0] : yMin
- finalYMax = yTicks.length > 0 ? yTicks[yTicks.length - 1] : yMax
- }
- }
- const ret = {
- width: '100%',
- height: 300,
- margin: {
- left: 100,
- },
- scales: {
- x: {
- time: true, // x轴为时间轴
- },
- y: {
- auto: false, // 禁用自动调整
- range: [finalYMin, finalYMax],
- },
- },
- axes: [
- {
- values: (self, ticks, space) => {
- return ticks.map(item => this.$moment(item * 1000).format('MM-DD') + '\n' + this.$moment(item * 1000).format('HH:mm'))
- },
- },
- {
- scale: 'y',
- size: 75,
- splits: (self, scaleMin, scaleMax) => {
- // 使用自定义生成的刻度
- return yTicks
- },
- values: (self, ticks, space) => {
- if (unit === 'NULL' || unit === 'count') {
- unit = ''
- }
- if (unit === 'ms') { // 时间类型的Y坐标,要取整 如 : 1小时10分钟30秒 -> 1小时
- unit = 'intms'
- }
- const list = ticks.map(item => {
- const val = transformUnit(item, unit, 1000, item < 100 ? '0.00' : '0.0')
- return val.text > 10000000 ? val.text / 10000 + 'w' : val.text
- })
- return list
- },
- },
- ],
- legend: {
- show: false,
- },
- grid: {
- show: false,
- },
- series: [
- {},
- ],
- tooltip: {
- valueFormatter: (value, unit) => {
- if (unit === 'NULL' || unit === 'count') {
- unit = ''
- }
- if (unit === 'ms') { // 时间类型的Y坐标,要取整 如 : 1小时10分钟30秒 -> 1小时
- unit = 'intms'
- }
- const val = transformUnit(value, unit, 1000, '0.0000')
- return val.text
- },
- },
- cursorMove: (x, y) => {
- this.$emit('cursorMove', [x, y])
- },
- }
- this.fixedSeries.forEach((item, i) => {
- const color = (this.colors && this.colors[i]) || this.colorHash.hex(`${i * 1000}`)
- if (this.highlights.some(item => item.index === i)) {
- const label = getChartTooltipLabel(item)
- ret.series.push({ label, width: 1, stroke: color, spanGaps: true, unit, color, points: { show: true, size: 3 } })
- }
- })
- return ret
- },
- },
- watch: {
- fixedSeries (val, oldV) {
- if (!R.equals(val, oldV)) {
- this.genColors()
- this.getMonitorLine()
- }
- },
- lineChartOptions: {
- deep: true,
- handler (val, oldV) {
- if (!R.equals(val, oldV)) {
- this.getMonitorLine()
- }
- },
- },
- description (val, oldV) {
- if (!R.equals(val, oldV)) {
- this.getMonitorLine()
- }
- },
- threshold () {
- this.showThreshold()
- },
- yMax () {
- this.showThreshold()
- },
- pager (val) {
- this.curPager = Object.assign({}, val || { seriesIndex: 0, total: 0, limit: 10, page: 0 })
- },
- tableData (val) {
- this.highlights = val.map((item, index) => ({ index, color: item.color }))
- },
- },
- created () {
- this.colorHash = new ColorHash({
- hue: [
- { min: 0, max: 360 },
- { min: 0, max: 360 },
- { min: 0, max: 360 },
- ],
- })
- this.getMonitorLine()
- },
- destroyed () {
- this.colorHash = null
- },
- methods: {
- // 检查数据是否都是整数
- isAllIntegers (uChartData) {
- if (!uChartData || uChartData.length <= 1) {
- return false
- }
- for (let i = 1; i < uChartData.length; i++) {
- if (Array.isArray(uChartData[i])) {
- for (let j = 0; j < uChartData[i].length; j++) {
- const val = uChartData[i][j]
- if (val !== null && val !== undefined && !isNaN(val)) {
- // 检查是否为整数
- if (!Number.isInteger(val)) {
- return false
- }
- }
- }
- }
- }
- return true
- },
- // 检查数据是否都在0-100之间
- isDataInRange0To100 (uChartData) {
- if (!uChartData || uChartData.length <= 1) {
- return false
- }
- // 收集所有有效数值(忽略 null)
- const allValues = []
- for (let i = 1; i < uChartData.length; i++) {
- if (Array.isArray(uChartData[i])) {
- uChartData[i].forEach(val => {
- if (val !== null && val !== undefined && !isNaN(val)) {
- allValues.push(val)
- }
- })
- }
- }
- if (allValues.length === 0) {
- return false
- }
- const min = Math.min(...allValues)
- const max = Math.max(...allValues)
- // 检查是否都在0-100之间
- return min >= 0 && max <= 100
- },
- // 计算 Y 轴范围,确保最小范围
- calculateYAxisRange (uChartData, minRange = 0.01) {
- if (!uChartData || uChartData.length <= 1) {
- return [0, 100]
- }
- // 收集所有有效数值(忽略 null)
- const allValues = []
- for (let i = 1; i < uChartData.length; i++) {
- if (Array.isArray(uChartData[i])) {
- uChartData[i].forEach(val => {
- if (val !== null && val !== undefined && !isNaN(val)) {
- allValues.push(val)
- }
- })
- }
- }
- if (allValues.length === 0) {
- return [0, 100]
- }
- let min = Math.min(...allValues)
- let max = Math.max(...allValues)
- const range = max - min
- // 如果数据中没有小于0的值,则最小值从0开始
- if (min >= 0) {
- min = 0
- }
- // 如果范围小于最小范围,使用固定范围
- if (range < minRange) {
- const center = (min + max) / 2
- // 如果最小值是0,确保不会小于0
- if (min === 0) {
- min = 0
- max = Math.max(max, minRange)
- } else {
- min = center - minRange / 2
- max = center + minRange / 2
- }
- } else {
- // 添加 10% 边距
- const padding = range * 0.1
- // 如果最小值是0,确保不会小于0
- if (min === 0) {
- min = 0
- } else {
- min = min - padding
- }
- max = max + padding
- }
- return [min, max]
- },
- // 生成 Y 轴刻度,确保间隔至少为 minStep,并在数据范围外自动增加刻度线,最少5条刻度线
- generateYTicks (min, max, minStep = 0.01, maxTicks = 10, isInteger = false) {
- const range = max - min
- const minTicks = 5 // 最少刻度线数量
- if (range <= 0) {
- // 如果最小值和最大值相同,生成至少5条刻度线
- const step = isInteger ? 1 : minStep
- const ticks = []
- // 如果最小值是0,从0开始生成刻度线
- if (min === 0) {
- for (let i = 0; i < minTicks; i++) {
- ticks.push(isInteger ? i * step : parseFloat((i * step).toFixed(10)))
- }
- } else {
- const center = isInteger ? Math.round(min) : min
- // 在中心上下各生成至少2条刻度线
- for (let i = -2; i <= 2; i++) {
- ticks.push(isInteger ? center + i * step : parseFloat((center + i * step).toFixed(10)))
- }
- }
- return ticks
- }
- // 计算理想步长(基于数据范围,确保至少生成 minTicks 条刻度线)
- let step = range / (minTicks - 1)
- if (isInteger) {
- // 如果数据都是整数,使用整数步长
- // 选择合适的整数步长:1, 2, 5, 10, 20, 50, 100, ...
- const niceSteps = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]
- step = Math.ceil(step)
- // 找到最接近的合适步长
- for (let i = 0; i < niceSteps.length; i++) {
- if (niceSteps[i] >= step) {
- step = niceSteps[i]
- break
- }
- }
- // 如果步长太大,使用计算出的步长向上取整
- if (step > niceSteps[niceSteps.length - 1]) {
- step = Math.ceil(step)
- }
- } else {
- // 确保步长不小于 minStep
- if (step < minStep) {
- step = minStep
- } else {
- // 将步长调整为 minStep 的倍数(向上取整)
- const multiplier = Math.ceil(step / minStep)
- step = multiplier * minStep
- }
- }
- // 计算起始刻度(向下取整到 step 的倍数,并在最小值之前至少一个步长)
- let startTick = Math.floor(min / step) * step
- // 如果最小值是0,确保起始刻度从0开始
- if (min === 0) {
- startTick = 0
- } else {
- // 确保起始刻度在最小值之前至少一个步长
- while (startTick >= min) {
- startTick -= step
- }
- }
- // 计算结束刻度(向上取整到 step 的倍数,并在最大值之后至少一个步长)
- let endTick = Math.ceil(max / step) * step
- // 确保结束刻度在最大值之后至少一个步长
- while (endTick <= max) {
- endTick += step
- }
- // 生成等间距的刻度数组
- const ticks = []
- let currentTick = startTick
- // 生成从起始到结束的所有刻度(包含边界)
- while (currentTick <= endTick + step * 0.001) {
- if (isInteger) {
- ticks.push(Math.round(currentTick))
- } else {
- ticks.push(parseFloat(currentTick.toFixed(10)))
- }
- currentTick += step
- }
- // 确保至少生成 minTicks 条刻度线
- if (ticks.length < minTicks) {
- // 如果刻度线太少,重新计算步长
- const totalRange = endTick - startTick
- step = totalRange / (minTicks - 1)
- if (isInteger) {
- // 整数步长:选择合适的整数步长
- const niceSteps = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]
- step = Math.ceil(step)
- for (let i = 0; i < niceSteps.length; i++) {
- if (niceSteps[i] >= step) {
- step = niceSteps[i]
- break
- }
- }
- if (step > niceSteps[niceSteps.length - 1]) {
- step = Math.ceil(step)
- }
- } else {
- // 确保步长是 minStep 的倍数
- const multiplier = Math.ceil(step / minStep)
- step = multiplier * minStep
- }
- // 重新计算起始和结束刻度
- if (min === 0) {
- // 如果最小值是0,从0开始
- startTick = 0
- endTick = step * (minTicks - 1)
- } else {
- const center = (min + max) / 2
- const halfRange = (step * (minTicks - 1)) / 2
- startTick = Math.floor((center - halfRange) / step) * step
- endTick = startTick + step * (minTicks - 1)
- }
- // 重新生成刻度
- ticks.length = 0
- currentTick = startTick
- while (currentTick <= endTick + step * 0.001) {
- if (isInteger) {
- ticks.push(Math.round(currentTick))
- } else {
- ticks.push(parseFloat(currentTick.toFixed(10)))
- }
- currentTick += step
- }
- }
- return ticks
- },
- genColors () {
- this.colors = this.fixedSeries.map((item, i) => {
- return this.colors[i] || this.colorHash.hex(`${i * 1000}`)
- })
- },
- checkAllClick (val) {
- const list = []
- if (this.isAllSelected) {
- this.tableData.map((item, index) => {
- if (this.highlights.some(l => l.index === index)) {
- list.push({ row: item, rowIndex: index })
- }
- })
- } else {
- this.tableData.map((item, index) => {
- if (!this.highlights.some(l => l.index === index)) {
- list.push({ row: item, rowIndex: index })
- }
- })
- }
- this.tableSetHighlights(list)
- },
- getTableData (series, reducedResult) {
- return series.map((val, i) => {
- const ret = { ...val.tags, raw_name: val.raw_name }
- const showMetric = !!this.groupBy
- if (showMetric) {
- ret.__metric = val.name
- }
- const { series, color = [] } = this.chartInstanceOption
- const colors = series.map(val => val.itemStyle.color)
- const c = colors[i] || color[0]
- ret.__color = c
- if (reducedResult && reducedResult.result) {
- ret.result = reducedResult.result[i]
- }
- return ret
- })
- },
- pageChange ({ type, currentPage, pageSize, $event }) {
- this.$emit('pageChange', { seriesIndex: this.curPager.seriesIndex, total: this.curPager.total, limit: pageSize, page: currentPage })
- },
- sortChange ({ order }) {
- this.$emit('reducedResultOrderChange', order)
- },
- cellClick ({ row, rowIndex }) {
- this.tableSetHighlight({ row, rowIndex })
- },
- tableSetHighlight ({ row, rowIndex, click }) {
- let seriesName = _.get(this.chartInstanceOption, `series[${rowIndex}].name`)
- seriesName = seriesName || `series${rowIndex}`
- this.highlightSeries([{ seriesName, row, rowIndex }])
- },
- tableSetHighlights (list) {
- const highlights = list.map(item => {
- const { row, rowIndex } = item
- let seriesName = _.get(this.chartInstanceOption, `series[${rowIndex}].name`)
- seriesName = seriesName || `series${rowIndex}`
- return { seriesName, row, rowIndex }
- })
- this.highlightSeries(highlights)
- },
- setChartInstance (v) {
- this.chartInstanceOption = v.getOption()
- this.chartInstance = v
- this.$emit('chartInstance', v)
- this.chartInstance.on('click', params => {
- this._cancelHighlight()
- this.highlightSeries([{ seriesName: params.seriesName, row: this.tableData[params.seriesIndex], rowIndex: params.seriesIndex }])
- })
- if (this.chartInstance && R.is(Function, this.chartInstance.getModel)) {
- const model = this.chartInstance.getModel()
- if (model && R.is(Function, model.getComponent)) {
- const component = model.getComponent('yAxis')
- const yMax = _.get(component, 'axis.scale._extent[1]')
- this.yMax = yMax
- }
- }
- },
- highlightSeries (list, seriesName, row, rowIndex) {
- let highlights = [...this.highlights]
- list.forEach(item => {
- const { row, rowIndex } = item
- const target = this.highlights.filter(item => item.index === rowIndex)
- if (target.length) {
- highlights = highlights.filter(item => item.index !== rowIndex)
- } else {
- highlights = [...highlights, { index: rowIndex, color: row.__color }]
- }
- })
- this.highlights = highlights
- },
- _cancelHighlight () {
- const selected = {}
- const option = {
- data: this.lineChartOptionsC.series.map(s => { selected[s.name] = true; return s.name }),
- selectedMode: 'multiple',
- show: false,
- selected: selected,
- }
- this.chartInstance.setOption({
- legend: option,
- })
- },
- _setHighlights (seriesNames) {
- const selected = {}
- const option = {
- data: this.lineChartOptionsC.series.map(s => { selected[s.name] = seriesNames.includes(s.name); return s.name }),
- selectedMode: 'multiple',
- selected: selected,
- show: false,
- }
- this.chartInstance.setOption({
- legend: option,
- })
- },
- getMonitorLine () {
- this.highlights = this.fixedSeries.map((item, index) => {
- return {
- index,
- }
- })
- },
- getRowStyle ({ $rowIndex, column, columnIndex, $columnIndex }) {
- if ($rowIndex === this.highlight.index) {
- return {
- color: this.highlight.color,
- }
- }
- return null
- },
- showThreshold () {
- if (this.threshold > this.yMax) {
- this.alertHandlerShow = true
- } else {
- this.alertHandlerShow = false
- }
- },
- exportTable () {
- const { total, limit } = this.curPager
- if (total > limit) {
- // 导出全量
- this.$emit('exportTable', total)
- } else {
- this.exportData(this.tableData)
- }
- },
- exportFullData (series, reducedResult) {
- const tableData = this.getTableData(series, reducedResult)
- this.exportData(tableData)
- },
- exportData (data) {
- const columns = this.columns.filter(item => item.field !== 'color')
- const list = [[...columns.map(item => item.title)]]
- data.forEach(item => {
- const row = []
- columns.forEach(col => {
- row.push(col.formatter ? col.formatter({ row: item, cellValue: item[col.field] }) : item[col.field])
- })
- list.push(row)
- })
- const filename = `${this.title || 'monitor table'}.xlsx`
- const ws_name = 'Sheet1'
- const wb = XLSX.utils.book_new()
- const ws = XLSX.utils.aoa_to_sheet(list)
- XLSX.utils.book_append_sheet(wb, ws, ws_name)
- XLSX.writeFile(wb, filename)
- },
- toggleShowTableLegend () {
- if (this.isTemplate) {
- return
- }
- this.showTableLegend = !this.showTableLegend
- },
- },
- }
- </script>
- <style lang="less" scoped>
- .explorer-monitor-line {
- ::v-deep .ant-card-body {
- width: 100%;
- }
- .alert-handler-wrapper {
- width: 50px;
- .alert-handler-line {
- background-color: red;
- height: 2px;
- z-index: 0;
- position: relative;
- width: 15px;
- }
- .alert-handler {
- font-size: 12px;
- color: red;
- }
- }
- }
- </style>
|