perf: 修改远程应用的列表界面

This commit is contained in:
jiangweidong
2023-01-19 18:00:19 +08:00
parent bba4750ec3
commit fbc94ac4f2
9 changed files with 370 additions and 151 deletions

View File

@@ -0,0 +1,115 @@
<template>
<div class="el-page">
<el-pagination
v-if="hasPagination"
:current-page="page"
:page-sizes="paginationSizes"
:page-size="size"
:total="total"
:background="paginationBackground"
:layout="paginationLayout"
v-bind="extraPaginationAttrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
const defaultFirstPage = 1
export default {
name: 'Pagination',
components: {},
props: {
hasPagination: {
type: Boolean,
default: true
},
firstPage: {
type: Number,
default: defaultFirstPage
},
pageSizeKey: {
type: String,
default: 'limit'
},
pageKey: {
type: String,
default: 'offset'
},
page: {
type: Number,
default: 1
},
noPaginationSize: {
type: Number,
default: -1
},
paginationSize: {
type: Number,
default: 10
},
total: {
type: Number,
default: 0
},
paginationSizes: {
type: Array,
default: () => [10, 20, 30, 40, 50, 100]
},
paginationBackground: {
type: Boolean,
default: true
},
paginationLayout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
extraPaginationAttrs: {
type: Object,
default: () => {}
},
transformQuery: {
type: Function,
default: null
}
},
data() {
return {
size: this.paginationSize || this.paginationSizes[0]
}
},
methods: {
handleSizeChange(val) {
this.$emit('sizeChange', val)
},
handleCurrentChange(val) {
this.$emit('currentSizeChange', val)
},
getPageQuery(currentPage, pageSize) {
// 构造query对象
let query = {}
query[this.pageSizeKey] = this.hasPagination
? pageSize
: this.noPaginationSize
const offset = (currentPage - 1) * pageSize
query[this.pageKey] = offset
if (this.transformQuery) {
query = this.transformQuery(query)
}
return query
}
}
}
</script>
<style scoped>
>>> .el-pagination {
text-align: right;
}
>>> .el-pagination__total {
float: left;
}
</style>

View File

@@ -28,3 +28,4 @@ export { default as AssetRelationCard } from './AssetRelationCard'
export { default as UserConfirmDialog } from './UserConfirmDialog'
export { default as Announcement } from './Announcement'
export { default as CronTab } from './CronTab'
export { default as Pagination } from './Pagination'

View File

