feat: 订单列表接口对接

This commit is contained in:
duanshuwen
2025-07-29 09:01:18 +08:00
parent 0efc7fe6a0
commit 8f2ce34669
66 changed files with 10650 additions and 1320 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,26 @@
<template>
<view class="empty-container">
<image
class="empty-image"
mode="aspectFit"
src="./images/empty.png"
></image>
<text class="empty-text">{{ statusText }}</text>
</view>
</template>
<script setup>
import { defineProps } from "vue";
const props = defineProps({
statusText: {
type: String,
default: "",
},
});
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,19 @@
.empty-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
height: 100%;
flex-direction: column;
align-items: center;
}
.empty-image {
height: 130px;
width: 130px;
}
.empty-text {
margin-top: 10px;
font-size: 14px;
color: #666666;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,14 @@
<template>
<view class="nomore">
<image
class="nomore-image"
mode="aspectFit"
src="./images/no_more.png"
></image>
<text class="nomore-text">已经到达宇宙尽头啦~</text>
</view>
</template>
<style scoped>
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,17 @@
.nomore {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
padding: 10px 0;
}
.nomore-image {
width: 100px;
height: 65px;
}
.nomore-text {
margin-top: 5px;
font-size: 12px;
color: #222963;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -0,0 +1,39 @@
<!-- z-paging自定义的下拉刷新view -->
<template>
<view class="refresher-container">
<image
class="refresher-image"
mode="aspectFit"
src="./images/refresher_loading.gif"
></image>
<text class="refresher-text">{{ statusText }}</text>
</view>
</template>
<script setup>
import { computed } from "vue";
const props = defineProps({
status: {
type: String,
default: "",
},
});
const statusText = computed(() => {
// 这里可以做i18n国际化相关操作可以通过uni.getLocale()获取当前语言(具体操作见i18n-demo.vue);
// 获取到当前语言之后,就可以自定义不同语言下的展示内容了
const statusTextMap = {
default: "哎呀,用点力继续下拉!",
"release-to-refresh": "拉疼我啦,松手刷新~~",
loading: "正在努力刷新中...",
complete: "刷新成功啦~",
};
return statusTextMap[this.status];
});
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,21 @@
.refresher-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
height: 150rpx;
flex-direction: column;
align-items: center;
justify-content: center;
}
.refresher-image {
margin-top: 10rpx;
height: 45px;
width: 45px;
}
.refresher-text {
margin-top: 10rpx;
font-size: 24rpx;
color: #666666;
}

View File

@@ -1,322 +0,0 @@
<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>

View File

@@ -1,265 +0,0 @@
<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>

View File

@@ -1,298 +0,0 @@
.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;
}
}

View File

@@ -1,195 +0,0 @@
<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>

View File

