Files
cmdb/cmdb-ui/src/modules/cmdb/views/resource_search/index.vue

530 lines
18 KiB
Python

<template>
<div id="resource_search" :style="{ height: fromCronJob ? `${windowHeight - 48}px` : `${windowHeight - 90}px` }">
<div class="cmdb-views-header">
<span>
<span class="cmdb-views-header-title">资源搜索</span>
</span>
</div>
<div :style="{ backgroundColor: '#fff', padding: '12px', borderRadius: '15px' }">
<SearchForm
ref="search"
type="resourceSearch"
@refresh="handleSearch"
:preferenceAttrList="allAttributesList"
@updateAllAttributesList="updateAllAttributesList"
@copyExpression="copyExpression"
/>
<div
v-if="!fromCronJob"
:style="{
display: 'flex',
justifyContent: 'space-between',
height: '32px',
marginBottom: '5px',
alignItems: 'center',
}"
>
<a-button icon="download" type="primary" ghost size="small" @click="handleExport">导出</a-button>
<PreferenceSearch
ref="preferenceSearch"
@getQAndSort="getQAndSort"
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
/>
</div>
<vxe-table
:id="`cmdb-resource`"
border
keep-source
show-overflow
resizable
ref="xTable"
size="small"
row-id="_id"
:loading="loading"
:height="fromCronJob ? windowHeight - 180 : windowHeight - 250"
show-header-overflow
highlight-hover-row
:data="instanceList"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="handleSortCol"
:row-key="true"
:column-key="true"
:cell-style="getCellStyle"
:scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }"
:export-config="{
isColgroup: true,
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
mode: 'current',
modes: ['current'],
isFooter: false,
isHeader: true,
isColgroup: true,
}"
class="ops-unstripe-table"
:style="{ margin: '0 -12px' }"
:custom-config="{ storage: true }"
>
<vxe-column
v-if="instanceList.length"
title="模型"
field="ci_type_alias"
:width="100"
fixed="left"
></vxe-column>
<vxe-colgroup v-for="colGroup in columnsGroup" :key="colGroup.value" :title="colGroup.label">
<template #header>
<span :style="{ display: 'inline-flex', alignItems: 'center' }">
{{ colGroup.label }}
<EditAttrsPopover
:style="{ borderLeft: 'none', width: '30px', height: '38px', cursor: 'pointer' }"
v-if="colGroup.isCiType"
:typeId="Number(colGroup.id.split('-')[1])"
@refresh="loadInstance"
/>
</span>
</template>
<vxe-column
v-for="(col, index) in colGroup.children"
:key="`${col.field}_${index}`"
:title="col.title"
:field="col.field"
:width="col.width"
:minWidth="100"
:cell-type="col.value_type === '2' ? 'string' : 'auto'"
>
<template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" #default="{row}">
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
<a
v-else-if="col.is_link && row[col.field]"
:href="
row[col.field].startsWith('http') || row[col.field].startsWith('https')
? `${row[col.field]}`
: `http://${row[col.field]}`
"
target="_blank"
>{{ row[col.field] }}</a
>
<PasswordField
v-else-if="col.is_password && row[col.field]"
:ci_id="row._id"
:attr_id="col.attr_id"
></PasswordField>
<template v-else-if="col.is_choice">
<template v-if="col.is_list">
<span
v-for="value in row[col.field]"
:key="value"
:style="{
borderRadius: '4px',
padding: '1px 5px',
margin: '2px',
...getChoiceValueStyle(col, value),
}"
><ops-icon
:style="{ color: getChoiceValueIcon(col, value).color }"
:type="getChoiceValueIcon(col, value).name"
/>{{ value }}</span
>
</template>
<span
v-else
:style="{
borderRadius: '4px',
padding: '1px 5px',
margin: '2px 0',
...getChoiceValueStyle(col, row[col.field]),
}"
>
<ops-icon
:style="{ color: getChoiceValueIcon(col, row[col.field]).color }"
:type="getChoiceValueIcon(col, row[col.field]).name"
/>
{{ row[col.field] }}</span
>
</template>
</template>
</vxe-column>
</vxe-colgroup>
<template #empty>
<div v-if="loading" style="height: 200px; line-height: 200px">加载中...</div>
<div v-else>
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div>
</div>
</template>
</vxe-table>
<div :style="{ textAlign: 'right', marginTop: '4px' }">
<a-pagination
:showSizeChanger="true"
:current="currentPage"
size="small"
:total="totalNumber"
show-quick-jumper
:page-size="pageSize"
:page-size-options="pageSizeOptions"
@showSizeChange="onShowSizeChange"
:show-total="(total, range) => `当前${range[0]}-${range[1]} 共 ${total}条记录`"
@change="
(page) => {
currentPage = page
loadInstance(sortByTable)
}
"
>
<template slot="buildOptionText" slot-scope="props">
<span v-if="props.value !== '100000'">{{ props.value }}/</span>
<span v-if="props.value === '100000'">全部</span>
</template>
</a-pagination>
</div>
</div>
<BatchDownload
:replaceFields="{ cildren: 'children', title: 'label', key: 'id' }"
ref="batchDownload"
@batchDownload="batchDownload"
treeType="tree"
/>
</div>
</template>
<script>
import _ from 'lodash'
import SearchForm from '../../components/searchForm/SearchForm.vue'
import { searchCI } from '../../api/ci'
import { searchAttributes, getCITypeAttributesByTypeIds } from '../../api/CITypeAttr'
import { getCITypes } from '../../api/CIType'
import { getSubscribeAttributes } from '../../api/preference'
import { getCITableColumns } from '../../utils/helper'
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
import PasswordField from '../../components/passwordField/index.vue'
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
export default {
name: 'ResourceSearch',
components: { SearchForm, EditAttrsPopover, PasswordField, BatchDownload, PreferenceSearch },
props: {
fromCronJob: {
type: Boolean,
default: false,
},
},
data() {
return {
ciTypes: [],
allAttributesList: [], // 当前选择的模型的全部attributes 默认全部
currentPage: 1,
pageSizeOptions: ['50', '100', '200', '100000'],
pageSize: 50,
totalNumber: 0,
instanceList: [],
// columns: {},
sortByTable: undefined,
loading: false,
columnsGroup: [],
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
},
provide() {
return {
setPreferenceSearchCurrent: this.setPreferenceSearchCurrent,
filterCompPreferenceSearch: () => {},
}
},
mounted() {
this.getAllAttr()
this.getAllCiTypes()
},
methods: {
getAllCiTypes() {
getCITypes().then((res) => {
this.ciTypes = res.ci_types
})
},
getAllAttr() {
searchAttributes({ page_size: 9999 }).then((res) => {
this.allAttributesList = res.attributes
})
},
updateAllAttributesList(value) {
if (value && value.length) {
getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => {
this.allAttributesList = res.attributes
})
} else {
this.getAllAttr()
}
},
async loadInstance(sortByTable = undefined) {
this.loading = true
// 若模糊搜索可以 queryParam相关后期可删除
// const queryParams = this.$refs['search'].queryParam || {}
const fuzzySearch = this.$refs['search'].fuzzySearch
const expression = this.$refs['search'].expression || ''
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const regSort = /(?<=sort=).+/g
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
// 如果是表格点击的排序 以表格为准
let sort
if (sortByTable) {
sort = sortByTable
} else {
sort = expression.match(regSort) ? expression.match(regSort)[0] : undefined
}
if (!sort) {
sort = '_type'
}
let currenCiType = this.$refs['search'].currenCiType
if (!currenCiType.length) {
const _currenCiType = []
this.$refs['search'].ciTypeGroup.forEach((item) => {
_currenCiType.push(...item.ci_types.map((type) => type.id))
})
currenCiType = _currenCiType
}
searchCI({
q: `${currenCiType && currenCiType.length ? `_type:(${currenCiType.join(';')})` : ''}${exp ? `,${exp}` : ''}${
fuzzySearch ? `,*${fuzzySearch}*` : ''
}`,
count: this.pageSize,
page: this.currentPage,
sort,
})
.then(async (res) => {
this.columnsGroup = []
this.instanceList = []
this.totalNumber = res['numfound']
const oldData = res.result
function allKeys(data) {
const keys = {}
const ignoreAttr = ['_id', '_type', 'ci_type', 'ci_type_alias', 'unique', 'unique_alias']
data.forEach((item) => {
Object.keys(item).forEach((key) => {
if (!ignoreAttr.includes(key)) {
keys[key] = ''
}
})
})
return keys
}
function tidy(data) {
const outputKeys = allKeys(data)
const common = {}
data.forEach((item) => {
const tmp = {}
Object.keys(outputKeys).forEach((j) => {
if (j in item) {
tmp[j] = item[j]
// 提取common
{
const key = item['ci_type_alias']
if (j in common) {
common[j][[key]] = ''
} else {
common[j] = { [key]: '' }
}
}
} else {
tmp[j] = null
}
})
})
const commonObject = {}
const commonKeys = []
// 整理common
Object.keys(common).forEach((key) => {
if (Object.keys(common[key]).length > 1) {
commonKeys.push(key)
const reverseKey = Object.keys(common[key]).join('&')
if (!commonObject[reverseKey]) {
commonObject[reverseKey] = [key]
} else {
commonObject[reverseKey].push(key)
}
}
})
return { commonObject, commonKeys }
}
const { commonObject, commonKeys } = tidy(oldData)
const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
return {
id: `parent-${key}`,
value: key,
label: key,
children: this.getColumns(
res.result,
commonObject[key].map((item) => {
const _find = this.allAttributesList.find((attr) => attr.name === item)
return _find
})
),
}
})
const _columnsGroup = Object.keys(res.counter).map((key) => {
const _find = this.ciTypes.find((item) => item.name === key)
return {
id: `parent-${_find.id}`,
value: key,
label: _find?.alias || _find?.name,
isCiType: true,
}
})
const promises = _columnsGroup.map((item) => {
return getSubscribeAttributes(item.id.split('-')[1]).then((res1) => {
item.children = this.getColumns(res.result, res1.attributes).filter(
(col) => !commonKeys.includes(col.field)
)
})
})
await Promise.all(promises).then(() => {
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
this.instanceList = res['result']
})
})
.finally(() => {
this.loading = false
})
},
getColumns(data, attrList) {
const width = document.getElementById('resource_search').clientWidth - 50
return getCITableColumns(data, attrList, width).map((item) => {
return { ...item, id: item.field, label: item.title }
})
},
handleSearch() {
this.currentPage = 1
this.loadInstance()
},
onShowSizeChange(current, pageSize) {
this.pageSize = pageSize
this.currentPage = 1
this.loadInstance()
},
handleSortCol() {},
getCellStyle({ row, rowIndex, $rowIndex, column, columnIndex, $columnIndex }) {
const { property } = column
const _find = this.allAttributesList.find((attr) => attr.name === property)
if (
_find &&
_find.option &&
_find.option.fontOptions &&
row[`${property}`] !== undefined &&
row[`${property}`] !== null
) {
return { ..._find.option.fontOptions }
}
},
getChoiceValueStyle(col, colValue) {
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
if (_find) {
return _find[1]?.style || {}
}
return {}
},
getChoiceValueIcon(col, colValue) {
const _find = col.filters.find((item) => String(item[0]) === String(colValue))
if (_find) {
return _find[1]?.icon || {}
}
return {}
},
handleExport() {
// this.$refs.xTable.openExport()
this.$refs.batchDownload.open({
preferenceAttrList: [{ id: `ci_type_alias`, value: 'ci_type_alias', label: '模型' }, ...this.columnsGroup],
})
},
batchDownload({ filename, type, checkedKeys }) {
const jsonAttrList = []
checkedKeys.forEach((key) => {
const _find = this.allAttributesList.find((attr) => attr.name === key)
if (_find && _find.value_type === '6') {
jsonAttrList.push(key)
}
})
const data = _.cloneDeep(this.instanceList)
this.$refs.xTable.exportData({
filename,
type,
columnFilterMethod({ column }) {
return checkedKeys.includes(column.property)
},
data: [
...data.map((item) => {
jsonAttrList.forEach((jsonAttr) => (item[jsonAttr] = item[jsonAttr] ? JSON.stringify(item[jsonAttr]) : ''))
return { ...item }
}),
],
download: false,
})
this.selectedRowKeys = []
this.$refs.xTable.clearCheckboxRow()
this.$refs.xTable.clearCheckboxReserve()
},
getQAndSort() {
const fuzzySearch = this.$refs['search'].fuzzySearch || ''
const expression = this.$refs['search'].expression || ''
const currenCiType = this.$refs['search'].currenCiType || undefined
this.$refs.preferenceSearch.savePreference({ fuzzySearch, expression, currenCiType })
},
setParamsFromPreferenceSearch(item) {
const { fuzzySearch, expression, currenCiType } = item.option
this.$refs.search.fuzzySearch = fuzzySearch
this.$refs.search.expression = expression
this.$refs.search.currenCiType = currenCiType
this.currentPage = 1
this.$nextTick(() => {
this.loadInstance()
})
},
setPreferenceSearchCurrent(id = null) {
if (this.$refs.preferenceSearch) {
this.$refs.preferenceSearch.currentPreferenceSearch = id
}
},
copyExpression() {
const expression = this.$refs['search'].expression || ''
const fuzzySearch = this.$refs['search'].fuzzySearch
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
let currenCiType = this.$refs['search'].currenCiType
if (!currenCiType.length) {
const _currenCiType = []
this.$refs['search'].ciTypeGroup.forEach((item) => {
_currenCiType.push(...item.ci_types.map((type) => type.id))
})
currenCiType = _currenCiType
}
const text = `q=${currenCiType && currenCiType.length ? `_type:(${currenCiType.join(';')})` : ''}${
exp ? `,${exp}` : ''
}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`
this.$copyText(text)
.then(() => {
this.$message.success('复制成功!')
this.$emit('copySuccess', text)
})
.catch(() => {
this.$message.error('复制失败!')
})
},
},
}
</script>