diff --git a/ui/src/views/attributes/index.vue b/ui/src/views/attributes/index.vue
new file mode 100644
index 0000000..b3d97fb
--- /dev/null
+++ b/ui/src/views/attributes/index.vue
@@ -0,0 +1,374 @@
+<template>
+  <a-card :bordered="false">
+
+    <div class="action-btn">
+      <a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem;">{{ btnName }}</a-button>
+    </div>
+
+    <s-table
+      :alert="options.alert"
+      :columns="columns"
+      :data="loadData"
+      :rowKey="record=>record.id"
+      :rowSelection="options.rowSelection"
+      :scroll="scroll"
+      :showPagination="showPagination"
+      ref="table"
+      size="middle"
+
+    >
+      <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
+        <a-input
+          v-ant-ref="c => searchInput = c"
+          :placeholder="`Search ${column.dataIndex}`"
+          :value="selectedKeys[0]"
+          @change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
+          @pressEnter="() => handleSearch(selectedKeys, confirm, column)"
+          style="width: 188px; margin-bottom: 8px; display: block;"
+        />
+        <a-button
+          type="primary"
+          @click="() => handleSearch(selectedKeys, confirm, column)"
+          icon="search"
+          size="small"
+          style="width: 90px; margin-right: 8px"
+        >Search</a-button>
+        <a-button
+          @click="() => handleReset(clearFilters, column)"
+          size="small"
+          style="width: 90px"
+        >Reset</a-button>
+      </div>
+      <a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
+
+      <template slot="nameSearchRender" slot-scope="text">
+        <span v-if="columnSearchText.name">
+          <template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
+            <mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
+            <template v-else>{{ fragment }}</template>
+          </template>
+        </span>
+        <template v-else>{{ text }}</template>
+      </template>
+
+      <template slot="aliasSearchRender" slot-scope="text">
+        <span v-if="columnSearchText.alias">
+          <template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.alias})|(?=${columnSearchText.alias})`, 'i'))">
+            <mark v-if="fragment.toLowerCase() === columnSearchText.alias.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
+            <template v-else>{{ fragment }}</template>
+          </template>
+        </span>
+        <template v-else>{{ text }}</template>
+      </template>
+
+      <span slot="is_check" slot-scope="text">
+        <a-icon type="check" v-if="text"/>
+      </span>
+
+      <span slot="action" slot-scope="text, record">
+        <template>
+          <a @click="handleEdit(record)">编辑</a>
+          <a-divider type="vertical"/>
+          <a @click="handleDelete(record)">删除</a>
+        </template>
+      </span>
+
+    </s-table>
+    <AttributeForm ref="attributeForm" :handleOk="handleOk"> </AttributeForm>
+
+  </a-card>
+</template>
+
+<script>
+import { STable } from '@/components'
+import AttributeForm from './module/attributeForm'
+import { valueTypeMap } from './module/const'
+import { deleteAttributesById, searchAttributes } from '@/api/cmdb/CITypeAttr'
+
+export default {
+  name: 'Index',
+  components: {
+    STable,
+    AttributeForm
+  },
+  data () {
+    return {
+      scroll: { x: 1400, y: 500 },
+      btnName: '新增属性',
+
+      CITypeName: this.$route.params.CITypeName,
+      CITypeId: this.$route.params.CITypeId,
+
+      formLayout: 'vertical',
+
+      attributes: [],
+      allAttributes: [],
+      transferData: [],
+      transferTargetKeys: [],
+      transferSelectedKeys: [],
+      originTargetKeys: [],
+
+      pagination: {
+        defaultPageSize: 20
+      },
+      showPagination: false,
+      columnSearchText: {
+        alias: '',
+        name: ''
+      },
+      columns: [
+        {
+          title: '名称',
+          dataIndex: 'alias',
+          sorter: false,
+          width: 200,
+          scopedSlots: {
+            customRender: 'aliasSearchRender',
+            filterDropdown: 'filterDropdown',
+            filterIcon: 'filterIcon'
+          },
+          onFilter: (value, record) => record.alias.toLowerCase().includes(value.toLowerCase()),
+          onFilterDropdownVisibleChange: (visible) => {
+            if (visible) {
+              setTimeout(() => {
+                this.searchInput.focus()
+              }, 0)
+            }
+          }
+        },
+        {
+          title: '英文名',
+          dataIndex: 'name',
+          sorter: false,
+          width: 200,
+          scopedSlots: {
+            customRender: 'nameSearchRender',
+            filterDropdown: 'filterDropdown',
+            filterIcon: 'filterIcon'
+          },
+          onFilter: (value, record) => record.name.toLowerCase().includes(value.toLowerCase()),
+          onFilterDropdownVisibleChange: (visible) => {
+            if (visible) {
+              setTimeout(() => {
+                this.searchInput.focus()
+              }, 0)
+            }
+          }
+        },
+        {
+          title: '类型',
+          dataIndex: 'value_type',
+          sorter: false,
+          width: 100,
+          scopedSlots: { customRender: 'value_type' },
+          customRender: (text) => valueTypeMap[text]
+
+        },
+        {
+          title: '唯一',
+          dataIndex: 'is_unique',
+          width: 80,
+          sorter: false,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '索引',
+          dataIndex: 'is_index',
+          sorter: false,
+          width: 80,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '排序',
+          dataIndex: 'is_sortable',
+          sorter: false,
+          width: 80,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '链接',
+          dataIndex: 'is_link',
+          sorter: false,
+          width: 80,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '密码',
+          dataIndex: 'is_password',
+          sorter: false,
+          width: 100,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '列表',
+          dataIndex: 'is_list',
+          sorter: false,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+
+        {
+          title: '操作',
+          dataIndex: 'action',
+          width: 100,
+          fixed: 'right',
+          scopedSlots: { customRender: 'action' }
+        }
+      ],
+      loadData: parameter => {
+        console.log('loadData.parameter', parameter)
+
+        return searchAttributes()
+          .then(res => {
+            this.allAttributes = res.attributes
+            return {
+              data: res.attributes
+
+            }
+          })
+      },
+
+      mdl: {},
+      // 高级搜索 展开/关闭
+      advanced: false,
+      // 查询参数
+      queryParam: {},
+      // 表头
+
+      selectedRowKeys: [],
+      selectedRows: [],
+
+      // custom table alert & rowSelection
+      options: {
+        alert: false,
+        rowSelection: null
+      },
+      optionAlertShow: false
+
+    }
+  },
+
+  beforeCreate () {
+    this.form = this.$form.createForm(this)
+  },
+
+  computed: {
+
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 5 },
+        wrapperCol: { span: 12 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+
+  },
+  mounted () {
+    this.getAttributes()
+    this.setScrollY()
+  },
+  methods: {
+    handleSearch (selectedKeys, confirm, column) {
+      confirm()
+      this.columnSearchText[column.dataIndex] = selectedKeys[0]
+    },
+
+    handleReset (clearFilters, column) {
+      clearFilters()
+      this.columnSearchText[column.dataIndex] = ''
+    },
+
+    getAttributes () {
+      searchAttributes().then(res => {
+        this.allAttributes = res.attributes
+      })
+    },
+
+    setScrollY () {
+      this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 200
+    },
+
+    handleEdit (record) {
+      this.$refs.attributeForm.handleEdit(record)
+    },
+    handleDelete (record) {
+      this.deleteAttribute(record.id)
+    },
+    handleOk () {
+      this.$refs.table.refresh()
+    },
+
+    handleCreate () {
+      this.$refs.attributeForm.handleCreate()
+    },
+
+    deleteAttribute (attrId) {
+      deleteAttributesById(attrId)
+        .then(res => {
+          this.$message.success(`删除成功`)
+          this.handleOk()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+  .custom-filter-dropdown {
+    padding: 8px;
+    border-radius: 4px;
+    background: #fff;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
+  }
+
+  .highlight {
+    background-color: rgb(255, 192, 105);
+    padding: 0px;
+  }
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/attributes/module/attributeForm.vue b/ui/src/views/attributes/module/attributeForm.vue
new file mode 100644
index 0000000..9959925
--- /dev/null
+++ b/ui/src/views/attributes/module/attributeForm.vue
@@ -0,0 +1,339 @@
+<template>
+  <a-drawer
+    :closable="false"
+    :title="drawerTitle"
+    :visible="drawerVisible"
+    @close="onClose"
+    placement="right"
+    width="30%"
+  >
+
+    <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
+
+      <a-form-item
+        :label-col="formItemLayout.labelCol"
+        :wrapper-col="formItemLayout.wrapperCol"
+        label="属性名(英文)"
+      >
+        <a-input
+          name="name"
+          placeholder="英文"
+          v-decorator="['name', {rules: [{ required: true, message: '请输入属性名'},{message: '不能以数字开头,可以是英文 数字以及下划线 (_)', pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$')}]} ]"
+        />
+      </a-form-item>
+      <a-form-item
+        :label-col="formItemLayout.labelCol"
+        :wrapper-col="formItemLayout.wrapperCol"
+        label="别名"
+      >
+        <a-input
+          name="alias"
+          v-decorator="['alias', {rules: []} ]"
+        />
+      </a-form-item>
+
+      <a-form-item
+        :label-col="formItemLayout.labelCol"
+        :wrapper-col="formItemLayout.wrapperCol"
+        label="数据类型"
+      >
+
+        <a-select
+          name="value_type"
+          style="width: 120px"
+          v-decorator="['value_type', {rules: [{required: true}], } ]"
+        >
+          <a-select-option :value="key" :key="key" v-for="(value, key) in ValueTypeMap">{{ value }}</a-select-option>
+        </a-select>
+
+      </a-form-item>
+
+      <a-form-item
+        :label-col="horizontalFormItemLayout.labelCol"
+        :wrapper-col="horizontalFormItemLayout.wrapperCol"
+        label="是否唯一"
+      >
+        <a-switch
+          @change="onChange"
+          name="is_unique"
+          v-decorator="['is_unique', {rules: [], valuePropName: 'checked',} ]"
+        />
+      </a-form-item>
+
+      <a-form-item
+        :label-col="horizontalFormItemLayout.labelCol"
+        :wrapper-col="horizontalFormItemLayout.wrapperCol"
+        label="是否索引"
+      >
+        <a-switch
+          @change="onChange"
+          name="is_index"
+          v-decorator="['is_index', {rules: [], valuePropName: 'checked',} ]"
+        />
+      </a-form-item>
+
+      <a-form-item
+        :label-col="horizontalFormItemLayout.labelCol"
+        :wrapper-col="horizontalFormItemLayout.wrapperCol"
+        label="是否可排序"
+      >
+        <a-switch
+          @change="onChange"
+          name="is_sortable"
+          v-decorator="['is_sortable', {rules: [], valuePropName: 'checked',} ]"
+        />
+      </a-form-item>
+      <a-form-item
+        :label-col="horizontalFormItemLayout.labelCol"
+        :wrapper-col="horizontalFormItemLayout.wrapperCol"
+        label="是否是链接"
+      >
+        <a-switch
+          @change="onChange"
+          name="is_link"
+          v-decorator="['is_link', {rules: [], valuePropName: 'checked',} ]"
+        />
+      </a-form-item>
+      <a-form-item
+        :label-col="horizontalFormItemLayout.labelCol"
+        :wrapper-col="horizontalFormItemLayout.wrapperCol"
+        label="是否是密码"
+      >
+        <a-switch
+          @change="onChange"
+          name="is_password"
+          v-decorator="['is_password', {rules: [], valuePropName: 'checked',} ]"
+        />
+      </a-form-item>
+      <a-form-item
+        :label-col="horizontalFormItemLayout.labelCol"
+        :wrapper-col="horizontalFormItemLayout.wrapperCol"
+        label="是否列表"
+      >
+        <a-switch
+          @change="onChange"
+          name="is_list"
+          v-decorator="['is_list', {rules: [], valuePropName: 'checked',} ]"
+        />
+      </a-form-item>
+      <a-form-item
+        :label-col="formItemLayout.labelCol"
+        :wrapper-col="formItemLayout.wrapperCol"
+        label="预定义值"
+      >
+        <a-textarea
+          :rows="5"
+          name="choice_value"
+          placeholder="多个值使用半角逗号“,”分隔"
+          v-decorator="['choice_value', {rules: []} ]"
+        />
+      </a-form-item>
+      <a-form-item>
+        <a-input
+          name="id"
+          type="hidden"
+          v-decorator="['id', {rules: []} ]"
+        />
+      </a-form-item>
+
+      <div
+        :style="{
+          position: 'absolute',
+          left: 0,
+          bottom: 0,
+          width: '100%',
+          borderTop: '1px solid #e9e9e9',
+          padding: '0.8rem 1rem',
+          background: '#fff',
+
+        }"
+      >
+        <a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
+        <a-button @click="onClose">取消</a-button>
+
+      </div>
+
+    </a-form>
+  </a-drawer>
+
+</template>
+
+<script>
+import { STable } from '@/components'
+import { createAttribute, createCITypeAttributes, updateAttributeById } from '@/api/cmdb/CITypeAttr'
+import { valueTypeMap } from './const'
+
+export default {
+  name: 'AttributeForm',
+  components: {
+    STable
+  },
+  data () {
+    return {
+
+      drawerTitle: '新增属性',
+      drawerVisible: false,
+      CITypeName: this.$route.params.CITypeName,
+      CITypeId: this.$route.params.CITypeId,
+
+      formLayout: 'vertical',
+
+      attributes: [],
+      allAttributes: [],
+
+      ValueTypeMap: valueTypeMap
+
+    }
+  },
+
+  beforeCreate () {
+    this.form = this.$form.createForm(this)
+  },
+
+  computed: {
+
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 5 },
+        wrapperCol: { span: 12 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+
+  },
+  mounted () {
+  },
+  methods: {
+
+    handleCreate () {
+      this.drawerVisible = true
+    },
+    onClose () {
+      this.form.resetFields()
+      this.drawerVisible = false
+    },
+    onChange (e) {
+      console.log(`checked = ${e}`)
+    },
+
+    handleEdit (record) {
+      this.drawerVisible = true
+      console.log(record)
+      this.$nextTick(() => {
+        this.form.setFieldsValue({
+
+          id: record.id,
+          alias: record.alias,
+          name: record.name,
+          value_type: record.value_type,
+          is_list: record.is_list,
+          is_unique: record.is_unique,
+          is_index: record.is_index,
+          is_password: record.is_password,
+          is_link: record.is_link,
+          is_sortable: record.is_sortable,
+          choice_value: (record.choice_value || []).join('\n')
+
+        })
+      })
+    },
+
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          // eslint-disable-next-line no-console
+          console.log('Received values of form: ', values)
+          if (values.choice_value) {
+            values.choice_value = values.choice_value.split('\n')
+          }
+
+          if (values.id) {
+            this.updateAttribute(values.id, values)
+          } else {
+            this.createAttribute(values)
+          }
+        }
+      })
+    },
+    updateAttribute (attrId, data) {
+      updateAttributeById(attrId, data)
+        .then(res => {
+          this.$message.success(`更新成功`)
+          this.handleOk()
+          this.onClose()
+        }).catch(err => this.requestFailed(err))
+    },
+
+    createAttribute (data) {
+      createAttribute(data)
+        .then(res => {
+          if (this.CITypeId) {
+            createCITypeAttributes(this.CITypeId, { attr_id: [res.attr_id] })
+              .then(res => {
+                this.$message.success(`添加成功`)
+                this.handleOk()
+                this.onClose()
+              }).catch(err => this.requestFailed(err))
+          } else {
+            this.$message.success(`添加成功`)
+            this.handleOk()
+            this.onClose()
+          }
+        })
+        .catch(err => this.requestFailed(err))
+    },
+
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+  watch: {},
+  props: {
+    handleOk: {
+      type: Function,
+      default: null
+    }
+  }
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/attributes/module/const.js b/ui/src/views/attributes/module/const.js
new file mode 100644
index 0000000..2f43d48
--- /dev/null
+++ b/ui/src/views/attributes/module/const.js
@@ -0,0 +1,8 @@
+export const valueTypeMap = {
+  '0': '整数',
+  '1': '浮点数',
+  '2': '文本',
+  '3': 'datetime',
+  '4': 'date',
+  '5': 'time'
+}
diff --git a/ui/src/views/ci_type/attributesTable.vue b/ui/src/views/ci_type/attributesTable.vue
new file mode 100644
index 0000000..22adaf3
--- /dev/null
+++ b/ui/src/views/ci_type/attributesTable.vue
@@ -0,0 +1,551 @@
+<template>
+
+  <div>
+    <div class="action-btn">
+      <a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem;">{{ singleAttrAction.btnName }}</a-button>
+      <a-button @click="handleUpdate" type="primary">{{ batchBindAttrAction.btnName }}</a-button>
+    </div>
+
+    <s-table
+      :alert="options.alert"
+      :columns="columns"
+      :data="loadData"
+      :rowKey="record=>record.id"
+      :rowSelection="options.rowSelection"
+      :scroll="scroll"
+      :showPagination="showPagination"
+      ref="table"
+      size="middle"
+    >
+      <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
+        <a-input
+          v-ant-ref="c => searchInput = c"
+          :placeholder="`Search ${column.dataIndex}`"
+          :value="selectedKeys[0]"
+          @change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
+          @pressEnter="() => handleSearch(selectedKeys, confirm, column)"
+          style="width: 188px; margin-bottom: 8px; display: block;"
+        />
+        <a-button
+          type="primary"
+          @click="() => handleSearch(selectedKeys, confirm, column)"
+          icon="search"
+          size="small"
+          style="width: 90px; margin-right: 8px"
+        >Search</a-button>
+        <a-button
+          @click="() => handleReset(clearFilters, column)"
+          size="small"
+          style="width: 90px"
+        >Reset</a-button>
+      </div>
+      <a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
+
+      <template slot="nameSearchRender" slot-scope="text">
+        <span v-if="columnSearchText.name">
+          <template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
+            <mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
+            <template v-else>{{ fragment }}</template>
+          </template>
+        </span>
+        <template v-else>{{ text }}</template>
+      </template>
+
+      <template slot="aliasSearchRender" slot-scope="text">
+        <span v-if="columnSearchText.alias">
+          <template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.alias})|(?=${columnSearchText.alias})`, 'i'))">
+            <mark v-if="fragment.toLowerCase() === columnSearchText.alias.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
+            <template v-else>{{ fragment }}</template>
+          </template>
+        </span>
+        <template v-else>{{ text }}</template>
+      </template>
+
+      <span slot="is_check" slot-scope="text">
+        <a-icon type="check" v-if="text"/>
+      </span>
+
+      <span slot="action" slot-scope="text, record">
+        <template>
+          <a @click="handleEdit(record)">编辑</a>
+          <a-divider type="vertical"/>
+          <a @click="handleDelete(record)">删除</a>
+        </template>
+      </span>
+
+    </s-table>
+    <AttributeForm ref="attributeForm" :handleOk="handleOk"> </AttributeForm>
+
+    <a-drawer
+      :closable="false"
+      :title="batchBindAttrAction.drawerTitle"
+      :visible="batchBindAttrAction.drawerVisible"
+      @close="onBatchBindAttrActionClose"
+      placement="right"
+      width="30%"
+    >
+      <a-form :form="form" :layout="formLayout" @submit="handleBatchUpdateSubmit">
+
+        <a-transfer
+          :dataSource="transferData"
+          :render="item=>item.title"
+          :selectedKeys="transferSelectedKeys"
+          :targetKeys="transferTargetKeys"
+          :titles="['当前项', '已选项']"
+          :listStyle="{
+            height: '600px',
+            width: '40%',
+          }"
+          showSearch
+          @change="handleTransferChange"
+          @scroll="handleTransferScroll"
+          @selectChange="handleTransferSelectChange"
+
+        />
+
+        <div
+          :style="{
+            position: 'absolute',
+            left: 0,
+            bottom: 0,
+            width: '100%',
+            borderTop: '1px solid #e9e9e9',
+            padding: '0.8rem 1rem',
+            background: '#fff',
+
+          }"
+        >
+          <a-button @click="handleBatchUpdateSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
+          <a-button @click="onBatchBindAttrActionClose">取消</a-button>
+
+        </div>
+      </a-form>
+
+    </a-drawer>
+  </div>
+</template>
+
+<script>
+import {
+  createCITypeAttributes,
+  deleteCITypeAttributesById,
+  getCITypeAttributesByName,
+  searchAttributes
+} from '@/api/cmdb/CITypeAttr'
+import { STable } from '@/components'
+import { mixin, mixinDevice } from '@/utils/mixin'
+import AttributeForm from '@/views/cmdb/attributes/module/attributeForm'
+import { valueTypeMap } from '@/views/cmdb/attributes/module/const'
+
+export default {
+  name: 'AttributesTable',
+  mixins: [mixin, mixinDevice],
+  components: {
+    STable,
+    AttributeForm
+  },
+  data () {
+    return {
+      form: this.$form.createForm(this),
+      scroll: { x: 1300, y: 600 },
+      singleAttrAction: {
+        btnName: '新增属性',
+        drawerTitle: '新增属性',
+        drawerVisible: false
+      },
+      batchBindAttrAction: {
+        btnName: '绑定属性',
+        drawerTitle: '绑定属性',
+        drawerVisible: false
+      },
+
+      CITypeName: this.$route.params.CITypeName,
+      CITypeId: this.$route.params.CITypeId,
+
+      formLayout: 'vertical',
+
+      attributes: [],
+      allAttributes: [],
+      transferData: [],
+      transferTargetKeys: [],
+      transferSelectedKeys: [],
+      originTargetKeys: [],
+
+      ValueTypeMap: valueTypeMap,
+      pagination: {
+        defaultPageSize: 20
+      },
+      showPagination: false,
+      columnSearchText: {
+        alias: '',
+        name: ''
+      },
+      columns: [
+        {
+          title: '名称',
+          dataIndex: 'alias',
+          sorter: false,
+          width: 200,
+          scopedSlots: {
+            customRender: 'aliasSearchRender',
+            filterDropdown: 'filterDropdown',
+            filterIcon: 'filterIcon'
+          },
+          onFilter: (value, record) => record.alias.toLowerCase().includes(value.toLowerCase()),
+          onFilterDropdownVisibleChange: (visible) => {
+            if (visible) {
+              setTimeout(() => {
+                this.searchInput.focus()
+              }, 0)
+            }
+          }
+        },
+        {
+          title: '英文名',
+          dataIndex: 'name',
+          sorter: false,
+          width: 200,
+          scopedSlots: {
+            customRender: 'nameSearchRender',
+            filterDropdown: 'filterDropdown',
+            filterIcon: 'filterIcon'
+          },
+          onFilter: (value, record) => record.name.toLowerCase().includes(value.toLowerCase()),
+          onFilterDropdownVisibleChange: (visible) => {
+            if (visible) {
+              setTimeout(() => {
+                this.searchInput.focus()
+              }, 0)
+            }
+          }
+        },
+        {
+          title: '类型',
+          dataIndex: 'value_type',
+          sorter: false,
+          width: 100,
+          scopedSlots: { customRender: 'value_type' },
+          customRender: (text) => valueTypeMap[text]
+
+        },
+        {
+          title: '唯一',
+          dataIndex: 'is_unique',
+          width: 50,
+          sorter: false,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '索引',
+          dataIndex: 'is_index',
+          sorter: false,
+          width: 50,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '排序',
+          dataIndex: 'is_sortable',
+          sorter: false,
+          width: 50,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '链接',
+          dataIndex: 'is_link',
+          sorter: false,
+          width: 50,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '密码',
+          dataIndex: 'is_password',
+          sorter: false,
+          width: 50,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '列表',
+          dataIndex: 'is_list',
+          sorter: false,
+          width: 50,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '必须',
+          dataIndex: 'is_required',
+          sorter: false,
+          width: 50,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '默认显示',
+          dataIndex: 'default_show',
+          sorter: false,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '操作',
+          dataIndex: 'action',
+          width: 100,
+          fixed: 'right',
+          scopedSlots: { customRender: 'action' }
+        }
+      ],
+      loadData: parameter => {
+        console.log('loadData.parameter', parameter)
+        return getCITypeAttributesByName(this.CITypeName)
+          .then(res => {
+            this.attributes = res.attributes
+            this.setTransferData()
+
+            return {
+              data: res.attributes
+
+            }
+          })
+      },
+
+      mdl: {},
+      // 高级搜索 展开/关闭
+      advanced: false,
+      // 查询参数
+      queryParam: {},
+      // 表头
+
+      selectedRowKeys: [],
+      selectedRows: [],
+
+      // custom table alert & rowSelection
+      options: {
+        alert: false,
+        rowSelection: null
+      },
+      optionAlertShow: false
+
+    }
+  },
+
+  beforeCreate () {
+  },
+
+  computed: {
+
+    removeTransferKeys () {
+      const { originTargetKeys, transferTargetKeys } = this
+      return originTargetKeys.filter(v => !originTargetKeys.includes(v) || !transferTargetKeys.includes(v))
+    },
+
+    addTransferKeys () {
+      const { originTargetKeys, transferTargetKeys } = this
+      return transferTargetKeys.filter(v => !transferTargetKeys.includes(v) || !originTargetKeys.includes(v))
+    },
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 5 },
+        wrapperCol: { span: 12 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+  },
+  mounted () {
+    this.getAttributes()
+    this.setScrollY()
+  },
+  methods: {
+    handleSearch (selectedKeys, confirm, column) {
+      confirm()
+      this.columnSearchText[column.dataIndex] = selectedKeys[0]
+    },
+
+    handleReset (clearFilters, column) {
+      clearFilters()
+      this.columnSearchText[column.dataIndex] = ''
+    },
+
+    getAttributes () {
+      searchAttributes().then(res => {
+        this.allAttributes = res.attributes
+      })
+    },
+    setScrollY () {
+      this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 250
+    },
+
+    setTransferData () {
+      const data = []
+      const target = []
+
+      this.attributes.forEach(i => target.push(i.id.toString()))
+
+      this.allAttributes.forEach(i => data.push({
+        key: i.id.toString(),
+        title: i.alias,
+        description: ''
+      }))
+
+      this.transferData = data
+      this.transferTargetKeys = target
+      this.originTargetKeys = target
+    },
+
+    handleTransferChange (nextTargetKeys, direction, moveKeys) {
+      this.transferTargetKeys = nextTargetKeys
+
+      console.log('targetKeys: ', nextTargetKeys)
+      console.log('direction: ', direction)
+      console.log('moveKeys: ', moveKeys)
+      console.log('addTransferKeys: ', this.addTransferKeys)
+      console.log('removeTransferKeys: ', this.removeTransferKeys)
+    },
+
+    handleTransferSelectChange (sourceSelectedKeys, targetSelectedKeys) {
+      this.transferSelectedKeys = [...sourceSelectedKeys, ...targetSelectedKeys]
+      console.log('sourceSelectedKeys: ', sourceSelectedKeys)
+      console.log('targetSelectedKeys: ', targetSelectedKeys)
+    },
+    handleTransferScroll (direction, e) {
+      console.log('direction:', direction)
+      console.log('target:', e.target)
+    },
+
+    callback (key) {
+      console.log(key)
+    },
+
+    handleEdit (record) {
+      this.$refs.attributeForm.handleEdit(record)
+    },
+    handleDelete (record) {
+      this.unbindAttribute([record.id])
+        .then(res => {
+          this.$message.success(`删除成功`)
+          this.handleOk()
+        }).catch(err => this.requestFailed(err))
+    },
+    handleOk () {
+      this.$refs.table.refresh()
+    },
+    handleCreate () {
+      this.$refs.attributeForm.handleCreate()
+    },
+
+    handleUpdate () {
+      this.setTransferData()
+      this.batchBindAttrAction.drawerVisible = true
+    },
+
+    onBatchBindAttrActionClose () {
+      this.batchBindAttrAction.drawerVisible = false
+    },
+
+    onChange (e) {
+      console.log(`checked = ${e}`)
+    },
+
+    handleBatchUpdateSubmit (e) {
+      e.preventDefault()
+      const p = []
+      if (this.addTransferKeys && this.addTransferKeys.length) {
+        p.push(this.bindAttribute(this.addTransferKeys))
+      }
+
+      if (this.removeTransferKeys && this.removeTransferKeys.length) {
+        p.push(this.unbindAttribute(this.removeTransferKeys))
+      }
+      const that = this
+      Promise.all(p).then(function (values) {
+        console.log(values)
+        that.$message.success(`修改成功`)
+        that.handleOk()
+        that.onBatchBindAttrActionClose()
+      }).catch(err => that.requestFailed(err))
+    },
+
+    bindAttribute (attrIds) {
+      return createCITypeAttributes(this.CITypeId, { attr_id: attrIds })
+    },
+    unbindAttribute (attrIds) {
+      return deleteCITypeAttributesById(this.CITypeId, { attr_id: attrIds })
+    },
+
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+  props: {
+  },
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+  .ant-transfer {
+    margin-bottom: 1rem;
+  }
+
+  .fixedWidthTable table {
+    table-layout: fixed;
+  }
+
+  .fixedWidthTable .ant-table-tbody > tr > td {
+    word-wrap: break-word;
+    word-break: break-all;
+  }
+  .custom-filter-dropdown {
+    padding: 8px;
+    border-radius: 4px;
+    background: #fff;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
+  }
+
+  .highlight {
+    background-color: rgb(255, 192, 105);
+    padding: 0px;
+  }
+
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/ci_type/checkTable.vue b/ui/src/views/ci_type/checkTable.vue
new file mode 100644
index 0000000..cf201b9
--- /dev/null
+++ b/ui/src/views/ci_type/checkTable.vue
@@ -0,0 +1,349 @@
+<template>
+  <div>
+    <a-button class="action-btn" @click="handleCreate" type="primary">批量修改</a-button>
+    <s-table
+      v-once
+      :alert="options.alert"
+      :columns="columns"
+      :data="loadData"
+      :pagination="pagination"
+      :rowKey="record=>record.id"
+      :rowSelection="options.rowSelection"
+      :showPagination="showPagination"
+      ref="table"
+      size="middle"
+      :scroll="scroll"
+    >
+
+      <span slot="is_check" slot-scope="text">
+        <a-icon type="check" v-if="text"/>
+      </span>
+
+      <span slot="action" slot-scope="text, record">
+        <template>
+          <a @click="handleDelete(record)">删除</a>
+        </template>
+      </span>
+
+    </s-table>
+    <a-drawer
+      :closable="false"
+      :title="drawerTitle"
+      :visible="visible"
+      @close="onClose"
+      placement="right"
+      width="30%"
+    >
+      <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
+
+        <a-transfer
+          :dataSource="transferData"
+          :render="item=>item.title"
+          :selectedKeys="transferSelectedKeys"
+          :targetKeys="transferTargetKeys"
+          :titles="['当前项', '已选项']"
+          :listStyle="{
+            height: '600px',
+            width: '40%',
+
+          }"
+          showSearch
+          @change="handleTransferChange"
+          @scroll="handleTransferScroll"
+          @selectChange="handleTransferSelectChange"
+        />
+
+        <div
+          :style="{
+            position: 'absolute',
+            left: 0,
+            bottom: 0,
+            width: '100%',
+            borderTop: '1px solid #e9e9e9',
+            padding: '0.8rem 1rem',
+            background: '#fff',
+
+          }"
+        >
+          <a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
+          <a-button @click="onClose">取消</a-button>
+
+        </div>
+      </a-form>
+    </a-drawer>
+
+  </div>
+
+</template>
+
+<script>
+
+import { STable } from '@/components'
+import { getCITypeAttributesByName, updateCITypeAttributesById } from '@/api/cmdb/CITypeAttr'
+
+export default {
+  name: 'CheckTable',
+  components: {
+    STable
+  },
+  data () {
+    return {
+      CITypeId: this.$route.params.CITypeId,
+      CITypeName: this.$route.params.CITypeName,
+
+      form: this.$form.createForm(this),
+      scroll: { x: 900, y: 600 },
+
+      visible: false,
+
+      drawerTitle: '',
+
+      formLayout: 'vertical',
+
+      transferData: [],
+      transferTargetKeys: [],
+      transferSelectedKeys: [],
+      originTargetKeys: [],
+
+      attributes: [],
+
+      pagination: {
+        defaultPageSize: 20
+      },
+      showPagination: false,
+      columns: [
+        {
+          title: '属性名',
+          dataIndex: 'alias',
+          sorter: false,
+          width: 200,
+          scopedSlots: { customRender: 'alias' }
+        },
+        {
+          title: '属性英文名',
+          dataIndex: 'name',
+          sorter: false,
+          width: 200,
+          scopedSlots: { customRender: 'name' }
+        },
+        {
+          title: '必须',
+          dataIndex: 'is_required',
+          sorter: false,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+
+        {
+          title: '操作',
+          dataIndex: 'action',
+          width: 100,
+          fixed: 'right',
+          scopedSlots: { customRender: 'action' }
+        }
+      ],
+      loadData: parameter => {
+        console.log('loadData.parameter', parameter)
+        return getCITypeAttributesByName(this.CITypeName)
+          .then(res => {
+            this.attributes = res.attributes
+            this.setTransferData()
+
+            return {
+              data: this.attributes.filter(o => o.is_required)
+            }
+          }).catch(err => this.requestFailed(err))
+      },
+
+      mdl: {},
+      // 高级搜索 展开/关闭
+      advanced: false,
+      // 查询参数
+      queryParam: {},
+
+      // custom table alert & rowSelection
+      options: {
+        alert: false,
+        rowSelection: null
+      },
+      optionAlertShow: false
+
+    }
+  },
+  beforeCreate () {
+  },
+  computed: {
+
+    removeTransferKeys () {
+      const { originTargetKeys, transferTargetKeys } = this
+      return originTargetKeys.filter(v => !originTargetKeys.includes(v) || !transferTargetKeys.includes(v))
+    },
+
+    addTransferKeys () {
+      const { originTargetKeys, transferTargetKeys } = this
+      return transferTargetKeys.filter(v => !transferTargetKeys.includes(v) || !originTargetKeys.includes(v))
+    },
+
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+  },
+  mounted () {
+
+  },
+  methods: {
+
+    setTransferData () {
+      const data = []
+      const target = []
+      this.attributes.forEach(
+        function (i) {
+          data.push({
+            key: i.id.toString(),
+            title: i.alias,
+            description: ''
+          })
+          if (i.is_required) {
+            target.push(i.id.toString())
+          }
+        }
+      )
+      this.transferData = data
+      this.transferTargetKeys = target
+      this.originTargetKeys = target
+    },
+    setScrollY () {
+      this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 200
+    },
+
+    handleTransferChange (nextTargetKeys, direction, moveKeys) {
+      this.transferTargetKeys = nextTargetKeys
+
+      console.log('targetKeys: ', nextTargetKeys)
+      console.log('direction: ', direction)
+      console.log('moveKeys: ', moveKeys)
+      console.log('addTransferKeys: ', this.addTransferKeys)
+      console.log('removeTransferKeys: ', this.removeTransferKeys)
+    },
+
+    handleTransferSelectChange (sourceSelectedKeys, targetSelectedKeys) {
+      this.transferSelectedKeys = [...sourceSelectedKeys, ...targetSelectedKeys]
+
+      console.log('sourceSelectedKeys: ', sourceSelectedKeys)
+      console.log('targetSelectedKeys: ', targetSelectedKeys)
+    },
+    handleTransferScroll (direction, e) {
+      console.log('direction:', direction)
+      console.log('target:', e.target)
+    },
+
+    handleDelete (record) {
+      console.log(record)
+
+      updateCITypeAttributesById(this.CITypeId, { attributes: [{ attr_id: record.id, is_required: false }] })
+        .then(res => {
+          this.$message.success(`删除成功`)
+          this.handleOk()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+    handleOk () {
+      this.$refs.table.refresh()
+    },
+
+    handleCreate () {
+      this.drawerTitle = '批量修改'
+      this.visible = true
+    },
+
+    onClose () {
+      this.form.resetFields()
+      this.visible = false
+    },
+
+    onChange (e) {
+      console.log(`checked = ${e.target.checked}`)
+    },
+
+    handleSubmit (e) {
+      e.preventDefault()
+      if (this.addTransferKeys || this.removeTransferKeys) {
+        const requestData = []
+        this.addTransferKeys.forEach(function (k) {
+          const data = { attr_id: k, is_required: true }
+          requestData.push(data)
+        })
+
+        this.removeTransferKeys.forEach(function (k) {
+          const data = { attr_id: k, is_required: false }
+          requestData.push(data)
+        })
+
+        const CITypeId = this.CITypeId
+
+        updateCITypeAttributesById(CITypeId, { attributes: requestData }).then(
+          res => {
+            this.$message.success(`更新成功`)
+            this.visible = false
+            this.handleOk()
+          }
+        ).catch(err => {
+          this.requestFailed(err)
+        })
+      }
+    },
+
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+  .ant-transfer {
+    margin-bottom: 1rem;
+  }
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/ci_type/defaultShowTable.vue b/ui/src/views/ci_type/defaultShowTable.vue
new file mode 100644
index 0000000..ac24abf
--- /dev/null
+++ b/ui/src/views/ci_type/defaultShowTable.vue
@@ -0,0 +1,355 @@
+<template>
+  <div>
+    <a-button class="action-btn" @click="handleCreate" type="primary">批量修改</a-button>
+    <s-table
+      :alert="options.alert"
+      :columns="columns"
+      :data="loadData"
+      :pagination="pagination"
+      :rowKey="record=>record.id"
+      :rowSelection="options.rowSelection"
+      :showPagination="showPagination"
+      ref="table"
+      size="middle"
+      :scroll="scroll"
+
+    >
+      <span slot="is_check" slot-scope="text">
+        <a-icon type="check" v-if="text"/>
+      </span>
+
+      <span slot="action" slot-scope="text, record">
+        <template>
+          <a @click="handleDelete(record)">删除</a>
+        </template>
+      </span>
+
+    </s-table>
+    <a-drawer
+      :closable="false"
+      :title="drawerTitle"
+      :visible="visible"
+      @close="onClose"
+      placement="right"
+      width="30%"
+    >
+      <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
+
+        <a-transfer
+          :dataSource="transferData"
+          :render="item=>item.title"
+          :selectedKeys="transferSelectedKeys"
+          :targetKeys="transferTargetKeys"
+          :titles="['当前项', '已选项']"
+          :listStyle="{
+            height: '600px',
+            width: '42%'
+
+          }"
+          showSearch
+          @change="handleTransferChange"
+          @scroll="handleTransferScroll"
+          @selectChange="handleTransferSelectChange"
+        />
+
+        <div
+          :style="{
+            position: 'absolute',
+            left: 0,
+            bottom: 0,
+            width: '100%',
+            borderTop: '1px solid #e9e9e9',
+            padding: '0.8rem 1rem',
+            background: '#fff',
+
+          }"
+        >
+          <a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
+          <a-button @click="onClose">取消</a-button>
+
+        </div>
+      </a-form>
+    </a-drawer>
+
+  </div>
+
+</template>
+
+<script>
+
+import { STable } from '@/components'
+import { getCITypeAttributesByName, updateCITypeAttributesById } from '@/api/cmdb/CITypeAttr'
+
+export default {
+  name: 'DefaultShowTable',
+  components: {
+    STable
+  },
+  data () {
+    return {
+
+      CITypeId: this.$route.params.CITypeId,
+      CITypeName: this.$route.params.CITypeName,
+
+      form: this.$form.createForm(this),
+      scroll: { x: 1000, y: 600 },
+
+      visible: false,
+
+      drawerTitle: '',
+
+      formLayout: 'vertical',
+
+      transferData: [],
+      transferTargetKeys: [],
+      transferSelectedKeys: [],
+      originTargetKeys: [],
+
+      attributes: [],
+
+      pagination: {
+        defaultPageSize: 20
+      },
+      showPagination: false,
+      columns: [
+        {
+          title: '属性名',
+          dataIndex: 'alias',
+          sorter: false,
+          width: 200,
+          scopedSlots: { customRender: 'alias' }
+        },
+        {
+          title: '属性英文名',
+          dataIndex: 'name',
+          sorter: false,
+          width: 200,
+          scopedSlots: { customRender: 'name' }
+        },
+        {
+          title: '默认显示',
+          dataIndex: 'default_show',
+          sorter: false,
+          scopedSlots: { customRender: 'is_check' }
+
+        },
+        {
+          title: '操作',
+          dataIndex: 'action',
+          width: 100,
+          fixed: 'right',
+          scopedSlots: { customRender: 'action' }
+        }
+      ],
+      loadData: parameter => {
+        console.log('loadData.parameter', parameter)
+        return getCITypeAttributesByName(this.CITypeName)
+          .then(res => {
+            this.attributes = res.attributes
+            this.setTransferData()
+
+            return {
+              data: this.attributes.filter(o => o.default_show)
+            }
+          })
+      },
+
+      mdl: {},
+      // 高级搜索 展开/关闭
+      advanced: false,
+      // 查询参数
+      queryParam: {},
+      // 表头
+
+      selectedRowKeys: [],
+      selectedRows: [],
+
+      // custom table alert & rowSelection
+      options: {
+        alert: false,
+        rowSelection: null
+      },
+      optionAlertShow: false
+
+    }
+  },
+  beforeCreate () {
+  },
+  computed: {
+
+    removeTransferKeys () {
+      const { originTargetKeys, transferTargetKeys } = this
+      return originTargetKeys.filter(v => !originTargetKeys.includes(v) || !transferTargetKeys.includes(v))
+    },
+
+    addTransferKeys () {
+      const { originTargetKeys, transferTargetKeys } = this
+      return transferTargetKeys.filter(v => !transferTargetKeys.includes(v) || !originTargetKeys.includes(v))
+    },
+
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+  },
+  mounted () {
+
+  },
+  methods: {
+
+    setTransferData () {
+      const data = []
+      const target = []
+      this.attributes.forEach(
+        function (i) {
+          data.push({
+            key: i.id.toString(),
+            title: i.alias,
+            description: ''
+          })
+          if (i.default_show) {
+            target.push(i.id.toString())
+          }
+        }
+      )
+      this.transferData = data
+      this.transferTargetKeys = target
+      this.originTargetKeys = target
+    },
+    setScrollY () {
+      this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 250
+    },
+
+    handleTransferChange (nextTargetKeys, direction, moveKeys) {
+      this.transferTargetKeys = nextTargetKeys
+
+      console.log('targetKeys: ', nextTargetKeys)
+      console.log('direction: ', direction)
+      console.log('moveKeys: ', moveKeys)
+      console.log('addTransferKeys: ', this.addTransferKeys)
+      console.log('removeTransferKeys: ', this.removeTransferKeys)
+    },
+
+    handleTransferSelectChange (sourceSelectedKeys, targetSelectedKeys) {
+      this.transferSelectedKeys = [...sourceSelectedKeys, ...targetSelectedKeys]
+      console.log('sourceSelectedKeys: ', sourceSelectedKeys)
+      console.log('targetSelectedKeys: ', targetSelectedKeys)
+    },
+    handleTransferScroll (direction, e) {
+      console.log('direction:', direction)
+      console.log('target:', e.target)
+    },
+
+    handleDelete (record) {
+      console.log(record)
+
+      updateCITypeAttributesById(this.CITypeId, { attributes: [{ attr_id: record.id, default_show: false }] })
+        .then(res => {
+          this.$message.success(`删除成功`)
+          this.handleOk()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+    handleOk () {
+      this.$refs.table.refresh()
+    },
+    onSelectChange (selectedRowKeys, selectedRows) {
+      this.selectedRowKeys = selectedRowKeys
+      this.selectedRows = selectedRows
+    },
+
+    handleCreate () {
+      this.drawerTitle = '批量修改'
+      this.visible = true
+    },
+
+    onClose () {
+      this.form.resetFields()
+      this.visible = false
+    },
+
+    onChange (e) {
+      console.log(`checked = ${e.target.checked}`)
+    },
+
+    handleSubmit (e) {
+      e.preventDefault()
+      if (this.addTransferKeys || this.removeTransferKeys) {
+        const requestData = []
+        this.addTransferKeys.forEach(function (k) {
+          const data = { 'attr_id': k, 'default_show': true }
+          requestData.push(data)
+        })
+
+        this.removeTransferKeys.forEach(function (k) {
+          const data = { 'attr_id': k, 'default_show': false }
+          requestData.push(data)
+        })
+
+        const CITypeId = this.CITypeId
+
+        updateCITypeAttributesById(CITypeId, { attributes: requestData }).then(
+          res => {
+            this.$message.success(`更新成功`)
+            this.visible = false
+            this.handleOk()
+          }
+        ).catch(err => {
+          this.requestFailed(err)
+        })
+      }
+    },
+
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+  .ant-transfer {
+    margin-bottom: 1rem;
+  }
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/ci_type/detail.vue b/ui/src/views/ci_type/detail.vue
new file mode 100644
index 0000000..6ba6f21
--- /dev/null
+++ b/ui/src/views/ci_type/detail.vue
@@ -0,0 +1,89 @@
+<template>
+  <a-card :bordered="false">
+
+    <a-tabs defaultActiveKey="1">
+      <a-tab-pane key="1" tab="模型属性">
+
+        <AttributesTable></AttributesTable>
+
+      </a-tab-pane>
+      <a-tab-pane forceRender key="2" tab="模型关联">
+        <RelationTable :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
+
+      </a-tab-pane>
+
+      <a-tab-pane forceRender key="3" tab="必须校验">
+        <CheckTable></CheckTable>
+
+      </a-tab-pane>
+
+      <a-tab-pane forceRender key="4" tab="默认显示属性">
+        <DefaultShowTable></DefaultShowTable>
+
+      </a-tab-pane>
+
+      <a-tab-pane forceRender key="5" tab="属性分组 & 排序">
+        <Group></Group>
+
+      </a-tab-pane>
+    </a-tabs>
+
+  </a-card>
+</template>
+
+<script>
+import { STable } from '@/components'
+import AttributesTable from './attributesTable'
+import RelationTable from './relationTable'
+import CheckTable from './checkTable'
+import DefaultShowTable from './defaultShowTable'
+import Group from './group'
+
+export default {
+  name: 'CITypeDetail',
+  components: {
+    STable,
+    AttributesTable,
+    RelationTable,
+    CheckTable,
+    DefaultShowTable,
+    Group
+  },
+  data () {
+    return {
+      CITypeId: this.$route.params.CITypeId,
+      CITypeName: this.$route.params.CITypeName
+    }
+  },
+  beforeCreate () {
+  },
+  mounted () {
+
+  },
+  methods: {
+  },
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/ci_type/group.vue b/ui/src/views/ci_type/group.vue
new file mode 100644
index 0000000..24bf748
--- /dev/null
+++ b/ui/src/views/ci_type/group.vue
@@ -0,0 +1,591 @@
+<template>
+  <div>
+    <div style="margin-bottom: 2rem">
+      <a-button type="primary" v-if="addGroupBtnVisible" @click="handleAddGroup">添加分组</a-button>
+
+      <template v-else>
+        <span>
+          <a-input
+            size="small"
+            type="text"
+            style="width: 10rem;margin-right: 0.5rem"
+            ref="addGroupInput"
+            v-model.trim="newGroupName" />
+          <a @click="handleCreateGroup" style="margin-right: 0.5rem">保存</a>
+          <a @click="handleCancelCreateGroup">取消</a>
+        </span>
+      </template>
+
+    </div>
+
+    <div :key="index" v-for="(CITypeGroup, index) in CITypeGroups">
+      <div class="group-header">
+
+        <template style="margin-bottom: 2rem;" v-if="!CITypeGroup.editable">
+
+          <span style="margin-right: 0.2rem">{{ CITypeGroup.name }}</span>
+          <span style="color: #c3cdd7; margin-right: 0.5rem">({{ CITypeGroup.attributes.length }})</span>
+
+          <a-button type="link" size="small" @click="handleEditGroupName(index, CITypeGroup)"><a-icon type="edit" /></a-button>
+        </template>
+        <template v-else>
+          <span style="font-size: 1rem">
+            <a-input
+              size="small"
+              type="text"
+              style="width: 15%;margin-right: 0.5rem"
+              ref="editGroupInput"
+              v-model.trim="CITypeGroup.name" />
+            <a @click="handleSaveGroupName(index, CITypeGroup)" style="margin-right: 0.5rem">保存</a>
+            <a @click="handleCancelGroupName(index, CITypeGroup)">取消</a>
+          </span>
+        </template>
+
+        <div style="float: right">
+
+          <a-button-group size="small">
+            <a-tooltip>
+              <template slot="title">
+                上移
+              </template>
+              <a-button icon="arrow-up" size="small" @click="handleMoveGroup(index, index-1)" :disabled="index===0"/>
+            </a-tooltip>
+
+            <a-tooltip>
+              <template slot="title">
+                下移
+              </template>
+              <a-button icon="arrow-down" size="small" @click="handleMoveGroup(index, index+1)" :disabled="index===CITypeGroups.length-1" />
+            </a-tooltip>
+
+            <a-tooltip>
+              <template slot="title">
+                添加属性
+              </template>
+              <a-button icon="plus" size="small" @click="handleAddExistGroupAttr(index)"/>
+            </a-tooltip>
+            <a-tooltip>
+              <template slot="title">
+                删除分组
+              </template>
+              <a-button icon="delete" size="small" @click="handleDeleteGroup(CITypeGroup.id)" :disabled="CITypeGroup.attributes.length!==0" />
+
+            </a-tooltip>
+
+          </a-button-group>
+        </div>
+
+      </div>
+
+      <div
+        class="box"
+        style="min-height: 2rem; margin-bottom: 1.5rem;"
+      >
+
+        <draggable
+          v-model="CITypeGroup.attributes"
+          group="properties"
+          @start="drag=true"
+          @end="handleEnd"
+          @change="handleChange"
+          :filter="'.filter-empty'"
+          :animation="100"
+          tag="div"
+          style="width: 100%; display: flex;flex-flow: wrap"
+        >
+
+          <li
+            class="property-item"
+            v-for="item in CITypeGroup.attributes"
+            :key="item.id"
+          >
+            {{ item.alias }}
+          </li>
+
+          <template
+            v-if="!CITypeGroup.attributes.length"
+            style="height: 2rem"
+          >
+            <li
+              class="property-item-empty"
+              @click="handleAddExistGroupAttr(index)"
+              style="">添加属性</li>
+
+          </template>
+
+        </draggable>
+
+      </div>
+
+    </div>
+    <div class="group-header">
+
+      <template>
+
+        <span style="margin-right: 0.2rem">更多属性</span>
+        <span style="color: #c3cdd7; margin-right: 0.5rem">({{ otherGroupAttributes.length }})</span>
+      </template>
+      <div style="float: right">
+        <a-button-group size="small">
+          <a-tooltip>
+            <template slot="title">
+              上移
+            </template>
+            <a-button icon="arrow-up" size="small" disabled/>
+          </a-tooltip>
+
+          <a-tooltip>
+            <template slot="title">
+              下移
+            </template>
+            <a-button icon="arrow-down" size="small" disabled />
+          </a-tooltip>
+
+          <a-tooltip>
+            <template slot="title">
+              添加属性
+            </template>
+            <a-button icon="plus" size="small" @click="handleAddOtherGroupAttr"/>
+          </a-tooltip>
+          <a-tooltip>
+            <template slot="title">
+              删除分组
+            </template>
+            <a-button icon="delete" size="small" disabled />
+
+          </a-tooltip>
+
+        </a-button-group>
+      </div>
+    </div>
+
+    <div class="box">
+      <draggable
+        v-model="otherGroupAttributes"
+        group="properties"
+        @start="drag=true"
+        @end="handleEnd"
+        @change="handleChange"
+        :animation="0"
+        style="min-height: 2rem; width: 100%; display: flex; flex-flow: wrap">
+
+        <li
+          class="property-item"
+          v-for="item in otherGroupAttributes"
+          :key="item.id"
+        >
+          {{ item.alias }}
+        </li>
+
+        <template
+          v-if="!otherGroupAttributes.length"
+          style="display: block"
+        >
+          <li
+            class="property-item-empty"
+            @click="handleAddOtherGroupAttr"
+            style="">添加属性</li>
+
+        </template>
+
+      </draggable>
+    </div>
+    <a-modal
+      title="添加字段"
+      :width="'80%'"
+      v-model="modalVisible"
+      @ok="handleSubmit"
+      @cancel="modalVisible = false"
+
+    >
+      <a-form :form="form" @submit="handleSubmit">
+
+        <a-form-item
+        >
+          <a-checkbox-group
+            v-decorator="['checkedAttributes']"
+            style="width: 90%"
+          >
+
+            <a-row :gutter="{ xs: 8, sm: 16, md: 24}" type="flex" justify="start">
+              <a-col
+                v-for="attribute in attributes"
+                :key="attribute.id"
+                :sm="8"
+                :md="6"
+                :lg="4"
+                :xxl="3"
+                style="line-height: 1.8rem"
+              >
+                <a-checkbox
+                  :value="attribute.id">
+                  {{ attribute.alias }}
+                </a-checkbox>
+
+              </a-col>
+
+            </a-row>
+          </a-checkbox-group>
+        </a-form-item>
+
+        <a-form-item>
+          <a-input
+            name="groupId"
+            type="hidden"
+            v-decorator="['groupId']"
+          />
+        </a-form-item>
+
+        <a-form-item>
+          <a-input
+            name="groupIndex"
+            type="hidden"
+            v-decorator="['groupIndex']"
+          />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+
+  </div >
+
+</template>
+
+<script>
+
+import {
+  deleteCITypeGroupById,
+  getCITypeGroupById,
+  createCITypeGroupById,
+  updateCITypeGroupById
+} from '@/api/cmdb/CIType'
+import { getCITypeAttributesById, updateCITypeAttributesById } from '@/api/cmdb/CITypeAttr'
+import draggable from 'vuedraggable'
+
+export default {
+  name: 'Group',
+  components: {
+    draggable
+  },
+  data () {
+    return {
+      form: this.$form.createForm(this),
+      CITypeId: this.$route.params.CITypeId,
+      CITypeName: this.$route.params.CITypeName,
+      CITypeGroups: [],
+      attributes: [],
+      otherGroupAttributes: [],
+      addGroupBtnVisible: true,
+      newGroupName: '',
+      modalVisible: false
+
+    }
+  },
+  beforeCreate () {
+  },
+  created () {
+
+  },
+  computed: {
+
+  },
+  mounted () {
+    this.getCITypeGroupData()
+  },
+  methods: {
+    setOtherGroupAttributes () {
+      const orderMap = this.attributes.reduce(function (map, obj) {
+        map[obj.id] = obj.order
+        return map
+      }, {})
+
+      const inGroupAttrKeys = this.CITypeGroups
+        .filter(x => x.attributes && x.attributes.length > 0)
+        .map(x => x.attributes).flat().map(x => x.id)
+
+      this.CITypeGroups.forEach(group => {
+        group.attributes.forEach(attribute => {
+          attribute.order = orderMap[attribute.id]
+          attribute.originOrder = attribute.order
+          attribute.originGroupName = group.name
+        })
+        group.originCount = group.attributes.length
+        group.editable = false
+        group.originOrder = group.order
+        group.originName = group.name
+        group.attributes = group.attributes.sort((a, b) => a.order - b.order)
+      })
+
+      this.otherGroupAttributes = this.attributes.filter(x => !inGroupAttrKeys.includes(x.id)).sort((a, b) => a.order - b.order)
+      this.attributes = this.attributes.sort((a, b) => a.order - b.order)
+      this.CITypeGroups = this.CITypeGroups.sort((a, b) => a.order - b.order)
+      this.otherGroupAttributes.forEach(attribute => {
+        attribute.originOrder = attribute.order
+      })
+
+      console.log('setOtherGroupAttributes', this.CITypeGroups, this.otherGroupAttributes)
+    },
+    getCITypeGroupData () {
+      const promises = [
+        getCITypeAttributesById(this.CITypeId),
+        getCITypeGroupById(this.CITypeId)
+      ]
+      Promise.all(promises)
+        .then(values => {
+          this.attributes = values[0].attributes
+          this.CITypeGroups = values[1]
+          this.setOtherGroupAttributes()
+        })
+    },
+
+    handleEditGroupName (index, CITypeGroup) {
+      CITypeGroup.editable = true
+      this.$set(this.CITypeGroups, index, CITypeGroup)
+    },
+    handleSaveGroupName (index, CITypeGroup) {
+      if (CITypeGroup.name === CITypeGroup.originName) {
+        this.handleCancelGroupName(index, CITypeGroup)
+      } else if (this.CITypeGroups.map(x => x.originName).includes(CITypeGroup.name)) {
+        this.$message.error('分组名称已存在')
+      } else {
+        updateCITypeGroupById(CITypeGroup.id, { name: CITypeGroup.name, attributes: CITypeGroup.attributes.map(x => x.id), order: CITypeGroup.order })
+          .then(res => {
+            CITypeGroup.editable = false
+            this.$set(this.CITypeGroups, index, CITypeGroup)
+            this.$message.success('修改成功')
+          })
+          .catch(err => this.requestFailed(err))
+      }
+    },
+    handleCancelGroupName (index, CITypeGroup) {
+      CITypeGroup.editable = false
+      this.$set(this.CITypeGroups, index, CITypeGroup)
+    },
+
+    handleCancel (CITypeGroup) {
+      CITypeGroup.editable = false
+    },
+    handleAddGroup () {
+      this.addGroupBtnVisible = false
+    },
+    handleCreateGroup () {
+      const groupOrders = this.CITypeGroups.map(x => x.order)
+
+      const maxGroupOrder = Math.max(groupOrders.length, groupOrders.length ? Math.max(...groupOrders) : 0)
+
+      console.log('groupOrder', groupOrders, 'maxOrder', maxGroupOrder)
+      createCITypeGroupById(this.CITypeId, { name: this.newGroupName, order: maxGroupOrder + 1 })
+        .then(res => {
+          this.addGroupBtnVisible = true
+          this.newGroupName = ''
+          this.getCITypeGroupData()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+    handleCancelCreateGroup () {
+      this.addGroupBtnVisible = true
+      this.newGroupName = ''
+    },
+
+    handleMoveGroup (beforeIndex, afterIndex) {
+      const beforeGroup = this.CITypeGroups[beforeIndex]
+      this.CITypeGroups[beforeIndex] = this.CITypeGroups[afterIndex]
+
+      this.$set(this.CITypeGroups, beforeIndex, this.CITypeGroups[afterIndex])
+      this.$set(this.CITypeGroups, afterIndex, beforeGroup)
+
+      this.updatePropertyIndex()
+    },
+    handleAddExistGroupAttr (index) {
+      const group = this.CITypeGroups[index]
+      this.modalVisible = true
+      this.$nextTick(() => {
+        this.form.setFieldsValue({
+          checkedAttributes: group.attributes.map(x => x.id),
+          groupId: group.id,
+          groupIndex: index
+
+        })
+      })
+    },
+
+    handleAddOtherGroupAttr () {
+      this.modalVisible = true
+      this.$nextTick(() => {
+        this.form.setFieldsValue({
+          checkedAttributes: this.otherGroupAttributes.map(x => x.id),
+          groupId: null
+
+        })
+      })
+    },
+
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          // eslint-disable-next-line no-console
+          console.log('Received values of form: ', values)
+
+          this.CITypeGroups.forEach(group => {
+            if (group.id === values.groupId) {
+              group.attributes = this.attributes.filter(x => values.checkedAttributes.includes(x.id))
+            } else {
+              group.attributes = group.attributes.filter(x => !values.checkedAttributes.includes(x.id))
+            }
+          })
+          // this.CITypeGroups = this.CITypeGroups
+
+          this.otherGroupAttributes.forEach(attributes => {
+            if (values.groupId === null) {
+              this.otherGroupAttributes = this.otherGroupAttributes.filter(x => values.checkedAttributes.includes(x.id))
+            } else {
+              this.otherGroupAttributes = this.otherGroupAttributes.filter(x => !values.checkedAttributes.includes(x.id))
+            }
+          })
+
+          console.log('add group attribute', this.otherGroupAttributes, this.CITypeGroups)
+          this.updatePropertyIndex()
+        }
+      })
+    },
+
+    handleDeleteGroup (groupId) {
+      deleteCITypeGroupById(groupId)
+        .then(res => {
+          this.updatePropertyIndex()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+    handleChange (e) {
+      console.log(e)
+      if (e.hasOwnProperty('moved')) {
+        this.shouldUpdatePropertyIndex = e.moved.newIndex !== e.moved.oldIndex
+      } else {
+        this.shouldUpdatePropertyIndex = true
+      }
+    },
+    handleEnd (e) {
+      if (this.shouldUpdatePropertyIndex) {
+        this.updatePropertyIndex()
+        this.shouldUpdatePropertyIndex = false
+      }
+    },
+    updatePropertyIndex () {
+      const attributes = []
+      let attributeOrder = 0
+      let groupOrder = 0
+      const promises = [
+
+      ]
+
+      this.CITypeGroups.forEach(group => {
+        const groupName = group.name
+
+        let groupAttributes = []
+        let groupUpdate = false
+        group.order = groupOrder
+
+        group.attributes.forEach(attribute => {
+          groupAttributes.push(attribute.id)
+
+          if (attribute.originGroupName !== group.name || attribute.originOrder !== attributeOrder) {
+            attributes.push({ attr_id: attribute.id, order: attributeOrder })
+            groupUpdate = true
+          }
+          attributeOrder++
+        })
+
+        groupAttributes = new Set(groupAttributes)
+        if (group.originCount !== groupAttributes.size || groupUpdate || group.originOrder !== group.order) {
+          promises.push(updateCITypeGroupById(group.id, { name: groupName, attributes: [...groupAttributes], order: groupOrder }))
+        }
+        groupOrder++
+      })
+
+      this.otherGroupAttributes.forEach(attribute => {
+        if (attribute.originOrder !== attributeOrder) {
+          console.log('this attribute:', attribute.name, 'old order', attribute.originOrder, 'new order', attributeOrder)
+          attributes.push({ attr_id: attribute.id, order: attributeOrder })
+        }
+
+        attributeOrder++
+      })
+
+      if (attributes && attributes.length > 0) {
+        promises.unshift(updateCITypeAttributesById(this.CITypeId, { attributes: attributes }))
+      }
+
+      const that = this
+      Promise.all(promises)
+        .then(values => {
+          that.$message.success(`修改成功`)
+          that.getCITypeGroupData()
+          that.modalVisible = false
+        })
+        .catch(err => that.requestFailed(err))
+    },
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+  },
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  .group-header {
+    font-size: 1.15rem;
+  }
+
+  .property-item {
+    width: calc(20% - 2rem);
+    margin:0.5rem 0.8rem;
+    border:1px solid #d9d9d9;
+    border-radius: 5px;
+    cursor: pointer;overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    text-align: center;
+    height: 2.5rem;
+    line-height: 2.5rem;
+  }
+
+  .property-item:hover{
+    border:1px dashed #40a9ff;
+  }
+
+  .property-item-empty {
+    width: calc(100% - 10px);
+    margin:0.5rem 0.8rem;
+    border:1px dashed #d9d9d9;
+    cursor: pointer;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    text-align: center;
+    height: 3.5rem;
+    line-height: 3.5rem;
+    color: #40a9ff;
+  }
+
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/ci_type/list.vue b/ui/src/views/ci_type/list.vue
new file mode 100644
index 0000000..5e6b225
--- /dev/null
+++ b/ui/src/views/ci_type/list.vue
@@ -0,0 +1,291 @@
+<template>
+  <a-card :bordered="false">
+
+    <a-list
+      :dataSource="CITypes"
+      :grid="{ gutter: 20, column: 4 }"
+      class="ci-type-list"
+      itemLayout="horizontal"
+      size="small"
+    >
+      <a-list-item slot="renderItem" slot-scope="item">
+
+        <template v-if="item === null">
+          <a-button class="new-btn" type="dashed" @click="handleCreate">
+            <a-icon type="plus"/>
+            新增
+          </a-button>
+        </template>
+        <template v-else>
+          <a-card
+            :bodyStyle="{padding: '0.9rem 0.8rem'}"
+            :hoverable="true"
+          >
+            <template class="ant-card-actions" slot="actions">
+              <router-link
+                :to="{ name: 'ci_type_detail', params: { CITypeName: item.name, CITypeId: item.id }}"
+              >
+                <a-icon type="setting" />
+              </router-link>
+              <a-icon type="edit" @click="handleEdit(item)"/>
+            </template>
+            <a-card-meta>
+              <div slot="title" style="margin-bottom: 3px">{{ item.alias || item.name }}</div>
+              <a-avatar
+                :src="item.icon_url"
+                class="card-avatar"
+                size="large"
+                slot="avatar"
+                style="color:  #7f9eb2; backgroundColor: #e1eef6; padding-left: -1rem;"
+              >
+                {{ item.name[0].toUpperCase() }}
+              </a-avatar>
+              <div class="meta-content" slot="description">{{ item.name }}</div>
+            </a-card-meta>
+
+          </a-card>
+        </template>
+
+      </a-list-item>
+
+    </a-list>
+
+    <a-drawer
+      :closable="false"
+      :title="drawerTitle"
+      :visible="drawerVisible"
+      @close="onClose"
+      placement="right"
+      width="30%"
+    >
+      <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
+
+        <a-form-item
+          :label-col="formItemLayout.labelCol"
+          :wrapper-col="formItemLayout.wrapperCol"
+          label="模型名(英文)"
+        >
+          <a-input
+            name="name"
+            placeholder="英文"
+            v-decorator="['name', {rules: [{ required: true, message: '请输入属性名'},{message: '不能以数字开头,可以是英文 数字以及下划线 (_)', pattern: RegExp('^(?!\\d)[a-zA-Z_0-9]+$')}]} ]"
+          />
+        </a-form-item>
+        <a-form-item
+          :label-col="formItemLayout.labelCol"
+          :wrapper-col="formItemLayout.wrapperCol"
+          label="别名"
+        >
+          <a-input
+            name="alias"
+            v-decorator="['alias', {rules: []} ]"
+          />
+        </a-form-item>
+
+        <a-form-item
+          :label-col="formItemLayout.labelCol"
+          :wrapper-col="formItemLayout.wrapperCol"
+          label="唯一标识*"
+        >
+
+          <a-select
+            name="unique_key"
+            style="width: 200px"
+            v-decorator="['unique_key', {rules: [{required: true}], } ]"
+          >
+            <a-select-option :key="item.id" :value="item.id" v-for="item in allAttributes">{{ item.alias || item.name }}</a-select-option>
+          </a-select>
+
+        </a-form-item>
+
+        <a-form-item>
+          <a-input
+            name="id"
+            type="hidden"
+            v-decorator="['id', {rules: []} ]"
+          />
+        </a-form-item>
+
+        <div
+          :style="{
+            position: 'absolute',
+            left: 0,
+            bottom: 0,
+            width: '100%',
+            borderTop: '1px solid #e9e9e9',
+            padding: '0.8rem 1rem',
+            background: '#fff',
+
+          }"
+        >
+          <a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
+          <a-button @click="onClose">取消</a-button>
+
+        </div>
+      </a-form>
+    </a-drawer>
+
+  </a-card>
+</template>
+
+<script>
+
+import { getCITypes, createCIType, updateCIType } from '@/api/cmdb/CIType'
+import { searchAttributes } from '@/api/cmdb/CITypeAttr'
+
+export default {
+  name: 'CITypeList',
+  components: {},
+  data () {
+    return {
+      CITypes: [],
+      allAttributes: [],
+      form: this.$form.createForm(this),
+
+      drawerVisible: false,
+
+      drawerTitle: '',
+
+      formLayout: 'vertical'
+    }
+  },
+  computed: {
+
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+  },
+  mounted () {
+    this.getCITypes()
+    this.getAttributes()
+  },
+  methods: {
+    getCITypes () {
+      getCITypes().then(res => {
+        this.CITypes = res.ci_types
+        this.CITypes.unshift(null)
+      })
+    },
+
+    getAttributes () {
+      searchAttributes().then(res => {
+        this.allAttributes = res.attributes
+      })
+    },
+    handleCreate () {
+      this.drawerTitle = '新增模型'
+      this.drawerVisible = true
+    },
+    onClose () {
+      this.form.resetFields()
+      this.drawerVisible = false
+    },
+    handleEdit (record) {
+      this.drawerTitle = '编辑模型'
+      this.drawerVisible = true
+
+      console.log(record)
+      this.$nextTick(() => {
+        this.form.setFieldsValue({
+          id: record.id,
+          alias: record.alias,
+          name: record.name,
+          unique_key: record.unique_id
+
+        })
+      })
+    },
+
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          // eslint-disable-next-line no-console
+          console.log('Received values of form: ', values)
+
+          if (values.id) {
+            this.updateCIType(values.id, values)
+          } else {
+            this.createCIType(values)
+          }
+        }
+      })
+    },
+    createCIType (data) {
+      createCIType(data)
+        .then(res => {
+          this.$message.success(`添加成功`)
+          this.drawerVisible = false
+          this.getCITypes()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+
+    updateCIType (CITypeId, data) {
+      updateCIType(CITypeId, data)
+        .then(res => {
+          this.$message.success(`修改成功`)
+          this.drawerVisible = false
+          this.getCITypes()
+        })
+        .catch(err => this.requestFailed(err))
+    },
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+  props: {},
+  watch: {}
+}
+
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+
+  .new-btn {
+    background-color: #fff;
+    border-radius: 2px;
+    width: 100%;
+    height: 7.5rem;
+  }
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>
diff --git a/ui/src/views/ci_type/relationTable.vue b/ui/src/views/ci_type/relationTable.vue
new file mode 100644
index 0000000..da5835a
--- /dev/null
+++ b/ui/src/views/ci_type/relationTable.vue
@@ -0,0 +1,337 @@
+<template>
+  <div>
+    <a-button class="action-btn" @click="handleCreate" type="primary">新增关系</a-button>
+    <s-table
+      :alert="options.alert"
+      :columns="columns"
+      :data="loadData"
+      :pagination="pagination"
+      :rowKey="record=>record.id"
+      :rowSelection="options.rowSelection"
+      :showPagination="showPagination"
+      ref="table"
+      size="middle"
+      :scroll="scroll"
+    >
+
+      <span slot="is_check" slot-scope="text">
+        <a-icon type="check" v-if="text"/>
+      </span>
+
+      <span slot="action" slot-scope="text, record">
+        <template>
+          <a @click="handleDelete(record)">删除</a>
+        </template>
+      </span>
+
+    </s-table>
+    <a-drawer
+      :closable="false"
+      :title="drawerTitle"
+      :visible="visible"
+      @close="onClose"
+      placement="right"
+      width="30%"
+    >
+      <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
+
+        <a-form-item
+          :label-col="formItemLayout.labelCol"
+          :wrapper-col="formItemLayout.wrapperCol"
+          label="源模型"
+        >
+          <a-select
+            name="source_ci_type_id"
+            style="width: 120px"
+            v-decorator="['source_ci_type_id', {rules: [], } ]"
+          >
+            <a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes" v-if="CITypeId === CIType.id">{{ CIType.alias }}</a-select-option>
+          </a-select>
+
+        </a-form-item>
+        <a-form-item
+          :label-col="formItemLayout.labelCol"
+          :wrapper-col="formItemLayout.wrapperCol"
+          label="目标模型"
+        >
+          <a-select
+            name="ci_type_id"
+            style="width: 120px"
+            v-decorator="['ci_type_id', {rules: [], } ]"
+          >
+            <a-select-option :value="CIType.id" :key="CIType.id" v-for="CIType in CITypes">{{ CIType.alias }}</a-select-option>
+          </a-select>
+        </a-form-item>
+
+        <a-form-item
+          :label-col="formItemLayout.labelCol"
+          :wrapper-col="formItemLayout.wrapperCol"
+          label="关联关系"
+        >
+          <a-select
+            name="relation_type_id"
+            style="width: 120px"
+            v-decorator="['relation_type_id', {rules: [], } ]"
+          >
+            <a-select-option :value="relationType.id" :key="relationType.id" v-for="relationType in relationTypes">{{ relationType.name }}
+            </a-select-option>
+          </a-select>
+
+        </a-form-item>
+
+        <div
+          :style="{
+            position: 'absolute',
+            left: 0,
+            bottom: 0,
+            width: '100%',
+            borderTop: '1px solid #e9e9e9',
+            padding: '0.8rem 1rem',
+            background: '#fff',
+
+          }"
+        >
+          <a-button @click="handleSubmit" type="primary" style="margin-right: 1rem">确定</a-button>
+          <a-button @click="onClose">取消</a-button>
+
+        </div>
+      </a-form>
+    </a-drawer>
+
+  </div>
+
+</template>
+
+<script>
+import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/api/cmdb/CITypeRelation'
+import { getCITypes } from '@/api/cmdb/CIType'
+
+import { STable } from '@/components'
+import PageView from '@/layouts/PageView'
+
+export default {
+  name: 'RelationTable',
+  components: {
+    PageView,
+    STable
+  },
+  data () {
+    return {
+      CITypeId: parseInt(this.$route.params.CITypeId),
+      CITypeName: this.$route.params.CITypeName,
+      form: this.$form.createForm(this),
+      scroll: { x: 1300, y: 600 },
+
+      visible: false,
+
+      drawerTitle: '',
+
+      formLayout: 'vertical',
+
+      CITypes: [],
+      relationTypes: [],
+
+      pagination: {
+        defaultPageSize: 20
+      },
+      showPagination: false,
+      columns: [
+        {
+          title: '源模型英文名',
+          dataIndex: 'source_ci_type_name',
+          sorter: false,
+          width: 200,
+          scopedSlots: { customRender: 'source_ci_type_name' }
+        },
+        {
+          title: '关联类型',
+          dataIndex: 'relation_type',
+          sorter: false,
+          width: 100,
+          scopedSlots: { customRender: 'name' }
+        },
+        {
+          title: '目标模型名',
+          dataIndex: 'alias',
+          sorter: false,
+          scopedSlots: { customRender: 'alias' }
+        },
+        {
+          title: '操作',
+          dataIndex: 'action',
+          width: 100,
+          fixed: 'right',
+          scopedSlots: { customRender: 'action' }
+        }
+      ],
+      loadData: parameter => {
+        console.log('loadData.parameter', parameter)
+        const CITypeId = this.CITypeId
+        const CITypeName = this.CITypeName
+
+        console.log('this CITypeId ', CITypeId, 'type', typeof CITypeName, 'CITypeName', CITypeName)
+
+        return getCITypeChildren(this.CITypeId)
+          .then(res => {
+            let data = res.children
+
+            data = data.map(function (obj, index) {
+              obj.source_ci_type_name = CITypeName
+              obj.source_ci_type_id = CITypeId
+              return obj
+            })
+
+            return {
+              data: data
+            }
+          })
+      },
+
+      mdl: {},
+      // 高级搜索 展开/关闭
+      advanced: false,
+      // 查询参数
+      queryParam: {},
+      // custom table alert & rowSelection
+      options: {
+        alert: false,
+        rowSelection: null
+      },
+      optionAlertShow: false
+
+    }
+  },
+  beforeCreate () {
+  },
+  computed: {
+    formItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      } : {}
+    },
+
+    horizontalFormItemLayout () {
+      return {
+        labelCol: { span: 4 },
+        wrapperCol: { span: 14 }
+      }
+    },
+    buttonItemLayout () {
+      const { formLayout } = this
+      return formLayout === 'horizontal' ? {
+        wrapperCol: { span: 14, offset: 4 }
+      } : {}
+    }
+  },
+  mounted () {
+    this.getCITypes()
+    this.getRelationTypes()
+  },
+  methods: {
+    setScrollY () {
+      this.scroll.y = window.innerHeight - this.$refs.table.$el.offsetTop - 250
+    },
+
+    getCITypes () {
+      getCITypes().then(res => {
+        this.CITypes = res.ci_types
+      })
+    },
+    getRelationTypes () {
+      getRelationTypes().then(res => {
+        this.relationTypes = res
+      })
+    },
+
+    callback (key) {
+      console.log(key)
+    },
+    handleDelete (record) {
+      console.log(record)
+
+      deleteRelation(record.source_ci_type_id, record.id)
+        .then(res => {
+          this.$message.success(`删除成功`)
+
+          this.handleOk()
+        }).catch(err => this.requestFailed(err))
+    },
+    handleOk () {
+      this.$refs.table.refresh()
+    },
+
+    handleCreate () {
+      this.drawerTitle = '新增关系'
+      this.visible = true
+      this.$nextTick(() => {
+        this.form.setFieldsValue({
+          source_ci_type_id: this.CITypeId
+
+        })
+      })
+    },
+
+    onClose () {
+      this.form.resetFields()
+      this.visible = false
+    },
+
+    onChange (e) {
+      console.log(`checked = ${e.target.checked}`)
+    },
+
+    handleSubmit (e) {
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          // eslint-disable-next-line no-console
+          console.log('Received values of form: ', values)
+
+          createRelation(values.source_ci_type_id, values.ci_type_id, values.relation_type_id)
+            .then(res => {
+              this.$message.success(`添加成功`)
+              this.onClose()
+              this.handleOk()
+            }).catch(err => this.requestFailed(err))
+        }
+      })
+    },
+
+    requestFailed (err) {
+      const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
+      this.$message.error(`${msg}`)
+    }
+
+  },
+
+  watch: {}
+
+}
+</script>
+
+<style lang="less" scoped>
+  .search {
+    margin-bottom: 54px;
+  }
+
+  .fold {
+    width: calc(100% - 216px);
+    display: inline-block
+  }
+
+  .operator {
+    margin-bottom: 18px;
+  }
+
+  .action-btn {
+    margin-bottom: 1rem;
+  }
+
+  @media screen and (max-width: 900px) {
+    .fold {
+      width: 100%;
+    }
+  }
+</style>