mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-15 14:24:39 +00:00
387 lines
9.3 KiB
Vue
387 lines
9.3 KiB
Vue
<template>
|
|
<div class="el-card-table">
|
|
<TableAction
|
|
:reload-table="reloadTable"
|
|
:search-table="search"
|
|
:table-url="tableUrl"
|
|
v-bind="headerActions"
|
|
/>
|
|
<el-row :gutter="10" class="the-row">
|
|
<IBox v-if="totalData.length === 0">
|
|
<el-empty :description="$t('NoData')" class="no-data" :image-size="200" style="padding: 20px" />
|
|
</IBox>
|
|
<el-col v-for="(d, index) in totalData" :key="index" :lg="8" :md="12" :sm="24" style="min-width: 335px;">
|
|
<el-card
|
|
:body-style="{ 'text-align': 'center', 'padding': '15px' }"
|
|
:class="{'is-disabled': isDisabled(d)}"
|
|
class="my-card"
|
|
shadow="hover"
|
|
@click.native="onView(d)"
|
|
>
|
|
<keep-alive>
|
|
<component :is="subComponent" v-if="subComponent" :object="d" @refresh="getList" />
|
|
<slot v-else :index="index" :item="d">
|
|
<span v-if="d.edition === 'enterprise'" class="enterprise">
|
|
{{ $t('Enterprise') }}
|
|
</span>
|
|
<el-row>
|
|
<el-col v-if="d.icon" :span="8" class="image">
|
|
<img
|
|
v-if="d.icon.startsWith('/') || d.icon.startsWith('data:')"
|
|
:alt="d.display_name"
|
|
:src="d.icon"
|
|
>
|
|
<Icon v-else :icon="d.icon" />
|
|
</el-col>
|
|
<el-col :span="16" class="text-zone">
|
|
<div class="one-line">
|
|
<b>{{ d.display_name }}</b>
|
|
<el-tag v-if="d.version" size="mini" style="margin-left: 5px">
|
|
{{ d.version }}
|
|
</el-tag>
|
|
</div>
|
|
<div :title="d.comment " class="comment">
|
|
{{ d.comment }}
|
|
</div>
|
|
<div class="tag-zone">
|
|
<el-tag v-for="tag of d.tags" :key="tag" size="mini">
|
|
{{ capitalize(tag) }}
|
|
</el-tag>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
</slot>
|
|
</keep-alive>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
<Pagination
|
|
v-show="pagination && total > paginationSize"
|
|
ref="pagination"
|
|
class="pagination"
|
|
v-bind="$data"
|
|
@currentSizeChange="handleCurrentChange"
|
|
@sizeChange="handleSizeChange"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import TableAction from '@/components/Table/ListTable/TableAction'
|
|
import { Pagination } from '@/components'
|
|
import Icon from '@/components/Widgets/Icon/index.vue'
|
|
import { mapGetters } from 'vuex'
|
|
import IBox from '@/components/IBox/index.vue'
|
|
|
|
const defaultFirstPage = 1
|
|
|
|
export default {
|
|
name: 'CardTable',
|
|
components: {
|
|
IBox,
|
|
TableAction,
|
|
Pagination,
|
|
Icon
|
|
},
|
|
props: {
|
|
// 定义 table 的配置
|
|
tableConfig: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
headerActions: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
pagination: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
subComponent: {
|
|
type: Object,
|
|
default: () => null
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
total: 0,
|
|
totalData: [],
|
|
page: defaultFirstPage,
|
|
extraQuery: {},
|
|
paginationSize: 6,
|
|
paginationLayout: 'total, sizes, prev, pager, next',
|
|
paginationSizes: [6, 18, 27],
|
|
axiosConfig: {
|
|
raw: 1,
|
|
params: {
|
|
display: 1,
|
|
draw: 1
|
|
}
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
...mapGetters(['hasValidLicense']),
|
|
tableUrl() {
|
|
return this.tableConfig.url || ''
|
|
}
|
|
},
|
|
mounted() {
|
|
this.getList()
|
|
},
|
|
methods: {
|
|
isDisabled(item) {
|
|
return item.edition?.value === 'enterprise' && !this.hasValidLicense
|
|
},
|
|
capitalize(str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
},
|
|
getIcon(status) {
|
|
let iconClass = 'fa-check-circle'
|
|
if (status === false) {
|
|
iconClass = 'fa-times-circle'
|
|
}
|
|
return `<i class="fa ${iconClass}" />`
|
|
},
|
|
getPageQuery(currentPage, pageSize) {
|
|
return this.$refs.pagination.getPageQuery(currentPage, pageSize)
|
|
},
|
|
getList() {
|
|
if (this.tableConfig.totalData) {
|
|
this.totalData = this.tableConfig.totalData
|
|
this.total = this.totalData.length
|
|
return
|
|
}
|
|
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) {
|
|
console.log(this.$route.name)
|
|
const defaultRoute = this.$route.name.replace('List', 'Detail')
|
|
const route = this.headerActions.detailRoute || defaultRoute
|
|
let detailRoute = { replace: true }
|
|
console.log(route)
|
|
if (typeof route === 'string') {
|
|
detailRoute.name = route
|
|
detailRoute.params = { id: obj.id }
|
|
} else {
|
|
detailRoute = route
|
|
}
|
|
|
|
console.log(defaultRoute)
|
|
this.$router.push(detailRoute)
|
|
},
|
|
defaultPerformDelete(obj) {
|
|
this.$axios.delete(
|
|
`${this.tableConfig.url}${obj.id}/`
|
|
)
|
|
},
|
|
onView(obj) {
|
|
if (this.isDisabled(obj)) {
|
|
return
|
|
}
|
|
const viewFunc = this.tableConfig.onView || this.defaultPerformView
|
|
viewFunc(obj)
|
|
},
|
|
onDelete(obj) {
|
|
const msg = `${this.$t('DeleteWarningMsg')} "${obj.name}" ?`
|
|
this.$confirm(msg, this.$tc('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('DeleteSuccessMsg'))
|
|
}
|
|
}).catch(() => {
|
|
/* 取消*/
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.the-row {
|
|
margin-top: 15px;
|
|
max-width: 1600px;
|
|
text-align: center;
|
|
|
|
.el-col, div {
|
|
.my-card {
|
|
min-width: 330px;
|
|
width: 100%;
|
|
position: relative;
|
|
margin-bottom: 20px;
|
|
|
|
::v-deep .el-card__body {
|
|
height: 100%;
|
|
|
|
.el-row {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
margin-top: 0;
|
|
height: 100%;
|
|
|
|
.image {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
img {
|
|
width: 60px;
|
|
height: 60px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
svg {
|
|
width: 40px;
|
|
height: 40px;
|
|
}
|
|
}
|
|
|
|
.text-zone {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
|
|
.one-line {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
padding-top: 10px;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
|
|
b {
|
|
padding-right: 5px;
|
|
}
|
|
|
|
span {
|
|
margin-left: 0 !important;
|
|
}
|
|
}
|
|
|
|
.comment {
|
|
display: -webkit-box;
|
|
height: 80px;
|
|
font-size: 12px;
|
|
padding: 10px 0;
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
-webkit-line-clamp: 4;
|
|
-webkit-box-flex: 1;
|
|
-webkit-box-orient: vertical;
|
|
text-align: left;
|
|
}
|
|
|
|
.tag-zone {
|
|
display: flex;
|
|
height: 30%;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
&.is-disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
&:hover {
|
|
.closeIcon {
|
|
visibility: visible;
|
|
}
|
|
}
|
|
|
|
.closeIcon {
|
|
float: right;
|
|
display: block;
|
|
visibility: hidden;
|
|
|
|
i {
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.enterprise {
|
|
position: absolute;
|
|
right: -1px;
|
|
top: -1px;
|
|
background-color: var(--color-primary);
|
|
color: #fff;
|
|
padding: 3px 8px 4px 9px;
|
|
font-size: 13px;
|
|
border-radius: 3px 3px 3px 8px;
|
|
}
|
|
|
|
.tag-zone {
|
|
margin-top: 10px;
|
|
|
|
.el-tag {
|
|
margin-right: 3px;
|
|
}
|
|
}
|
|
|
|
.text-zone {
|
|
text-align: left;
|
|
height: 100%;
|
|
}
|
|
|
|
.pagination {
|
|
padding-top: 10px;
|
|
border-top: 1px solid #e7eaec;
|
|
}
|
|
|
|
.no-data {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
height: 65vh;
|
|
width: 100%;
|
|
}
|
|
</style>
|