mirror of
https://github.com/jumpserver/lina.git
synced 2025-07-06 11:47:34 +00:00
commit
e58ec6057c
@ -57,7 +57,7 @@
|
|||||||
@click="handleClick(action)"
|
@click="handleClick(action)"
|
||||||
>
|
>
|
||||||
<el-tooltip :content="action.tip" :disabled="!action.tip" :open-delay="500" placement="top">
|
<el-tooltip :content="action.tip" :disabled="!action.tip" :open-delay="500" placement="top">
|
||||||
<span>
|
<span :title="action.tip">
|
||||||
<span v-if="action.icon && !action.icon.startsWith('el-')" style="vertical-align: initial">
|
<span v-if="action.icon && !action.icon.startsWith('el-')" style="vertical-align: initial">
|
||||||
<i v-if="action.icon.startsWith('fa')" :class="'fa ' + action.icon" />
|
<i v-if="action.icon.startsWith('fa')" :class="'fa ' + action.icon" />
|
||||||
<svg-icon v-else :icon-class="action.icon" />
|
<svg-icon v-else :icon-class="action.icon" />
|
||||||
@ -249,10 +249,11 @@ $color-drop-menu-border: #e4e7ed;
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
display: flex;
|
padding: 2px 5px;
|
||||||
align-items: center;
|
|
||||||
padding: 2px 6px;
|
|
||||||
color: $btn-text-color;
|
color: $btn-text-color;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
vertical-align: baseline !important;
|
vertical-align: baseline !important;
|
||||||
|
@ -223,6 +223,10 @@ export default {
|
|||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: var(--color-icon-primary);
|
color: var(--color-icon-primary);
|
||||||
}
|
}
|
||||||
|
@ -34,14 +34,12 @@ class StrategyNormal extends StrategyAbstract {
|
|||||||
onSelectionChange(val) {
|
onSelectionChange(val) {
|
||||||
this.elDataTable.selected = val
|
this.elDataTable.selected = val
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* toggleRowSelection和clearSelection的表现与el-table一致
|
* toggleRowSelection和clearSelection的表现与el-table一致
|
||||||
*/
|
*/
|
||||||
toggleRowSelection(...args) {
|
toggleRowSelection(...args) {
|
||||||
return this.elTable.toggleRowSelection(...args)
|
return this.elTable.toggleRowSelection(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
return this.elTable.clearSelection()
|
return this.elTable.clearSelection()
|
||||||
}
|
}
|
||||||
@ -65,24 +63,54 @@ class StrategyPersistSelection extends StrategyAbstract {
|
|||||||
*/
|
*/
|
||||||
onSelect(selection, row) {
|
onSelect(selection, row) {
|
||||||
const isChosen = selection.indexOf(row) > -1
|
const isChosen = selection.indexOf(row) > -1
|
||||||
|
|
||||||
this.toggleRowSelection(row, isChosen)
|
this.toggleRowSelection(row, isChosen)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 用户切换当前页的多选
|
* 用户切换当前页的多选
|
||||||
*/
|
*/
|
||||||
onSelectAll(selection, selectable = () => true) {
|
onSelectAll(selection, selectable = () => true) {
|
||||||
// 获取当前所有已选择的项
|
const { id, selected, data } = this.elDataTable
|
||||||
const selectedRows = this.elDataTable.data.filter(r => selection.includes(r))
|
const selectableRows = data.filter(selectable)
|
||||||
|
// const isSelected = !!selection.length
|
||||||
|
|
||||||
// 判断是否已全选
|
// 创建已选择项的 id 集合,用于快速查找
|
||||||
const isSelected = this.elDataTable.data.every(r => selectable(r) && selectedRows.includes(r))
|
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)) {
|
const currentPageSelectedCount = selectableRows.filter(row =>
|
||||||
this.toggleRowSelection(r, isSelected)
|
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数组
|
* toggleRowSelection和clearSelection管理elDataTable的selected数组
|
||||||
@ -105,29 +133,26 @@ class StrategyPersistSelection extends StrategyAbstract {
|
|||||||
this.elDataTable.$emit('toggle-row-selection', isSelected, row)
|
this.elDataTable.$emit('toggle-row-selection', isSelected, row)
|
||||||
this.updateElTableSelection()
|
this.updateElTableSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.elDataTable.selected = []
|
this.elDataTable.selected = []
|
||||||
this.updateElTableSelection()
|
this.updateElTableSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将selected状态同步到el-table中
|
* 将selected状态同步到el-table中
|
||||||
*/
|
*/
|
||||||
updateElTableSelection() {
|
updateElTableSelection() {
|
||||||
const { data, id, selected } = this.elDataTable
|
const { data, id, selected } = this.elDataTable
|
||||||
|
const selectedIds = new Set(selected.map(r => r[id]))
|
||||||
|
|
||||||
// 历史勾选的行已经不在当前页了,所以要将当前页的行数据和selected合并
|
this.elTable.clearSelection()
|
||||||
const mergeData = _.uniqWith([...data, ...selected], _.isEqual)
|
|
||||||
|
|
||||||
mergeData.forEach(r => {
|
data.forEach(row => {
|
||||||
const isSelected = !!selected.find(r2 => r[id] === r2[id])
|
const shouldBeSelected = selectedIds.has(row[id])
|
||||||
|
if (!this.elTable) return
|
||||||
|
|
||||||
if (!this.elTable) {
|
if (shouldBeSelected) {
|
||||||
return
|
this.elTable.toggleRowSelection(row, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.elTable.toggleRowSelection(r, isSelected)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,13 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
iExportOptions() {
|
iExportOptions() {
|
||||||
return assignIfNot(this.exportOptions, { url: this.tableUrl })
|
/**
|
||||||
|
* 原本是使用 assignIfNot 此函数内部使用 partialRight, 该函数
|
||||||
|
* 只在目标对象的属性未定义时才从源对象复制属性,如果目标对象已经有值,则保留原值
|
||||||
|
* 那如果首次点击的树节点,那么此时 url 就会被确定,后续点击的树节点,那么 url 就不会
|
||||||
|
* 改变了
|
||||||
|
*/
|
||||||
|
return Object.assign({}, this.exportOptions, { url: this.tableUrl })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
type="info"
|
type="info"
|
||||||
@click="handleTagClick(v,k)"
|
@click="handleTagClick(v,k)"
|
||||||
@close="handleTagClose(k)"
|
@close.stop="handleTagClose(k)"
|
||||||
>
|
>
|
||||||
<strong v-if="v.label">{{ v.label + ':' }}</strong>
|
<strong v-if="v.label">{{ v.label + ':' }}</strong>
|
||||||
<span v-if="v.valueLabel">{{ v.valueLabel }}</span>
|
<span v-if="v.valueLabel">{{ v.valueLabel }}</span>
|
||||||
@ -128,7 +128,7 @@ export default {
|
|||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
filterTags: {
|
filterTags: {
|
||||||
handler() {
|
handler(newValue) {
|
||||||
this.$emit('tag-search', this.filterMaps)
|
this.$emit('tag-search', this.filterMaps)
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
@ -137,6 +137,15 @@ export default {
|
|||||||
if (newValue === '' && oldValue !== '') {
|
if (newValue === '' && oldValue !== '') {
|
||||||
this.emptyCount = 1
|
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() {
|
mounted() {
|
||||||
|
@ -79,6 +79,9 @@ export default {
|
|||||||
case 'account_template':
|
case 'account_template':
|
||||||
url = '/api/v1/accounts/account-templates/'
|
url = '/api/v1/accounts/account-templates/'
|
||||||
break
|
break
|
||||||
|
case 'label':
|
||||||
|
url = '/api/v1/labels/labels/'
|
||||||
|
break
|
||||||
case 'name_strategy':
|
case 'name_strategy':
|
||||||
options = this.nameOptions
|
options = this.nameOptions
|
||||||
break
|
break
|
||||||
|
@ -6,6 +6,7 @@ export const resourceTypeOptions = [
|
|||||||
{ label: i18n.t('Node'), value: 'node' },
|
{ label: i18n.t('Node'), value: 'node' },
|
||||||
{ label: i18n.t('Zone'), value: 'domain' },
|
{ label: i18n.t('Zone'), value: 'domain' },
|
||||||
{ label: i18n.t('AccountTemplate'), value: 'account_template' },
|
{ label: i18n.t('AccountTemplate'), value: 'account_template' },
|
||||||
|
{ label: i18n.t('Tags'), value: 'label' },
|
||||||
{ label: i18n.t('Strategy'), value: 'name_strategy' }
|
{ label: i18n.t('Strategy'), value: 'name_strategy' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
|
|||||||
[vmware]: {
|
[vmware]: {
|
||||||
name: vmware,
|
name: vmware,
|
||||||
title: 'VMware',
|
title: 'VMware',
|
||||||
attrs: ['host', 'port', 'username', 'password'],
|
attrs: ['host', 'port', 'username', 'password', 'auto_sync_node'],
|
||||||
image: require('@/assets/img/cloud/vmware.svg')
|
image: require('@/assets/img/cloud/vmware.svg')
|
||||||
},
|
},
|
||||||
[nutanix]: {
|
[nutanix]: {
|
||||||
|
@ -10,17 +10,24 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-table-column :label="$tc('Ranking')" width="80px">
|
<el-table-column :label="$tc('Ranking')" width="80px">
|
||||||
<template v-slot="scope">
|
<template v-slot="scope" #header>
|
||||||
<span>{{ scope.$index + 1 }}</span>
|
<el-tooltip :content="$t('Ranking')" placement="top" :open-delay="500">
|
||||||
|
<span style="cursor: pointer;">{{ $t('Ranking') }}</span>
|
||||||
|
</el-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="i in config.columns"
|
v-for="i in config.columns"
|
||||||
:key="i.prop"
|
:key="i.prop"
|
||||||
:label="i.label"
|
|
||||||
:prop="i.prop"
|
:prop="i.prop"
|
||||||
:width="i.width"
|
:width="i.width"
|
||||||
/>
|
>
|
||||||
|
<template #header>
|
||||||
|
<el-tooltip :content="i.label" placement="top" :open-delay="500">
|
||||||
|
<span style="cursor: pointer;">{{ i.label }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -30,17 +30,35 @@ export default {
|
|||||||
let percentage = activeDecimal.dividedBy(totalDecimal).times(100)
|
let percentage = activeDecimal.dividedBy(totalDecimal).times(100)
|
||||||
percentage = isNaN(percentage) ? 0 : percentage
|
percentage = isNaN(percentage) ? 0 : percentage
|
||||||
percentage = percentage.toFixed(2)
|
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 {
|
return {
|
||||||
title: [
|
title: [
|
||||||
{
|
{
|
||||||
text: this.config.chartTitle,
|
text: formatTitle(this.config.chartTitle),
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#646A73',
|
color: '#646A73',
|
||||||
fontSize: 12
|
fontSize: 12,
|
||||||
|
lineHeight: 16,
|
||||||
|
rich: {
|
||||||
|
width: 100,
|
||||||
|
overflow: 'break'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
left: '48%',
|
left: '48%',
|
||||||
top: '32%'
|
top: '32%',
|
||||||
|
width: 100,
|
||||||
|
overflow: 'break'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
left: '48%',
|
left: '48%',
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
type="info"
|
type="info"
|
||||||
/>
|
/>
|
||||||
<QuickActions
|
<QuickActions
|
||||||
|
v-if="biometricFeaturesActions.some(action => action.has)"
|
||||||
:title="$tc('BiometricFeatures')"
|
:title="$tc('BiometricFeatures')"
|
||||||
type="warning"
|
type="warning"
|
||||||
:actions="biometricFeaturesActions"
|
:actions="biometricFeaturesActions"
|
||||||
@ -85,13 +86,17 @@ export default {
|
|||||||
biometricFeaturesActions: [
|
biometricFeaturesActions: [
|
||||||
{
|
{
|
||||||
title: this.$t('FacialFeatures'),
|
title: this.$t('FacialFeatures'),
|
||||||
|
has: this.$store.getters.publicSettings.FACE_RECOGNITION_ENABLED &&
|
||||||
|
this.$store.getters.publicSettings.XPACK_LICENSE_EDITION_ULTIMATE,
|
||||||
attrs: {
|
attrs: {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
label: this.$store.state.users.profile.is_face_code_set ? this.$t('Unbind') : this.$t('Bind')
|
label: this.$store.state.users.profile.is_face_code_set ? this.$t('Unbind') : this.$t('Bind')
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
click: () => {
|
click: () => {
|
||||||
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')
|
window.open(next_url, '_blank')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user