diff --git a/src/components/DataActions/index.vue b/src/components/DataActions/index.vue index 2071cf468..caa5de075 100644 --- a/src/components/DataActions/index.vue +++ b/src/components/DataActions/index.vue @@ -57,7 +57,7 @@ @click="handleClick(action)" > - + @@ -249,10 +249,11 @@ $color-drop-menu-border: #e4e7ed; align-items: flex-end; .el-button { - display: flex; - align-items: center; - padding: 2px 6px; + padding: 2px 5px; color: $btn-text-color; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; * { vertical-align: baseline !important; diff --git a/src/components/Form/DataForm/index.vue b/src/components/Form/DataForm/index.vue index 7b71c4e29..bc980d1ef 100644 --- a/src/components/Form/DataForm/index.vue +++ b/src/components/Form/DataForm/index.vue @@ -223,6 +223,10 @@ export default { line-height: 30px; color: var(--color-text-primary); + span { + display: unset; + } + i { color: var(--color-icon-primary); } diff --git a/src/components/Table/DataTable/compenents/el-data-table/utils/select-strategy.js b/src/components/Table/DataTable/compenents/el-data-table/utils/select-strategy.js index 6bf7c0e59..cfb6bafaa 100644 --- a/src/components/Table/DataTable/compenents/el-data-table/utils/select-strategy.js +++ b/src/components/Table/DataTable/compenents/el-data-table/utils/select-strategy.js @@ -34,14 +34,12 @@ class StrategyNormal extends StrategyAbstract { onSelectionChange(val) { this.elDataTable.selected = val } - /** * toggleRowSelection和clearSelection的表现与el-table一致 */ toggleRowSelection(...args) { return this.elTable.toggleRowSelection(...args) } - clearSelection() { return this.elTable.clearSelection() } @@ -52,12 +50,12 @@ class StrategyNormal extends StrategyAbstract { */ class StrategyPersistSelection extends StrategyAbstract { /** - * el-table 的 selection-change 事件不适用于开启跨页保存的情况。 - * 比如,当开启 persistSelection时,发生以下两个场景: + * el-table的selection-change事件不适用于开启跨页保存的情况。 + * 比如,当开启persistSelection时,发生以下两个场景: * 1. 用户点击翻页 * 2. 用户点击行首的切换全选项按钮,清空当前页多选项数据 - * 其中场景 1 应该保持 selected 不变;而场景 2 只应该从 selected 移除当前页所有行,保留其他页面的多选状态。 - * 但 el-table 的 selection-change 事件在两个场景中无差别发生,所以这里不处理这个事件 + * 其中场景1应该保持selected不变;而场景2只应该从selected移除当前页所有行,保留其他页面的多选状态。 + * 但el-table的selection-change事件在两个场景中无差别发生,所以这里不处理这个事件 */ /** @@ -65,24 +63,54 @@ class StrategyPersistSelection extends StrategyAbstract { */ onSelect(selection, row) { const isChosen = selection.indexOf(row) > -1 - this.toggleRowSelection(row, isChosen) } /** * 用户切换当前页的多选 */ onSelectAll(selection, selectable = () => true) { - // 获取当前所有已选择的项 - const selectedRows = this.elDataTable.data.filter(r => selection.includes(r)) + const { id, selected, data } = this.elDataTable + const selectableRows = data.filter(selectable) + // const isSelected = !!selection.length - // 判断是否已全选 - const isSelected = this.elDataTable.data.every(r => selectable(r) && selectedRows.includes(r)) + // 创建已选择项的 id 集合,用于快速查找 + const selectedIds = new Set(selected.map(r => r[id])) + const currentPageIds = new Set(selectableRows.map(row => row[id])) - this.elDataTable.data.forEach(r => { - if (selectable(r)) { - this.toggleRowSelection(r, isSelected) - } - }) + // 前页面的选中状态 + const currentPageSelectedCount = selectableRows.filter(row => + selectedIds.has(row[id]) + ).length + + // 判断是全选还是取消全选 + const shouldSelectAll = currentPageSelectedCount < selectableRows.length + + this.elTable.clearSelection() + + if (shouldSelectAll) { + selectableRows.forEach(row => { + if (!selectedIds.has(row[id])) selected.push(row) + + this.elTable.toggleRowSelection(row, true) + + // ! 这里需要触发事件,否则在 el-table 中无法触发 selection-change 事件 + this.elDataTable.$emit('toggle-row-selection', true, row) + }) + } else { + const newSelected = [] + + selected.forEach(row => { + if (!currentPageIds.has(row[id])) { + newSelected.push(row) + } else { + this.elDataTable.$emit('toggle-row-selection', false, row) + } + }) + + this.elDataTable.selected = newSelected + } + + this.elDataTable.$emit('selection-change', this.elDataTable.selected) } /** * toggleRowSelection和clearSelection管理elDataTable的selected数组 @@ -105,29 +133,26 @@ class StrategyPersistSelection extends StrategyAbstract { this.elDataTable.$emit('toggle-row-selection', isSelected, row) this.updateElTableSelection() } - clearSelection() { this.elDataTable.selected = [] this.updateElTableSelection() } - /** * 将selected状态同步到el-table中 */ updateElTableSelection() { const { data, id, selected } = this.elDataTable + const selectedIds = new Set(selected.map(r => r[id])) - // 历史勾选的行已经不在当前页了,所以要将当前页的行数据和selected合并 - const mergeData = _.uniqWith([...data, ...selected], _.isEqual) + this.elTable.clearSelection() - mergeData.forEach(r => { - const isSelected = !!selected.find(r2 => r[id] === r2[id]) + data.forEach(row => { + const shouldBeSelected = selectedIds.has(row[id]) + if (!this.elTable) return - if (!this.elTable) { - return + if (shouldBeSelected) { + this.elTable.toggleRowSelection(row, true) } - - this.elTable.toggleRowSelection(r, isSelected) }) } } diff --git a/src/components/Table/ListTable/TableAction/RightSide.vue b/src/components/Table/ListTable/TableAction/RightSide.vue index d7a555b1f..dc8fa1417 100644 --- a/src/components/Table/ListTable/TableAction/RightSide.vue +++ b/src/components/Table/ListTable/TableAction/RightSide.vue @@ -155,7 +155,13 @@ export default { }) }, iExportOptions() { - return assignIfNot(this.exportOptions, { url: this.tableUrl }) + /** + * 原本是使用 assignIfNot 此函数内部使用 partialRight, 该函数 + * 只在目标对象的属性未定义时才从源对象复制属性,如果目标对象已经有值,则保留原值 + * 那如果首次点击的树节点,那么此时 url 就会被确定,后续点击的树节点,那么 url 就不会 + * 改变了 + */ + return Object.assign({}, this.exportOptions, { url: this.tableUrl }) } }, methods: { diff --git a/src/components/Table/TagSearch/index.vue b/src/components/Table/TagSearch/index.vue index 5e2e59d18..cae8a3930 100644 --- a/src/components/Table/TagSearch/index.vue +++ b/src/components/Table/TagSearch/index.vue @@ -17,7 +17,7 @@ size="small" type="info" @click="handleTagClick(v,k)" - @close="handleTagClose(k)" + @close.stop="handleTagClose(k)" > {{ v.label + ':' }} {{ v.valueLabel }} @@ -128,7 +128,7 @@ export default { deep: true }, filterTags: { - handler() { + handler(newValue) { this.$emit('tag-search', this.filterMaps) }, deep: true @@ -137,6 +137,15 @@ export default { if (newValue === '' && oldValue !== '') { this.emptyCount = 1 } + }, + '$route'(to, from) { + if (from.query !== to.query) { + this.filterTags = {} + if (to.query && Object.keys(to.query).length) { + const routeFilter = this.checkInTableColumns(this.options) + this.filterTagSearch(routeFilter) + } + } } }, mounted() { diff --git a/src/views/assets/Cloud/Strategy/components/ActionInput.vue b/src/views/assets/Cloud/Strategy/components/ActionInput.vue index 632784159..eab9b9ee6 100644 --- a/src/views/assets/Cloud/Strategy/components/ActionInput.vue +++ b/src/views/assets/Cloud/Strategy/components/ActionInput.vue @@ -79,6 +79,9 @@ export default { case 'account_template': url = '/api/v1/accounts/account-templates/' break + case 'label': + url = '/api/v1/labels/labels/' + break case 'name_strategy': options = this.nameOptions break diff --git a/src/views/assets/Cloud/Strategy/components/const.js b/src/views/assets/Cloud/Strategy/components/const.js index 5823a8ef5..9e0d0a4b6 100644 --- a/src/views/assets/Cloud/Strategy/components/const.js +++ b/src/views/assets/Cloud/Strategy/components/const.js @@ -6,6 +6,7 @@ export const resourceTypeOptions = [ { label: i18n.t('Node'), value: 'node' }, { label: i18n.t('Zone'), value: 'domain' }, { label: i18n.t('AccountTemplate'), value: 'account_template' }, + { label: i18n.t('Tags'), value: 'label' }, { label: i18n.t('Strategy'), value: 'name_strategy' } ] diff --git a/src/views/assets/Cloud/const.js b/src/views/assets/Cloud/const.js index cf1ddc5ac..d3ec982d9 100644 --- a/src/views/assets/Cloud/const.js +++ b/src/views/assets/Cloud/const.js @@ -117,7 +117,7 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = { [vmware]: { name: vmware, title: 'VMware', - attrs: ['host', 'port', 'username', 'password'], + attrs: ['host', 'port', 'username', 'password', 'auto_sync_node'], image: require('@/assets/img/cloud/vmware.svg') }, [nutanix]: { diff --git a/src/views/dashboard/components/RankTable.vue b/src/views/dashboard/components/RankTable.vue index 2550aa809..9b66c4dff 100644 --- a/src/views/dashboard/components/RankTable.vue +++ b/src/views/dashboard/components/RankTable.vue @@ -10,17 +10,24 @@ style="width: 100%" > - diff --git a/src/views/dashboard/components/RingChart.vue b/src/views/dashboard/components/RingChart.vue index 3935ca74e..d8c611a32 100644 --- a/src/views/dashboard/components/RingChart.vue +++ b/src/views/dashboard/components/RingChart.vue @@ -30,17 +30,35 @@ export default { let percentage = activeDecimal.dividedBy(totalDecimal).times(100) percentage = isNaN(percentage) ? 0 : percentage percentage = percentage.toFixed(2) + + const formatTitle = (text) => { + if (!text) return '' + const maxLength = 23 + const lines = [] + for (let i = 0; i < text.length; i += maxLength) { + lines.push(text.slice(i, i + maxLength)) + } + return lines.join('\n') + } + return { title: [ { - text: this.config.chartTitle, + text: formatTitle(this.config.chartTitle), textStyle: { color: '#646A73', - fontSize: 12 + fontSize: 12, + lineHeight: 16, + rich: { + width: 100, + overflow: 'break' + } }, textAlign: 'center', left: '48%', - top: '32%' + top: '32%', + width: 100, + overflow: 'break' }, { left: '48%', diff --git a/src/views/profile/index.vue b/src/views/profile/index.vue index dc862cae5..88e2fc154 100644 --- a/src/views/profile/index.vue +++ b/src/views/profile/index.vue @@ -19,6 +19,7 @@ type="info" /> { - const next_url = this.$store.state.users.profile.is_face_code_set ? '/core/auth/profile/face/disable/' : '/core/auth/profile/face/enable/' + const next_url = this.$store.state.users.profile.is_face_code_set + ? '/core/auth/profile/face/disable/' + : '/core/auth/profile/face/enable/' window.open(next_url, '_blank') } }