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>