@@ -802,6 +802,7 @@
},
"route": {
"": "",
"AppletDetail": "Remote apps",
"CreateEndpoint": "Create endpoint",
"UpdateEndpoint": "Update endpoint",
"CreateEndpointRule": "Create endpoint rule",

View File

@@ -806,6 +806,7 @@
},
"route": {
"": "",
"AppletDetail": "遠隔応用です",
"AssignedTicketList": "割り当て済みワークオーダー",
"CreateEndpoint": "エンドポイントを作成する",
"UpdateEndpoint": "エンドポイントを更新",

View File

@@ -995,6 +995,7 @@
},
"route": {
"": "",
"AppletDetail": "远程应用",
"AppletHostDetail": "远程应用发布机详情",
"AppletHostCreate": "添加远程应用发布机",
"AppletHostUpdate": "更新远程应用发布机",

View File

@@ -3,22 +3,16 @@
<el-col :md="14" :sm="24">
<AutoDetailCard :url="url" :fields="detailFields" :object="object" />
</el-col>
<el-col :md="10" :sm="24">
<QuickActions type="primary" :actions="quickActions" />
</el-col>
</el-row>
</template>
<script>
import AutoDetailCard from '@/components/DetailCard/auto'
import QuickActions from '@/components/QuickActions'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'Detail',
components: {
AutoDetailCard,
QuickActions
AutoDetailCard
},
props: {
object: {
@@ -27,86 +21,26 @@ export default {
}
},
data() {
const vm = this
return {
quickActions: [
{
title: this.$t('assets.IsActive'),
type: 'switch',
attrs: {
label: this.$t('common.Test'),
model: this.object.is_active,
disabled: !vm.$hasPerm('assets.change_asset')
},
callbacks: {
change: function(val) {
this.$axios.patch(
`/api/v1/assets/assets/${this.object.id}/`,
{ is_active: val }
).then(res => {
this.$message.success(this.$tc('common.updateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$tc('common.updateErrorMsg' + ' ' + err))
})
}.bind(this)
}
},
{
title: this.$t('assets.RefreshHardware'),
attrs: {
type: 'primary',
label: this.$t('assets.Refresh'),
disabled: !vm.$hasPerm('assets.refresh_assethardwareinfo')
},
callbacks: {
click: function() {
this.$axios.post(
`/api/v1/assets/assets/${this.object.id}/tasks/`,
{ action: 'refresh' }
).then(res => {
openTaskPage(res['task'])
})
}.bind(this)
}
},
{
title: this.$t('assets.TestAssetsConnective'),
attrs: {
type: 'primary',
label: this.$t('assets.Test'),
disabled: !vm.$hasPerm('assets.test_assetconnectivity')
},
callbacks: {
click: function() {
this.$axios.post(
`/api/v1/assets/assets/${this.object.id}/tasks/`,
{ action: 'test' }
).then(res => {
openTaskPage(res['task'])
})
}.bind(this)
}
}
],
url: `/api/v1/terminal/applets/${this.object.id}`,
detailFields: [
'name', 'ip',
'name', 'author', 'display_name',
{
key: this.$t('assets.Protocols'),
value: this.object.protocols.map(i => i.name).join(',')
formatter: () => {
const data = this.object.protocols.map(p => <el-tag size='mini'>{p} </el-tag>)
return <span> {data} </span>
}
},
'public_ip', 'admin_user_display',
{
key: this.$t('assets.Domain'),
value: this.object.domain?.name || ''
key: this.$t('assets.Label'),
value: this.object.tags.join(',')
},
'vendor', 'model', 'cpu_model', 'memory', 'disk_info',
{
key: this.$t('assets.Platform'),
value: this.object.platform?.name || ''
key: this.$t('assets.Type'),
value: this.object.type.label
},
'os_arch', 'is_active', 'sn', 'number', 'date_created',
'created_by', 'comment'
'date_created', 'date_updated', 'comment'
]
}
},

View File

@@ -1,19 +1,18 @@
<template>
<GenericDetailPage
:object.sync="asset"
:object.sync="applet"
:active-menu.sync="config.activeMenu"
v-bind="config"
v-on="$listeners"
>
<keep-alive>
<component :is="config.activeMenu" :object="asset" />
<component :is="config.activeMenu" :object="applet" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import Account from '@/views/assets/Asset/AssetDetail/Account'
import Detail from './Detail'
export default {
@@ -21,46 +20,33 @@ export default {
components: {
GenericDetailPage,
TabPage,
Detail,
Account
Detail
},
data() {
return {
asset: {},
applet: {},
config: {
url: '/api/v1/terminal/applet-hosts/',
url: '/api/v1/terminal/applets',
activeMenu: 'Detail',
submenu: [
{
'title': this.$t('common.Detail'),
'name': 'Detail'
},
{
title: this.$t('assets.Account'),
name: 'Account'
},
{
'title': this.$t('terminal.Applets'),
'name': 'Applets'
}
],
hasRightSide: true,
actions: {
updateCallback: () => {
const category = this.asset.category.value || 'host'
const routerName = _.capitalize(category) + 'Update'
this.$router.push({
name: routerName,
params: { id: this.$route.params.id },
query: { platform: this.asset.platform.id }
})
}
}
hasUpdate: false,
canDelete: () => {
return this.$hasPerm('terminal.delete_applet')
},
deleteSuccessRoute: 'Applets'
},
titlePrefix: this.$tc('route.AppletDetail')
}
}
},
mounted() {
}
mounted() {}
}
</script>

View File

@@ -1,17 +1,17 @@
<template>
<div>
<ListTable ref="ListTable" v-bind="$data" />
<CardTable ref="CardTable" v-bind="$data" />
<UploadDialog :visible.sync="uploadDialogVisible" @upload-event="handleUpload" />
</div>
</template>
<script>
import { ListTable } from '@/components'
import CardTable from './components/CardTable'
import UploadDialog from './UploadDialog'
export default {
name: 'Applets',
components: {
ListTable,
CardTable,
UploadDialog
},
data() {
@@ -19,55 +19,18 @@ export default {
uploadDialogVisible: false,
tableConfig: {
url: '/api/v1/terminal/applets/',
columnsShow: {
min: ['icon', 'name', 'version', 'author', 'protocols', 'actions'],
default: [
'icon', 'name', 'version', 'author', 'protocols',
'type', 'comment', 'actions'
]
},
columnsMeta: {
icon: {
align: 'center',
width: '60px',
formatter: (row) => {
return <img src={row.icon} width='30' height='30' alt='icon'></img>
}
},
name: {
formatter: function(row) {
return <span>{row.display_name}</span>
},
formatterArgs: {
getTitle: ({ row }) => row['display_name'],
getIcon: ({ row }) => row['icon']
}
},
version: {
width: '80px'
},
type: {
width: '80px'
},
protocols: {
formatter: (row) => {
return row.protocols.map(tag => <el-tag size='mini'>{tag}</el-tag>)
}
},
actions: {
formatterArgs: {
hasUpdate: false,
hasClone: false
}
}
}
deletePerm: 'terminal.delete_applet'
},
headerActions: {
onCreate: () => {
this.uploadDialogVisible = true
},
detailRoute: 'AppletDetail',
hasExport: false,
hasImport: false
hasImport: false,
hasBulkDelete: false,
hasBulkUpdate: false,
hasColumnSetting: false
// moreCreates: {
// callback: (option) => {
// this.uploadDialogVisible = true
@@ -88,7 +51,7 @@ export default {
},
methods: {
handleUpload(res) {
this.$refs.ListTable.reloadTable()
this.$refs.CardTable.reloadTable()
}
}
}

View File

@@ -0,0 +1,217 @@
<template>
<div class="el-card-table">
<TableAction
:table-url="tableUrl"
:search-table="search"
:reload-table="reloadTable"
v-bind="headerActions"
/>
<div style="padding-top: 15px">
<el-row :gutter="40">
<el-col v-for="(d, index) in totalData" :key="index" :span="4">
<el-card
shadow="hover"
:body-style="{ 'text-align': 'center', 'padding': '10px' }"
class="my-card"
@click.native="onView(d)"
>
<span class="closeIcon">
<i class="el-icon-close" @click.stop="onDelete(d)" />
</span>
<!-- <div style="padding-top: 15px">-->
<!-- <el-button v-if="$hasPerm(tableConfig.deletePerm)" type="danger" size="mini" @click="onDelete(d)">{{ $tc('common.Delete') }}</el-button>-->
<!-- </div>-->
<div>
<img :src="d.icon" class="image">
</div>
<div>{{ d.display_name }}</div>
<div style="margin: 10px 0" />
<el-tag size="mini">{{ d.author }}</el-tag>
<el-divider class="my-divider" />
<el-tooltip placement="top">
<div slot="content">{{ d.comment_i18n }}</div>
<div class="line-limit">{{ d.comment_i18n }}</div>
</el-tooltip>
</el-card>
</el-col>
</el-row>
</div>
<Pagination
ref="pagination"
v-bind="$data"
@sizeChange="handleSizeChange"
@currentSizeChange="handleCurrentChange"
/>
</div>
</template>
<script>
import TableAction from '@/components/ListTable/TableAction'
import { Pagination } from '@/components'
const defaultFirstPage = 1
export default {
name: 'CardTable',
components: {
TableAction,
Pagination
},
props: {
// 定义 table 的配置
tableConfig: {
type: Object,
default: () => ({})
},
headerActions: {
type: Object,
default: () => ({})
}
},
data() {
return {
total: 0,
totalData: [],
page: defaultFirstPage,
extraQuery: {},
paginationSize: 12,
paginationLayout: 'total, sizes, prev, pager, next',
paginationSizes: [12, 24, 36, 48, 60, 120],
axiosConfig: {
raw: 1,
params: {
display: 1,
draw: 1
}
}
}
},
computed: {
tableUrl() {
return this.tableConfig.url || ''
}
},
watch: {},
mounted() {
this.getList()
},
methods: {
getPageQuery(currentPage, pageSize) {
return this.$refs.pagination.getPageQuery(currentPage, pageSize)
},
getList() {
if (!this.tableUrl) {
return
}
const pageQuery = this.getPageQuery(this.page, this.paginationSize)
const query = Object.assign(this.extraQuery, pageQuery)
const queryString = Object.keys(query).map(key => key + '=' + query[key]).join('&')
const url = `${this.tableUrl}?${queryString}`
this.$axios
.get(url, this.axiosConfig)
.then(({ data: resp }) => {
this.total = resp?.count || 0
this.totalData = resp?.results || []
})
.catch(err => {
this.$log.error('Error occur: ', err)
this.total = 0
})
},
reloadTable() {
this.getList()
},
search(attrs) {
this.extraQuery = attrs
this.getList()
},
handleSizeChange(val) {
this.page = defaultFirstPage
this.paginationSize = val
this.getList()
},
handleCurrentChange(val) {
this.page = val
this.getList()
},
defaultPerformView(obj) {
const defaultRoute = this.$route.name.replace('List', 'Detail')
const route = this.headerActions.detailRoute || defaultRoute
let detailRoute = { replace: true }
if (typeof route === 'string') {
detailRoute.name = route
detailRoute.params = { id: obj.id }
} else {
detailRoute = route
}
console.log(detailRoute)
this.$router.push(detailRoute)
},
defaultPerformDelete(obj) {
this.$axios.delete(
`${this.tableConfig.url}${obj.id}/`
)
},
onView(obj) {
const viewFunc = this.tableConfig.onView || this.defaultPerformView
viewFunc(obj)
},
onDelete(obj) {
const msg = `${this.$t('common.deleteWarningMsg')} "${obj.name}" ?`
this.$confirm(msg, this.$t('common.Info'), {
type: 'warning',
confirmButtonClass: 'el-button--danger',
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
const deleteFunc = this.tableConfig.onDelete || this.defaultPerformDelete
await deleteFunc(obj)
done()
this.reloadTable()
this.$message.success(this.$tc('common.deleteSuccessMsg'))
}
}).catch(() => {
/* 取消*/
})
}
}
}
</script>
<style lang="scss" scoped>
.my-card {
margin: 0 0 20px 0;
}
.my-divider {
margin: 10px 0
}
.image {
width: 60px;
height: 60px;
display: block;
margin: 0 auto;
}
.line-limit {
line-height: 14px;
height: 34px;
overflow: hidden;
text-overflow: ellipsis;
}
.closeIcon {
text-align: right;
display: block;
visibility: hidden;
i {
font-size: 20px;
cursor: pointer;
}
}
.my-card:hover .closeIcon {
visibility: visible;
}
</style>