@@ -1,7 +1,11 @@
<template>
<view class="order-page">
<!-- 顶部导航栏 - 固定定位 -->
<view class="top-nav-fixed">
<z-paging
ref="paging"
v-model="dataList"
safe-area-inset-bottom
@query="queryList"
>
<template #top>
<TopNavBar>
<template #title>
<Tabs
@@ -11,33 +15,40 @@
/>
</template>
</TopNavBar>
</view>
</template>
<!-- 工单列表区域 - 全屏模式 -->
<OrderList
:orderList="currentOrderList"
:hasMore="hasMore"
:isLoading="isLoading"
:currentTab="currentTab"
:emptyText="getEmptyText()"
:useVirtualList="false"
:virtualListHeight="'100vh'"
:cellHeightMode="'auto'"
:fixedHeight="120"
@refresh="handleRefresh"
@loadMore="handleLoadMore"
@orderClick="handleOrderClick"
@orderCall="handleOrderCall"
@orderComplete="handleOrderComplete"
<template #empty>
<CustomEmpty statusText="您暂无订单" />
</template>
<template #refresher="{ refresherStatus }">
<CustomRefresher :status="refresherStatus" />
</template>
<template #loadingMoreNoMore>
<CustomNoMore />
</template>
<OrderCard
v-for="(item, index) in dataList"
:key="item.id || index"
:orderData="item"
@click="handleOrderClick"
@call="handleOrderCall"
@complete="handleOrderComplete"
/>
</view>
</z-paging>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import TopNavBar from "./components/TopNavBar/index.vue";
import Tabs from "./components/Tabs/index.vue";
import OrderList from "./components/OrderList/index.vue";
import OrderCard from "./components/OrderCard/index.vue";
import CustomEmpty from "./components/CustomEmpty/index.vue";
import CustomRefresher from "./components/CustomRefresher/index.vue";
import CustomNoMore from "./components/CustomNoMore/index.vue";
import { userOrderList, userWorkOrderList } from "@/request/api/OrderApi";
// Tab配置
const tabList = ref([
@@ -46,214 +57,69 @@ const tabList = ref([
]);
// 当前状态
const currentTab = ref("all");
const currentTabIndex = ref(0);
const isLoading = ref(false);
const hasMore = ref(true);
const pageNum = ref(1);
const pageSize = ref(10);
const showConsultationBar = ref(false);
const dataList = ref([]);
const paging = ref(null);
// 工单数据
const allOrderList = ref([]);
const serviceOrderList = ref([]);
const queryList = async (pageNum, pageSize) => {
try {
let apiCall;
// 根据当前Tab选择不同的API
if (currentTabIndex.value === 0) {
// 全部订单
apiCall = userOrderList({ pageNum, pageSize });
} else {
// 服务工单
apiCall = userWorkOrderList({ pageNum, pageSize });
}
// 模拟工单数据
const mockOrderData = [
{
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",
},
{
id: "004",
title: "普通订单 - 商品配送",
createTime: "2024-01-14 09:15",
contactName: "赵女士",
contactPhone: "136****6666",
status: "completed",
type: "order",
},
{
id: "005",
title: "网络连接异常",
createTime: "2024-01-15 11:30",
contactName: "刘先生",
contactPhone: "135****5555",
status: "pending",
type: "service",
},
];
const res = await apiCall;
console.log("API响应:", res);
// 计算当前显示的工单列表
const currentOrderList = computed(() => {
return currentTab.value === "all"
? allOrderList.value
: serviceOrderList.value;
});
if (res && res.data && res.data.records) {
const records = res.data.records;
const hasMore = pageNum < res.data.pages; // 判断是否还有更多数据
// 获取空状态文案
const getEmptyText = () => {
return currentTab.value === "all" ? "暂无订单数据" : "暂无服务工单";
// 完成数据加载,第二个参数表示是否还有更多数据
paging.value.complete(records, !hasMore);
console.log("数据加载完成", records.length, "条记录hasMore:", hasMore);
} else {
// 没有数据
paging.value.complete([], true);
}
} catch (error) {
console.error("查询列表失败:", error);
// 加载失败
paging.value.complete(false);
}
};
// Tab切换处理
const handleTabChange = ({ index, item }) => {
console.log("切换到:", item.label);
currentTab.value = item.value;
const handleTabChange = ({ index }) => {
console.log("Tab切换到:", index);
currentTabIndex.value = index;
// 如果当前Tab没有数据则加载数据
if (currentOrderList.value.length === 0) {
loadOrderData(true);
}
// 清空当前数据并重新加载
dataList.value = [];
paging.value.reload();
};
// 加载工单数据
const loadOrderData = async (isRefresh = false) => {
if (isLoading.value) return;
isLoading.value = true;
try {
// 模拟API请求延迟
await new Promise((resolve) => setTimeout(resolve, 1000));
// 模拟数据过滤
let filteredData = [];
if (currentTab.value === "all") {
filteredData = mockOrderData;
} else {
filteredData = mockOrderData.filter((item) => item.type === "service");
}
if (isRefresh) {
// 刷新数据
if (currentTab.value === "all") {
allOrderList.value = [...filteredData];
} else {
serviceOrderList.value = [...filteredData];
}
pageNum.value = 1;
hasMore.value = true;
} else {
// 加载更多
if (currentTab.value === "all") {
allOrderList.value.push(...filteredData.slice(0, 2));
} else {
serviceOrderList.value.push(...filteredData.slice(0, 2));
}
pageNum.value++;
// 模拟没有更多数据
if (pageNum.value >= 3) {
hasMore.value = false;
}
}
} catch (error) {
console.error("加载数据失败:", error);
uni.showToast({
title: "加载失败,请重试",
icon: "none",
});
} finally {
isLoading.value = false;
}
};
// 下拉刷新
const handleRefresh = async () => {
await loadOrderData(true);
};
// 上拉加载更多
const handleLoadMore = async () => {
if (!hasMore.value) return;
await loadOrderData(false);
};
// 工单点击处理
// 处理订单点击
const handleOrderClick = (orderData) => {
console.log("点击工单:", orderData);
uni.navigateTo({
url: `/pages/order/detail?id=${orderData.id}`,
});
console.log("订单点击:", orderData);
// 这里可以添加订单详情跳转逻辑
};
// 单呼叫处理
// 处理订单呼叫
const handleOrderCall = (orderData) => {
console.log("呼叫:", orderData);
// 显示底部咨询栏
showConsultationBar.value = true;
uni.makePhoneCall({
phoneNumber: orderData.contactPhone.replace(/\*/g, ""),
fail: () => {
uni.showToast({
title: "拨号失败",
icon: "none",
});
},
});
console.log("订单呼叫:", orderData);
// 这里可以添加呼叫逻辑
};
// 单完成处理
// 处理订单完成
const handleOrderComplete = (orderData) => {
console.log("标记完成:", orderData);
uni.showModal({
title: "确认操作",
content: "确定要标记此工单为已完成吗?",
success: (res) => {
if (res.confirm) {
// 更新工单状态
const targetList =
currentTab.value === "all"
? allOrderList.value
: serviceOrderList.value;
const orderIndex = targetList.findIndex(
(item) => item.id === orderData.id
);
if (orderIndex !== -1) {
targetList[orderIndex].status = "completed";
}
uni.showToast({
title: "操作成功",
icon: "success",
});
}
},
});
console.log("订单完成:", orderData);
// 这里可以添加订单完成逻辑
};
// 页面加载时初始化数据
onMounted(() => {
loadOrderData(true);
});
</script>
<style scoped lang="scss">

View File

@@ -1,12 +0,0 @@
.order-page {
height: 100vh;
position: relative;
}
.top-nav-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}