mirror of
				https://github.com/veops/cmdb.git
				synced 2025-10-31 11:09:21 +08:00 
			
		
		
		
	dashboard ui update
This commit is contained in:
		| @@ -68,7 +68,8 @@ export default { | ||||
|   }, | ||||
|  | ||||
|   methods: { | ||||
|     visibleChange(open) { | ||||
|     visibleChange(open, isInitOne = true) { | ||||
|       // isInitOne  初始化exp为空时,ruleList是否默认给一条 | ||||
|       //   const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g | ||||
|       const exp = this.expression.match(new RegExp(this.regQ, 'g')) | ||||
|         ? this.expression.match(new RegExp(this.regQ, 'g'))[0] | ||||
| @@ -151,15 +152,20 @@ export default { | ||||
|         }) | ||||
|         this.ruleList = [...expArray] | ||||
|       } else if (open) { | ||||
|         this.ruleList = [ | ||||
|           { | ||||
|             id: uuidv4(), | ||||
|             type: 'and', | ||||
|             property: this.canSearchPreferenceAttrList[0].name, | ||||
|             exp: 'is', | ||||
|             value: null, | ||||
|           }, | ||||
|         ] | ||||
|         this.ruleList = isInitOne | ||||
|           ? [ | ||||
|               { | ||||
|                 id: uuidv4(), | ||||
|                 type: 'and', | ||||
|                 property: | ||||
|                   this.canSearchPreferenceAttrList && this.canSearchPreferenceAttrList.length | ||||
|                     ? this.canSearchPreferenceAttrList[0].name | ||||
|                     : undefined, | ||||
|                 exp: 'is', | ||||
|                 value: null, | ||||
|               }, | ||||
|             ] | ||||
|           : [] | ||||
|       } | ||||
|     }, | ||||
|     handleClear() { | ||||
|   | ||||
| @@ -77,6 +77,14 @@ export function getCITypeAttributesByTypeIds(params) { | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function getCITypeCommonAttributesByTypeIds(params) { | ||||
|   return axios({ | ||||
|     url: `/v0.1/ci_types/common_attributes`, | ||||
|     method: 'get', | ||||
|     params: params | ||||
|   }) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 删除属性 | ||||
|  * @param attrId | ||||
|   | ||||
| @@ -61,3 +61,10 @@ export function revokeTypeRelation(first_type_id, second_type_id, rid, data) { | ||||
|     data | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export function getRecursive_level2children(type_id) { | ||||
|   return axios({ | ||||
|     url: `/v0.1/ci_type_relations/${type_id}/recursive_level2children`, | ||||
|     method: 'GET' | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -37,3 +37,11 @@ export function batchUpdateCustomDashboard(data) { | ||||
|         data | ||||
|     }) | ||||
| } | ||||
|  | ||||
| export function postCustomDashboardPreview(data) { | ||||
|     return axios({ | ||||
|         url: '/v0.1/custom_dashboard/preview', | ||||
|         method: 'post', | ||||
|         data | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,55 @@ | ||||
| <template> | ||||
|   <div :style="{ width: '100%', height: 'calc(100% - 2.2vw)' }"> | ||||
|     <div v-if="category === 0" class="cmdb-dashboard-grid-item-chart"> | ||||
|   <div | ||||
|     :id="`cmdb-dashboard-${chartId}-${editable}-${isPreview}`" | ||||
|     :style="{ width: '100%', height: 'calc(100% - 2.2vw)' }" | ||||
|   > | ||||
|     <div | ||||
|       v-if="options.chartType === 'count'" | ||||
|       :style="{ color: options.fontColor || '#fff' }" | ||||
|       class="cmdb-dashboard-grid-item-chart" | ||||
|     > | ||||
|       <div class="cmdb-dashboard-grid-item-chart-icon" v-if="options.showIcon && ciType"> | ||||
|         <template v-if="ciType.icon"> | ||||
|           <img v-if="ciType.icon.split('$$')[2]" :src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`" /> | ||||
|           <ops-icon | ||||
|             v-else | ||||
|             :style="{ | ||||
|               color: ciType.icon.split('$$')[1], | ||||
|             }" | ||||
|             :type="ciType.icon.split('$$')[0]" | ||||
|           /> | ||||
|         </template> | ||||
|         <span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span> | ||||
|       </div> | ||||
|       <span :style="{ ...options.fontConfig }">{{ toThousands(data) }}</span> | ||||
|     </div> | ||||
|     <vxe-table | ||||
|       :max-height="tableHeight" | ||||
|       :data="tableData" | ||||
|       :stripe="!!options.ret" | ||||
|       size="mini" | ||||
|       class="ops-stripe-table" | ||||
|       v-if="options.chartType === 'table'" | ||||
|       :span-method="mergeRowMethod" | ||||
|       :border="!options.ret" | ||||
|       :show-header="!!options.ret" | ||||
|     > | ||||
|       <template v-if="options.ret"> | ||||
|         <vxe-column v-for="col in columns" :key="col" :title="col" :field="col"></vxe-column> | ||||
|       </template> | ||||
|       <template v-else> | ||||
|         <vxe-column | ||||
|           v-for="(key, index) in Array(keyLength)" | ||||
|           :key="`key${index}`" | ||||
|           :title="`key${index}`" | ||||
|           :field="`key${index}`" | ||||
|         ></vxe-column> | ||||
|         <vxe-column field="value" title="value"></vxe-column> | ||||
|       </template> | ||||
|     </vxe-table> | ||||
|     <div | ||||
|       :id="`cmdb-dashboard-${chartId}-${editable}`" | ||||
|       v-if="category === 1 || category === 2" | ||||
|       v-else-if="category === 1 || category === 2" | ||||
|       class="cmdb-dashboard-grid-item-chart" | ||||
|     ></div> | ||||
|   </div> | ||||
| @@ -15,17 +59,27 @@ | ||||
| import * as echarts from 'echarts' | ||||
| import { mixin } from '@/utils/mixin' | ||||
| import { toThousands } from '../../utils/helper' | ||||
| import { category_1_bar_options, category_1_pie_options, category_2_bar_options } from './chartOptions' | ||||
| import { | ||||
|   category_1_bar_options, | ||||
|   category_1_line_options, | ||||
|   category_1_pie_options, | ||||
|   category_2_bar_options, | ||||
|   category_2_pie_options, | ||||
| } from './chartOptions' | ||||
| export default { | ||||
|   name: 'Chart', | ||||
|   mixins: [mixin], | ||||
|   props: { | ||||
|     ci_types: { | ||||
|       type: Array, | ||||
|       default: () => [], | ||||
|     }, | ||||
|     chartId: { | ||||
|       type: Number, | ||||
|       default: 0, | ||||
|     }, | ||||
|     data: { | ||||
|       type: [Number, Object], | ||||
|       type: [Number, Object, Array], | ||||
|       default: 0, | ||||
|     }, | ||||
|     category: { | ||||
| @@ -40,20 +94,65 @@ export default { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|     type_id: { | ||||
|       type: [Number, Array], | ||||
|       default: null, | ||||
|     }, | ||||
|     isPreview: { | ||||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       chart: null, | ||||
|       columns: [], | ||||
|       tableHeight: '', | ||||
|       tableData: [], | ||||
|       keyLength: 0, | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ciType() { | ||||
|       if (this.type_id || this.options?.type_ids) { | ||||
|         const _find = this.ci_types.find((item) => item.id === this.type_id || item.id === this.options?.type_ids[0]) | ||||
|         return _find || null | ||||
|       } | ||||
|       return null | ||||
|     }, | ||||
|   }, | ||||
|   watch: { | ||||
|     data: { | ||||
|       immediate: true, | ||||
|       deep: true, | ||||
|       handler(newValue, oldValue) { | ||||
|         if (this.category === 1 || this.category === 2) { | ||||
|           if (Object.prototype.toString.call(newValue) === '[object Object]') { | ||||
|             this.setChart() | ||||
|           if (this.options.chartType !== 'table' && Object.prototype.toString.call(newValue) === '[object Object]') { | ||||
|             if (this.isPreview) { | ||||
|               this.$nextTick(() => { | ||||
|                 this.setChart() | ||||
|               }) | ||||
|             } else { | ||||
|               this.setChart() | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         if (this.options.chartType === 'table') { | ||||
|           this.$nextTick(() => { | ||||
|             const dom = document.getElementById(`cmdb-dashboard-${this.chartId}-${this.editable}-${this.isPreview}`) | ||||
|             this.tableHeight = dom.offsetHeight | ||||
|           }) | ||||
|           if (this.options.ret) { | ||||
|             const excludeKeys = ['_X_ROW_KEY', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias', '_id', '_type'] | ||||
|             if (newValue && newValue.length) { | ||||
|               this.columns = Object.keys(newValue[0]).filter((keys) => !excludeKeys.includes(keys)) | ||||
|               this.tableData = newValue | ||||
|             } | ||||
|           } else { | ||||
|             const _data = [] | ||||
|             this.keyLength = this.options?.attr_ids?.length ?? 0 | ||||
|             this.formatTableData(_data, this.data, {}) | ||||
|             this.tableData = _data | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
| @@ -81,13 +180,19 @@ export default { | ||||
|         this.chart = echarts.init(document.getElementById(`cmdb-dashboard-${this.chartId}-${this.editable}`)) | ||||
|       } | ||||
|       if (this.category === 1 && this.options.chartType === 'bar') { | ||||
|         this.chart.setOption(category_1_bar_options(this.data), true) | ||||
|         this.chart.setOption(category_1_bar_options(this.data, this.options), true) | ||||
|       } | ||||
|       if (this.category === 1 && this.options.chartType === 'line') { | ||||
|         this.chart.setOption(category_1_line_options(this.data, this.options), true) | ||||
|       } | ||||
|       if (this.category === 1 && this.options.chartType === 'pie') { | ||||
|         this.chart.setOption(category_1_pie_options(this.data), true) | ||||
|         this.chart.setOption(category_1_pie_options(this.data, this.options), true) | ||||
|       } | ||||
|       if (this.category === 2) { | ||||
|         this.chart.setOption(category_2_bar_options(this.data), true) | ||||
|       if (this.category === 2 && ['bar', 'line'].includes(this.options.chartType)) { | ||||
|         this.chart.setOption(category_2_bar_options(this.data, this.options, this.options.chartType), true) | ||||
|       } | ||||
|       if (this.category === 2 && this.options.chartType === 'pie') { | ||||
|         this.chart.setOption(category_2_pie_options(this.data, this.options), true) | ||||
|       } | ||||
|     }, | ||||
|     resizeChart() { | ||||
| @@ -97,6 +202,34 @@ export default { | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     formatTableData(_data, data, obj) { | ||||
|       Object.keys(data).forEach((k) => { | ||||
|         if (typeof data[k] === 'number') { | ||||
|           _data.push({ ...obj, [`key${Object.keys(obj).length}`]: k, value: data[k] }) | ||||
|         } else { | ||||
|           this.formatTableData(_data, data[k], { ...obj, [`key${Object.keys(obj).length}`]: k }) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     mergeRowMethod({ row, _rowIndex, column, visibleData }) { | ||||
|       const fields = ['key0', 'key1', 'key2'] | ||||
|       const cellValue = row[column.field] | ||||
|       if (cellValue && fields.includes(column.field)) { | ||||
|         const prevRow = visibleData[_rowIndex - 1] | ||||
|         let nextRow = visibleData[_rowIndex + 1] | ||||
|         if (prevRow && prevRow[column.field] === cellValue) { | ||||
|           return { rowspan: 0, colspan: 0 } | ||||
|         } else { | ||||
|           let countRowspan = 1 | ||||
|           while (nextRow && nextRow[column.field] === cellValue) { | ||||
|             nextRow = visibleData[++countRowspan + _rowIndex] | ||||
|           } | ||||
|           if (countRowspan > 1) { | ||||
|             return { rowspan: countRowspan, colspan: 1 } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
| @@ -106,14 +239,28 @@ export default { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   position: relative; | ||||
|   padding: 10px; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   > span { | ||||
|     font-size: 50px; | ||||
|     font-weight: 700; | ||||
|     position: absolute; | ||||
|     top: 50%; | ||||
|     left: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|   } | ||||
|   .cmdb-dashboard-grid-item-chart-icon { | ||||
|     > i { | ||||
|       font-size: 4vw; | ||||
|     } | ||||
|     > img { | ||||
|       width: 4vw; | ||||
|     } | ||||
|     > span { | ||||
|       display: inline-block; | ||||
|       width: 4vw; | ||||
|       height: 4vw; | ||||
|       font-size: 50px; | ||||
|       text-align: center; | ||||
|       line-height: 50px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,65 +1,307 @@ | ||||
| <template> | ||||
|   <a-modal :title="`${type === 'add' ? '新增' : '编辑'}图表`" :visible="visible" @cancel="handleclose" @ok="handleok"> | ||||
|     <a-form-model ref="chartForm" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 14 }"> | ||||
|       <a-form-model-item label="类型" prop="category"> | ||||
|         <a-select v-model="form.category" @change="changeDashboardCategory"> | ||||
|           <a-select-option v-for="cate in Object.keys(dashboardCategory)" :key="cate" :value="Number(cate)">{{ | ||||
|             dashboardCategory[cate].label | ||||
|           }}</a-select-option> | ||||
|         </a-select> | ||||
|       </a-form-model-item> | ||||
|       <a-form-model-item v-if="form.category !== 0" label="名称" prop="name"> | ||||
|         <a-input v-model="form.name" placeholder="请输入图表名称"></a-input> | ||||
|       </a-form-model-item> | ||||
|       <a-form-model-item label="模型" prop="type_id"> | ||||
|         <a-select | ||||
|           show-search | ||||
|           optionFilterProp="children" | ||||
|           @change="changeCIType" | ||||
|           v-model="form.type_id" | ||||
|           placeholder="请选择模型" | ||||
|         > | ||||
|           <a-select-option v-for="ci_type in ci_types" :key="ci_type.id" :value="ci_type.id">{{ | ||||
|             ci_type.alias || ci_type.name | ||||
|           }}</a-select-option> | ||||
|         </a-select> | ||||
|       </a-form-model-item> | ||||
|       <a-form-model-item v-if="form.category === 1" label="模型属性" prop="attr_id"> | ||||
|         <a-select show-search optionFilterProp="children" v-model="form.attr_id" placeholder="请选择模型属性"> | ||||
|           <a-select-option v-for="attr in attributes" :key="attr.id" :value="attr.id">{{ | ||||
|             attr.alias || attr.name | ||||
|           }}</a-select-option> | ||||
|         </a-select> | ||||
|       </a-form-model-item> | ||||
|       <a-form-model-item v-if="form.category === 1" label="图表类型" prop="chartType"> | ||||
|         <a-radio-group v-model="chartType"> | ||||
|           <a-radio value="bar"> | ||||
|             柱状图 | ||||
|           </a-radio> | ||||
|           <a-radio value="pie"> | ||||
|             饼图 | ||||
|           </a-radio> | ||||
|         </a-radio-group> | ||||
|       </a-form-model-item> | ||||
|       <a-form-model-item v-if="form.category === 2" label="关系层级" prop="level"> | ||||
|         <a-input v-model="form.level" placeholder="请输入关系层级"></a-input> | ||||
|       </a-form-model-item> | ||||
|       <a-form-model-item v-if="form.category === 0" label="字体"> | ||||
|         <FontConfig ref="fontConfig" /> | ||||
|       </a-form-model-item> | ||||
|     </a-form-model> | ||||
|   <a-modal | ||||
|     width="1100px" | ||||
|     :title="`${type === 'add' ? '新增' : '编辑'}图表`" | ||||
|     :visible="visible" | ||||
|     @cancel="handleclose" | ||||
|     @ok="handleok" | ||||
|     :bodyStyle="{ paddingTop: 0 }" | ||||
|   > | ||||
|     <div class="chart-wrapper"> | ||||
|       <div class="chart-left"> | ||||
|         <a-form-model ref="chartForm" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }"> | ||||
|           <a-form-model-item label="标题" prop="name"> | ||||
|             <a-input v-model="form.name" placeholder="请输入图表标题"></a-input> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="类型" prop="category" v-if="chartType !== 'count' && chartType !== 'table'"> | ||||
|             <a-radio-group | ||||
|               @change=" | ||||
|                 () => { | ||||
|                   resetForm() | ||||
|                 } | ||||
|               " | ||||
|               :default-value="1" | ||||
|               v-model="form.category" | ||||
|             > | ||||
|               <a-radio-button :value="Number(key)" :key="key" v-for="key in Object.keys(dashboardCategory)"> | ||||
|                 {{ dashboardCategory[key].label }} | ||||
|               </a-radio-button> | ||||
|             </a-radio-group> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="类型" prop="tableCategory" v-if="chartType === 'table'"> | ||||
|             <a-radio-group | ||||
|               @change=" | ||||
|                 () => { | ||||
|                   resetForm() | ||||
|                 } | ||||
|               " | ||||
|               :default-value="1" | ||||
|               v-model="form.tableCategory" | ||||
|             > | ||||
|               <a-radio-button :value="1"> | ||||
|                 计算指标 | ||||
|               </a-radio-button> | ||||
|               <a-radio-button :value="2"> | ||||
|                 资源数据 | ||||
|               </a-radio-button> | ||||
|             </a-radio-group> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item | ||||
|             v-if="(chartType !== 'table' && form.category !== 2) || (chartType === 'table' && form.tableCategory === 1)" | ||||
|             label="模型" | ||||
|             prop="type_ids" | ||||
|           > | ||||
|             <a-select | ||||
|               show-search | ||||
|               optionFilterProp="children" | ||||
|               @change="changeCIType" | ||||
|               v-model="form.type_ids" | ||||
|               placeholder="请选择模型" | ||||
|               mode="multiple" | ||||
|             > | ||||
|               <a-select-option v-for="ci_type in ci_types" :key="ci_type.id" :value="ci_type.id">{{ | ||||
|                 ci_type.alias || ci_type.name | ||||
|               }}</a-select-option> | ||||
|             </a-select> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item v-else label="模型" prop="type_id"> | ||||
|             <a-select | ||||
|               show-search | ||||
|               optionFilterProp="children" | ||||
|               @change="changeCIType" | ||||
|               v-model="form.type_id" | ||||
|               placeholder="请选择模型" | ||||
|             > | ||||
|               <a-select-option v-for="ci_type in ci_types" :key="ci_type.id" :value="ci_type.id">{{ | ||||
|                 ci_type.alias || ci_type.name | ||||
|               }}</a-select-option> | ||||
|             </a-select> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item | ||||
|             label="维度" | ||||
|             prop="attr_ids" | ||||
|             v-if="(['bar', 'line', 'pie'].includes(chartType) && form.category === 1) || chartType === 'table'" | ||||
|           > | ||||
|             <a-select @change="changeAttr" v-model="form.attr_ids" placeholder="请选择维度" mode="multiple" show-search> | ||||
|               <a-select-option v-for="attr in commonAttributes" :key="attr.id" :value="attr.id">{{ | ||||
|                 attr.alias || attr.name | ||||
|               }}</a-select-option> | ||||
|             </a-select> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item | ||||
|             prop="type_ids" | ||||
|             label="关系模型" | ||||
|             v-if="['bar', 'line', 'pie'].includes(chartType) && form.category === 2" | ||||
|           > | ||||
|             <a-select | ||||
|               show-search | ||||
|               optionFilterProp="children" | ||||
|               mode="multiple" | ||||
|               v-model="form.type_ids" | ||||
|               placeholder="请选择模型" | ||||
|             > | ||||
|               <a-select-opt-group | ||||
|                 v-for="(key, index) in Object.keys(level2children)" | ||||
|                 :key="key" | ||||
|                 :label="`层级${index + 1}`" | ||||
|               > | ||||
|                 <a-select-option | ||||
|                   @click="(e) => clickLevel2children(e, citype, index + 1)" | ||||
|                   v-for="citype in level2children[key]" | ||||
|                   :key="citype.id" | ||||
|                   :value="citype.id" | ||||
|                 > | ||||
|                   {{ citype.alias || citype.name }} | ||||
|                 </a-select-option> | ||||
|               </a-select-opt-group> | ||||
|             </a-select> | ||||
|           </a-form-model-item> | ||||
|           <div class="chart-left-preview"> | ||||
|             <span class="chart-left-preview-operation" @click="showPreview"><a-icon type="play-circle" /> 预览</span> | ||||
|             <template v-if="isShowPreview"> | ||||
|               <div v-if="chartType !== 'count'" class="cmdb-dashboard-grid-item-title"> | ||||
|                 <template v-if="form.showIcon && ciType"> | ||||
|                   <template v-if="ciType.icon"> | ||||
|                     <img | ||||
|                       v-if="ciType.icon.split('$$')[2]" | ||||
|                       :src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`" | ||||
|                     /> | ||||
|                     <ops-icon | ||||
|                       v-else | ||||
|                       :style="{ | ||||
|                         color: ciType.icon.split('$$')[1], | ||||
|                       }" | ||||
|                       :type="ciType.icon.split('$$')[0]" | ||||
|                     /> | ||||
|                   </template> | ||||
|                   <span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span> | ||||
|                 </template> | ||||
|                 <span :style="{ color: '#000' }"> {{ form.name }}</span> | ||||
|               </div> | ||||
|               <div | ||||
|                 class="chart-left-preview-box" | ||||
|                 :style="{ | ||||
|                   height: chartType === 'count' ? '120px' : '', | ||||
|                   marginTop: chartType === 'count' ? '80px' : '', | ||||
|                   background: | ||||
|                     chartType === 'count' | ||||
|                       ? Array.isArray(bgColor) | ||||
|                         ? `linear-gradient(to bottom, ${bgColor[0]} 0%, ${bgColor[1]} 100%)` | ||||
|                         : bgColor | ||||
|                       : '#fafafa', | ||||
|                 }" | ||||
|               > | ||||
|                 <div :style="{ color: fontColor }">{{ form.name }}</div> | ||||
|                 <Chart | ||||
|                   :ref="`chart_${item.id}`" | ||||
|                   :chartId="item.id" | ||||
|                   :data="previewData" | ||||
|                   :category="form.category" | ||||
|                   :options="{ | ||||
|                     ...item.options, | ||||
|                     name: form.name, | ||||
|                     fontColor: fontColor, | ||||
|                     bgColor: bgColor, | ||||
|                     chartType: chartType, | ||||
|                     showIcon: form.showIcon, | ||||
|                     barDirection: barDirection, | ||||
|                     barStack: barStack, | ||||
|                     chartColor: chartColor, | ||||
|                     type_ids: form.type_ids, | ||||
|                     attr_ids: form.attr_ids, | ||||
|                     isShadow: isShadow, | ||||
|                   }" | ||||
|                   :editable="false" | ||||
|                   :ci_types="ci_types" | ||||
|                   :type_id="form.type_id || form.type_ids" | ||||
|                   isPreview | ||||
|                 /> | ||||
|               </div> | ||||
|             </template> | ||||
|           </div> | ||||
|           <a-form-model-item label="是否显示icon" prop="showIcon" :label-col="{ span: 5 }" :wrapper-col="{ span: 18 }"> | ||||
|             <a-switch v-model="form.showIcon"></a-switch> | ||||
|           </a-form-model-item> | ||||
|         </a-form-model> | ||||
|       </div> | ||||
|  | ||||
|       <div class="chart-right"> | ||||
|         <h4>图表类型</h4> | ||||
|         <div class="chart-right-type"> | ||||
|           <div | ||||
|             :class="{ 'chart-right-type-box': true, 'chart-right-type-box-selected': chartType === t.value }" | ||||
|             v-for="t in chartTypeList" | ||||
|             :key="t.value" | ||||
|             @click="changeChartType(t)" | ||||
|           > | ||||
|             <ops-icon :type="`cmdb-${t.value}`" /> | ||||
|             <span>{{ t.label }}</span> | ||||
|           </div> | ||||
|         </div> | ||||
|         <h4>数据筛选</h4> | ||||
|         <FilterComp | ||||
|           ref="filterComp" | ||||
|           :isDropdown="false" | ||||
|           :canSearchPreferenceAttrList="attributes" | ||||
|           @setExpFromFilter="setExpFromFilter" | ||||
|           :expression="filterExp ? `q=${filterExp}` : ''" | ||||
|         /> | ||||
|         <h4>格式</h4> | ||||
|         <a-form-model :colon="false" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> | ||||
|           <a-form-model-item label="字体颜色" v-if="chartType === 'count'"> | ||||
|             <ColorPicker | ||||
|               v-model="fontColor" | ||||
|               :colorList="[ | ||||
|                 '#1D2129', | ||||
|                 '#4E5969', | ||||
|                 '#103C93', | ||||
|                 '#86909C', | ||||
|                 '#ffffff', | ||||
|                 '#C9F2FF', | ||||
|                 '#FFEAC0', | ||||
|                 '#D6FFE6', | ||||
|                 '#F2DEFF', | ||||
|               ]" | ||||
|             /> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="背景颜色" v-if="chartType === 'count'"> | ||||
|             <ColorPicker | ||||
|               v-model="bgColor" | ||||
|               :colorList="[ | ||||
|                 ['#6ABFFE', '#5375EB'], | ||||
|                 ['#C69EFF', '#A377F9'], | ||||
|                 ['#85EBC9', '#4AB8D8'], | ||||
|                 ['#FEB58B', '#DF6463'], | ||||
|                 '#ffffff', | ||||
|                 '#FFFBF0', | ||||
|                 '#FFF1EC', | ||||
|                 '#E5FFFE', | ||||
|                 '#E5E7FF', | ||||
|               ]" | ||||
|             /> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="图表颜色" v-else-if="chartType !== 'table'"> | ||||
|             <ColorListPicker v-model="chartColor" /> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="图表长度(%)"> | ||||
|             <a-radio-group class="chart-width" style="width:100%;" v-model="width"> | ||||
|               <a-radio-button :value="3"> | ||||
|                 25 | ||||
|               </a-radio-button> | ||||
|               <a-radio-button :value="6"> | ||||
|                 50 | ||||
|               </a-radio-button> | ||||
|               <a-radio-button :value="9"> | ||||
|                 75 | ||||
|               </a-radio-button> | ||||
|               <a-radio-button :value="12"> | ||||
|                 100 | ||||
|               </a-radio-button> | ||||
|             </a-radio-group> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="柱状图类型" v-if="chartType === 'bar'"> | ||||
|             <a-radio-group v-model="barStack"> | ||||
|               <a-radio value="total"> | ||||
|                 堆积柱状图 | ||||
|               </a-radio> | ||||
|               <a-radio value=""> | ||||
|                 多系列柱状图 | ||||
|               </a-radio> | ||||
|             </a-radio-group> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="方向" v-if="chartType === 'bar'"> | ||||
|             <a-radio-group v-model="barDirection"> | ||||
|               <a-radio value="x"> | ||||
|                 X轴 | ||||
|               </a-radio> | ||||
|               <a-radio value="y"> | ||||
|                 y轴 | ||||
|               </a-radio> | ||||
|             </a-radio-group> | ||||
|           </a-form-model-item> | ||||
|           <a-form-model-item label="下方阴影" v-if="chartType === 'line'"> | ||||
|             <a-switch v-model="isShadow" /> | ||||
|           </a-form-model-item> | ||||
|         </a-form-model> | ||||
|       </div> | ||||
|     </div> | ||||
|   </a-modal> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Chart from './chart.vue' | ||||
| import { dashboardCategory } from './constant' | ||||
| import { postCustomDashboard, putCustomDashboard } from '../../api/customDashboard' | ||||
| import { getCITypeAttributesById } from '../../api/CITypeAttr' | ||||
| import { postCustomDashboard, putCustomDashboard, postCustomDashboardPreview } from '../../api/customDashboard' | ||||
| import { getCITypeAttributesByTypeIds, getCITypeCommonAttributesByTypeIds } from '../../api/CITypeAttr' | ||||
| import { getRecursive_level2children } from '../../api/CITypeRelation' | ||||
| import { getLastLayout } from '../../utils/helper' | ||||
| import FontConfig from './fontConfig.vue' | ||||
| import FilterComp from '@/components/CMDBFilterComp' | ||||
| import ColorPicker from './colorPicker.vue' | ||||
| import ColorListPicker from './colorListPicker.vue' | ||||
|  | ||||
| export default { | ||||
|   name: 'ChartForm', | ||||
|   components: { FontConfig }, | ||||
|   components: { Chart, FilterComp, ColorPicker, ColorListPicker }, | ||||
|   props: { | ||||
|     ci_types: { | ||||
|       type: Array, | ||||
| @@ -67,100 +309,226 @@ export default { | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     const chartTypeList = [ | ||||
|       { | ||||
|         value: 'count', | ||||
|         label: '指标', | ||||
|       }, | ||||
|       { | ||||
|         value: 'bar', | ||||
|         label: '柱状图', | ||||
|       }, | ||||
|       { | ||||
|         value: 'line', | ||||
|         label: '折线图', | ||||
|       }, | ||||
|       { | ||||
|         value: 'pie', | ||||
|         label: '饼状图', | ||||
|       }, | ||||
|       { | ||||
|         value: 'table', | ||||
|         label: '表格', | ||||
|       }, | ||||
|     ] | ||||
|     return { | ||||
|       dashboardCategory, | ||||
|       chartTypeList, | ||||
|       visible: false, | ||||
|       attributes: [], | ||||
|       type: 'add', | ||||
|       form: { | ||||
|         category: 0, | ||||
|         tableCategory: 1, | ||||
|         name: undefined, | ||||
|         type_id: undefined, | ||||
|         attr_id: undefined, | ||||
|         type_ids: undefined, | ||||
|         attr_ids: undefined, | ||||
|         level: undefined, | ||||
|         showIcon: false, | ||||
|       }, | ||||
|       rules: { | ||||
|         category: [{ required: true, trigger: 'change' }], | ||||
|         name: [{ required: true, message: '请输入图表名称' }], | ||||
|         type_id: [{ required: true, message: '请选择模型', trigger: 'change' }], | ||||
|         attr_id: [{ required: true, message: '请选择模型属性', trigger: 'change' }], | ||||
|         type_ids: [{ required: true, message: '请选择模型', trigger: 'change' }], | ||||
|         attr_ids: [{ required: true, message: '请选择模型属性', trigger: 'change' }], | ||||
|         level: [{ required: true, message: '请输入关系层级' }], | ||||
|         showIcon: [{ required: false }], | ||||
|       }, | ||||
|       item: {}, | ||||
|       chartType: 'bar', | ||||
|       chartType: 'count', // table,bar,line,pie,count | ||||
|       width: 3, | ||||
|       fontColor: '#ffffff', | ||||
|       bgColor: ['#6ABFFE', '#5375EB'], | ||||
|       chartColor: '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', // 图表颜色 | ||||
|       isShowPreview: false, | ||||
|       filterExp: undefined, | ||||
|       previewData: null, | ||||
|       barStack: 'total', | ||||
|       barDirection: 'y', | ||||
|       commonAttributes: [], | ||||
|       level2children: {}, | ||||
|       isShadow: false, | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ciType() { | ||||
|       if (this.form.type_id || this.form.type_ids) { | ||||
|         const _find = this.ci_types.find((item) => item.id === this.form.type_id || item.id === this.form.type_ids[0]) | ||||
|         return _find || null | ||||
|       } | ||||
|       return null | ||||
|     }, | ||||
|   }, | ||||
|   inject: ['layout'], | ||||
|   methods: { | ||||
|     open(type, item = {}) { | ||||
|     async open(type, item = {}) { | ||||
|       this.visible = true | ||||
|       this.type = type | ||||
|       this.item = item | ||||
|       const { category = 0, name, type_id, attr_id, level } = item | ||||
|       const chartType = (item.options || {}).chartType || 'bar' | ||||
|       const chartType = (item.options || {}).chartType || 'count' | ||||
|       const fontColor = (item.options || {}).fontColor || '#ffffff' | ||||
|       const bgColor = (item.options || {}).bgColor || ['#6ABFFE', '#5375EB'] | ||||
|       const width = (item.options || {}).w | ||||
|       const showIcon = (item.options || {}).showIcon | ||||
|       const type_ids = item?.options?.type_ids || [] | ||||
|       const attr_ids = item?.options?.attr_ids || [] | ||||
|       const ret = item?.options?.ret || '' | ||||
|       this.width = width | ||||
|       this.chartType = chartType | ||||
|       if (type_id && attr_id) { | ||||
|         getCITypeAttributesById(type_id).then((res) => { | ||||
|       this.filterExp = item?.options?.filter ?? '' | ||||
|       this.chartColor = item?.options?.chartColor ?? '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD' | ||||
|       this.isShadow = item?.options?.isShadow ?? false | ||||
|  | ||||
|       if (chartType === 'count') { | ||||
|         this.fontColor = fontColor | ||||
|         this.bgColor = bgColor | ||||
|       } | ||||
|       if (type_ids && type_ids.length) { | ||||
|         await getCITypeAttributesByTypeIds({ type_ids: type_ids.join(',') }).then((res) => { | ||||
|           this.attributes = res.attributes | ||||
|         }) | ||||
|         if ((['bar', 'line', 'pie'].includes(chartType) && category === 1) || chartType === 'table') { | ||||
|           this.barDirection = item?.options?.barDirection ?? 'y' | ||||
|           this.barStack = item?.options?.barStack ?? 'total' | ||||
|           await getCITypeCommonAttributesByTypeIds({ | ||||
|             type_ids: type_ids.join(','), | ||||
|           }).then((res) => { | ||||
|             this.commonAttributes = res.attributes | ||||
|           }) | ||||
|         } | ||||
|       } | ||||
|       if (type_id) { | ||||
|         getRecursive_level2children(type_id).then((res) => { | ||||
|           this.level2children = res | ||||
|         }) | ||||
|         await getCITypeCommonAttributesByTypeIds({ | ||||
|           type_ids: type_id, | ||||
|         }).then((res) => { | ||||
|           this.commonAttributes = res.attributes | ||||
|         }) | ||||
|       } | ||||
|       this.$nextTick(() => { | ||||
|         this.$refs.filterComp.visibleChange(true, false) | ||||
|       }) | ||||
|       const default_form = { | ||||
|         category: 0, | ||||
|         name: undefined, | ||||
|         type_id: undefined, | ||||
|         attr_id: undefined, | ||||
|         type_ids: undefined, | ||||
|         attr_ids: undefined, | ||||
|         level: undefined, | ||||
|         showIcon: false, | ||||
|         tableCategory: 1, | ||||
|       } | ||||
|       this.form = { | ||||
|         ...default_form, | ||||
|         category, | ||||
|         name, | ||||
|         type_id, | ||||
|         attr_id, | ||||
|         type_ids, | ||||
|         attr_ids, | ||||
|         level, | ||||
|       } | ||||
|       if (category === 0) { | ||||
|         this.$nextTick(() => { | ||||
|           this.$refs.fontConfig.setConfig((item.options || {}).fontConfig) | ||||
|         }) | ||||
|         showIcon, | ||||
|         tableCategory: ret === 'cis' ? 2 : 1, | ||||
|       } | ||||
|     }, | ||||
|     handleclose() { | ||||
|       this.attributes = [] | ||||
|       this.$refs.chartForm.clearValidate() | ||||
|       this.isShowPreview = false | ||||
|       this.visible = false | ||||
|     }, | ||||
|     changeCIType(value) { | ||||
|       getCITypeAttributesById(value).then((res) => { | ||||
|       this.form.attr_ids = [] | ||||
|       this.commonAttributes = [] | ||||
|       getCITypeAttributesByTypeIds({ type_ids: Array.isArray(value) ? value.join(',') : value }).then((res) => { | ||||
|         this.attributes = res.attributes | ||||
|         this.form = { | ||||
|           ...this.form, | ||||
|           attr_id: undefined, | ||||
|         } | ||||
|       }) | ||||
|       if (!Array.isArray(value)) { | ||||
|         getRecursive_level2children(value).then((res) => { | ||||
|           this.level2children = res | ||||
|         }) | ||||
|       } | ||||
|       if ((['bar', 'line', 'pie'].includes(this.chartType) && this.form.category === 1) || this.chartType === 'table') { | ||||
|         getCITypeCommonAttributesByTypeIds({ type_ids: Array.isArray(value) ? value.join(',') : value }).then((res) => { | ||||
|           this.commonAttributes = res.attributes | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
|     handleok() { | ||||
|       this.$refs.chartForm.validate(async (valid) => { | ||||
|         if (valid) { | ||||
|           const fontConfig = this.form.category === 0 ? this.$refs.fontConfig.getConfig() : undefined | ||||
|           const _find = this.ci_types.find((attr) => attr.id === this.form.type_id) | ||||
|           const name = this.form.name || (_find || {}).alias || (_find || {}).name | ||||
|           const name = this.form.name | ||||
|           const { chartType, fontColor, bgColor } = this | ||||
|           this.$refs.filterComp.handleSubmit() | ||||
|           if (this.item.id) { | ||||
|             await putCustomDashboard(this.item.id, { | ||||
|             const params = { | ||||
|               ...this.form, | ||||
|               options: { | ||||
|                 ...this.item.options, | ||||
|                 name, | ||||
|                 fontConfig, | ||||
|                 w: this.width, | ||||
|                 chartType: this.chartType, | ||||
|                 showIcon: this.form.showIcon, | ||||
|                 type_ids: this.form.type_ids, | ||||
|                 filter: this.filterExp, | ||||
|                 isShadow: this.isShadow, | ||||
|               }, | ||||
|             }) | ||||
|             } | ||||
|             if (chartType === 'count') { | ||||
|               params.options.fontColor = fontColor | ||||
|               params.options.bgColor = bgColor | ||||
|             } | ||||
|             if (['bar', 'line', 'pie'].includes(chartType)) { | ||||
|               if (this.form.category === 1) { | ||||
|                 params.options.attr_ids = this.form.attr_ids | ||||
|               } | ||||
|               params.options.chartColor = this.chartColor | ||||
|             } | ||||
|             if (chartType === 'bar') { | ||||
|               params.options.barDirection = this.barDirection | ||||
|               params.options.barStack = this.barStack | ||||
|             } | ||||
|             if (chartType === 'table') { | ||||
|               params.options.attr_ids = this.form.attr_ids | ||||
|               if (this.form.tableCategory === 2) { | ||||
|                 params.options.ret = 'cis' | ||||
|               } | ||||
|             } | ||||
|             delete params.showIcon | ||||
|             delete params.type_ids | ||||
|             delete params.attr_ids | ||||
|             delete params.tableCategory | ||||
|             await putCustomDashboard(this.item.id, params) | ||||
|           } else { | ||||
|             const { xLast, yLast, wLast } = getLastLayout(this.layout()) | ||||
|             const w = 3 | ||||
|             const w = this.width | ||||
|             const x = xLast + wLast + w > 12 ? 0 : xLast + wLast | ||||
|             const y = xLast + wLast + w > 12 ? yLast + 1 : yLast | ||||
|             await postCustomDashboard({ | ||||
|             const params = { | ||||
|               ...this.form, | ||||
|               options: { | ||||
|                 x, | ||||
| @@ -169,23 +537,216 @@ export default { | ||||
|                 h: this.form.category === 0 ? 3 : 5, | ||||
|                 name, | ||||
|                 chartType: this.chartType, | ||||
|                 fontConfig, | ||||
|                 showIcon: this.form.showIcon, | ||||
|                 type_ids: this.form.type_ids, | ||||
|                 filter: this.filterExp, | ||||
|                 isShadow: this.isShadow, | ||||
|               }, | ||||
|             }) | ||||
|             } | ||||
|             if (chartType === 'count') { | ||||
|               params.options.fontColor = fontColor | ||||
|               params.options.bgColor = bgColor | ||||
|             } | ||||
|             if (['bar', 'line', 'pie'].includes(chartType)) { | ||||
|               if (this.form.category === 1) { | ||||
|                 params.options.attr_ids = this.form.attr_ids | ||||
|               } | ||||
|               params.options.chartColor = this.chartColor | ||||
|             } | ||||
|             if (chartType === 'bar') { | ||||
|               params.options.barDirection = this.barDirection | ||||
|               params.options.barStack = this.barStack | ||||
|             } | ||||
|             if (chartType === 'table') { | ||||
|               params.options.attr_ids = this.form.attr_ids | ||||
|               if (this.form.tableCategory === 2) { | ||||
|                 params.options.ret = 'cis' | ||||
|               } | ||||
|             } | ||||
|             delete params.showIcon | ||||
|             delete params.type_ids | ||||
|             delete params.attr_ids | ||||
|             delete params.tableCategory | ||||
|             await postCustomDashboard(params) | ||||
|           } | ||||
|           this.handleclose() | ||||
|           this.$emit('refresh') | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     changeDashboardCategory(value) { | ||||
|       this.$refs.chartForm.clearValidate() | ||||
|       if (value === 1 && this.form.type_id) { | ||||
|         this.changeCIType(this.form.type_id) | ||||
|     // changeDashboardCategory(value) { | ||||
|     //   this.$refs.chartForm.clearValidate() | ||||
|     //   if (value === 1 && this.form.type_id) { | ||||
|     //     this.changeCIType(this.form.type_id) | ||||
|     //   } | ||||
|     // }, | ||||
|     changeChartType(t) { | ||||
|       this.chartType = t.value | ||||
|       this.isShowPreview = false | ||||
|       if (t.value === 'count') { | ||||
|         this.form.category = 0 | ||||
|       } else { | ||||
|         this.form.category = 1 | ||||
|       } | ||||
|       this.resetForm() | ||||
|     }, | ||||
|     showPreview() { | ||||
|       this.$refs.chartForm.validate(async (valid) => { | ||||
|         if (valid) { | ||||
|           this.isShowPreview = false | ||||
|           const name = this.form.name | ||||
|           const { chartType, fontColor, bgColor } = this | ||||
|           this.$refs.filterComp.handleSubmit() | ||||
|           const params = { | ||||
|             ...this.form, | ||||
|             options: { | ||||
|               name, | ||||
|               chartType, | ||||
|               showIcon: this.form.showIcon, | ||||
|               type_ids: this.form.type_ids, | ||||
|               filter: this.filterExp, | ||||
|               isShadow: this.isShadow, | ||||
|             }, | ||||
|           } | ||||
|           if (chartType === 'count') { | ||||
|             params.options.fontColor = fontColor | ||||
|             params.options.bgColor = bgColor | ||||
|           } | ||||
|           if (['bar', 'line', 'pie'].includes(chartType)) { | ||||
|             if (this.form.category === 1) { | ||||
|               params.options.attr_ids = this.form.attr_ids | ||||
|             } | ||||
|             params.options.chartColor = this.chartColor | ||||
|           } | ||||
|           if (chartType === 'bar') { | ||||
|             params.options.barDirection = this.barDirection | ||||
|             params.options.barStack = this.barStack | ||||
|           } | ||||
|           if (chartType === 'table') { | ||||
|             params.options.attr_ids = this.form.attr_ids | ||||
|             if (this.form.tableCategory === 2) { | ||||
|               params.options.ret = 'cis' | ||||
|             } | ||||
|           } | ||||
|           delete params.showIcon | ||||
|           delete params.type_ids | ||||
|           delete params.attr_ids | ||||
|           delete params.tableCategory | ||||
|           postCustomDashboardPreview(params).then((res) => { | ||||
|             this.isShowPreview = true | ||||
|             this.previewData = res.counter | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     setExpFromFilter(filterExp) { | ||||
|       if (filterExp) { | ||||
|         this.filterExp = `${filterExp}` | ||||
|       } else { | ||||
|         this.filterExp = undefined | ||||
|       } | ||||
|     }, | ||||
|     resetForm() { | ||||
|       this.form.type_id = undefined | ||||
|       this.form.type_ids = [] | ||||
|       this.form.attr_ids = [] | ||||
|       this.$refs.chartForm.clearValidate() | ||||
|     }, | ||||
|     changeAttr(value) { | ||||
|       if (value && value.length) { | ||||
|         if (['line', 'pie'].includes(this.chartType)) { | ||||
|           this.form.attr_ids = [value[value.length - 1]] | ||||
|         } | ||||
|         if (['bar'].includes(this.chartType) && value.length > 2) { | ||||
|           this.form.attr_ids = value.slice(value.length - 2, value.length) | ||||
|         } | ||||
|         if (['table'].includes(this.chartType) && value.length > 3) { | ||||
|           this.form.attr_ids = value.slice(value.length - 3, value.length) | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     clickLevel2children(e, citype, level) { | ||||
|       if (this.form.level !== level) { | ||||
|         this.$nextTick(() => { | ||||
|           this.form.type_ids = [citype.id] | ||||
|         }) | ||||
|       } | ||||
|       this.form.level = level | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style></style> | ||||
| <style lang="less" scoped> | ||||
| .chart-wrapper { | ||||
|   display: flex; | ||||
|   .chart-left { | ||||
|     width: 50%; | ||||
|     .chart-left-preview { | ||||
|       border: 1px solid #e4e7ed; | ||||
|       border-radius: 2px; | ||||
|       height: 280px; | ||||
|       width: 92%; | ||||
|       position: relative; | ||||
|       padding: 12px; | ||||
|       .chart-left-preview-operation { | ||||
|         color: #86909c; | ||||
|         position: absolute; | ||||
|         top: 12px; | ||||
|         right: 12px; | ||||
|         cursor: pointer; | ||||
|       } | ||||
|       .chart-left-preview-box { | ||||
|         padding: 6px 12px; | ||||
|         height: 250px; | ||||
|         border-radius: 8px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   .chart-right { | ||||
|     width: 50%; | ||||
|     h4 { | ||||
|       font-weight: 700; | ||||
|       color: #000; | ||||
|     } | ||||
|     .chart-right-type { | ||||
|       display: flex; | ||||
|       justify-content: space-between; | ||||
|       background-color: #f0f5ff; | ||||
|       padding: 6px 12px; | ||||
|       .chart-right-type-box { | ||||
|         cursor: pointer; | ||||
|         width: 70px; | ||||
|         height: 60px; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         > i { | ||||
|           font-size: 32px; | ||||
|         } | ||||
|         > span { | ||||
|           font-size: 12px; | ||||
|         } | ||||
|       } | ||||
|       .chart-right-type-box-selected { | ||||
|         background-color: #e5f1ff; | ||||
|       } | ||||
|     } | ||||
|     .chart-width { | ||||
|       width: 100%; | ||||
|       > label { | ||||
|         width: 25%; | ||||
|         text-align: center; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| <style lang="less"> | ||||
| .chart-wrapper { | ||||
|   .ant-form-item { | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -1,23 +1,61 @@ | ||||
| export const colorList = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'] | ||||
|  | ||||
| export const category_1_bar_options = (data) => { | ||||
| export const category_1_bar_options = (data, options) => { | ||||
|     // 计算一级分类 | ||||
|     const xData = Object.keys(data) | ||||
|     // 计算共有多少二级分类 | ||||
|     const secondCategory = {} | ||||
|     Object.keys(data).forEach(key => { | ||||
|         if (Object.prototype.toString.call(data[key]) === '[object Object]') { | ||||
|             Object.keys(data[key]).forEach(key1 => { | ||||
|                 secondCategory[key1] = Array.from({ length: xData.length }).fill(0) | ||||
|             }) | ||||
|         } else { | ||||
|             secondCategory['其他'] = Array.from({ length: xData.length }).fill(0) | ||||
|         } | ||||
|     }) | ||||
|     Object.keys(secondCategory).forEach(key => { | ||||
|         xData.forEach((x, idx) => { | ||||
|             if (data[x][key]) { | ||||
|                 secondCategory[key][idx] = data[x][key] | ||||
|             } | ||||
|             if (typeof data[x] === 'number') { | ||||
|                 secondCategory['其他'][idx] = data[x] | ||||
|             } | ||||
|         }) | ||||
|     }) | ||||
|     return { | ||||
|         color: options.chartColor.split(','), | ||||
|         grid: { | ||||
|             top: 15, | ||||
|             left: 'left', | ||||
|             right: 0, | ||||
|             bottom: 0, | ||||
|             right: 10, | ||||
|             bottom: 20, | ||||
|             containLabel: true, | ||||
|         }, | ||||
|         xAxis: { | ||||
|             type: 'category', | ||||
|             data: Object.keys(data) | ||||
|         legend: { | ||||
|             data: Object.keys(secondCategory), | ||||
|             bottom: 0, | ||||
|             type: 'scroll', | ||||
|         }, | ||||
|         yAxis: { | ||||
|         xAxis: options.barDirection === 'y' ? { | ||||
|             type: 'category', | ||||
|             axisTick: { show: false }, | ||||
|             data: xData | ||||
|         } | ||||
|             : { | ||||
|                 type: 'value', | ||||
|                 splitLine: { | ||||
|                     show: false | ||||
|                 } | ||||
|             }, | ||||
|         yAxis: options.barDirection === 'y' ? { | ||||
|             type: 'value', | ||||
|             splitLine: { | ||||
|                 show: false | ||||
|             } | ||||
|         } : { | ||||
|             type: 'category', | ||||
|             axisTick: { show: false }, | ||||
|             data: xData | ||||
|         }, | ||||
|         tooltip: { | ||||
|             trigger: 'axis', | ||||
| @@ -25,34 +63,76 @@ export const category_1_bar_options = (data) => { | ||||
|                 type: 'shadow' | ||||
|             } | ||||
|         }, | ||||
|         series: Object.keys(secondCategory).map(key => { | ||||
|             return { | ||||
|                 name: key, | ||||
|                 type: 'bar', | ||||
|                 stack: options?.barStack ?? 'total', | ||||
|                 barGap: 0, | ||||
|                 emphasis: { | ||||
|                     focus: 'series' | ||||
|                 }, | ||||
|                 data: secondCategory[key] | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const category_1_line_options = (data, options) => { | ||||
|     const xData = Object.keys(data) | ||||
|     return { | ||||
|         color: options.chartColor.split(','), | ||||
|         grid: { | ||||
|             top: 15, | ||||
|             left: 'left', | ||||
|             right: 10, | ||||
|             bottom: 20, | ||||
|             containLabel: true, | ||||
|         }, | ||||
|         tooltip: { | ||||
|             trigger: 'axis' | ||||
|         }, | ||||
|         xAxis: { | ||||
|             type: 'category', | ||||
|             data: xData | ||||
|         }, | ||||
|         yAxis: { | ||||
|             type: 'value' | ||||
|         }, | ||||
|         series: [ | ||||
|             { | ||||
|                 data: Object.keys(data).map((key, index) => { | ||||
|                     return { | ||||
|                         value: data[key], | ||||
|                         itemStyle: { color: colorList[0] } | ||||
|                 data: xData.map(item => data[item]), | ||||
|                 type: 'line', | ||||
|                 smooth: true, | ||||
|                 showSymbol: false, | ||||
|                 areaStyle: options?.isShadow ? { | ||||
|                     opacity: 0.5, | ||||
|                     color: { | ||||
|                         type: 'linear', | ||||
|                         x: 0, | ||||
|                         y: 0, | ||||
|                         x2: 0, | ||||
|                         y2: 1, | ||||
|                         colorStops: [{ | ||||
|                             offset: 0, color: options.chartColor.split(',')[0] // 0% 处的颜色 | ||||
|                         }, { | ||||
|                             offset: 1, color: '#ffffff' // 100% 处的颜色 | ||||
|                         }], | ||||
|                         global: false // 缺省为 false | ||||
|                     } | ||||
|                 }), | ||||
|                 type: 'bar', | ||||
|                 label: { | ||||
|                     show: true, | ||||
|                     position: 'top', | ||||
|                     fontSize: 10, | ||||
|                     formatter(data) { | ||||
|                         return `${data.value || ''}` | ||||
|                     } | ||||
|                 }, | ||||
|                 } : null | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const category_1_pie_options = (data) => { | ||||
| export const category_1_pie_options = (data, options) => { | ||||
|     return { | ||||
|         color: options.chartColor.split(','), | ||||
|         grid: { | ||||
|             top: 10, | ||||
|             left: 'left', | ||||
|             right: 0, | ||||
|             right: 10, | ||||
|             bottom: 0, | ||||
|             containLabel: true, | ||||
|         }, | ||||
| @@ -89,7 +169,7 @@ export const category_1_pie_options = (data) => { | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const category_2_bar_options = (data) => { | ||||
| export const category_2_bar_options = (data, options, chartType) => { | ||||
|     const xAxisData = Object.keys(data.detail) | ||||
|     const _legend = [] | ||||
|     xAxisData.forEach(key => { | ||||
| @@ -97,10 +177,11 @@ export const category_2_bar_options = (data) => { | ||||
|     }) | ||||
|     const legend = [...new Set(_legend)] | ||||
|     return { | ||||
|         color: options.chartColor.split(','), | ||||
|         grid: { | ||||
|             top: 15, | ||||
|             left: 'left', | ||||
|             right: 0, | ||||
|             right: 10, | ||||
|             bottom: 20, | ||||
|             containLabel: true, | ||||
|         }, | ||||
| @@ -116,41 +197,110 @@ export const category_2_bar_options = (data) => { | ||||
|             type: 'scroll', | ||||
|             data: legend | ||||
|         }, | ||||
|         xAxis: [ | ||||
|             { | ||||
|                 type: 'category', | ||||
|                 axisTick: { show: false }, | ||||
|                 data: xAxisData | ||||
|             } | ||||
|         ], | ||||
|         yAxis: [ | ||||
|             { | ||||
|         xAxis: options.barDirection === 'y' || chartType === 'line' ? { | ||||
|             type: 'category', | ||||
|             axisTick: { show: false }, | ||||
|             data: xAxisData | ||||
|         } | ||||
|             : { | ||||
|                 type: 'value', | ||||
|                 splitLine: { | ||||
|                     show: false | ||||
|                 } | ||||
|             }, | ||||
|         yAxis: options.barDirection === 'y' || chartType === 'line' ? { | ||||
|             type: 'value', | ||||
|             splitLine: { | ||||
|                 show: false | ||||
|             } | ||||
|         ], | ||||
|         series: legend.map(le => { | ||||
|         } : { | ||||
|             type: 'category', | ||||
|             axisTick: { show: false }, | ||||
|             data: xAxisData | ||||
|         }, | ||||
|         series: legend.map((le, index) => { | ||||
|             return { | ||||
|                 name: le, | ||||
|                 type: 'bar', | ||||
|                 type: chartType, | ||||
|                 barGap: 0, | ||||
|                 emphasis: { | ||||
|                     focus: 'series' | ||||
|                 }, | ||||
|                 stack: chartType === 'line' ? '' : options?.barStack ?? 'total', | ||||
|                 data: xAxisData.map(x => { | ||||
|                     return data.detail[x][le] || 0 | ||||
|                 }), | ||||
|                 smooth: true, | ||||
|                 showSymbol: false, | ||||
|                 label: { | ||||
|                     show: true, | ||||
|                     position: 'top', | ||||
|                     fontSize: 10, | ||||
|                     formatter(data) { | ||||
|                         return `${data.value || ''}` | ||||
|                     } | ||||
|                     show: false, | ||||
|                 }, | ||||
|                 areaStyle: chartType === 'line' && options?.isShadow ? { | ||||
|                     opacity: 0.5, | ||||
|                     color: { | ||||
|                         type: 'linear', | ||||
|                         x: 0, | ||||
|                         y: 0, | ||||
|                         x2: 0, | ||||
|                         y2: 1, | ||||
|                         colorStops: [{ | ||||
|                             offset: 0, color: options.chartColor.split(',')[index % 8] // 0% 处的颜色 | ||||
|                         }, { | ||||
|                             offset: 1, color: '#ffffff' // 100% 处的颜色 | ||||
|                         }], | ||||
|                         global: false // 缺省为 false | ||||
|                     } | ||||
|                 } : null | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export const category_2_pie_options = (data, options) => { | ||||
|     console.log(1111, options) | ||||
|     const _legend = [] | ||||
|     Object.keys(data.detail).forEach(key => { | ||||
|         Object.keys(data.detail[key]).forEach(key2 => { | ||||
|             _legend.push({ value: data.detail[key][key2], name: `${key}-${key2}` }) | ||||
|         }) | ||||
|     }) | ||||
|     return { | ||||
|         color: options.chartColor.split(','), | ||||
|         grid: { | ||||
|             top: 15, | ||||
|             left: 'left', | ||||
|             right: 10, | ||||
|             bottom: 20, | ||||
|             containLabel: true, | ||||
|         }, | ||||
|         tooltip: { | ||||
|             trigger: 'item' | ||||
|         }, | ||||
|         legend: { | ||||
|             orient: 'vertical', | ||||
|             left: 'left', | ||||
|             type: 'scroll', | ||||
|             formatter: function (name) { | ||||
|                 const _find = _legend.find(item => item.name === name) | ||||
|                 return `${name}:${_find.value}` | ||||
|             } | ||||
|         }, | ||||
|         series: [ | ||||
|             { | ||||
|                 type: 'pie', | ||||
|                 radius: '90%', | ||||
|                 data: _legend, | ||||
|                 label: { | ||||
|                     show: false, | ||||
|                 }, | ||||
|                 emphasis: { | ||||
|                     itemStyle: { | ||||
|                         shadowBlur: 10, | ||||
|                         shadowOffsetX: 0, | ||||
|                         shadowColor: 'rgba(0, 0, 0, 0.5)' | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,54 @@ | ||||
| <template> | ||||
|   <a-select v-model="currenColor"> | ||||
|     <a-select-option v-for="i in list" :value="i" :key="i"> | ||||
|       <div> | ||||
|         <span :style="{ backgroundColor: color }" class="color-box" v-for="color in i.split(',')" :key="color"></span> | ||||
|       </div> | ||||
|     </a-select-option> | ||||
|   </a-select> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: 'ColorListPicker', | ||||
|   model: { | ||||
|     prop: 'value', | ||||
|     event: 'change', | ||||
|   }, | ||||
|   props: { | ||||
|     value: { | ||||
|       type: [String, Array], | ||||
|       default: null, | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       list: [ | ||||
|         '#6592FD,#6EE3EB,#44C2FD,#5F59F7,#1A348F,#7D8FCF,#A6D1E5,#8E56DD', | ||||
|         '#C1A9DC,#E2B5CD,#EE8EBC,#8483C3,#4D66BD,#213764,#D9B6E9,#DD88EB', | ||||
|         '#6FC4DF,#9FE8CE,#16B4BE,#86E6FB,#1871A3,#E1BF8D,#ED8D8D,#DD88EB', | ||||
|         '#F8B751,#FC9054,#FFE380,#DF963F,#AB5200,#EA9387,#FFBB7C,#D27467', | ||||
|       ], | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     currenColor: { | ||||
|       get() { | ||||
|         return this.value | ||||
|       }, | ||||
|       set(val) { | ||||
|         this.$emit('change', val) | ||||
|         return val | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="less" scoped> | ||||
| .color-box { | ||||
|   display: inline-block; | ||||
|   width: 40px; | ||||
|   height: 10px; | ||||
| } | ||||
| </style> | ||||
| @@ -0,0 +1,79 @@ | ||||
| <template> | ||||
|   <div class="color-picker"> | ||||
|     <div | ||||
|       :style="{ | ||||
|         background: Array.isArray(item) ? `linear-gradient(to bottom, ${item[0]} 0%, ${item[1]} 100%)` : item, | ||||
|       }" | ||||
|       :class="{ 'color-picker-box': true, 'color-picker-box-selected': isEqual(currenColor, item) }" | ||||
|       v-for="item in colorList" | ||||
|       :key="Array.isArray(item) ? item.join() : item" | ||||
|       @click="changeColor(item)" | ||||
|     ></div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import _ from 'lodash' | ||||
| export default { | ||||
|   name: 'ColorPicker', | ||||
|   model: { | ||||
|     prop: 'value', | ||||
|     event: 'change', | ||||
|   }, | ||||
|   props: { | ||||
|     value: { | ||||
|       type: [String, Array], | ||||
|       default: null, | ||||
|     }, | ||||
|     colorList: { | ||||
|       type: Array, | ||||
|       default: () => [], | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     currenColor: { | ||||
|       get() { | ||||
|         return this.value | ||||
|       }, | ||||
|       set(val) { | ||||
|         this.$emit('change', val) | ||||
|         return val | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     isEqual: _.isEqual, | ||||
|     changeColor(item) { | ||||
|       this.$emit('change', item) | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="less" scoped> | ||||
| .color-picker { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   margin-top: 10px; | ||||
|   .color-picker-box { | ||||
|     width: 19px; | ||||
|     height: 19px; | ||||
|     border: 1px solid #dae2e7; | ||||
|     border-radius: 1px; | ||||
|     cursor: pointer; | ||||
|   } | ||||
|   .color-picker-box-selected { | ||||
|     position: relative; | ||||
|     &:after { | ||||
|       content: ''; | ||||
|       position: absolute; | ||||
|       width: 24px; | ||||
|       height: 24px; | ||||
|       border: 1px solid #43bbff; | ||||
|       top: -3px; | ||||
|       left: -3px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @@ -1,5 +1,4 @@ | ||||
| export const dashboardCategory = { | ||||
|     0: { label: 'CI数统计' }, | ||||
|     1: { label: '按属性值分类统计' }, | ||||
|     2: { label: '关系统计' } | ||||
|     1: { label: '默认' }, | ||||
|     2: { label: '关系' } | ||||
| } | ||||
|   | ||||
| @@ -11,8 +11,8 @@ | ||||
|     <template v-if="layout && layout.length"> | ||||
|       <div v-if="editable"> | ||||
|         <a-button | ||||
|           :style="{ marginLeft: '10px' }" | ||||
|           @click="openChartForm('add', {})" | ||||
|           :style="{ marginLeft: '22px', marginTop: '20px' }" | ||||
|           @click="openChartForm('add', { options: { w: 3 } })" | ||||
|           ghost | ||||
|           type="primary" | ||||
|           size="small" | ||||
| @@ -39,11 +39,44 @@ | ||||
|           :h="item.h" | ||||
|           :i="item.i" | ||||
|           :key="item.i" | ||||
|           :style="{ backgroundColor: '#fafafa' }" | ||||
|           :style="{ | ||||
|             background: | ||||
|               item.options.chartType === 'count' | ||||
|                 ? Array.isArray(item.options.bgColor) | ||||
|                   ? `linear-gradient(to bottom, ${item.options.bgColor[0]} 0%, ${item.options.bgColor[1]} 100%)` | ||||
|                   : item.options.bgColor | ||||
|                 : '#fafafa', | ||||
|           }" | ||||
|         > | ||||
|           <CardTitle>{{ item.options.name }}</CardTitle> | ||||
|           <div class="cmdb-dashboard-grid-item-title"> | ||||
|             <template v-if="item.options.chartType !== 'count' && item.options.showIcon && getCiType(item)"> | ||||
|               <template v-if="getCiType(item).icon"> | ||||
|                 <img | ||||
|                   v-if="getCiType(item).icon.split('$$')[2]" | ||||
|                   :src="`/api/common-setting/v1/file/${getCiType(item).icon.split('$$')[3]}`" | ||||
|                 /> | ||||
|                 <ops-icon | ||||
|                   v-else | ||||
|                   :style="{ | ||||
|                     color: getCiType(item).icon.split('$$')[1], | ||||
|                   }" | ||||
|                   :type="getCiType(item).icon.split('$$')[0]" | ||||
|                 /> | ||||
|               </template> | ||||
|               <span :style="{ color: '#2f54eb' }" v-else>{{ getCiType(item).name[0].toUpperCase() }}</span> | ||||
|             </template> | ||||
|             <span :style="{ color: item.options.chartType === 'count' ? item.options.fontColor : '#000' }">{{ | ||||
|               item.options.name | ||||
|             }}</span> | ||||
|           </div> | ||||
|           <a-dropdown v-if="editable"> | ||||
|             <a class="cmdb-dashboard-grid-item-operation"><a-icon type="menu"></a-icon></a> | ||||
|             <a | ||||
|               class="cmdb-dashboard-grid-item-operation" | ||||
|               :style="{ | ||||
|                 color: item.options.chartType === 'count' ? item.options.fontColor : '', | ||||
|               }" | ||||
|             ><a-icon type="menu"></a-icon | ||||
|             ></a> | ||||
|             <a-menu slot="overlay"> | ||||
|               <a-menu-item> | ||||
|                 <a @click="() => openChartForm('edit', item)"><a-icon style="margin-right:5px" type="edit" />编辑</a> | ||||
| @@ -53,13 +86,13 @@ | ||||
|               </a-menu-item> | ||||
|             </a-menu> | ||||
|           </a-dropdown> | ||||
|           <a | ||||
|           <!-- <a | ||||
|             v-if="editable && item.category === 1" | ||||
|             class="cmdb-dashboard-grid-item-chart-type" | ||||
|             @click="changeChartType(item)" | ||||
|           ><a-icon | ||||
|             :type="item.options.chartType === 'bar' ? 'bar-chart' : 'pie-chart'" | ||||
|           /></a> | ||||
|           /></a> --> | ||||
|           <Chart | ||||
|             :ref="`chart_${item.id}`" | ||||
|             :chartId="item.id" | ||||
| @@ -67,18 +100,26 @@ | ||||
|             :category="item.category" | ||||
|             :options="item.options" | ||||
|             :editable="editable" | ||||
|             :ci_types="ci_types" | ||||
|             :type_id="item.type_id" | ||||
|           /> | ||||
|         </GridItem> | ||||
|       </GridLayout> | ||||
|     </template> | ||||
|     <div v-else class="dashboard-empty"> | ||||
|       <a-empty :image="emptyImage" description=""></a-empty> | ||||
|       <a-button @click="openChartForm('add', {})" v-if="editable" size="small" type="primary" icon="plus"> | ||||
|       <a-button | ||||
|         @click="openChartForm('add', { options: { w: 3 } })" | ||||
|         v-if="editable" | ||||
|         size="small" | ||||
|         type="primary" | ||||
|         icon="plus" | ||||
|       > | ||||
|         定制仪表盘 | ||||
|       </a-button> | ||||
|       <span v-else>管理员暂未定制仪表盘</span> | ||||
|     </div> | ||||
|     <ChartForm ref="chartForm" @refresh="refresh" :ci_types="ci_types" /> | ||||
|     <ChartForm ref="chartForm" @refresh="refresh" :ci_types="ci_types" :totalData="totalData" /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @@ -127,12 +168,14 @@ export default { | ||||
|       }, | ||||
|     } | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.getLayout() | ||||
|   created() { | ||||
|     getCITypes().then((res) => { | ||||
|       this.ci_types = res.ci_types | ||||
|     }) | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.getLayout() | ||||
|   }, | ||||
|   methods: { | ||||
|     async getLayout() { | ||||
|       const res = await getCustomDashboard() | ||||
| @@ -196,6 +239,13 @@ export default { | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
|     getCiType(item) { | ||||
|       if (item.type_id || item.options?.type_ids) { | ||||
|         const _find = this.ci_types.find((type) => type.id === item.type_id || type.id === item.options?.type_ids[0]) | ||||
|         return _find || null | ||||
|       } | ||||
|       return null | ||||
|     }, | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
| @@ -206,15 +256,18 @@ export default { | ||||
|   text-align: center; | ||||
| } | ||||
| .cmdb-dashboard-grid-item { | ||||
|   border-radius: 15px; | ||||
|   border-radius: 8px; | ||||
|   padding: 6px 12px; | ||||
|   .cmdb-dashboard-grid-item-title { | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
|     white-space: nowrap; | ||||
|     font-weight: 700; | ||||
|     padding-left: 6px; | ||||
|     color: #000000bd; | ||||
|     color: #000000; | ||||
|   } | ||||
|   .cmdb-dashboard-grid-item-operation { | ||||
|     position: absolute; | ||||
|     right: 6px; | ||||
|     right: 12px; | ||||
|     top: 6px; | ||||
|   } | ||||
|   .cmdb-dashboard-grid-item-chart-type { | ||||
| @@ -224,3 +277,26 @@ export default { | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | ||||
| <style lang="less"> | ||||
| .cmdb-dashboard-grid-item-title { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   > i { | ||||
|     font-size: 16px; | ||||
|     margin-right: 5px; | ||||
|   } | ||||
|   > img { | ||||
|     width: 16px; | ||||
|     margin-right: 5px; | ||||
|   } | ||||
|   > span:not(:last-child) { | ||||
|     display: inline-block; | ||||
|     width: 16px; | ||||
|     height: 16px; | ||||
|     font-size: 16px; | ||||
|     text-align: center; | ||||
|     margin-right: 5px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user