Compare commits

...

1 Commits

Author SHA1 Message Date
Ewall555
1e678a81ea feat: Asset permossion add user selection component and its dialog 2025-06-19 07:10:11 +00:00
3 changed files with 300 additions and 6 deletions

View File

@@ -0,0 +1,162 @@
<template>
<Dialog
:close-on-click-modal="false"
:title="$tc('Users')"
custom-class="user-select-dialog"
v-bind="$attrs"
@cancel="handleCancel"
@confirm="handleConfirm"
v-on="$listeners"
>
<ListTable
ref="ListPage"
:header-actions="headerActions"
:sync-select-to-url="false"
:table-config="tableConfig"
:url="baseUrl"
v-bind="$attrs"
@loaded="handleTableLoaded"
v-on="$listeners"
/>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import ListTable from '@/components/Table/ListTable'
export default {
componentName: 'AssetSelectDialog',
components: { Dialog, ListTable },
props: {
baseUrl: {
type: String,
default: '/api/v1/users/users/?fields_size=small'
},
value: {
type: Array,
default: () => []
},
canSelect: {
type: Function,
default(row, index) {
return true
}
},
disabled: {
type: [Boolean, Function],
default: false
}
},
data() {
const vm = this
return {
isLoaded: false,
dialogVisible: false,
rowSelected: _.cloneDeep(this.value) || [],
rowsAdd: [],
tableConfig: {
url: this.baseUrl,
canSelect: this.canSelect,
columns: [
{
prop: 'name',
label: this.$t('Name'),
sortable: true
},
{
prop: 'username',
label: this.$t('Username'),
sortable: true
},
{
prop: 'email',
label: this.$t('Email'),
sortable: true
},
{
prop: 'mfa_level.label',
label: this.$t('MfaLevel'),
sortable: true,
formatter: function(row) {
return row.mfa_level.label
}
},
{
prop: 'source.label',
label: this.$t('Source'),
sortable: true,
formatter: function(row) {
return row.source.label
}
},
{
prop: 'org_roles.display_name',
label: this.$t('OrgRoles'),
sortable: true,
formatter: function(row) {
return row.org_roles.map(role => role.display_name).join(', ')
}
}
],
columnsMeta: {
actions: {
has: false
}
},
listeners: {
'toggle-row-selection': (isSelected, row) => {
if (isSelected) {
vm.addRowToSelect(row)
} else {
vm.removeRowFromSelect(row)
}
}
},
theRowDefaultIsSelected: (row) => {
return this.value.indexOf(row.id) > -1
}
},
headerActions: {
hasLeftActions: false,
hasImport: false,
hasExport: false
}
}
},
methods: {
handleTableLoaded() {
this.isLoaded = true
},
handleConfirm() {
this.$emit('confirm', this.rowSelected, this.rowsAdd)
},
handleCancel() {
this.$emit('cancel')
},
addRowToSelect(row) {
const selectValueIndex = this.rowSelected.indexOf(row.id)
if (selectValueIndex === -1) {
this.rowSelected.push(row.id)
this.rowsAdd.push(row)
}
},
removeRowFromSelect(row) {
const selectValueIndex = this.rowSelected.indexOf(row.id)
if (selectValueIndex > -1) {
this.rowSelected.splice(selectValueIndex, 1)
}
}
}
}
</script>
<style lang="scss" scoped>
.page ::v-deep .page-heading {
display: none;
}
.user-select-dialog ::v-deep .el-icon-circle-check {
display: none;
}
</style>

View File

@@ -0,0 +1,130 @@
<template>
<div class="user-select-formatter">
<Select2
ref="select2"
v-model="select2Config.value"
v-bind="select2Config"
@input="onInputChange"
v-on="$listeners"
@focus.stop="handleFocus"
/>
<UserSelectDialog
v-if="dialogVisible"
ref="dialog"
:base-url="baseUrl"
:value="value"
:visible.sync="dialogVisible"
v-bind="$attrs"
@cancel="handleCancel"
@confirm="handleConfirm"
v-on="$listeners"
/>
</div>
</template>
<script>
import Select2 from '@/components/Form/FormFields/Select2.vue'
import UserSelectDialog from './dialog.vue'
export default {
componentName: 'UserSelect',
components: { UserSelectDialog, Select2 },
props: {
baseUrl: {
type: String,
default: '/api/v1/users/users/?fields_size=small'
},
defaultPageSize: {
type: Number,
default: 10
},
value: {
type: Array,
default: () => []
},
disabled: {
type: [Boolean, Function],
default: false
}
},
data() {
const iValue = []
for (const item of this.value) {
if (typeof item === 'object') {
iValue.push(item.id)
} else {
iValue.push(item)
}
}
return {
dialogVisible: false,
initialValue: _.cloneDeep(iValue),
select2Config: {
disabled: this.disabled,
value: iValue,
multiple: true,
clearable: true,
defaultPageSize: this.defaultPageSize,
ajax: {
url: this.baseUrl,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
},
methods: {
handleFocus() {
this.$refs.select2.selectRef.blur()
this.dialogVisible = true
},
handleConfirm(valueSelected, rowsAdd) {
if (valueSelected === undefined) {
return
}
this.$refs.select2.iValue = valueSelected
this.addRowsToSelect(rowsAdd)
this.onInputChange(valueSelected)
this.dialogVisible = false
},
handleCancel() {
this.dialogVisible = false
},
onInputChange(val) {
this.$emit('change', val)
},
addToSelect(options, row) {
const selectOptionsHas = options.find(item => item.value === row.id)
// 如果select2的options中没有那么可能无法显示正常的值
if (selectOptionsHas === undefined) {
const option = {
label: `${row.name}(${row.username})`,
value: row.id
}
options.push(option)
}
},
addRowsToSelect(rows) {
const outSelectOptions = this.$refs.select2.options
for (const row of rows) {
this.addToSelect(outSelectOptions, row)
}
}
}
}
</script>
<style lang="scss" scoped>
.el-select {
width: 100%;
}
.page ::v-deep .page-heading {
display: none;
}
.page ::v-deep .treebox {
height: inherit !important;
}
</style>

View File

@@ -10,6 +10,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import UserSelect from '@/components/Apps/UserSelect'
import AssetSelect from '@/components/Apps/AssetSelect'
import AccountFormatter from './components/AccountFormatter'
import { AllAccount } from '../const'
@@ -49,14 +50,15 @@ export default {
createSuccessNextRoute: { name: 'AssetPermissionDetail' },
fieldsMeta: {
users: {
type: 'userSelect',
component: UserSelect,
rules: [{
required: false
}],
el: {
value: [],
ajax: {
url: '/api/v1/users/users/?fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
defaultPageSize: 300,
baseUrl: '/api/v1/users/users/?fields_size=small'
}
},
user_groups: {