diff --git a/cmdb-ui/src/modules/cmdb/api/history.js b/cmdb-ui/src/modules/cmdb/api/history.js
index 7de88a7..aeff68a 100644
--- a/cmdb-ui/src/modules/cmdb/api/history.js
+++ b/cmdb-ui/src/modules/cmdb/api/history.js
@@ -1,89 +1,92 @@
-import { axios } from '@/utils/request'
-
-export function getCIHistory(ciId) {
-  return axios({
-    url: `/v0.1/history/ci/${ciId}`,
-    method: 'GET'
-  })
-}
-
-export function getCIHistoryTable(params) {
-  return axios({
-    url: `/v0.1/history/records/attribute`,
-    method: 'GET',
-    params: params
-  })
-}
-
-export function getRelationTable(params) {
-  return axios({
-    url: `/v0.1/history/records/relation`,
-    method: 'GET',
-    params: params
-  })
-}
-
-export function getCITypesTable(params) {
-  return axios({
-    url: `/v0.1/history/ci_types`,
-    method: 'GET',
-    params: params
-  })
-}
-
-export function getUsers(params) {
-  return axios({
-    url: `/v1/acl/users/employee`,
-    method: 'GET',
-    params: params
-  })
-}
-
-export function getCiTriggers(params) {
-  return axios({
-    url: `/v0.1/history/ci_triggers`,
-    method: 'GET',
-    params: params
-  })
-}
-
-export function getCiTriggersByCiId(ci_id, params) {
-  return axios({
-    url: `/v0.1/history/ci_triggers/${ci_id}`,
-    method: 'GET',
-    params
-  })
-}
-
-export function getCiRelatedTickets(params) {
-  return axios({
-    url: `/itsm/v1/process_ticket/get_tickets_by`,
-    method: 'POST',
-    data: params,
-    isShowMessage: false
-  })
-}
-
-export function judgeItsmInstalled() {
-  return axios({
-    url: `/itsm/v1/process_ticket/itsm_existed`,
-    method: 'GET',
-    isShowMessage: false
-  })
-}
-
-export function getCIsBaseline(params) {
-  return axios({
-    url: `/v0.1/ci/baseline`,
-    method: 'GET',
-    params
-  })
-}
-
-export function CIBaselineRollback(ciId, params) {
-  return axios({
-    url: `/v0.1/ci/${ciId}/baseline/rollback`,
-    method: 'POST',
-    data: params
-  })
-}
+import { axios } from '@/utils/request'
+
+export function getCIHistory(ciId) {
+  return axios({
+    url: `/v0.1/history/ci/${ciId}`,
+    method: 'GET'
+  })
+}
+
+export function getCIHistoryTable(params) {
+  return axios({
+    url: `/v0.1/history/records/attribute`,
+    method: 'GET',
+    params: params,
+    timeout: 30 * 1000
+  })
+}
+
+export function getRelationTable(params) {
+  return axios({
+    url: `/v0.1/history/records/relation`,
+    method: 'GET',
+    params: params,
+    timeout: 30 * 1000
+  })
+}
+
+export function getCITypesTable(params) {
+  return axios({
+    url: `/v0.1/history/ci_types`,
+    method: 'GET',
+    params: params,
+    timeout: 30 * 1000
+  })
+}
+
+export function getUsers(params) {
+  return axios({
+    url: `/v1/acl/users/employee`,
+    method: 'GET',
+    params: params
+  })
+}
+
+export function getCiTriggers(params) {
+  return axios({
+    url: `/v0.1/history/ci_triggers`,
+    method: 'GET',
+    params: params
+  })
+}
+
+export function getCiTriggersByCiId(ci_id, params) {
+  return axios({
+    url: `/v0.1/history/ci_triggers/${ci_id}`,
+    method: 'GET',
+    params
+  })
+}
+
+export function getCiRelatedTickets(params) {
+  return axios({
+    url: `/itsm/v1/process_ticket/get_tickets_by`,
+    method: 'POST',
+    data: params,
+    isShowMessage: false
+  })
+}
+
+export function judgeItsmInstalled() {
+  return axios({
+    url: `/itsm/v1/process_ticket/itsm_existed`,
+    method: 'GET',
+    isShowMessage: false
+  })
+}
+
+export function getCIsBaseline(params) {
+  return axios({
+    url: `/v0.1/ci/baseline`,
+    method: 'GET',
+    params
+  })
+}
+
+export function CIBaselineRollback(ciId, params) {
+  return axios({
+    url: `/v0.1/ci/${ciId}/baseline/rollback`,
+    method: 'POST',
+    data: params
+  })
+}
diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue
index ffc3362..7511f1b 100644
--- a/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue
+++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/ciDetailTab.vue
@@ -1,449 +1,463 @@
-<template>
-  <div :style="{ height: '100%' }">
-    <a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
-      <a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
-        <a-icon type="share-alt" />
-        {{ $t('cmdb.ci.share') }}
-      </a>
-      <a-tab-pane key="tab_1">
-        <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
-        <div class="ci-detail-attr">
-          <el-descriptions
-            :title="group.name || $t('other')"
-            :key="group.name"
-            v-for="group in attributeGroups"
-            border
-            :column="3"
-          >
-            <el-descriptions-item
-              :label="`${attr.alias || attr.name}`"
-              :key="attr.name"
-              v-for="attr in group.attributes"
-            >
-              <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
-            </el-descriptions-item>
-          </el-descriptions>
-        </div>
-      </a-tab-pane>
-      <a-tab-pane key="tab_2">
-        <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
-        <div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
-          <ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
-        </div>
-      </a-tab-pane>
-      <a-tab-pane key="tab_3">
-        <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
-        <div :style="{ padding: '24px', height: '100%' }">
-          <a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
-            <a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
-              <ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
-            </a-button>
-          </a-space>
-          <ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
-          <vxe-table
-            ref="xTable"
-            show-overflow
-            show-header-overflow
-            :data="ciHistory"
-            size="small"
-            :height="tableHeight"
-            highlight-hover-row
-            :span-method="mergeRowMethod"
-            :scroll-y="{ enabled: false, gt: 20 }"
-            :scroll-x="{ enabled: false, gt: 0 }"
-            border
-            resizable
-            class="ops-unstripe-table"
-          >
-            <template #empty>
-              <a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
-                <img slot="image" :src="require('@/assets/data_empty.png')" />
-                <span slot="description"> {{ $t('noData') }} </span>
-              </a-empty>
-            </template>
-            <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
-            <vxe-table-column
-              field="username"
-              :title="$t('user')"
-              :filters="[]"
-              :filter-method="filterUsernameMethod"
-            ></vxe-table-column>
-            <vxe-table-column
-              field="operate_type"
-              :filters="[
-                { value: 0, label: $t('new') },
-                { value: 1, label: $t('delete') },
-                { value: 2, label: $t('update') },
-              ]"
-              :filter-method="filterOperateMethod"
-              :title="$t('operation')"
-            >
-              <template #default="{ row }">
-                {{ operateTypeMap[row.operate_type] }}
-              </template>
-            </vxe-table-column>
-            <vxe-table-column
-              field="attr_alias"
-              :title="$t('cmdb.attribute')"
-              :filters="[]"
-              :filter-method="filterAttrMethod"
-            ></vxe-table-column>
-            <vxe-table-column field="old" :title="$t('cmdb.history.old')">
-              <template #default="{ row }">
-                <span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
-                <span v-else>{{ row.old }}</span>
-              </template>
-            </vxe-table-column>
-            <vxe-table-column field="new" :title="$t('cmdb.history.new')">
-              <template #default="{ row }">
-                <span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
-                <span v-else>{{ row.new }}</span>
-              </template>
-            </vxe-table-column>
-          </vxe-table>
-        </div>
-      </a-tab-pane>
-      <a-tab-pane key="tab_4">
-        <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
-        <div :style="{ padding: '24px', height: '100%' }">
-          <TriggerTable :ci_id="ci._id" />
-        </div>
-      </a-tab-pane>
-      <a-tab-pane key="tab_5">
-        <span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
-        <div :style="{ padding: '24px', height: '100%' }">
-          <RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
-        </div>
-      </a-tab-pane>
-    </a-tabs>
-    <a-empty
-      v-else
-      :image-style="{
-        height: '100px',
-      }"
-      :style="{ paddingTop: '20%' }"
-    >
-      <img slot="image" :src="require('@/assets/data_empty.png')" />
-      <span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
-    </a-empty>
-  </div>
-</template>
-
-<script>
-import _ from 'lodash'
-import { Descriptions, DescriptionsItem } from 'element-ui'
-import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
-import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
-import { getCIById } from '@/modules/cmdb/api/ci'
-import CiDetailAttrContent from './ciDetailAttrContent.vue'
-import CiDetailRelation from './ciDetailRelation.vue'
-import TriggerTable from '../../operation_history/modules/triggerTable.vue'
-import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
-import CiRollbackForm from './ciRollbackForm.vue'
-export default {
-  name: 'CiDetailTab',
-  components: {
-    ElDescriptions: Descriptions,
-    ElDescriptionsItem: DescriptionsItem,
-    CiDetailAttrContent,
-    CiDetailRelation,
-    TriggerTable,
-    RelatedItsmTable,
-    CiRollbackForm,
-  },
-  props: {
-    typeId: {
-      type: Number,
-      required: true,
-    },
-    treeViewsLevels: {
-      type: Array,
-      default: () => [],
-    },
-    attributeHistoryTableHeight: {
-      type: Number,
-      default: null
-    }
-  },
-  data() {
-    return {
-      ci: {},
-      item: [],
-      attributeGroups: [],
-      activeTabKey: 'tab_1',
-      rowSpanMap: {},
-      ciHistory: [],
-      ciId: null,
-      ci_types: [],
-      hasPermission: true,
-      itsmInstalled: true,
-      tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
-    }
-  },
-  computed: {
-    windowHeight() {
-      return this.$store.state.windowHeight
-    },
-    operateTypeMap() {
-      return {
-        0: this.$t('new'),
-        1: this.$t('delete'),
-        2: this.$t('update'),
-      }
-    },
-  },
-  provide() {
-    return {
-      ci_types: () => {
-        return this.ci_types
-      },
-    }
-  },
-  inject: {
-    reload: {
-      from: 'reload',
-      default: null,
-    },
-    handleSearch: {
-      from: 'handleSearch',
-      default: null,
-    },
-    attrList: {
-      from: 'attrList',
-      default: () => [],
-    },
-  },
-  methods: {
-    async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
-      this.activeTabKey = activeTabKey
-      if (activeTabKey === 'tab_2') {
-        this.$nextTick(() => {
-          this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
-        })
-      }
-      this.ciId = ciId
-      await this.getCI()
-      await this.judgeItsmInstalled()
-      if (this.hasPermission) {
-        this.getAttributes()
-        this.getCIHistory()
-        getCITypes().then((res) => {
-          this.ci_types = res.ci_types
-        })
-      }
-    },
-    getAttributes() {
-      getCITypeGroupById(this.typeId, { need_other: 1 })
-        .then((res) => {
-          this.attributeGroups = res
-        })
-        .catch((e) => {})
-    },
-    async getCI() {
-      await getCIById(this.ciId)
-        .then((res) => {
-          if (res.result.length) {
-            this.ci = res.result[0]
-          } else {
-            this.hasPermission = false
-          }
-        })
-        .catch((e) => {
-          if (e.response.status === 404) {
-            this.itsmInstalled = false
-          }
-        })
-    },
-    async judgeItsmInstalled() {
-      await judgeItsmInstalled().catch((e) => {
-        this.itsmInstalled = false
-      })
-    },
-
-    getCIHistory() {
-      getCIHistory(this.ciId)
-        .then((res) => {
-          this.ciHistory = res
-
-          const rowSpanMap = {}
-          let startIndex = 0
-          let startCount = 1
-          res.forEach((item, index) => {
-            if (index === 0) {
-              return
-            }
-            if (res[index].record_id === res[startIndex].record_id) {
-              startCount += 1
-              rowSpanMap[index] = 0
-              if (index === res.length - 1) {
-                rowSpanMap[startIndex] = startCount
-              }
-            } else {
-              rowSpanMap[startIndex] = startCount
-              startIndex = index
-              startCount = 1
-              if (index === res.length - 1) {
-                rowSpanMap[index] = 1
-              }
-            }
-          })
-          this.rowSpanMap = rowSpanMap
-        })
-        .catch((e) => {
-          console.log(e)
-        })
-    },
-    changeTab(key) {
-      this.activeTabKey = key
-      if (key === 'tab_3') {
-        this.$nextTick(() => {
-          const $table = this.$refs.xTable
-          if ($table) {
-            const usernameColumn = $table.getColumnByField('username')
-            const attrColumn = $table.getColumnByField('attr_alias')
-            if (usernameColumn) {
-              const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
-              $table.setFilter(
-                usernameColumn,
-                usernameList.map((item) => {
-                  return {
-                    value: item,
-                    label: item,
-                  }
-                })
-              )
-            }
-            if (attrColumn) {
-              $table.setFilter(
-                attrColumn,
-                this.attrList().map((attr) => {
-                  return { value: attr.alias || attr.name, label: attr.alias || attr.name }
-                })
-              )
-            }
-          }
-        })
-      }
-    },
-    filterUsernameMethod({ value, row, column }) {
-      return row.username === value
-    },
-    filterOperateMethod({ value, row, column }) {
-      return Number(row.operate_type) === Number(value)
-    },
-    filterAttrMethod({ value, row, column }) {
-      return row.attr_alias === value
-    },
-    refresh(editAttrName) {
-      this.getCI()
-      const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
-      // 修改的字段为树形视图订阅的字段 则全部reload
-      setTimeout(() => {
-        if (_find) {
-          if (this.reload) {
-            this.reload()
-          }
-        } else {
-          if (this.handleSearch) {
-            this.handleSearch()
-          }
-        }
-      }, 500)
-    },
-    mergeRowMethod({ row, _rowIndex, column, visibleData }) {
-      const fields = ['created_at', 'username']
-      const cellValue1 = row['created_at']
-      const cellValue2 = row['username']
-      if (cellValue1 && cellValue2 && fields.includes(column.property)) {
-        const prevRow = visibleData[_rowIndex - 1]
-        let nextRow = visibleData[_rowIndex + 1]
-        if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
-          return { rowspan: 0, colspan: 0 }
-        } else {
-          let countRowspan = 1
-          while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
-            nextRow = visibleData[++countRowspan + _rowIndex]
-          }
-          if (countRowspan > 1) {
-            return { rowspan: countRowspan, colspan: 1 }
-          }
-        }
-      }
-    },
-    updateCIByself(params, editAttrName) {
-      const _ci = { ..._.cloneDeep(this.ci), ...params }
-      this.ci = _ci
-      const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
-      // 修改的字段为树形视图订阅的字段 则全部reload
-      setTimeout(() => {
-        if (_find) {
-          if (this.reload) {
-            this.reload()
-          }
-        } else {
-          if (this.handleSearch) {
-            this.handleSearch()
-          }
-        }
-      }, 500)
-    },
-    shareCi() {
-      const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
-      this.$copyText(text)
-        .then(() => {
-          this.$message.success(this.$t('copySuccess'))
-        })
-        .catch(() => {
-          this.$message.error(this.$t('cmdb.ci.copyFailed'))
-        })
-    },
-    handleRollbackCI() {
-      this.$nextTick(() => {
-        this.$refs.ciRollbackForm.onOpen()
-      })
-    },
-  },
-}
-</script>
-
-<style lang="less">
-.ci-detail-tab {
-  height: 100%;
-  .ant-tabs-content {
-    height: calc(100% - 45px);
-    .ant-tabs-tabpane {
-      height: 100%;
-    }
-  }
-  .ant-tabs-bar {
-    margin: 0;
-  }
-  .ant-tabs-extra-content {
-    line-height: 44px;
-  }
-  .ci-detail-attr {
-    height: 100%;
-    overflow: auto;
-    padding: 24px;
-    .el-descriptions-item__content {
-      cursor: default;
-      &:hover a {
-        opacity: 1 !important;
-      }
-    }
-    .el-descriptions:first-child > .el-descriptions__header {
-      margin-top: 0;
-    }
-    .el-descriptions__header {
-      margin-bottom: 5px;
-      margin-top: 20px;
-    }
-    .ant-form-item {
-      margin-bottom: 0;
-    }
-    .ant-form-item-control {
-      line-height: 19px;
-    }
-  }
-}
-</style>
+<template>
+  <div :style="{ height: '100%' }">
+    <a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
+      <a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
+        <a-icon type="share-alt" />
+        {{ $t('cmdb.ci.share') }}
+      </a>
+      <a-tab-pane key="tab_1">
+        <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
+        <div class="ci-detail-attr">
+          <el-descriptions
+            :title="group.name || $t('other')"
+            :key="group.name"
+            v-for="group in attributeGroups"
+            border
+            :column="3"
+          >
+            <el-descriptions-item
+              :label="`${attr.alias || attr.name}`"
+              :key="attr.name"
+              v-for="attr in group.attributes"
+            >
+              <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+      </a-tab-pane>
+      <a-tab-pane key="tab_2">
+        <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
+        <div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
+          <ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
+        </div>
+      </a-tab-pane>
+      <a-tab-pane key="tab_3">
+        <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
+        <div :style="{ padding: '24px', height: '100%' }">
+          <a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
+            <a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
+              <ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
+            </a-button>
+            <a-button type="primary" class="ops-button-ghost" ghost @click="handleExport">
+              <ops-icon type="veops-export" />{{ $t('export') }}
+            </a-button>
+          </a-space>
+          <ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
+          <vxe-table
+            ref="xTable"
+            show-overflow
+            show-header-overflow
+            :data="ciHistory"
+            size="small"
+            :height="tableHeight"
+            highlight-hover-row
+            :span-method="mergeRowMethod"
+            :scroll-y="{ enabled: false, gt: 20 }"
+            :scroll-x="{ enabled: false, gt: 0 }"
+            border
+            resizable
+            class="ops-unstripe-table"
+          >
+            <template #empty>
+              <a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
+                <img slot="image" :src="require('@/assets/data_empty.png')" />
+                <span slot="description"> {{ $t('noData') }} </span>
+              </a-empty>
+            </template>
+            <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
+            <vxe-table-column
+              field="username"
+              :title="$t('user')"
+              :filters="[]"
+              :filter-method="filterUsernameMethod"
+            ></vxe-table-column>
+            <vxe-table-column
+              field="operate_type"
+              :filters="[
+                { value: 0, label: $t('new') },
+                { value: 1, label: $t('delete') },
+                { value: 2, label: $t('update') },
+              ]"
+              :filter-method="filterOperateMethod"
+              :title="$t('operation')"
+            >
+              <template #default="{ row }">
+                {{ operateTypeMap[row.operate_type] }}
+              </template>
+            </vxe-table-column>
+            <vxe-table-column
+              field="attr_alias"
+              :title="$t('cmdb.attribute')"
+              :filters="[]"
+              :filter-method="filterAttrMethod"
+            ></vxe-table-column>
+            <vxe-table-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')">
+              <template #default="{ row }">
+                <span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
+                <span v-else>{{ row.old }}</span>
+              </template>
+            </vxe-table-column>
+            <vxe-table-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')">
+              <template #default="{ row }">
+                <span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
+                <span v-else>{{ row.new }}</span>
+              </template>
+            </vxe-table-column>
+          </vxe-table>
+        </div>
+      </a-tab-pane>
+      <a-tab-pane key="tab_4">
+        <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
+        <div :style="{ padding: '24px', height: '100%' }">
+          <TriggerTable :ci_id="ci._id" />
+        </div>
+      </a-tab-pane>
+      <a-tab-pane key="tab_5">
+        <span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
+        <div :style="{ padding: '24px', height: '100%' }">
+          <RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
+        </div>
+      </a-tab-pane>
+    </a-tabs>
+    <a-empty
+      v-else
+      :image-style="{
+        height: '100px',
+      }"
+      :style="{ paddingTop: '20%' }"
+    >
+      <img slot="image" :src="require('@/assets/data_empty.png')" />
+      <span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
+    </a-empty>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash'
+import { Descriptions, DescriptionsItem } from 'element-ui'
+import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
+import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
+import { getCIById } from '@/modules/cmdb/api/ci'
+import CiDetailAttrContent from './ciDetailAttrContent.vue'
+import CiDetailRelation from './ciDetailRelation.vue'
+import TriggerTable from '../../operation_history/modules/triggerTable.vue'
+import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
+import CiRollbackForm from './ciRollbackForm.vue'
+export default {
+  name: 'CiDetailTab',
+  components: {
+    ElDescriptions: Descriptions,
+    ElDescriptionsItem: DescriptionsItem,
+    CiDetailAttrContent,
+    CiDetailRelation,
+    TriggerTable,
+    RelatedItsmTable,
+    CiRollbackForm,
+  },
+  props: {
+    typeId: {
+      type: Number,
+      required: true,
+    },
+    treeViewsLevels: {
+      type: Array,
+      default: () => [],
+    },
+    attributeHistoryTableHeight: {
+      type: Number,
+      default: null
+    }
+  },
+  data() {
+    return {
+      ci: {},
+      item: [],
+      attributeGroups: [],
+      activeTabKey: 'tab_1',
+      rowSpanMap: {},
+      ciHistory: [],
+      ciId: null,
+      ci_types: [],
+      hasPermission: true,
+      itsmInstalled: true,
+      tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
+    }
+  },
+  computed: {
+    windowHeight() {
+      return this.$store.state.windowHeight
+    },
+    operateTypeMap() {
+      return {
+        0: this.$t('new'),
+        1: this.$t('delete'),
+        2: this.$t('update'),
+      }
+    },
+  },
+  provide() {
+    return {
+      ci_types: () => {
+        return this.ci_types
+      },
+    }
+  },
+  inject: {
+    reload: {
+      from: 'reload',
+      default: null,
+    },
+    handleSearch: {
+      from: 'handleSearch',
+      default: null,
+    },
+    attrList: {
+      from: 'attrList',
+      default: () => [],
+    },
+  },
+  methods: {
+    async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
+      this.activeTabKey = activeTabKey
+      if (activeTabKey === 'tab_2') {
+        this.$nextTick(() => {
+          this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
+        })
+      }
+      this.ciId = ciId
+      await this.getCI()
+      await this.judgeItsmInstalled()
+      if (this.hasPermission) {
+        this.getAttributes()
+        this.getCIHistory()
+        getCITypes().then((res) => {
+          this.ci_types = res.ci_types
+        })
+      }
+    },
+    getAttributes() {
+      getCITypeGroupById(this.typeId, { need_other: 1 })
+        .then((res) => {
+          this.attributeGroups = res
+        })
+        .catch((e) => {})
+    },
+    async getCI() {
+      await getCIById(this.ciId)
+        .then((res) => {
+          if (res.result.length) {
+            this.ci = res.result[0]
+          } else {
+            this.hasPermission = false
+          }
+        })
+        .catch((e) => {
+          if (e.response.status === 404) {
+            this.itsmInstalled = false
+          }
+        })
+    },
+    async judgeItsmInstalled() {
+      await judgeItsmInstalled().catch((e) => {
+        this.itsmInstalled = false
+      })
+    },
+
+    getCIHistory() {
+      getCIHistory(this.ciId)
+        .then((res) => {
+          this.ciHistory = res
+
+          const rowSpanMap = {}
+          let startIndex = 0
+          let startCount = 1
+          res.forEach((item, index) => {
+            if (index === 0) {
+              return
+            }
+            if (res[index].record_id === res[startIndex].record_id) {
+              startCount += 1
+              rowSpanMap[index] = 0
+              if (index === res.length - 1) {
+                rowSpanMap[startIndex] = startCount
+              }
+            } else {
+              rowSpanMap[startIndex] = startCount
+              startIndex = index
+              startCount = 1
+              if (index === res.length - 1) {
+                rowSpanMap[index] = 1
+              }
+            }
+          })
+          this.rowSpanMap = rowSpanMap
+        })
+        .catch((e) => {
+          console.log(e)
+        })
+    },
+    changeTab(key) {
+      this.activeTabKey = key
+      if (key === 'tab_3') {
+        this.$nextTick(() => {
+          const $table = this.$refs.xTable
+          if ($table) {
+            const usernameColumn = $table.getColumnByField('username')
+            const attrColumn = $table.getColumnByField('attr_alias')
+            if (usernameColumn) {
+              const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
+              $table.setFilter(
+                usernameColumn,
+                usernameList.map((item) => {
+                  return {
+                    value: item,
+                    label: item,
+                  }
+                })
+              )
+            }
+            if (attrColumn) {
+              $table.setFilter(
+                attrColumn,
+                this.attrList().map((attr) => {
+                  return { value: attr.alias || attr.name, label: attr.alias || attr.name }
+                })
+              )
+            }
+          }
+        })
+      }
+    },
+    filterUsernameMethod({ value, row, column }) {
+      return row.username === value
+    },
+    filterOperateMethod({ value, row, column }) {
+      return Number(row.operate_type) === Number(value)
+    },
+    filterAttrMethod({ value, row, column }) {
+      return row.attr_alias === value
+    },
+    refresh(editAttrName) {
+      this.getCI()
+      const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
+      // 修改的字段为树形视图订阅的字段 则全部reload
+      setTimeout(() => {
+        if (_find) {
+          if (this.reload) {
+            this.reload()
+          }
+        } else {
+          if (this.handleSearch) {
+            this.handleSearch()
+          }
+        }
+      }, 500)
+    },
+    mergeRowMethod({ row, _rowIndex, column, visibleData }) {
+      const fields = ['created_at', 'username']
+      const cellValue1 = row['created_at']
+      const cellValue2 = row['username']
+      if (cellValue1 && cellValue2 && fields.includes(column.property)) {
+        const prevRow = visibleData[_rowIndex - 1]
+        let nextRow = visibleData[_rowIndex + 1]
+        if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
+          return { rowspan: 0, colspan: 0 }
+        } else {
+          let countRowspan = 1
+          while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
+            nextRow = visibleData[++countRowspan + _rowIndex]
+          }
+          if (countRowspan > 1) {
+            return { rowspan: countRowspan, colspan: 1 }
+          }
+        }
+      }
+    },
+    updateCIByself(params, editAttrName) {
+      const _ci = { ..._.cloneDeep(this.ci), ...params }
+      this.ci = _ci
+      const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
+      // 修改的字段为树形视图订阅的字段 则全部reload
+      setTimeout(() => {
+        if (_find) {
+          if (this.reload) {
+            this.reload()
+          }
+        } else {
+          if (this.handleSearch) {
+            this.handleSearch()
+          }
+        }
+      }, 500)
+    },
+    shareCi() {
+      const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
+      this.$copyText(text)
+        .then(() => {
+          this.$message.success(this.$t('copySuccess'))
+        })
+        .catch(() => {
+          this.$message.error(this.$t('cmdb.ci.copyFailed'))
+        })
+    },
+    handleRollbackCI() {
+      this.$nextTick(() => {
+        this.$refs.ciRollbackForm.onOpen()
+      })
+    },
+    async handleExport() {
+      this.$refs.xTable.exportData({
+        filename: this.$t('cmdb.ci.history'),
+        sheetName: 'Sheet1',
+        type: 'xlsx',
+        types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
+        data: this.ciHistory,
+        isMerge: true,
+        isColgroup: true,
+      })
+    }
+  },
+}
+</script>
+
+<style lang="less">
+.ci-detail-tab {
+  height: 100%;
+  .ant-tabs-content {
+    height: calc(100% - 45px);
+    .ant-tabs-tabpane {
+      height: 100%;
+    }
+  }
+  .ant-tabs-bar {
+    margin: 0;
+  }
+  .ant-tabs-extra-content {
+    line-height: 44px;
+  }
+  .ci-detail-attr {
+    height: 100%;
+    overflow: auto;
+    padding: 24px;
+    .el-descriptions-item__content {
+      cursor: default;
+      &:hover a {
+        opacity: 1 !important;
+      }
+    }
+    .el-descriptions:first-child > .el-descriptions__header {
+      margin-top: 0;
+    }
+    .el-descriptions__header {
+      margin-bottom: 5px;
+      margin-top: 20px;
+    }
+    .ant-form-item {
+      margin-bottom: 0;
+    }
+    .ant-form-item-control {
+      line-height: 19px;
+    }
+  }
+}
+</style>
diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue
index 8178e27..fc73711 100644
--- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue
+++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/ciTable.vue
@@ -7,6 +7,7 @@
       @search="handleSearch"
       @searchFormReset="searchFormReset"
       @searchFormChange="searchFormChange"
