perf: add loading page

This commit is contained in:
ibuler
2025-03-05 18:45:38 +08:00
parent bfab828813
commit d4bcfb3750
14 changed files with 106 additions and 353 deletions

View File

@@ -268,6 +268,7 @@ export default {
}
.drawer__content, .tab-page-content {
height: 100%;
background: #f3f3f3;
}

View File

@@ -1,25 +1,27 @@
<template>
<DataForm
v-if="!loading"
ref="dataForm"
:fields="totalFields"
:form="iForm"
v-bind="$attrs"
v-on="$listeners"
>
<template
v-for="(group, i) in groups"
:slot="'id:'+group.name"
<div v-loading="loading">
<DataForm
v-if="!loading"
ref="dataForm"
:fields="totalFields"
:form="iForm"
v-bind="$attrs"
v-on="$listeners"
>
<FormGroupHeader
v-if="!groupHidden(group, i)"
:key="'group-' + group.name"
:group="group"
:index="i"
:line="i !== 0 && !groupHidden(groups[i - 1], i - 1)"
/>
</template>
</DataForm>
<template
v-for="(group, i) in groups"
:slot="'id:'+group.name"
>
<FormGroupHeader
v-if="!groupHidden(group, i)"
:key="'group-' + group.name"
:group="group"
:index="i"
:line="i !== 0 && !groupHidden(groups[i - 1], i - 1)"
/>
</template>
</DataForm>
</div>
</template>
<script>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div v-loading="loading">
<DataTable
v-if="!loading"
ref="dataTable"

View File

