增添分页配置
This commit is contained in:
@@ -122,23 +122,46 @@
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-footer">
|
||||
<text class="total-txt">共 {{ dataList.length }} 条</text>
|
||||
<view class="page-select">
|
||||
<text class="page-val">10 条/页 ▼</text>
|
||||
</view>
|
||||
<view class="pagination-footer" v-if="dataList.length > 0 || pageState.loading">
|
||||
<text class="total-txt">共 {{ pageState.total }} 条</text>
|
||||
|
||||
<picker class="page-select" :range="pageSizeOptionLabels" :value="pageSizeIndex" @change="handlePageSizeChange">
|
||||
<view class="page-select-inner">
|
||||
<text class="page-val">{{ pageState.pageSize }} 条/页</text>
|
||||
<text class="arrow-down">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view class="page-btns">
|
||||
<view class="p-btn disabled"><text><</text></view>
|
||||
<view class="p-btn active"><text>1</text></view>
|
||||
<view class="p-btn"><text>2</text></view>
|
||||
<view class="p-btn"><text>></text></view>
|
||||
<view class="p-btn" :class="{ disabled: pageState.currentPage <= 1 }" @click="handlePageChange(pageState.currentPage - 1)">
|
||||
<text><</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-for="(p, index) in visiblePages"
|
||||
:key="index"
|
||||
class="p-btn"
|
||||
:class="{ active: p === pageState.currentPage, 'ellipsis-btn': p === -1 }"
|
||||
@click="p !== -1 && handlePageChange(p)">
|
||||
<text>{{ p === -1 ? '...' : p }}</text>
|
||||
</view>
|
||||
|
||||
<view class="p-btn" :class="{ disabled: pageState.currentPage >= totalPage }" @click="handlePageChange(pageState.currentPage + 1)">
|
||||
<text>></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-jump">
|
||||
<text class="jump-txt">前往</text>
|
||||
<input class="jump-input" placeholder="1" />
|
||||
<input class="jump-input" type="number" v-model="pageState.jumpPageInput" @confirm="handleJumpPage" @blur="handleJumpPage" placeholder="页码" />
|
||||
<text class="jump-txt">页</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空数据状态 -->
|
||||
<view class="table-empty" v-if="dataList.length === 0 && !pageState.loading">
|
||||
<text class="empty-txt">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -175,7 +198,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
interface CouponItem {
|
||||
@@ -220,30 +243,127 @@ const filter = reactive({
|
||||
|
||||
const dataList = ref<CouponItem[]>([])
|
||||
|
||||
// --- 分页与列表状态管理 ---
|
||||
const pageState = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
loading: false,
|
||||
jumpPageInput: ''
|
||||
})
|
||||
|
||||
const pageSizeOptions = [10, 20, 30, 50, 100]
|
||||
const pageSizeOptionLabels = computed((): string[] => pageSizeOptions.map((size: number): string => `${size} 条/页`))
|
||||
const pageSizeIndex = computed((): number => {
|
||||
const index = pageSizeOptions.indexOf(pageState.pageSize)
|
||||
return index === -1 ? 0 : index
|
||||
})
|
||||
|
||||
const totalPage = computed((): number => {
|
||||
return Math.ceil(pageState.total / pageState.pageSize)
|
||||
})
|
||||
|
||||
const visiblePages = computed((): number[] => {
|
||||
const current = pageState.currentPage
|
||||
const total = totalPage.value
|
||||
if (total <= 7) {
|
||||
const pages: number[] = []
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
if (current <= 4) {
|
||||
return [1, 2, 3, 4, 5, -1, total]
|
||||
}
|
||||
|
||||
if (current >= total - 3) {
|
||||
return [1, -1, total - 4, total - 3, total - 2, total - 1, total]
|
||||
}
|
||||
|
||||
return [1, -1, current - 1, current, current + 1, -1, total]
|
||||
})
|
||||
|
||||
const handlePageSizeChange = (e: any) => {
|
||||
if (pageState.loading) return
|
||||
let val = 0
|
||||
if (typeof e.detail.value === 'string') {
|
||||
val = parseInt(e.detail.value)
|
||||
} else {
|
||||
val = e.detail.value as number
|
||||
}
|
||||
pageState.pageSize = pageSizeOptions[val]
|
||||
pageState.currentPage = 1
|
||||
fetchCouponTemplates()
|
||||
}
|
||||
|
||||
const handlePageChange = (p: number) => {
|
||||
if (pageState.loading || p < 1 || p > totalPage.value || p === pageState.currentPage) return
|
||||
pageState.currentPage = p
|
||||
pageState.jumpPageInput = ''
|
||||
fetchCouponTemplates()
|
||||
}
|
||||
|
||||
const handleJumpPage = () => {
|
||||
if (pageState.loading) return
|
||||
let jumpTo = parseInt(pageState.jumpPageInput)
|
||||
if (isNaN(jumpTo)) return
|
||||
if (jumpTo < 1) jumpTo = 1
|
||||
if (jumpTo > totalPage.value) jumpTo = totalPage.value
|
||||
|
||||
pageState.jumpPageInput = String(jumpTo)
|
||||
if (jumpTo !== pageState.currentPage) {
|
||||
pageState.currentPage = jumpTo
|
||||
fetchCouponTemplates()
|
||||
}
|
||||
}
|
||||
|
||||
const fetchCouponTemplates = async () => {
|
||||
pageState.loading = true
|
||||
uni.showLoading({ title: '加载中...', mask: true })
|
||||
|
||||
try {
|
||||
let query = supa.from('ml_coupon_templates').select('*')
|
||||
// 兼容使用 { count: 'exact' } 获取精确总数
|
||||
let query = supa.from('ml_coupon_templates').select('*', { count: 'exact' })
|
||||
|
||||
// 如果有名称筛选
|
||||
if (filter.name.trim() != '') {
|
||||
query = query.like('name', `%${filter.name}%`)
|
||||
}
|
||||
|
||||
const res = await query.order('created_at', { ascending: false }).execute()
|
||||
// 计算分页 range
|
||||
const start = (pageState.currentPage - 1) * pageState.pageSize
|
||||
const end = pageState.currentPage * pageState.pageSize - 1
|
||||
|
||||
const res = await query.order('created_at', { ascending: false }).range(start, end).execute()
|
||||
|
||||
if (res.error != null || res.status >= 400) {
|
||||
const msg = res.error?.message ?? `获取数据失败 (${res.status})`
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 尝试安全获取总数。如果有的封装库剥离了 count 属性,则以当前返回的数据长度作为保底
|
||||
let fCount = 0
|
||||
if (res.count != null) {
|
||||
fCount = res.count as number
|
||||
} else if (res['total'] != null) {
|
||||
fCount = res['total'] as number
|
||||
}
|
||||
|
||||
if (!Array.isArray(res.data)) {
|
||||
console.warn('Expected array but got:', res.data)
|
||||
dataList.value = []
|
||||
pageState.total = fCount
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = res.data as Array<UTSJSONObject>
|
||||
|
||||
// 如果获取到的总数为 0 但实际有数据,说明接口 count 丢了,用当前拉取的数据量兜底防止分页区坍塌
|
||||
pageState.total = Math.max(fCount, rawData.length)
|
||||
|
||||
dataList.value = rawData.map((item : UTSJSONObject) : CouponItem => {
|
||||
// 优先获取 cid (自增 ID),如果没有则取 id (UUID) 的后几位或 0
|
||||
const displayId = (item.get('cid') as number | null) ?? 0
|
||||
@@ -290,10 +410,14 @@ const fetchCouponTemplates = async () => {
|
||||
} catch (e) {
|
||||
console.error('Fetch Coupons Error:', e)
|
||||
uni.showToast({ title: '访问数据库异常', icon: 'none' })
|
||||
} finally {
|
||||
pageState.loading = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
pageState.currentPage = 1 // 重置到第一页
|
||||
fetchCouponTemplates()
|
||||
}
|
||||
const handleAdd = () => {
|
||||
@@ -379,9 +503,15 @@ const handleDelete = (item: CouponItem) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 移除本地列表数据
|
||||
dataList.value = dataList.value.filter(i => i.id_uuid !== item.id_uuid)
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
|
||||
// 如果当前页只有一条数据,且不是第一页,则退回上一页
|
||||
if (dataList.value.length === 1 && pageState.currentPage > 1) {
|
||||
pageState.currentPage--
|
||||
}
|
||||
|
||||
// 重新拉取当前页数据
|
||||
fetchCouponTemplates()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '操作数据库异常', icon: 'none' })
|
||||
}
|
||||
@@ -626,21 +756,80 @@ onMounted(() => {
|
||||
|
||||
/* 分页 */
|
||||
.pagination-footer {
|
||||
padding: 24px;
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
gap: 16px;
|
||||
border-top: 1px solid #e8eaec;
|
||||
background-color: #fff;
|
||||
}
|
||||
.total-txt { font-size: 14px; color: #606266; }
|
||||
.page-btns { display: flex; flex-direction: row; gap: 8px; }
|
||||
.total-txt { font-size: 14px; color: #515a6e; }
|
||||
|
||||
.page-select {
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
.page-select:hover { border-color: #2d8cf0; }
|
||||
.page-select-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
height: 32px;
|
||||
gap: 8px;
|
||||
}
|
||||
.page-val { font-size: 14px; color: #515a6e; }
|
||||
|
||||
.page-btns { display: flex; flex-direction: row; gap: 4px; }
|
||||
.p-btn {
|
||||
width: 32px; height: 32px; border: 1px solid #dcdfe6; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666;
|
||||
min-width: 32px; height: 32px; padding: 0 4px; border: 1px solid #dcdee2; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 14px; color: #515a6e;
|
||||
background-color: #fff; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.p-btn:hover:not(.disabled):not(.active):not(.ellipsis-btn) {
|
||||
border-color: #2d8cf0; color: #2d8cf0;
|
||||
}
|
||||
.p-btn.active { background-color: #2d8cf0; border-color: #2d8cf0; color: #fff; }
|
||||
.p-btn.disabled { color: #c5c8ce; background-color: #f7f7f7; cursor: not-allowed; border-color: #dcdee2; }
|
||||
.p-btn.ellipsis-btn { border: none; cursor: default; }
|
||||
|
||||
.page-jump {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.jump-txt { font-size: 14px; color: #515a6e; }
|
||||
.jump-input {
|
||||
width: 50px;
|
||||
height: 32px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #515a6e;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
.jump-input:focus { border-color: #2d8cf0; outline: none; }
|
||||
|
||||
/* 空数据状态 */
|
||||
.table-empty {
|
||||
padding: 60px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e8eaec;
|
||||
}
|
||||
.empty-txt {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 🎭 模态框样式 */
|
||||
.modal-mask {
|
||||
|
||||
Reference in New Issue
Block a user