Browse Source

```

feat(alert): 为模型卡片列表添加分页和无限滚动功能

- 添加 PageResult 泛型接口用于分页数据结构
- 添加 ModelCardQueryParams 接口支持分页参数
- 修改 modelCardListApi 返回类型为 PageResult<ModelCardItem>
- 实现无限滚动加载功能,使用 IntersectionObserver 监听滚动
- 添加加载状态显示(加载中、没有更多、暂无数据)
- 优化模型列表加载逻辑,支持追加加载模式
- 添加页面生命周期钩子处理
pull/93/head
chenjiale 3 weeks ago
parent
commit
a8520e20ba
  1. 13
      src/api/alert/model/model/models.ts
  2. 6
      src/api/alert/model/models.ts
  3. 134
      src/views/model/list/ModelCard.vue

13
src/api/alert/model/model/models.ts

@ -7,6 +7,14 @@ export interface ModelCardItem {
createTime: string createTime: string
} }
export interface PageResult<T> {
total: number
current: number
size: number
records: T[]
pages: number
}
interface MovingWindows { interface MovingWindows {
windowLength: number windowLength: number
samplingInterval: number samplingInterval: number
@ -32,3 +40,8 @@ export interface ModelQueryParams {
visible?: number | null visible?: number | null
trash?: number | null trash?: number | null
} }
export interface ModelCardQueryParams extends ModelQueryParams {
pageNo?: number
pageSize?: number
}

6
src/api/alert/model/models.ts

@ -1,4 +1,4 @@
import type { ModelCardItem, ModelInfo, ModelQueryParams } from './model/models' import type { ModelCardItem, ModelCardQueryParams, ModelInfo, PageResult } from './model/models'
import { defHttp } from '@/utils/http/axios' import { defHttp } from '@/utils/http/axios'
enum Api { enum Api {
@ -17,8 +17,8 @@ enum Api {
VERSION_LIST = '/alert/model/version/', VERSION_LIST = '/alert/model/version/',
VERSION_NEW = '/alert/model/version/new/', VERSION_NEW = '/alert/model/version/new/',
} }
export function modelCardListApi(params?: ModelQueryParams) { export function modelCardListApi(params?: ModelCardQueryParams) {
return defHttp.get<ModelCardItem[]>({ url: Api.MODEL_CARD_LIST, params }) return defHttp.get<PageResult<ModelCardItem>>({ url: Api.MODEL_CARD_LIST, params })
} }
export const modelInfoApi = (idOrPath: any) => defHttp.get<any>({ url: `${Api.MODEL_INFO}/${idOrPath}` }) export const modelInfoApi = (idOrPath: any) => defHttp.get<any>({ url: `${Api.MODEL_INFO}/${idOrPath}` })

134
src/views/model/list/ModelCard.vue

@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Card, Dropdown, Menu, Popconfirm } from 'ant-design-vue' import { Card, Dropdown, Menu, Popconfirm } from 'ant-design-vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { ref, watch } from 'vue' import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import type { ModelItem } from './data' import type { ModelItem } from './data'
import Icon from '@/components/Icon/index' import Icon from '@/components/Icon/index'
import { modelCardListApi, modelDeleteApi } from '@/api/alert/model/models' import { modelCardListApi, modelDeleteApi } from '@/api/alert/model/models'
import type { ModelQueryParams } from '@/api/alert/model/model/models' import type { ModelCardQueryParams } from '@/api/alert/model/model/models'
import { useGo } from '@/hooks/web/usePage' import { useGo } from '@/hooks/web/usePage'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
@ -34,12 +34,19 @@ function changeModel(id, version?) {
} }
const modelCardList = ref<Array<ModelItem>>([]) const modelCardList = ref<Array<ModelItem>>([])
const lastQuery = ref<ModelQueryParams | null>(null) const rootRef = ref<HTMLElement | null>(null)
const loadMoreTriggerRef = ref<HTMLElement | null>(null)
const lastQuery = ref<ModelCardQueryParams | null>(null)
const pageNo = ref(1)
const pageSize = 25
const listLoading = ref(false)
const noMore = ref(false)
let loadMoreObserver: IntersectionObserver | null = null
const colors = ['#8dc63f', '#dbb09e'] const colors = ['#8dc63f', '#dbb09e']
const statusStr = ['未下装', '已下装'] const statusStr = ['未下装', '已下装']
const statusIcons = ['ant-design:unlock-outlined', 'ant-design:lock-outlined'] const statusIcons = ['ant-design:unlock-outlined', 'ant-design:lock-outlined']
function buildQuery(value: any): ModelQueryParams { function buildQuery(value: any): ModelCardQueryParams {
return { return {
unitId: value?.unit, unitId: value?.unit,
typeId: value?.type, typeId: value?.type,
@ -48,14 +55,7 @@ function buildQuery(value: any): ModelQueryParams {
} }
} }
async function loadModelList(value: any) { function normalizeCards(modelList: any[] = []): ModelItem[] {
const queryParams = buildQuery(value)
lastQuery.value = queryParams
const modelList = await modelCardListApi(queryParams)
if (!modelList) {
modelCardList.value = []
return
}
const cardList: ModelItem[] = [] const cardList: ModelItem[] = []
for (const modelCard of modelList) { for (const modelCard of modelList) {
const statusIndex = Number(modelCard.status) || 0 const statusIndex = Number(modelCard.status) || 0
@ -78,7 +78,40 @@ async function loadModelList(value: any) {
} }
cardList.push(card) cardList.push(card)
} }
modelCardList.value = cardList return cardList
}
async function fetchModelList(queryParams: ModelCardQueryParams, append: boolean) {
if (listLoading.value)
return
listLoading.value = true
try {
const pageResult = await modelCardListApi({
...queryParams,
pageNo: pageNo.value,
pageSize,
})
if (!pageResult) {
modelCardList.value = []
noMore.value = true
return
}
const records = pageResult.records || []
const cardList = normalizeCards(records)
modelCardList.value = append ? modelCardList.value.concat(cardList) : cardList
noMore.value = pageResult.pages ? pageResult.current >= pageResult.pages : records.length < pageSize
}
finally {
listLoading.value = false
}
}
async function loadModelList(value: any) {
pageNo.value = 1
noMore.value = false
const queryParams = buildQuery(value)
lastQuery.value = queryParams
await fetchModelList(queryParams, false)
} }
watch( watch(
@ -103,16 +136,64 @@ function goCreateModel() {
query.push(`unitId=${encodeURIComponent(String(props.unitId))}`) query.push(`unitId=${encodeURIComponent(String(props.unitId))}`)
go(`/model/create${query.length ? `?${query.join('&')}` : ''}`) go(`/model/create${query.length ? `?${query.join('&')}` : ''}`)
} }
async function loadMore() {
if (listLoading.value || noMore.value || !lastQuery.value)
return
pageNo.value += 1
await fetchModelList(lastQuery.value, true)
}
function getScrollParent(el: HTMLElement | null) {
let current = el?.parentElement || null
while (current) {
const style = window.getComputedStyle(current)
if (/(auto|scroll)/.test(style.overflowY))
return current
current = current.parentElement
}
return null
}
function initLoadMoreObserver() {
if (loadMoreObserver) {
loadMoreObserver.disconnect()
loadMoreObserver = null
}
const target = loadMoreTriggerRef.value
if (!target)
return
const root = getScrollParent(rootRef.value)
loadMoreObserver = new IntersectionObserver(
(entries) => {
if (entries.some(entry => entry.isIntersecting))
loadMore()
},
{ root, rootMargin: '160px 0px' },
)
loadMoreObserver.observe(target)
}
onMounted(() => {
initLoadMoreObserver()
})
onBeforeUnmount(() => {
if (loadMoreObserver) {
loadMoreObserver.disconnect()
loadMoreObserver = null
}
})
</script> </script>
<template> <template>
<div class="enter-y"> <div ref="rootRef" class="enter-y">
<div class="card-grid"> <div class="card-grid">
<template v-for="item in modelCardList" :key="item.title"> <template v-for="item in modelCardList" :key="item.title">
<Dropdown :trigger="['contextmenu']"> <Dropdown :trigger="['contextmenu']">
<Card <Card
size="small" size="small"
:loading="loading" :loading="loading || (listLoading && !modelCardList.length)"
:hoverable="true" :hoverable="true"
class="model-card" class="model-card"
:style="{ borderLeft: `6px solid ${item.statusColor}` }" :style="{ borderLeft: `6px solid ${item.statusColor}` }"
@ -168,6 +249,14 @@ function goCreateModel() {
<Icon icon="ic:sharp-add" :size="80" color="#a7a9a7" /> <Icon icon="ic:sharp-add" :size="80" color="#a7a9a7" />
</Card> </Card>
</div> </div>
<div ref="loadMoreTriggerRef" class="load-more-trigger" />
<div v-if="modelCardList.length" class="load-status">
<span v-if="listLoading">加载中...</span>
<span v-else-if="noMore">没有更多了</span>
</div>
<div v-else-if="!listLoading" class="load-status empty">
暂无数据
</div>
</div> </div>
</template> </template>
@ -315,4 +404,19 @@ function goCreateModel() {
background-color: #eef2ff; background-color: #eef2ff;
border-color: #4c7af0; border-color: #4c7af0;
} }
.load-status {
padding: 12px 0 4px;
font-size: 12px;
color: #9ca3af;
text-align: center;
}
.load-status.empty {
padding-top: 24px;
}
.load-more-trigger {
height: 1px;
}
</style> </style>

Loading…
Cancel
Save