@@ -10,8 +10,10 @@
<IBox v-if="totalData.length === 0">
<el-empty :description="$t('NoData')" :image-size="200" class="no-data" style="padding: 20px" />
</IBox>
<el-col v-for="(d, index) in totalData" :key="index" :lg="8" :md="12" :sm="24" class="el-col">
<div class="card-container">
<el-card
v-for="(d, index) in totalData"
:key="index"
:body-style="{ 'text-align': 'center', 'padding': '15px' }"
:class="{'is-disabled': isDisabled(d)}"
class="my-card"
@@ -52,7 +54,7 @@
</slot>
</keep-alive>
</el-card>
</el-col>
</div>
</el-row>
<Pagination
v-show="pagination && total > paginationSize"
@@ -207,9 +209,7 @@ export default {
this.$router.push(detailRoute)
},
defaultPerformDelete(obj) {
this.$axios.delete(
`${this.tableConfig.url}${obj.id}/`
)
this.$axios.delete(`${this.tableConfig.url}${obj.id}/`)
},
onView(obj) {
if (this.isDisabled(obj)) {
@@ -246,11 +246,15 @@ export default {
text-align: center;
.el-col, div {
display: flex;
gap: 20px;
.my-card {
min-width: 330px;
width: 100%;
position: relative;
margin-bottom: 20px;
height: 230px;
width: 380px;
::v-deep .el-card__body {
height: 100%;

View File

@@ -20,7 +20,7 @@
v-bind="iHeaderActions"
@done="handleActionInitialDone"
/>
<IBox class="table-content">
<IBox v-loading="!actionInit" class="table-content">
<AutoDataTable
v-if="actionInit"
ref="dataTable"

View File

@@ -1,5 +1,5 @@
<template>
<span v-if="loading" v-loading="loading" class="loading" />
<span v-if="loading" v-loading="loading" :style="{ height: loadingHeight + 'px'}" class="loading" />
</template>
<script>
@@ -9,11 +9,14 @@ export default {
loading: {
type: Boolean,
default: true
},
loadingHeight: {
type: Number,
default: 200
}
},
data() {
return {
}
return {}
}
}
</script>

View File

@@ -1,18 +1,20 @@
<template>
<AutoDataForm
v-if="!loading"
ref="form"
:form="form"
:has-reset="iHasReset"
:has-save-continue="iHasSaveContinue"
:is-submitting="isSubmitting"
:method="method"
:url="iUrl"
v-bind="$attrs"
@afterRemoteMeta="handleAfterRemoteMeta"
@submit="handleSubmit"
v-on="$listeners"
/>
<div v-loading="loading">
<AutoDataForm
v-if="!loading"
ref="form"
:form="form"
:has-reset="iHasReset"
:has-save-continue="iHasSaveContinue"
:is-submitting="isSubmitting"
:method="method"
:url="iUrl"
v-bind="$attrs"
@afterRemoteMeta="handleAfterRemoteMeta"
@submit="handleSubmit"
v-on="$listeners"
/>
</div>
</template>
<script>
import AutoDataForm from '@/components/Form/AutoDataForm'
@@ -297,6 +299,7 @@ export default {
this.$log.debug('Object init is: ', this.object, this.method)
await this.setDrawerMeta()
this.setMethod()
try {
const values = await this.getFormValue()
this.$log.debug('Final object is: ', values)

View File

@@ -694,3 +694,7 @@ li.rmenu i.fa {
//background-color: #fff;
}
}
div.el-loading-parent--relative {
min-height: 200px;
}

View File

@@ -2,7 +2,7 @@
<div>
<SmallCard ref="table" class="account-table" v-bind="$data" />
<CreateDialog v-if="visible" :visible.sync="visible" v-bind="providerConfig" />
<Dialog
<Drawer
v-if="updateVisible"
:destroy-on-close="true"
:show-buttons="false"
@@ -17,7 +17,7 @@
origin="update"
@submitSuccess="onSubmitSuccess"
/>
</Dialog>
</Drawer>
<Dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
@@ -65,6 +65,7 @@ import CreateDialog from './components/CreateDialog.vue'
import SmallCard from '@/components/Table/CardTable/DataCardTable/index.vue'
import { ACCOUNT_PROVIDER_ATTRS_MAP } from '@/views/assets/Cloud/const'
import Dialog from '@/components/Dialog/index.vue'
import Drawer from '@/components/Drawer/index.vue'
import AssetPanel from './components/AssetPanel.vue'
import AuthPanel from './components/AuthPanel.vue'
import { toSafeLocalDateStr } from '@/utils/time'
@@ -72,6 +73,7 @@ import { toSafeLocalDateStr } from '@/utils/time'
export default {
name: 'CloudAccountList',
components: {
Drawer,
AuthPanel,
AssetPanel,
Dialog,

View File

@@ -1,8 +1,9 @@
<template>
<Dialog
<Drawer
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:has-footer="false"
:show-buttons="false"
:show-close="false"
:title="$tc('CloudSyncConfig')"
@@ -12,7 +13,7 @@
v-on="$listeners"
>
<el-row :gutter="5" style="padding: 10px">
<el-col :span="4" class="left-step-zone">
<el-col :span="6" class="left-step-zone">
<el-steps :active="active" direction="vertical">
<el-step :description="firstStepDesc" />
<el-step :description="$tc('Authentication')" />
@@ -20,7 +21,7 @@
<el-step :description="$tc('Result')" />
</el-steps>
</el-col>
<el-col :span="20">
<el-col :span="18">
<component
:is="activeMenu"
:active.sync="active"
@@ -32,11 +33,11 @@
/>
</el-col>
</el-row>
</Dialog>
</Drawer>
</template>
<script>
import Dialog from '@/components/Dialog'
import Drawer from '@/components/Drawer'
import ProviderPanel from '@/views/assets/Cloud/Account/components/ProviderPanel'
import AuthPanel from '@/views/assets/Cloud/Account/components/AuthPanel'
import AssetPanel from '@/views/assets/Cloud/Account/components/AssetPanel'
@@ -46,7 +47,7 @@ import { ACCOUNT_PROVIDER_ATTRS_MAP } from '@/views/assets/Cloud/const'
export default {
name: 'CreateDialog',
components: {
Dialog,
Drawer,
AuthPanel,
AssetPanel,
ResultPanel,
@@ -111,6 +112,10 @@ export default {
.left-step-zone {
border-right: solid 1px var(--color-border);
height: 350px;
.el-steps {
padding-left: 15px;
}
}
::v-deep .el-step {

View File

@@ -18,16 +18,17 @@
</el-card>
</el-col>
</el-row>
<el-row>
<el-button size="small" style="float: right;" @click="handleCancel">{{ $tc('Cancel') }}</el-button>
<el-button
size="small"
style="float: right; margin-right: 10px;"
type="primary"
@click="handleNext"
>
{{ $tc('Next') }}
</el-button>
<el-row class="buttons">
<el-col>
<el-button size="small" @click="handleCancel">{{ $tc('Cancel') }}</el-button>
<el-button
size="small"
type="primary"
@click="handleNext"
>
{{ $tc('Next') }}
</el-button>
</el-col>
</el-row>
</div>
</template>
@@ -77,6 +78,10 @@ export default {
</script>
<style lang='scss' scoped>
.buttons {
margin-top: 10px;
}
.cloud-select-wrap {
height: 300px;

View File

@@ -6,7 +6,7 @@
</template>
<script>
import CardTable from './components/CardTable'
import CardTable from '@/components/Table/CardTable'
import UploadDialog from './UploadDialog'
export default {

View File

@@ -1,278 +0,0 @@
<template>
<div class="el-card-table">
<TableAction
:reload-table="reloadTable"
:search-table="search"
:table-url="tableUrl"
v-bind="headerActions"
/>
<div style="padding-top: 15px">
<el-row :gutter="20">
<IBox v-if="totalData.length === 0" class="empty-box">
<el-empty />
</IBox>
<el-col v-for="(d, index) in totalData" :key="index" :span="6">
<el-card
:body-style="{ 'text-align': 'center', 'padding': '20px' }"
class="my-card"
shadow="hover"
@click.native="onView(d)"
>
<span v-if="d.edition === 'enterprise'" class="enterprise">
{{ $t('Enterprise') }}
</span>
<el-row :gutter="20">
<el-col :span="8">
<img :src="d.icon" class="image">
</el-col>
<el-col :span="16" style="text-align: left; padding: 5px 0">
<div class="one-line">
<b>{{ d.display_name }}</b>
<el-tag size="mini" style="margin-left: 5px">
{{ d.version }}
</el-tag>
</div>
<el-divider class="my-divider" />
<div class="comment">
{{ d.comment }}
</div>
<el-tag v-for="tag of d.tags" :key="tag" size="mini">
{{ capitalize(tag) }}
</el-tag>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
</div>
<Pagination
ref="pagination"
v-bind="$data"
@currentSizeChange="handleCurrentChange"
@sizeChange="handleSizeChange"
/>
</div>
</template>
<script>
import TableAction from '@/components/Table/ListTable/TableAction'
import { Pagination } from '@/components'
import { toSafeLocalDateStr } from '@/utils/time'
import IBox from '@/components/IBox/index.vue'
const defaultFirstPage = 1
export default {
name: 'CardTable',
components: {
IBox,
TableAction,
Pagination
},
props: {
// 定义 table 的配置
tableConfig: {
type: Object,
default: () => ({})
},
headerActions: {
type: Object,
default: () => ({})
}
},
data() {
return {
total: 0,
totalData: [],
page: defaultFirstPage,
extraQuery: {},
paginationSize: 8,
paginationLayout: 'total, sizes, prev, pager, next',
paginationSizes: [8, 12, 20, 36, 52, 120],
axiosConfig: {
raw: 1,
params: {
display: 1,
draw: 1
}
}
}
},
computed: {
tableUrl() {
return this.tableConfig.url || ''
}
},
mounted() {
this.getList()
},
methods: {
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}" />`
},
convertData(data) {
return toSafeLocalDateStr(data)
},
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
}
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('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>
.my-card {
margin: 0 0 20px 0;
position: relative;
}
.my-divider {
margin: 10px 0
}
.image {
width: 60px;
height: 60px;
display: block;
margin: 50% auto;
}
.one-line {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.comment {
font-size: 12px;
height: 50px;
overflow: hidden;
margin-bottom: 10px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.closeIcon {
float: right;
display: block;
visibility: hidden;
i {
font-size: 20px;
cursor: pointer;
}
}
.my-card:hover {
cursor: pointer;
}
.my-card:hover .closeIcon {
visibility: visible;
}
.empty-box ::v-deep .el-empty {
max-width: 200px;
margin: 0 auto;
.el-empty__description {
margin-top: 20px;
text-align: center;
}
}
.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 10px;
}
</style>

View File

@@ -1,21 +1,23 @@
<template>
<GenericCreateUpdatePage
v-if="!loading"
class="user-create-update"
v-bind="$data"
@getObjectDone="afterGetUser"
v-on="$listeners"
/>
<div v-loading="loading">
<GenericCreateUpdatePage
v-if="!loading"
class="user-create-update"
v-bind="$data"
@getObjectDone="afterGetUser"
v-on="$listeners"
/>
</div>
</template>
<script>
import store from '@/store'
import { mapGetters } from 'vuex'
import { Select2 } from '@/components'
import { GenericCreateUpdatePage } from '@/layout/components'
import { PhoneInput, UserPassword } from '@/components/Form/FormFields'
import rules from '@/components/Form/DataForm/rules'
import { mapGetters } from 'vuex'
import { Select2 } from '@/components'
import store from '@/store'
import { MFASystemSetting, MFALevel } from '../const'
import { MFALevel, MFASystemSetting } from '../const'
export default {
components: {