feat: 新增订单列表交互
This commit is contained in:
322
pages/order/components/OrderList/demo.vue
Normal file
322
pages/order/components/OrderList/demo.vue
Normal file
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<view class="demo-container">
|
||||
<view class="demo-header">
|
||||
<text class="demo-title">OrderList组件演示</text>
|
||||
<text class="demo-subtitle">集成z-paging组件,支持虚拟列表、下拉刷新、上拉加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 功能控制区 -->
|
||||
<view class="control-section">
|
||||
<view class="control-row">
|
||||
<button class="control-btn" @click="toggleVirtualList">
|
||||
{{ useVirtualList ? '关闭' : '开启' }}虚拟列表
|
||||
</button>
|
||||
<button class="control-btn" @click="toggleEmptyState">
|
||||
{{ showEmpty ? '显示数据' : '显示空状态' }}
|
||||
</button>
|
||||
</view>
|
||||
<view class="control-row">
|
||||
<button class="control-btn" @click="addMoreData">
|
||||
添加更多数据
|
||||
</button>
|
||||
<button class="control-btn" @click="clearData">
|
||||
清空数据
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- OrderList组件演示 -->
|
||||
<view class="demo-list">
|
||||
<OrderList
|
||||
:orderList="orderList"
|
||||
:hasMore="hasMore"
|
||||
:isLoading="isLoading"
|
||||
:currentTab="currentTab"
|
||||
:emptyText="emptyText"
|
||||
:useVirtualList="useVirtualList"
|
||||
:virtualListHeight="'500px'"
|
||||
:cellHeightMode="cellHeightMode"
|
||||
:fixedHeight="120"
|
||||
@refresh="handleRefresh"
|
||||
@loadMore="handleLoadMore"
|
||||
@orderClick="handleOrderClick"
|
||||
@orderCall="handleOrderCall"
|
||||
@orderComplete="handleOrderComplete"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 功能说明 -->
|
||||
<view class="feature-section">
|
||||
<text class="feature-title">新功能特性:</text>
|
||||
<view class="feature-list">
|
||||
<text class="feature-item">✅ 集成z-paging组件</text>
|
||||
<text class="feature-item">✅ 支持虚拟列表,提升大数据渲染性能</text>
|
||||
<text class="feature-item">✅ 自定义下拉刷新样式和文案</text>
|
||||
<text class="feature-item">✅ 自定义上拉加载更多样式和文案</text>
|
||||
<text class="feature-item">✅ 自动管理空数据状态</text>
|
||||
<text class="feature-item">✅ 支持固定高度和自适应高度模式</text>
|
||||
<text class="feature-item">✅ 完整的事件回调机制</text>
|
||||
<text class="feature-item">✅ 响应式设计,适配各种屏幕</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import OrderList from './index.vue'
|
||||
|
||||
// 响应式数据
|
||||
const orderList = ref([])
|
||||
const hasMore = ref(true)
|
||||
const isLoading = ref(false)
|
||||
const currentTab = ref('all')
|
||||
const useVirtualList = ref(true)
|
||||
const cellHeightMode = ref('auto')
|
||||
const showEmpty = ref(false)
|
||||
|
||||
// 计算属性
|
||||
const emptyText = computed(() => {
|
||||
return showEmpty.value ? '暂无数据,点击刷新重试' : '暂无工单数据'
|
||||
})
|
||||
|
||||
// 模拟数据
|
||||
const generateMockData = (count = 10, startId = 1) => {
|
||||
const data = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
data.push({
|
||||
id: `${startId + i}`,
|
||||
title: `工单标题 ${startId + i} - ${['空调维修', '水管漏水', '电路故障', '网络异常', '设备更换'][i % 5]}`,
|
||||
createTime: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toLocaleString(),
|
||||
contactName: ['张先生', '李女士', '王先生', '赵女士', '刘先生'][i % 5],
|
||||
contactPhone: `138****${String(Math.floor(Math.random() * 10000)).padStart(4, '0')}`,
|
||||
status: ['pending', 'processing', 'completed'][i % 3],
|
||||
type: 'service'
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = () => {
|
||||
orderList.value = generateMockData(20)
|
||||
}
|
||||
|
||||
// 切换虚拟列表
|
||||
const toggleVirtualList = () => {
|
||||
useVirtualList.value = !useVirtualList.value
|
||||
uni.showToast({
|
||||
title: `虚拟列表已${useVirtualList.value ? '开启' : '关闭'}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 切换空状态
|
||||
const toggleEmptyState = () => {
|
||||
showEmpty.value = !showEmpty.value
|
||||
if (showEmpty.value) {
|
||||
orderList.value = []
|
||||
hasMore.value = false
|
||||
} else {
|
||||
initData()
|
||||
hasMore.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 添加更多数据
|
||||
const addMoreData = () => {
|
||||
const newData = generateMockData(10, orderList.value.length + 1)
|
||||
orderList.value.push(...newData)
|
||||
uni.showToast({
|
||||
title: '已添加10条数据',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 清空数据
|
||||
const clearData = () => {
|
||||
orderList.value = []
|
||||
hasMore.value = false
|
||||
uni.showToast({
|
||||
title: '数据已清空',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
console.log('下拉刷新')
|
||||
isLoading.value = true
|
||||
|
||||
// 模拟网络请求
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
|
||||
// 重新加载数据
|
||||
orderList.value = generateMockData(15)
|
||||
hasMore.value = true
|
||||
isLoading.value = false
|
||||
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 上拉加载更多
|
||||
const handleLoadMore = async () => {
|
||||
console.log('上拉加载更多')
|
||||
|
||||
if (!hasMore.value) return
|
||||
|
||||
// 模拟网络请求
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 添加新数据
|
||||
const newData = generateMockData(10, orderList.value.length + 1)
|
||||
orderList.value.push(...newData)
|
||||
|
||||
// 模拟没有更多数据
|
||||
if (orderList.value.length >= 50) {
|
||||
hasMore.value = false
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: `加载了${newData.length}条数据`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 工单点击
|
||||
const handleOrderClick = (orderData) => {
|
||||
console.log('点击工单:', orderData)
|
||||
uni.showToast({
|
||||
title: `点击了工单: ${orderData.title}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 工单呼叫
|
||||
const handleOrderCall = (orderData) => {
|
||||
console.log('呼叫工单:', orderData)
|
||||
uni.showToast({
|
||||
title: `呼叫: ${orderData.contactName}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 工单完成
|
||||
const handleOrderComplete = (orderData) => {
|
||||
console.log('完成工单:', orderData)
|
||||
// 更新状态
|
||||
const index = orderList.value.findIndex(item => item.id === orderData.id)
|
||||
if (index !== -1) {
|
||||
orderList.value[index].status = 'completed'
|
||||
}
|
||||
uni.showToast({
|
||||
title: '工单已标记为完成',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化
|
||||
initData()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.demo-container {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.demo-title {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.demo-subtitle {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.control-section {
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
background: linear-gradient(135deg, #0056cc 0%, #003d99 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.demo-list {
|
||||
height: 500px;
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.feature-section {
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
.feature-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
265
pages/order/components/OrderList/index.vue
Normal file
265
pages/order/components/OrderList/index.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<view class="order-list-container">
|
||||
<!-- z-paging组件 -->
|
||||
<z-paging
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:refresher-enabled="true"
|
||||
:refresher-threshold="45"
|
||||
refresher-default-text="下拉刷新"
|
||||
refresher-pulling-text="下拉刷新"
|
||||
refresher-refreshing-text="正在刷新..."
|
||||
refresher-complete-text="刷新完成"
|
||||
:loading-more-enabled="true"
|
||||
loading-more-default-text="点击加载更多"
|
||||
loading-more-loading-text="正在加载..."
|
||||
loading-more-no-more-text="没有更多了"
|
||||
loading-more-fail-text="加载失败,点击重试"
|
||||
:empty-view-text="emptyText"
|
||||
:empty-view-img="emptyIcon"
|
||||
:auto="false"
|
||||
:use-virtual-list="false"
|
||||
:virtual-list-height="virtualListHeight"
|
||||
:cell-height-mode="cellHeightMode"
|
||||
:fixed-height="fixedHeight"
|
||||
:safe-area-inset-top="true"
|
||||
:use-page-scroll="true"
|
||||
:top-offset="120"
|
||||
@query="queryList"
|
||||
@emptyViewReload="handleEmptyReload"
|
||||
>
|
||||
<!-- 非虚拟列表模式下的数据渲染 -->
|
||||
<view class="order-list-content">
|
||||
<OrderCard
|
||||
v-for="(item, index) in currentDataList"
|
||||
:key="item.id || index"
|
||||
:orderData="item"
|
||||
@click="handleOrderClick"
|
||||
@call="handleOrderCall"
|
||||
@complete="handleOrderComplete"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 虚拟列表模式下的数据渲染 -->
|
||||
<template #cell="{ item }" v-if="useVirtualList">
|
||||
<OrderCard
|
||||
:orderData="item"
|
||||
@click="handleOrderClick"
|
||||
@call="handleOrderCall"
|
||||
@complete="handleOrderComplete"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 自定义空状态 -->
|
||||
<template #empty v-if="customEmptyView">
|
||||
<view class="custom-empty">
|
||||
<image :src="emptyIcon" class="empty-icon" />
|
||||
<text class="empty-text">{{ emptyText }}</text>
|
||||
<button
|
||||
class="refresh-btn"
|
||||
@click="handleEmptyReload"
|
||||
v-if="showRefreshBtn"
|
||||
>
|
||||
刷新重试
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, watch, onMounted, computed, nextTick } from "vue";
|
||||
import OrderCard from "../OrderCard/index.vue";
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
// 工单数据列表
|
||||
orderList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 是否有更多数据
|
||||
hasMore: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 是否正在加载
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 空状态文案
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: "暂无工单数据",
|
||||
},
|
||||
// 空状态图标
|
||||
emptyIcon: {
|
||||
type: String,
|
||||
default: "/static/images/empty.png",
|
||||
},
|
||||
// 是否显示刷新按钮
|
||||
showRefreshBtn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 当前Tab类型
|
||||
currentTab: {
|
||||
type: String,
|
||||
default: "all",
|
||||
},
|
||||
// 是否启用虚拟列表
|
||||
useVirtualList: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 虚拟列表高度
|
||||
virtualListHeight: {
|
||||
type: [String, Number],
|
||||
default: "100%",
|
||||
},
|
||||
// 单元格高度模式
|
||||
cellHeightMode: {
|
||||
type: String,
|
||||
default: "auto", // auto | fixed
|
||||
},
|
||||
// 固定高度(当cellHeightMode为fixed时使用)
|
||||
fixedHeight: {
|
||||
type: Number,
|
||||
default: 120,
|
||||
},
|
||||
// 是否使用自定义空状态
|
||||
customEmptyView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits([
|
||||
"refresh",
|
||||
"loadMore",
|
||||
"orderClick",
|
||||
"orderCall",
|
||||
"orderComplete",
|
||||
]);
|
||||
|
||||
// 响应式数据
|
||||
const paging = ref(null);
|
||||
const dataList = ref([]);
|
||||
const pageNum = ref(1);
|
||||
const pageSize = ref(10);
|
||||
|
||||
// 计算属性
|
||||
const currentDataList = computed(() => {
|
||||
return props.orderList || [];
|
||||
});
|
||||
|
||||
// 查询列表数据
|
||||
const queryList = async (pageNo, pageSize, from) => {
|
||||
console.log("z-paging查询:", { pageNo, pageSize, from });
|
||||
|
||||
try {
|
||||
// 触发父组件的数据加载事件
|
||||
if (pageNo === 1) {
|
||||
// 下拉刷新
|
||||
emit("refresh");
|
||||
} else {
|
||||
// 上拉加载更多
|
||||
emit("loadMore");
|
||||
}
|
||||
|
||||
// 等待数据更新
|
||||
await nextTick();
|
||||
|
||||
// 直接使用当前的orderList数据
|
||||
const currentData = props.orderList || [];
|
||||
|
||||
// 在非虚拟列表模式下,z-paging主要用于下拉刷新和上拉加载
|
||||
// 数据渲染通过模板中的v-for完成
|
||||
paging.value?.complete(currentData, !props.hasMore);
|
||||
|
||||
} catch (error) {
|
||||
console.error("查询列表失败:", error);
|
||||
paging.value?.complete(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理空状态重新加载
|
||||
const handleEmptyReload = () => {
|
||||
console.log("空状态重新加载");
|
||||
paging.value?.reload();
|
||||
};
|
||||
|
||||
// 处理工单点击
|
||||
const handleOrderClick = (orderData) => {
|
||||
emit("orderClick", orderData);
|
||||
};
|
||||
|
||||
// 处理工单呼叫
|
||||
const handleOrderCall = (orderData) => {
|
||||
emit("orderCall", orderData);
|
||||
};
|
||||
|
||||
// 处理工单完成
|
||||
const handleOrderComplete = (orderData) => {
|
||||
emit("orderComplete", orderData);
|
||||
};
|
||||
|
||||
// 监听orderList变化,更新z-paging数据
|
||||
watch(
|
||||
() => props.orderList,
|
||||
(newList) => {
|
||||
if (newList && newList.length >= 0) {
|
||||
dataList.value = [...newList];
|
||||
// 在非虚拟列表模式下,主要是通知z-paging数据已更新
|
||||
if (paging.value) {
|
||||
paging.value.complete(newList, !props.hasMore);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
// 监听Tab切换,重新加载数据
|
||||
watch(
|
||||
() => props.currentTab,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
paging.value?.reload();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 组件挂载后初始化
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 初始化数据
|
||||
if (currentDataList.value.length > 0) {
|
||||
dataList.value = [...currentDataList.value];
|
||||
// 手动触发z-paging的数据更新
|
||||
if (paging.value) {
|
||||
paging.value.complete(currentDataList.value, !props.hasMore);
|
||||
}
|
||||
} else {
|
||||
// 如果没有初始数据,触发第一次查询
|
||||
if (paging.value) {
|
||||
paging.value.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
reload: () => paging.value?.reload(),
|
||||
refresh: () => paging.value?.reload(),
|
||||
complete: (data, noMore) => paging.value?.complete(data, noMore),
|
||||
getPaging: () => paging.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
298
pages/order/components/OrderList/styles/index.scss
Normal file
298
pages/order/components/OrderList/styles/index.scss
Normal file
@@ -0,0 +1,298 @@
|
||||
.order-list-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-top: 120rpx; /* 为固定导航栏留出空间 */
|
||||
|
||||
.z-paging-container {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 120rpx);
|
||||
}
|
||||
|
||||
// z-paging内部列表项样式
|
||||
:deep(.z-paging-content) {
|
||||
padding: 0 32rpx;
|
||||
|
||||
.order-card {
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 非虚拟列表内容区域样式
|
||||
.order-list-content {
|
||||
padding: 0 32rpx;
|
||||
|
||||
.order-card {
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 空状态样式(z-paging内置)
|
||||
:deep(.z-paging-empty-view) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 60rpx;
|
||||
text-align: center;
|
||||
|
||||
.z-paging-empty-view-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 40rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.z-paging-empty-view-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.z-paging-empty-view-reload-btn {
|
||||
padding: 20rpx 40rpx;
|
||||
background-color: #007aff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background-color: #0056cc;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义空状态样式
|
||||
.custom-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 60rpx;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 40rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
padding: 20rpx 40rpx;
|
||||
background-color: #007aff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background-color: #0056cc;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// z-paging加载更多样式
|
||||
:deep(.z-paging-load-more) {
|
||||
padding: 40rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.z-paging-load-more-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20rpx;
|
||||
|
||||
.z-paging-load-more-loading {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 4rpx solid #e5e5e5;
|
||||
border-top: 4rpx solid #007aff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.z-paging-load-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.z-paging-load-more-no-more-line {
|
||||
.z-paging-load-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// z-paging刷新器样式
|
||||
:deep(.z-paging-refresh) {
|
||||
.z-paging-refresh-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx;
|
||||
|
||||
.z-paging-refresh-loading {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid #e5e5e5;
|
||||
border-top: 4rpx solid #007aff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.z-paging-refresh-text {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 下拉刷新样式优化 */
|
||||
:deep(.uni-scroll-view-refresher) {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.scroll-area::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.order-list {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 60px 16px;
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
padding: 8px 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色模式支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.order-list-container {
|
||||
background: linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 50%, #404040 100%);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border-color: #404040;
|
||||
border-top-color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* 列表项动画 */
|
||||
.order-list view {
|
||||
animation: fadeInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载状态过渡 */
|
||||
.global-loading {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 空状态动画 */
|
||||
.empty-state {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 刷新状态优化 */
|
||||
.scroll-area[refresher-triggered="true"] {
|
||||
.order-list {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
}
|
||||
195
pages/order/components/OrderList/test.vue
Normal file
195
pages/order/components/OrderList/test.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<view class="test-page">
|
||||
<view class="test-header">
|
||||
<text class="test-title">OrderList 组件测试</text>
|
||||
<button @click="toggleData" class="test-btn">{{ hasData ? '清空数据' : '加载数据' }}</button>
|
||||
</view>
|
||||
|
||||
<view class="test-content">
|
||||
<OrderList
|
||||
:orderList="testOrderList"
|
||||
:hasMore="hasMore"
|
||||
:isLoading="isLoading"
|
||||
:currentTab="currentTab"
|
||||
:emptyText="'暂无测试数据'"
|
||||
:useVirtualList="true"
|
||||
:virtualListHeight="'400px'"
|
||||
:cellHeightMode="'auto'"
|
||||
:fixedHeight="120"
|
||||
@refresh="handleRefresh"
|
||||
@loadMore="handleLoadMore"
|
||||
@orderClick="handleOrderClick"
|
||||
@orderCall="handleOrderCall"
|
||||
@orderComplete="handleOrderComplete"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="test-info">
|
||||
<text class="info-text">当前数据量: {{ testOrderList.length }}</text>
|
||||
<text class="info-text">是否有更多: {{ hasMore ? '是' : '否' }}</text>
|
||||
<text class="info-text">是否加载中: {{ isLoading ? '是' : '否' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import OrderList from './index.vue'
|
||||
|
||||
// 测试数据
|
||||
const testOrderList = ref([])
|
||||
const hasMore = ref(true)
|
||||
const isLoading = ref(false)
|
||||
const currentTab = ref('all')
|
||||
const hasData = ref(false)
|
||||
|
||||
// 模拟订单数据
|
||||
const mockData = [
|
||||
{
|
||||
id: '001',
|
||||
title: '空调维修服务',
|
||||
createTime: '2024-01-15 14:30',
|
||||
contactName: '张先生',
|
||||
contactPhone: '138****8888',
|
||||
status: 'pending',
|
||||
type: 'service'
|
||||
},
|
||||
{
|
||||
id: '002',
|
||||
title: '水管漏水维修',
|
||||
createTime: '2024-01-15 10:20',
|
||||
contactName: '李女士',
|
||||
contactPhone: '139****9999',
|
||||
status: 'processing',
|
||||
type: 'service'
|
||||
},
|
||||
{
|
||||
id: '003',
|
||||
title: '电灯安装服务',
|
||||
createTime: '2024-01-14 16:45',
|
||||
contactName: '王先生',
|
||||
contactPhone: '137****7777',
|
||||
status: 'completed',
|
||||
type: 'service'
|
||||
}
|
||||
]
|
||||
|
||||
// 切换数据
|
||||
const toggleData = () => {
|
||||
if (hasData.value) {
|
||||
testOrderList.value = []
|
||||
hasData.value = false
|
||||
} else {
|
||||
testOrderList.value = [...mockData]
|
||||
hasData.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = () => {
|
||||
console.log('测试: 刷新事件触发')
|
||||
isLoading.value = true
|
||||
setTimeout(() => {
|
||||
testOrderList.value = [...mockData]
|
||||
isLoading.value = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 处理加载更多
|
||||
const handleLoadMore = () => {
|
||||
console.log('测试: 加载更多事件触发')
|
||||
isLoading.value = true
|
||||
setTimeout(() => {
|
||||
const newData = mockData.map((item, index) => ({
|
||||
...item,
|
||||
id: item.id + '_' + Date.now(),
|
||||
title: item.title + ' (新增)'
|
||||
}))
|
||||
testOrderList.value.push(...newData)
|
||||
isLoading.value = false
|
||||
hasMore.value = testOrderList.value.length < 20
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 处理订单点击
|
||||
const handleOrderClick = (orderData) => {
|
||||
console.log('测试: 订单点击', orderData)
|
||||
uni.showToast({
|
||||
title: `点击了订单: ${orderData.title}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 处理订单呼叫
|
||||
const handleOrderCall = (orderData) => {
|
||||
console.log('测试: 订单呼叫', orderData)
|
||||
uni.showToast({
|
||||
title: `呼叫: ${orderData.contactName}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
// 处理订单完成
|
||||
const handleOrderComplete = (orderData) => {
|
||||
console.log('测试: 订单完成', orderData)
|
||||
uni.showToast({
|
||||
title: `完成订单: ${orderData.title}`,
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.test-page {
|
||||
padding: 20rpx;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.test-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.test-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
padding: 10rpx 20rpx;
|
||||
background: #007aff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.test-content {
|
||||
flex: 1;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.test-info {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 20rpx;
|
||||
background: #f9f9f9;
|
||||
border-radius: 10rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user