+      @export="handleExport"
     ></search-form>
     <vxe-table
       ref="xTable"
@@ -86,13 +87,13 @@
         </template>
       </vxe-column>
       <vxe-column field="attr_alias" :title="$t('cmdb.history.attribute')"></vxe-column>
-      <vxe-column field="old" :title="$t('cmdb.history.old')"></vxe-column>
-      <vxe-column field="new" :title="$t('cmdb.history.new')"></vxe-column>
+      <vxe-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')"></vxe-column>
+      <vxe-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')"></vxe-column>
     </vxe-table>
     <pager
       :current-page.sync="queryParams.page"
       :page-size.sync="queryParams.page_size"
-      :page-sizes="[50, 100, 200]"
+      :page-sizes="[50, 100, 200, 500]"
       :total="total"
       :isLoading="loading"
       @change="onChange"
@@ -391,6 +392,37 @@ export default {
     filterOption(input, option) {
       return option.componentOptions.children[0].text.indexOf(input) >= 0
     },
+
+    async handleExport(params) {
+      const hide = this.$message.loading(this.$t('loading'), 0)
+      const res = await getCIHistoryTable({
+        ...params,
+        page: this.queryParams.page,
+        page_size: this.queryParams.page_size,
+      })
+      hide()
+      const data = []
+      res.records.forEach((item) => {
+        item[0].type_id = this.handleTypeId(item[0].type_id)
+        item[1].forEach((subItem) => {
+          subItem.operate_type = this.handleOperateType(subItem.operate_type)
+          subItem.new = subItem.new || ''
+          subItem.old = subItem.old || ''
+          const tempObj = Object.assign(subItem, item[0])
+          data.push(tempObj)
+        })
+      })
+
+      this.$refs.xTable.exportData({
+        filename: this.$t('cmdb.history.ciChange'),
+        sheetName: 'Sheet1',
+        type: 'xlsx',
+        types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
+        isMerge: true,
+        isColgroup: true,
+        data,
+      })
+    }
   },
 }
 </script>
diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue
index 540b074..d7fb859 100644
--- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue
+++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/relation.vue
@@ -5,6 +5,7 @@
       @expandChange="handleExpandChange"
       @search="handleSearch"
       @searchFormReset="searchFormReset"
+      @export="handleExport"
     ></search-form>
     <vxe-table
       ref="xTable"
@@ -122,7 +123,7 @@
     <pager
       :current-page.sync="queryParams.page"
       :page-size.sync="queryParams.page_size"
-      :page-sizes="[50, 100, 200]"
+      :page-sizes="[50, 100, 200, 500]"
       :total="total"
       :isLoading="loading"
       @change="onChange"
@@ -379,6 +380,58 @@ export default {
     filterOption(input, option) {
       return option.componentOptions.children[0].text.indexOf(input) >= 0
     },
+
+    async handleExport(params) {
+      const hide = this.$message.loading(this.$t('loading'), 0)
+      const res = await getRelationTable({
+        ...params,
+        page: this.queryParams.page,
+        page_size: this.queryParams.page_size,
+      })
+      hide()
+      const data = []
+      res.records.forEach((item) => {
+        item[1].forEach((subItem) => {
+          subItem.operate_type = this.handleOperateType(subItem.operate_type)
+          subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id)
+          subItem.first = res.cis[String(subItem.first_ci_id)]
+          subItem.second = res.cis[String(subItem.second_ci_id)]
+
+          const tempObj = Object.assign(subItem, item[0])
+
+          tempObj.changeDescription = this.getExportChangeDescription(tempObj)
+
+          data.push(tempObj)
+        })
+      })
+
+      this.$refs.xTable.exportData({
+        filename: this.$t('cmdb.history.relationChange'),
+        sheetName: 'Sheet1',
+        type: 'xlsx',
+        types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
+        isMerge: true,
+        isColgroup: true,
+        data,
+      })
+    },
+
+    getExportChangeDescription(item) {
+      const first = item.first ? `${item.first.ci_type_alias}${item.first.unique_alias && item.first[item.first.unique] ? `(${item.first.unique_alias}:${item.first[item.first.unique]})` : ''}` : ''
+      const second = item.second ? `${item.second.ci_type_alias}${item.second.unique_alias && item.second[item.second.unique] ? `(${item.second.unique_alias}:${item.second[item.second.unique]})` : ''}` : ''
+      let center = ''
+      if (item.changeDescription === this.$t('cmdb.history.noUpdate')) {
+        center = item.relation_type_id
+      } else if (item.operate_type.includes(this.$t('update'))) {
+        center = item.changeArr.join(';')
+      } else if (item.operate_type.includes(this.$t('new'))) {
+        center = item.relation_type_id
+      } else if (item.operate_type.includes(this.$t('delete'))) {
+        center = item.relation_type_id
+      }
+
+      return `${first || ''} => ${center || ''} => ${second || ''}`
+    }
   },
 }
 </script>
diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue
index 66d80c5..4afd340 100644
--- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue
+++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/searchForm.vue
@@ -27,7 +27,7 @@
               <a-select-option
                 :value="Object.values(choice)[0]"
                 v-for="(choice, index) in attr.choice_value"
-                :key="'Search_' + attr.name + index"
+                :key="'Search_' + attr.name + Object.values(choice)[0] + index"
               >
                 {{ Object.keys(choice)[0] }}
               </a-select-option>
@@ -42,7 +42,7 @@
                 hideDisabledOptions: true,
                 defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
               }"
-              v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'"
+              v-else-if="attr.value_type === '3'"
             />
             <a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
           </a-form-item>
@@ -85,7 +85,7 @@
                 @change="onChange"
                 format="YYYY-MM-DD HH:mm"
                 :placeholder="[$t('cmdb.history.startTime'), $t('cmdb.history.endTime')]"
-                v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
+                v-else-if="attr.value_type === '3'"
                 :show-time="{
                   hideDisabledOptions: true,
                   defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
@@ -101,6 +101,9 @@
           <a-button type="primary" html-type="submit" @click="handleSearch">
             {{ $t('query') }}
           </a-button>
+          <a-button :style="{ marginLeft: '8px' }" @click="handleExport">
+            {{ $t('export') }}
+          </a-button>
           <a-button :style="{ marginLeft: '8px' }" @click="handleReset">
             {{ $t('reset') }}
           </a-button>
