vue3 表格下拉刷新
<script setup lang="ts"> import { Empty } from 'ant-design-vue'; export interface TBColumn { title: string dataIndex: string // key: string ellipsis?: boolean width?: string align?: string [key: string]: any } const props = withDefaults(defineProps<{ modelValue: any[] // 重要: 外部在使用这个的时候,不要修改,要通过endSuccess这些方法来修改 columns: TBColumn[] defaultPageSize: number defaultPage: number }>(), { modelValue: () => [], columns: () => [], defaultPageSize: 10, defaultPage: 1, }); const emit = defineEmits<{ (e: 'update:modelValue', val: any[]): void (e: 'load', val: { page: number, pageSize: number }): void (e: 'clickRow', val: any): void }>(); const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE; const dataSource = ref<any[]>([]); const page = ref(props.defaultPage); const pageSize = ref(props.defaultPageSize); const noMoreData = ref(false); // 是否有更多数据 const loading = ref(false); // 是否正在加载中 let lockFlag = false; // 锁,用于保证一次加载只能调用一次回调方法 const clickIdx = ref(-1); // 当前点击的数据序号 watch(() => props.modelValue, () => { dataSource.value = props.modelValue || []; }, { immediate: true }); onMounted(() => { // dataSource.value = []; // page.value = props.defaultPage; // pageSize.value = props.defaultPageSize; // noMoreData.value = false; // loading.value = false; nextTick(() => { lockFlag = false; emit('load', { page: page.value, pageSize: pageSize.value }); }); }); /** * 加载更多 */ function loadMore() { if (noMoreData.value) { return; } if (loading.value) { return; } loading.value = true; lockFlag = false; emit('load', { page: page.value, pageSize: pageSize.value }); } function endSuccess(data: any[]) { if (lockFlag) { return; } lockFlag = true; if (page.value === 1) { dataSource.value = data; } else { dataSource.value = [...dataSource.value, ...data]; } if (data.length < pageSize.value) { noMoreData.value = true; } else { noMoreData.value = false; page.value += 1; } emit('update:modelValue', [...dataSource.value]); loading.value = false; } function endError() { if (lockFlag) { return; } lockFlag = true; loading.value = false; } function reload() { if (loading.value && page.value === props.defaultPage) { return; } dataSource.value = []; page.value = props.defaultPage; pageSize.value = props.defaultPageSize; noMoreData.value = false; loading.value = false; clickIdx.value = -1; emit('update:modelValue', []); lockFlag = false; emit('load', { page: page.value, pageSize: pageSize.value }); } function onClickRow(val: any) { if (clickIdx.value === val.index) { clickIdx.value = -1; } else { clickIdx.value = val.index; } emit('clickRow', { ...val, highlighted: clickIdx.value !== -1 }); } function deselectRow() { clickIdx.value = -1; } defineExpose({ endSuccess, endError, reload, deselectRow, }); </script> <template> <div class="loadmore-table"> <div class="loadmore-table-head"> <div v-for="(column, index) in columns" :key="index" class="loadmore-table-th" :style="{ width: column.width ? column.width : 'auto', textAlign: column.align ? column.align : 'center', }" > {{ column.title }} </div> </div> <ScrollLoad class="loddmore-table-body" @load-more="loadMore"> <div v-if="!dataSource.length"> <a-empty :image="simpleImage" /> </div> <div v-else> <div v-for="(record, index) in dataSource" :key="index" class="loadmore-table-tr" :class="[clickIdx === index ? 'actived-cls' : '']" > <div v-for="(column, idx) in columns" :key="idx" class="loadmore-table-td" :style="{ width: column.width ? column.width : 'auto', textAlign: column.align ? column.align : 'center', }" @click="onClickRow({ text: record[column.dataIndex], record, index, column })" > <div class="loadmore-table-td-con" :class="[column.ellipsis ? 'ellipsis' : '']"> <slot name="bodyCell" :text="record[column.dataIndex]" :record="record" :index="index" :column="column"> <a-tooltip> <template #title> {{ record[column.dataIndex] || '' }} </template> {{ record[column.dataIndex] || '' }} </a-tooltip> </slot> </div> </div> </div> </div> </ScrollLoad> </div> </template> <style lang="less" scoped> .loadmore-table { width: 100%; height: 100%; overflow: hidden; display: flex; flex-direction: column; .loadmore-table-head { display: flex; background-color: rgba(28, 85, 155, 0.7); .loadmore-table-th { flex-shrink: 1; flex-grow: 1; position: relative; padding: 7px 5px; // height: 34px; line-height: 20px; font-weight: 500; font-size: 15px; color: #FFFFFF; &::after { content: ''; display: inline-block; width: 1px; height: 20px; background-color: #1C559B; position: absolute; top: 50%; right: 0; transform: translateY(-50%); } } } .loddmore-table-body { flex: 1; overflow: auto; padding-top: 4px; .loadmore-table-tr { display: flex; .loadmore-table-td { flex-shrink: 1; flex-grow: 1; display: flex; align-items: center; // height: 40px; line-height: 20px; padding: 10px 5px; font-weight: 400; font-size: 15px; color: rgba(255, 255, 255, 0.9); overflow: hidden; .loadmore-table-td-con { flex-grow: 1; flex-shrink: 1; } } } .loadmore-table-tr:nth-of-type(2n) { background-color: rgba(10, 46, 81, 0.4); } .ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } } </style>
<script setup lang="ts"> import dayjs from 'dayjs'; import loadMoreTable from '@/views/components/loadMoreTable.vue'; import type { TBColumn } from '@/views/components/loadMoreTable.vue'; import { postAlarmAlarmSum } from '@/api/modules/backend'; const emit = defineEmits<{ (e: 'choose', val: any): void (e: 'addAlarm', val: any): void }>(); const tableRef = ref(); const list = ref<any[]>([]); // 表格选项 const columns: TBColumn[] = [ { title: '设备ID/SN', dataIndex: 'deviceSn', key: 'deviceSn', ellipsis: true, width: '48%', align: 'left', }, { title: '更新时间', dataIndex: 'maxAlarmTime', key: 'maxAlarmTime', ellipsis: true, width: '24%', align: 'center', }, { title: '告警', dataIndex: 'alarmCount', key: 'alarmCount', ellipsis: true, width: '14%', align: 'center', }, { title: '处置', dataIndex: 'alarmStatus', key: 'alarmStatus', ellipsis: true, width: '14%', align: 'center', }, ]; async function featchData(pageConfig: { page: number, pageSize: number }) { try { const params = { currentPage: pageConfig.page, pageSize: pageConfig.pageSize, }; const res = await postAlarmAlarmSum(params); if (res.success) { const arr = res.data?.records || []; tableRef.value.endSuccess(arr); } else { tableRef.value.endError(); } } /* eslint-disable-next-line */ catch (error) { tableRef.value.endError(); } } function onClickRow(val: any) { const obj = { ...val.record, highlighted: val.highlighted }; emit('choose', obj); emit('addAlarm', obj); } function deselectRow() { tableRef.value.deselectRow(); } function getData() { tableRef.value?.reload(); } defineExpose({ getData, deselectRow, }); </script> <template> <div class="w-full h-full"> <loadMoreTable ref="tableRef" v-model="list" :columns="columns" :default-page-size="25" :default-page="1" @load="featchData" @click-row="onClickRow" > <template #bodyCell="{ column, text }"> <template v-if="column.dataIndex === 'maxAlarmTime'"> <div>{{ text ? dayjs(text).format('HH:mm:ss') : '' }}</div> </template> <template v-if="column.dataIndex === 'alarmCount'"> <div class="text-[#FF4242] font-medium"> {{ text }} </div> </template> <template v-if="column.dataIndex === 'alarmStatus'"> <div :class="[text === '1' ? 'tag-gray' : text === '3' ? 'tag-green' : '']"> {{ text === '1' ? '否' : text === '3' ? '是' : text }} </div> </template> </template> </loadMoreTable> </div> </template> <style lang="less" scoped> .tag-green { width: 28px; height: 20px; background: #002800; background-image: radial-gradient(circle at 50% 100%, #00ff47c9 0%, #00430b00 76%); box-shadow: inset 0 0 20px 0 #00ff0361; border-radius: 2px; } .tag-gray { width: 28px; height: 20px; background: #333333; background-image: radial-gradient(circle at 50% 100%, #cdcdcdc9 0%, #43434300 76%); box-shadow: inset 0 0 20px 0 #cfcfcf61; border-radius: 2px; } :deep(.loadmore-table) { .loadmore-table-td { color: rgba(255, 255, 255, 0.6) !important; } .actived-cls { box-shadow: 0 0 9px 0 #32669C, inset 0 0 21px 0 #4B8ED3; background-color: rgba(10, 46, 81, 1) !important; border-radius: 4px; } } </style>