perf: 修改我的资产 table

This commit is contained in:
ibuler
2024-05-07 17:30:16 +08:00
parent 2b9af5070b
commit 0ca7d3a2ad
11 changed files with 167 additions and 248 deletions

View File

@@ -33,6 +33,10 @@ export default {
vm.tableConfig.url = url
}
},
actions: {
type: Object,
default: null
},
getShowUrl: {
type: Function,
default({ row, col }) {
@@ -62,7 +66,7 @@ export default {
columnsExclude: ['spec_info'],
columnsShow: {
min: ['name', 'address', 'accounts'],
default: ['name', 'address', 'platform', 'view_account', 'connectivity']
default: ['name', 'address', 'platform', 'connectivity', 'view_account', 'actions']
},
columnsMeta: {
name: {
@@ -71,8 +75,14 @@ export default {
route: 'AssetDetail'
}
},
labels: {
formatterArgs: {
showEditBtn: false
}
},
actions: {
has: false
// has: this.actions !== null,
...this.actions
},
view_account: {
label: this.$t('Account'),
@@ -80,6 +90,11 @@ export default {
width: '100px'
},
connectivity: connectivityMeta
},
tableAttrs: {
rowClassName({ row }) {
return !row.is_active ? 'row_disabled' : ''
}
}
},
headerActions: {
@@ -102,4 +117,8 @@ export default {
</script>
<style scoped>
.row_disabled,.row_disabled:hover,.row_disabled:hover > td{
cursor: not-allowed;
background-color:rgba(192,196,204,0.28) !important;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<IBox v-if="loading" style="width: 100%; height: 200px" />
<div v-else>
<DetailCard v-if="hasObject && items.length > 0" :items="items" :loading="loading" v-bind="$attrs" />
<DetailCard v-if="hasObject && items.length > 0" :items="validItems" :loading="loading" v-bind="$attrs" />
</div>
</template>
@@ -59,6 +59,9 @@ export default {
},
hasObject() {
return Object.keys(this.iObject).length > 0
},
validItems() {
return this.items.filter(item => this.isHidden(item))
}
},
async mounted() {
@@ -82,6 +85,77 @@ export default {
}
return formatter
},
isHidden(item) {
let has = item.has
if (typeof has === 'function') {
has = has()
}
if (has === undefined) {
has = true
}
return has
},
parseValue(value, tp) {
if (value === null || value === '') {
value = '-'
} else if (value === 0) {
value = 0
} else if (tp === 'datetime') {
value = toSafeLocalDateStr(value)
} else if (tp === 'labeled_choice') {
value = value?.['label']
} else if (tp === 'related_field' || tp === 'nested object' || value?.name) {
value = value?.['name']
} else if (tp === 'm2m_related_field') {
value = value?.map(item => item['name']).join(', ')
} else if (tp === 'boolean') {
value = value ? this.$t('Yes') : this.$t('No')
}
return value
},
parseArrayValue(value, excludes, label) {
if (Array.isArray(value)) {
const tp = typeof value[0]
for (const [index, item] of value.entries()) {
let object = {}
if (tp === 'object') {
const firstValue = value[0]
if (firstValue.hasOwnProperty('name')) {
value.forEach(item => {
const fieldName = `${name}.${item.name}`
if (excludes.includes(fieldName)) {
return
}
object = {
key: item.label,
value: item.value
}
})
} else {
const fieldName = `${name}.${item.name}`
if (excludes.includes(fieldName)) {
continue
}
object = {
key: item.label,
value: item.value
}
}
} else if (tp === 'string') {
object = {
value: value[index]
}
if (index === 0) {
object['key'] = label
}
}
if (index !== value.length - 1) {
object['class'] = 'array-item'
}
this.items.push(object)
}
}
},
async optionAndGenFields() {
const data = await this.$store.dispatch('common/getUrlMeta', { url: this.url })
let remoteMeta = data.actions['GET'] || {}
@@ -94,6 +168,7 @@ export default {
const excludes = (this.excludes || []).concat(defaultExcludes)
fields = fields.filter(item => !excludes.includes(item))
const defaultFormatter = this.defaultFormatter(fields)
for (const name of fields) {
if (typeof name === 'object') {
this.items.push(name)
@@ -121,62 +196,10 @@ export default {
}
if (Array.isArray(value)) {
const tp = typeof value[0]
for (const [index, item] of value.entries()) {
let object = {}
if (tp === 'object') {
const firstValue = value[0]
if (firstValue.hasOwnProperty('name')) {
value.forEach(item => {
const fieldName = `${name}.${item.name}`
if (excludes.includes(fieldName)) {
return
}
object = {
key: item.label,
value: item.value
}
})
} else {
const fieldName = `${name}.${item.name}`
if (excludes.includes(fieldName)) {
continue
}
object = {
key: item.label,
value: item.value
}
}
} else if (tp === 'string') {
object = {
value: value[index]
}
if (index === 0) {
object['key'] = label
}
}
if (index !== value.length - 1) {
object['class'] = 'array-item'
}
this.items.push(object)
}
this.parseArrayValue(value, excludes, label)
continue
}
if (value === null || value === '') {
value = '-'
} else if (value === 0) {
value = 0
} else if (fieldMeta.type === 'datetime') {
value = toSafeLocalDateStr(value)
} else if (fieldMeta.type === 'labeled_choice') {
value = value?.['label']
} else if (fieldMeta.type === 'related_field' || fieldMeta.type === 'nested object' || value?.name) {
value = value?.['name']
} else if (fieldMeta.type === 'm2m_related_field') {
value = value?.map(item => item['name']).join(', ')
} else if (fieldMeta.type === 'boolean') {
value = value ? this.$t('Yes') : this.$t('No')
}
value = this.parseValue(value, fieldMeta.type)
if (value === undefined) {
if (this.showUndefine) {

View File

@@ -85,13 +85,8 @@ export default {
}
}
&:hover {
}
>>> .el-form-item__label {
padding-right: 8%;
//white-space: nowrap;
//text-overflow: ellipsis;
overflow: hidden;
span {
@@ -102,6 +97,7 @@ export default {
>>> .el-form-item__content {
font-size: 13px;
line-height: 40px;
}
>>> .el-tag--mini {

View File

@@ -6,11 +6,14 @@
@show="getAsyncItems"
>
<div class="detail-content">
<div v-if="accountData.length === 0" class="empty-item">
<span>{{ $t('No accounts') }}</span>
</div>
<div v-for="account of accountData" :key="account.id" class="detail-item">
<span>{{ account.name }}({{ account.username }})</span>
</div>
</div>
<el-button slot="reference" size="mini" type="primary">{{ $t('View') }}</el-button>
<el-button slot="reference" size="mini" type="text">{{ $t('View') }}</el-button>
</el-popover>
</template>
@@ -34,7 +37,7 @@ export default {
},
methods: {
async getAsyncItems() {
const userId = this.$route.params.id
const userId = this.$route.params.id || 'self'
const url = `/api/v1/perms/users/${userId}/assets/${this.row.id}`
this.$axios.get(url).then(res => {
this.accountData = res?.permed_accounts || []
@@ -48,6 +51,7 @@ export default {
.detail-content {
max-height: 150px;
overflow-y: auto;
min-width: 300px;
}
.detail-item {
@@ -59,4 +63,12 @@ export default {
background-color: #F5F7FA;
}
}
.el-button--text {
color: var(--color-link);
&:hover {
color: var(--color-link);
}
}
</style>

View File

@@ -246,11 +246,11 @@ export default {
.organization {
border-radius: 3px;
background-color: rgba(255, 255, 255, .15);
background-color: rgba(0, 0, 0, .12);
padding-left: 10px !important;
&:hover {
background-color: rgba(0, 0, 0, .12);
background-color: rgba(0, 0, 0, .18);
}
}
</style>

View File

@@ -98,7 +98,7 @@ export default {
}
.page-content {
height: calc(100% - 90px);
height: calc(100% - 10px);
overflow-x: hidden;
overflow-y: auto;
}

View File

@@ -31,11 +31,7 @@
.el-menu-item, .el-submenu-sidebar .el-menu-item {
background-color: $subMenuBg;
color: $menuText;
list-style: circle inside;
span {
//padding-left: 10px;
}
list-style: disc inside;
&.submenu-title-noDropdown {
list-style: none;
@@ -56,10 +52,6 @@
color: $subMenuActiveText;
list-style-type: disc;
}
i {
//color: $menuText;
}
}
i.fa {

View File

@@ -200,10 +200,10 @@ export default {
label: 'PostgreSQL', value: 'postgresql'
},
{
label: 'SQL Server', value: 'sqlserver'
label: 'SQLServer', value: 'sqlserver'
},
{
label: 'HUAWEI', value: 'huawei'
label: 'CloudEngine', value: 'huawei'
}
],
callback: (option) => {

View File

@@ -18,8 +18,8 @@ export default {
},
data() {
return {
treeUrl: `/api/v1/perms/users/${this.object.id}/nodes/children/tree/?cache_policy=1`,
tableUrl: `/api/v1/perms/users/${this.object.id}/assets/?cache_policy=1&all=1`
treeUrl: `/api/v1/perms/users/${this.object.id}/nodes/children/tree/`,
tableUrl: `/api/v1/perms/users/${this.object.id}/assets/?all=1`
}
}
}

View File

@@ -202,6 +202,9 @@ export default {
key: this.$t('OrgsAndRoles'),
has: this.$store.getters.currentOrgIsRoot,
formatter: (item, val) => {
if (!this.$store.getters.currentOrgIsRoot) {
return ''
}
const doms = []
const orgsRoles = this.object.orgs_roles
const allowKeyMaxLength = 50
@@ -211,14 +214,14 @@ export default {
prettyKey = key.substring(0, allowKeyMaxLength - 3) + '...'
}
const item = prettyKey + ': ' + value.join(', ')
doms.push([item, <br/>])
doms.push([item, <br />])
})
return <div>{doms}</div>
}
},
'mfa_level', 'source',
'wecom_id', 'dingtalk_id', 'feishu_id',
'mfa_level', 'source', 'created_by', 'date_joined', 'date_expired',
'wecom_id', 'dingtalk_id', 'feishu_id', 'mfa_level',
'source', 'labels',
'created_by', 'date_joined', 'date_expired',
'date_password_last_updated', 'last_login', 'comment'
],
relationConfig: {

View File

@@ -1,178 +1,54 @@
<template>
<div>
<GenericTreeListPage
ref="GenericTreeListPage"
:header-actions="headerActions"
:table-config="tableConfig"
:tree-setting="treeSetting"
/>
</div>
<Page>
<GrantedAssets :actions="actions" :table-url="tableUrl" :tree-url="treeUrl" />
</Page>
</template>
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage'
import { AccountShowFormatter, DialogDetailFormatter } from '@/components/Table/TableFormatters'
import { connectivityMeta } from '@/components/Apps/AccountListTable/const'
import GrantedAssets from '@/components/Apps/GrantedAssets/index.vue'
import Page from '@/layout/components/Page/index.vue'
export default {
components: {
GenericTreeListPage
Page,
GrantedAssets
},
data() {
return {
allFavorites: [],
treeSetting: {
showMenu: false,
showRefresh: true,
showAssets: false,
url: '/api/v1/perms/users/self/users/assets/',
nodeUrl: '/api/v1/perms/users/self/nodes/',
// ?assets=0不显示资产. =1显示资产
treeUrl: '/api/v1/perms/users/self/nodes/children/tree/',
callback: {
refresh: () => {},
onSelected: function(event, treeNode) {
if (treeNode.meta.type === 'node') {
const currentNodeId = treeNode.meta.data.id
this.tableConfig.url = `/api/v1/perms/users/self/nodes/${currentNodeId}/assets/?cache_policy=1`
}
}.bind(this)
}
},
tableConfig: {
url: '/api/v1/perms/users/self/assets/',
hasTree: true,
columnsExclude: ['spec_info'],
columnsShow: {
default: ['name', 'address', 'platform', 'accounts', 'is_active', 'actions'],
min: ['name', 'address', 'actions']
},
columns: [
'name', 'address', 'domain', 'platform', 'connectivity', 'is_active',
'nodes', 'org_name', 'created_by', 'labels', 'accounts', 'comment', 'actions'
],
columnsMeta: {
name: {
prop: 'name',
formatter: DialogDetailFormatter,
formatterArgs: {
getDialogTitle: function({ col, row, cellValue }) { this.$t('AssetDetail') }.bind(this),
getDetailItems: function({ col, row, cellValue }) {
return [
{
key: this.$t('Name'),
value: row.name
},
{
key: this.$t('AssetAddress'),
value: row.address
},
{
key: this.$t('Protocols'),
formatter: () => {
return this.$axios.get(`/api/v1/perms/users/self/assets/${row.id}/`).then(res => {
const protocols = res.permed_protocols
const names = protocols.map(item => item.name).join(', ')
return names
})
}
},
{
key: this.$t('Category'),
value: row.category.label
},
{
key: this.$t('Type'),
value: row.type.label
},
{
key: this.$t('Platform'),
value: row.platform?.name || ''
},
{
key: this.$t('Active'),
value: row.is_active
},
{
key: this.$t('Comment'),
value: row.comment
}
]
}.bind(this)
},
sortable: true
},
labels: {
formatterArgs: {
showEditBtn: false
}
},
address: {
sortable: 'custom',
width: '150px'
},
accounts: {
align: 'center',
label: this.$t('Account'),
width: '120px',
formatter: AccountShowFormatter,
formatterArgs: {
getUrl: ({ row }) => {
return `/api/v1/perms/users/self/assets/${row.id}/`
treeUrl: `/api/v1/perms/users/self/nodes/children/tree/`,
tableUrl: `/api/v1/perms/users/self/assets/`,
actions: {
width: '88px',
align: 'center',
formatterArgs: {
hasDelete: false,
loading: true,
hasClone: false,
hasUpdate: false,
extraActions: [
{
name: 'connect',
icon: 'fa-terminal',
type: 'primary',
can: ({ row }) => row.is_active,
callback: ({ row }) => {
const oid = this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : ''
const url = `/luna/?login_to=${row.id}${oid ? `&oid=${oid}` : ''}`
window.open(url, '_blank')
}
},
{
name: 'favor',
type: 'info',
icon: ({ row }) => {
return this.checkFavorite(row.id) ? 'fa-star' : 'fa-star-o'
},
callback: ({ row }) => this.toggleFavorite(row.id)
}
},
platform: {
width: '120px'
},
comment: {
width: '100px'
},
connectivity: connectivityMeta,
actions: {
width: '88px',
align: 'center',
formatterArgs: {
hasDelete: false,
loading: true,
hasClone: false,
hasUpdate: false,
extraActions: [
{
name: 'connect',
icon: 'fa-terminal',
type: 'primary',
can: ({ row }) => row.is_active,
callback: ({ row }) => {
const oid = this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : ''
const url = `/luna/?login_to=${row.id}${oid ? `&oid=${oid}` : ''}`
window.open(url, '_blank')
}
},
{
name: 'favor',
type: 'info',
icon: ({ row }) => {
return this.checkFavorite(row.id) ? 'fa-star' : 'fa-star-o'
},
callback: ({ row }) => this.toggleFavorite(row.id)
}
]
}
}
},
tableAttrs: {
rowClassName({ row }) {
return !row.is_active ? 'row_disabled' : ''
}
]
}
},
headerActions: {
hasExport: false,
hasImport: false,
hasLeftActions: false,
hasSearch: true
}
allFavorites: []
}
},
mounted() {
@@ -180,7 +56,7 @@ export default {
},
methods: {
refreshAllFavorites() {
const formatterArgs = this.tableConfig.columnsMeta.actions.formatterArgs
const formatterArgs = this.actions.formatterArgs
formatterArgs.loading = true
this.$axios.get('/api/v1/assets/favorite-assets/').then(resp => {
this.allFavorites = resp
@@ -192,14 +68,12 @@ export default {
const url = '/api/v1/assets/favorite-assets/'
this.$axios.post(url, data).then(() => {
this.allFavorites.push({ asset: assetId })
this.$message.success(this.$i18n.t('CollectionSucceed'))
})
},
disfavor(assetId) {
const url = `/api/v1/assets/favorite-assets/?asset=${assetId}`
this.$axios.delete(url).then(() => {
this.allFavorites = this.allFavorites.filter(item => item['asset'] !== assetId)
this.$message.success(this.$i18n.t('CancelCollection'))
})
},
toggleFavorite(assetId) {