完成全部页面分页组件的抽取2

This commit is contained in:
2026-03-17 11:21:29 +08:00
parent e266482f88
commit 4041933e42
19 changed files with 344 additions and 492 deletions

View File

@@ -28,7 +28,7 @@
<view v-if="list.length === 0" class="empty-box">
<text class="empty-text">暂无数据</text>
</view>
<view v-for="(item, index) in list" :key="index" class="table-row">
<view v-for="(item, index) in pagedList" :key="index" class="table-row">
<text class="td-cell flex-1 color-9">{{ item.id }}</text>
<text class="td-cell flex-3">{{ item.name }}</text>
<view class="td-cell flex-5">
@@ -41,6 +41,23 @@
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
<!-- 添加/编辑参数抽屉 (右侧 50%) -->
@@ -101,7 +118,8 @@
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
interface ParamKV {
label: string;
@@ -115,10 +133,55 @@ sort: number;
params: ParamKV[];
}
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 list 为 fetchParamList() 调用
const list = reactive<ParamItem[]>([
{ id: 1, name: '手机数码', sort: 1, params: [{label: '品牌', value: '华为'}, {label: '型号', value: 'Mate 60'}] as ParamKV[] },
{ id: 2, name: '家用电器', sort: 2, params: [{label: '能效等级', value: '一级'}, {label: '产地', value: '中国'}] as ParamKV[] }
{ id: 2, name: '家用电器', sort: 2, params: [{label: '能效等级', value: '一级'}, {label: '产地', value: '中国'}] as ParamKV[] },
{ id: 3, name: '服装鞋履', sort: 3, params: [{label: '面料', value: '纯棉'}, {label: '适用季节', value: '春夏'}] as ParamKV[] },
{ id: 4, name: '食品饮料', sort: 4, params: [{label: '保质期', value: '12个月'}, {label: '常温存储', value: '是'}] as ParamKV[] },
{ id: 5, name: '家具家居', sort: 5, params: [{label: '材质', value: '实木'}, {label: '风格', value: '新中式'}] as ParamKV[] },
{ id: 6, name: '美妆护肤', sort: 6, params: [{label: '肉质', value: '混合背'}, {label: '容量', value: '50ml'}] as ParamKV[] },
{ id: 7, name: '图书文具', sort: 7, params: [{label: '出版社', value: '人民兰山'}, {label: '平装/精装', value: '精装'}] as ParamKV[] },
{ id: 8, name: '运动户外', sort: 8, params: [{label: '适用季节', value: '冬季'}, {label: '防水等级', value: 'IPX5'}] as ParamKV[] },
{ id: 9, name: '母婴童装', sort: 9, params: [{label: '适用年龄', value: '0-3岁'}, {label: '安全认证', value: 'CCC'}] as ParamKV[] },
{ id: 10, name: '創业特惠', sort: 10, params: [{label: '折扣力度', value: '9折'}, {label: '限时时间', value: '7天'}] as ParamKV[] },
{ id: 11, name: '山地车辆', sort: 11, params: [{label: '厂家', value: '丰田'}, {label: '排量', value: '2.0T'}] as ParamKV[] },
{ id: 12, name: '唨具家电', sort: 12, params: [{label: '功率', value: '1500W'}, {label: '容量', value: '5L'}] as ParamKV[] }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(10)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => list.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return list.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
const showDrawerMask = ref(false)
const showDrawer = ref(false)

View File

@@ -24,7 +24,7 @@
<view v-if="list.length === 0" class="empty-box">
<text class="empty-text">暂无数据</text>
</view>
<view v-for="(item, index) in list" :key="index" class="table-row-item">
<view v-for="(item, index) in pagedList" :key="index" class="table-row-item">
<text class="td flex-1 color-9">{{ item.id }}</text>
<view class="td flex-2">
<image class="protection-icon-img" :src="item.icon" mode="aspectFit"></image>
@@ -42,6 +42,22 @@
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
<!-- 添加/编辑弹窗 (居中 Modal) -->
<view class="modal-overlay" v-if="showModal" @click="closeModal">
@@ -102,8 +118,9 @@
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import StatusSwitch from '@/components/StatusSwitch.uvue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
interface ProtectionItem {
id: number;
@@ -114,10 +131,53 @@ interface ProtectionItem {
sort: number;
}
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 list 为 fetchProtectionList() 调用
const list = reactive<ProtectionItem[]>([
{ id: 1, name: '正品保障', icon: '/static/logo.png', desc: '该商品由平台认证,保证百分百正品。', status: true, sort: 0 },
{ id: 2, name: '七天无理由', icon: '/static/logo.png', desc: '商品在不影响二次销售的情况下,支持7天无理由退换。', status: true, sort: 0 }
{ id: 2, name: '七天无理由', icon: '/static/logo.png', desc: '商品在不影响二次销售的情况下,支持 7 天无理由退换。', status: true, sort: 0 },
{ id: 3, name: '价格保指', icon: '/static/logo.png', desc: '购买后 30 天内如遇同款低价,即可申请价差补偿。', status: true, sort: 1 },
{ id: 4, name: '全程颜料隐形', icon: '/static/logo.png', desc: '顺丰乐丰包装,不露商品信息,注重隐次保护。', status: true, sort: 2 },
{ id: 5, name: '隐私保护', icon: '/static/logo.png', desc: '尥尺保护您的个人信息,不向任何第三方泄露。', status: true, sort: 3 },
{ id: 6, name: '即时客服', icon: '/static/logo.png', desc: '7×24小时在线客服随时解决您的问题。', status: true, sort: 4 },
{ id: 7, name: '准时发货', icon: '/static/logo.png', desc: '下单后 48 小时内发货,快递全程跟踪。', status: false, sort: 5 },
{ id: 8, name: '免费退返运', icon: '/static/logo.png', desc: '指定品类商品支持免费退返运。', status: true, sort: 6 },
{ id: 9, name: '官方维修', icon: '/static/logo.png', desc: '各地维修中心 500+,提供上门维修服务。', status: true, sort: 7 },
{ id: 10, name: '分期免息', icon: '/static/logo.png', desc: '支持花唉/支付分期,指定商品免息付款。', status: false, sort: 8 }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(10)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => list.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return list.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
const showModal = ref(false)
const isEdit = ref(false)

View File

@@ -55,7 +55,7 @@
<view class="th col-op"><text>操作</text></view>
</view>
<view v-for="item in replyList" :key="item.id" class="tr-row">
<view v-for="item in pagedList" :key="item.id" class="tr-row">
<view class="td col-check"><text>□</text></view>
<view class="td col-id"><text>{{ item.id }}</text></view>
<view class="td col-product">
@@ -84,8 +84,8 @@
<!-- 分页 -->
<CommonPagination
v-if="replyList.length > 0"
:total="replyList.length"
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
@@ -146,24 +146,25 @@ const replyList = ref([
}
])
// 分页适配状态
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(10)
let jumpPageInput = ''
const jumpPageInput = ref('')
const pageSizeOptions = [10, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => {
const idx = pageSizeOptions.indexOf(pageSize.value)
return idx >= 0 ? idx : 0
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => replyList.value.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return replyList.value.slice(start, start + pageSize.value)
})
const totalPage = computed(() => Math.max(1, Math.ceil(replyList.value.length / pageSize.value)))
const visiblePages = computed(() => {
const total = totalPage.value
const cur = currentPage.value
if (total <= 7) return Array.from({ length: total }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, total]
if (cur >= total - 3) return [1, -1, total - 4, total - 3, total - 2, total - 1, total]
return [1, -1, cur - 1, cur, cur + 1, -1, total]
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
@@ -172,9 +173,10 @@ const handlePageSizeChange = (e: any) => {
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput)
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
</script>
<style scoped lang="scss">
@@ -274,7 +276,7 @@ const handleJumpPage = () => {
.col-reply { flex: 1; }
.col-status { width: 100px; }
.col-time { width: 160px; }
.col-op { width: 180px; }
.col-op { width: 180px; display: flex; flex-direction: row; }
.p-img { width: 40px; height: 40px; border-radius: 4px; }
.p-name-txt { font-size: 13px; line-height: 1.4; color: #1890ff; }

View File

@@ -33,7 +33,7 @@
<view v-if="list.length === 0" class="empty-box">
<text class="empty-text">暂无数据</text>
</view>
<view v-for="(item, index) in list" :key="index" class="table-row">
<view v-for="(item, index) in pagedList" :key="index" class="table-row">
<view class="td-cell flex-1 row-center">
<view class="checkbox-mock" :class="item.selected ? 'checked' : ''" @click="item.selected = !item.selected">
<text v-if="item.selected" class="check-mark">✓</text>
@@ -50,6 +50,22 @@
</view>
</view>
</view>
<CommonPagination
v-if="true"
:total="total"
:loading="false"
:currentPage="currentPage"
:pageSize="pageSize"
:pageSizeOptionLabels="pageSizeOptionLabels"
:pageSizeIndex="pageSizeIndex"
:visiblePages="visiblePages"
:totalPage="totalPage"
:jumpPageInput="jumpPageInput"
@page-size-change="handlePageSizeChange"
@page-change="handlePageChange"
@update:jumpPageInput="(val: string) => { jumpPageInput.value = val }"
@jump-page="handleJumpPage"
/>
</view>
<!-- 添加规格弹窗 -->
@@ -91,7 +107,8 @@
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import CommonPagination from '@/components/CommonPagination/CommonPagination.uvue'
interface AttrItem {
id: number;
@@ -101,13 +118,58 @@ interface AttrItem {
selected: boolean;
}
// ========== MOCK DATA START ==========
// TODO: 接真实接口时替换此处 list 为 fetchSpecList() 调用
const list = reactive<AttrItem[]>([
{ id: 104, name: '颜色', specs: '红色,蓝色,黑色,白色', attrs: '颜色属性', selected: false },
{ id: 105, name: '尺寸', specs: 'S,M,L,XL,XXL', attrs: '服装尺寸', selected: false },
{ id: 106, name: '材质', specs: '纯棉,纶,真丝', attrs: '面料材质', selected: false },
{ id: 106, name: '材质', specs: '纯棉,纶,真丝', attrs: '面料材质', selected: false },
{ id: 107, name: '内存', specs: '8G,16G,32G', attrs: '硬件参数', selected: false },
{ id: 108, name: '存储', specs: '128G,256G,512G', attrs: '容量', selected: false }
{ id: 108, name: '存储', specs: '128G,256G,512G', attrs: '容量', selected: false },
{ id: 109, name: '重量', specs: '100g,200g,500g,1kg', attrs: '包装规格', selected: false },
{ id: 110, name: '口味', specs: '原味,辣味,甜味,咋味', attrs: '食品口味', selected: false },
{ id: 111, name: '风格', specs: '日系,韩系,欧美,新中式', attrs: '服装风格', selected: false },
{ id: 112, name: '屏幕尺寸', specs: '6.1小时,6.7小时,6.9小时', attrs: '手机屏幕', selected: false },
{ id: 113, name: '套餐选择', specs: '套餐A,套餐B,套餐C', attrs: '餐飲套餐', selected: false },
{ id: 114, name: '独立包装', specs: '独立包装,组合装', attrs: '包装方式', selected: false },
{ id: 115, name: '靘刀', specs: '靘刀7天,靘刀14天,靘刀30天', attrs: '服装保洁', selected: false },
{ id: 116, name: '宣传图', specs: '带宣传图,不带宣传图', attrs: '商品配送', selected: false },
{ id: 117, name: '主题色', specs: '科技蓝,记忆红,森林绿,太空黑', attrs: '设备配色', selected: false },
{ id: 118, name: '等级', specs: '普通版,标准版,旗舰版', attrs: '商品等级', selected: false }
])
// ========== MOCK DATA END ==========
// ========== PAGINATION STATE ==========
const currentPage = ref(1)
const pageSize = ref(10)
const jumpPageInput = ref('')
const pageSizeOptions = [10, 15, 20, 30, 50]
const pageSizeOptionLabels = computed(() => pageSizeOptions.map((n: number) => `${n}条/页`))
const pageSizeIndex = computed(() => { const idx = pageSizeOptions.indexOf(pageSize.value); return idx >= 0 ? idx : 0 })
const total = computed(() => list.length)
const totalPage = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)))
const pagedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return list.slice(start, start + pageSize.value)
})
const visiblePages = computed((): number[] => {
const t = totalPage.value; const cur = currentPage.value
if (t <= 7) return Array.from({ length: t }, (_: any, i: number) => i + 1)
if (cur <= 4) return [1, 2, 3, 4, 5, -1, t]
if (cur >= t - 3) return [1, -1, t - 4, t - 3, t - 2, t - 1, t]
return [1, -1, cur - 1, cur, cur + 1, -1, t]
})
const handlePageChange = (p: number) => { currentPage.value = p }
const handlePageSizeChange = (e: any) => {
const idx = Number(e.detail.value)
pageSize.value = pageSizeOptions[idx] ?? pageSizeOptions[0]
currentPage.value = 1
}
const handleJumpPage = () => {
const p = parseInt(jumpPageInput.value)
if (!isNaN(p) && p >= 1 && p <= totalPage.value) currentPage.value = p
}
// ========== END PAGINATION STATE ==========
const showModal = ref(false)
const form = reactive({