@@ -155,6 +158,13 @@ export default {
       this.$emit('search', this.queryParams)
     },
 
+    handleExport() {
+      const queryParams = {
+        ...this.queryParams
+      }
+      this.$emit('export', queryParams)
+    },
+
     handleReset() {
       this.queryParams = {
         page: 1,
diff --git a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue
index 09f01f0..486b0a0 100644
--- a/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue
+++ b/cmdb-ui/src/modules/cmdb/views/operation_history/modules/typeTable.vue
@@ -5,6 +5,7 @@
       @expandChange="handleExpandChange"
       @search="handleSearch"
       @searchFormReset="searchFormReset"
+      @export="handleExport"
     ></search-form>
     <vxe-table
       ref="xTable"
@@ -121,7 +122,7 @@ export default {
       relationTypeList: null,
       typeList: null,
       userList: [],
-      pageSizeOptions: ['50', '100', '200'],
+      pageSizeOptions: ['50', '100', '200', '500'],
       isExpand: false,
       current: 1,
       pageSize: 50,
@@ -508,6 +509,36 @@ export default {
     filterOption(input, option) {
       return option.componentOptions.children[0].text.indexOf(input) >= 0
     },
+
+    async handleExport(params) {
+      const hide = this.$message.loading(this.$t('loading'), 0)
+      const res = await getCITypesTable({
+        ...params,
+        page: this.queryParams.page,
+        page_size: this.queryParams.page_size,
+      })
+      hide()
+
+      res.result.forEach((item) => {
+        this.handleChangeDescription(item, item.operate_type)
+        item.operate_type = this.handleOperateType(item.operate_type)
+        item.type_id = this.handleTypeId(item.type_id)
+        item.uid = this.handleUID(item.uid)
+        if (item.operate_type.includes(this.$t('update'))) {
+          item.changeDescription = item.changeArr.join(';')
+        }
+      })
+
+      this.$refs.xTable.exportData({
+        filename: this.$t('cmdb.history.ciTypeChange'),
+        sheetName: 'Sheet1',
+        type: 'xlsx',
+        types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
+        isMerge: true,
+        isColgroup: true,
+        data: res.result,
+      })
+    },
   },
 }
 </script>