Parcourir la source

feat: 新增工单管理模块

新增回收工单和商城工单的完整功能模块,包括:
- 工单列表展示、详情查看、创建和编辑功能
- 任务跟踪记录组件
- 工单状态管理和统计展示
ylong il y a 1 mois
Parent
commit
fd7ce9f505

+ 69 - 6
pages.json

@@ -1,5 +1,5 @@
 {
-	"pages": [ 
+	"pages": [
 		{
 			"path": "pages/login/login",
 			"style": {
@@ -31,9 +31,11 @@
 			}
 		}
 	],
-	"subPackages": [{
+	"subPackages": [
+		{
 			"root": "pages/index",
-			"pages": [{
+			"pages": [
+				{
 					"path": "express/transfer-sign",
 					"style": {
 						"navigationBarTitleText": "中转签收"
@@ -319,12 +321,25 @@
 						"navigationStyle": "custom",
 						"navigationBarTitleText": "选择商品"
 					}
+				},
+				{
+					"path": "work-order/mall",
+					"style": {
+						"navigationBarTitleText": "卖书工单"
+					}
+				},
+				{
+					"path": "work-order/recycle",
+					"style": {
+						"navigationBarTitleText": "回收工单"
+					}
 				}
 			]
 		},
 		{
 			"root": "pages/my",
-			"pages": [{
+			"pages": [
+				{
 					"path": "page/user-info",
 					"style": {
 						"navigationBarTitleText": "用户信息"
@@ -376,7 +391,8 @@
 		},
 		{
 			"root": "pages/order/stat",
-			"pages": [{
+			"pages": [
+				{
 					"path": "pending-review",
 					"style": {
 						"navigationBarTitleText": "待初审统计"
@@ -419,6 +435,52 @@
 					}
 				}
 			]
+		},
+		{
+			"root": "pages/order/recycle",
+			"pages": [
+				{
+					"path": "pending",
+					"style": {
+						"navigationBarTitleText": "待我处理"
+					}
+				},
+				{
+					"path": "created",
+					"style": {
+						"navigationBarTitleText": "我创建的"
+					}
+				},
+				{
+					"path": "detail",
+					"style": {
+						"navigationBarTitleText": "工单详情"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/order/mall",
+			"pages": [
+				{
+					"path": "pending",
+					"style": {
+						"navigationBarTitleText": "待我处理"
+					}
+				},
+				{
+					"path": "created",
+					"style": {
+						"navigationBarTitleText": "我创建的"
+					}
+				},
+				{
+					"path": "detail",
+					"style": {
+						"navigationBarTitleText": "工单详情"
+					}
+				}
+			]
 		}
 	],
 	"globalStyle": {
@@ -430,7 +492,8 @@
 		"backgroundColor": "#ffffff",
 		"color": "#767676",
 		"selectedColor": "#1b77f0",
-		"list": [{
+		"list": [
+			{
 				"pagePath": "pages/index/index",
 				"iconPath": "/static/tabbar/home.png",
 				"selectedIconPath": "/static/tabbar/homeHl.png",

+ 89 - 0
pages/index/work-order/mall.vue

@@ -0,0 +1,89 @@
+<template>
+    <view class="container">
+        <!-- 底部按钮 -->
+        <view class="footer">
+            <!-- 查询区域 -->
+            <view class="query-section">
+                <u-radio-group v-model="formData.searchType">
+                    <u-radio label="查订单" name="1" labelSize="16px" iconSize="16px" />
+                    <u-radio label="查物流" custom-style="margin-left:20px" name="2" labelSize="16px" iconSize="16px" />
+                </u-radio-group>
+
+                <view class="search-box">
+                    <u-input
+                        custom-style="font-size:32rpx"
+                        placeholder-style="fong-size:32rpx"
+                        v-model="formData.search"
+                        :placeholder="formData.searchType == '1' ? '扫描/输入订单号' : '扫描/输入物流单号'"
+                        border="surround"
+                        clearable
+                    >
+                    </u-input>
+                    <u-button color="#c8c8c8" text="查询" @click="handleSubmit" />
+                </view>
+            </view>
+
+            <u-divider></u-divider>
+
+            <view style="display: flex">
+                <u-button size="large" type="success" text="扫码" @click="handleScan" />
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref, onUnmounted } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+
+const formData = ref({
+    searchType: "2",
+    search: "",
+});
+
+// 处理查询
+const handleSubmit = () => {
+    if (!formData.value.search) {
+        uni.$u.toast("请扫描/输入物流单号");
+        return;
+    }
+    uni.$u.http.post("/app/orderinfo/confirmOrder", formData.value).then((res) => {
+        if (res.code == 200) {
+            let text = formData.value.search + "收货成功";
+            uni.$u.ttsModule.speak(text);
+        } else {
+            let text = formData.value.search + "订单不存在";
+            uni.$u.ttsModule.speak(text);
+        }
+    });
+};
+
+// 处理扫码
+const handleScan = () => {
+    uni.scanCode({
+        success: (res) => {
+            formData.value.search = res.result;
+            handleSubmit();
+        },
+    });
+};
+
+// 全局监听事件
+// #ifdef APP-PLUS
+const { unregister } = uni.$u.useEventListener((e) => {
+    formData.value.search = e.barcode;
+    handleSubmit();
+});
+// #endif
+
+onUnmounted(() => {
+    // #ifdef APP-PLUS
+    unregister();
+    // #endif
+});
+
+</script>
+
+<style lang="scss" scoped>
+@import "../components/common.scss";
+</style>

+ 89 - 0
pages/index/work-order/recycle.vue

@@ -0,0 +1,89 @@
+<template>
+    <view class="container">
+        <!-- 底部按钮 -->
+        <view class="footer">
+            <!-- 查询区域 -->
+            <view class="query-section">
+                <u-radio-group v-model="formData.searchType">
+                    <u-radio label="查订单" name="1" labelSize="16px" iconSize="16px" />
+                    <u-radio label="查物流" custom-style="margin-left:20px" name="2" labelSize="16px" iconSize="16px" />
+                </u-radio-group>
+
+                <view class="search-box">
+                    <u-input
+                        custom-style="font-size:32rpx"
+                        placeholder-style="fong-size:32rpx"
+                        v-model="formData.search"
+                        :placeholder="formData.searchType == '1' ? '扫描/输入订单号' : '扫描/输入物流单号'"
+                        border="surround"
+                        clearable
+                    >
+                    </u-input>
+                    <u-button color="#c8c8c8" text="查询" @click="handleSubmit" />
+                </view>
+            </view>
+
+            <u-divider></u-divider>
+
+            <view style="display: flex">
+                <u-button size="large" type="success" text="扫码" @click="handleScan" />
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref, onUnmounted } from "vue";
+import { onLoad, onShow } from "@dcloudio/uni-app";
+
+const formData = ref({
+    searchType: "2",
+    search: "",
+});
+
+// 处理查询
+const handleSubmit = () => {
+    if (!formData.value.search) {
+        uni.$u.toast("请扫描/输入物流单号");
+        return;
+    }
+    uni.$u.http.post("/app/orderinfo/confirmOrder", formData.value).then((res) => {
+        if (res.code == 200) {
+            let text = formData.value.search + "收货成功";
+            uni.$u.ttsModule.speak(text);
+        } else {
+            let text = formData.value.search + "订单不存在";
+            uni.$u.ttsModule.speak(text);
+        }
+    });
+};
+
+// 处理扫码
+const handleScan = () => {
+    uni.scanCode({
+        success: (res) => {
+            formData.value.search = res.result;
+            handleSubmit();
+        },
+    });
+};
+
+// 全局监听事件
+// #ifdef APP-PLUS
+const { unregister } = uni.$u.useEventListener((e) => {
+    formData.value.search = e.barcode;
+    handleSubmit();
+});
+// #endif
+
+onUnmounted(() => {
+    // #ifdef APP-PLUS
+    unregister();
+    // #endif
+});
+
+</script>
+
+<style lang="scss" scoped>
+@import "../components/common.scss";
+</style>

+ 116 - 0
pages/order/components/info-card.vue

@@ -0,0 +1,116 @@
+<template>
+    <view class="info-card">
+        <view class="row">
+            <text class="label">快递单号:</text>
+            <text class="value">{{ detail.waybillCode || '-' }}</text>
+        </view>
+        <view class="row" v-if="detail.orderId">
+            <text class="label">订单号:</text>
+            <text class="link" @tap="copy(detail.orderId)">{{ detail.orderId }}</text>
+        </view>
+        <view class="row">
+            <text class="label">{{ type === 'mall' ? '任务类型:' : '任务类型:' }}</text>
+            <text class="value">{{ detail.taskTypeName || detail.taskType || '-' }}</text>
+            <text class="sub" v-if="detail.taskTypeExt">{{ detail.taskTypeExt }}</text>
+        </view>
+        <view class="row">
+            <text class="label">{{ type === 'mall' ? '验货状态:' : '订单状态:' }}</text>
+            <text class="value">{{ detail.verifyStatusName || detail.orderStatusName || '-' }}</text>
+        </view>
+        <view class="row">
+            <text class="label">任务详情:</text>
+            <text class="value">{{ detail.taskDetail || '-' }}</text>
+        </view>
+        <view class="row">
+            <text class="label">创建人:</text>
+            <text class="value">{{ detail.createUser || '-' }}</text>
+        </view>
+        <view class="row">
+            <text class="label">创建时间:</text>
+            <text class="value">{{ detail.createTime || '-' }}</text>
+        </view>
+        <view class="row" v-if="detail.taskStatusName">
+            <text class="label">任务状态:</text>
+            <text class="status" :class="{ danger: detail.taskStatusName === '待处理' }">{{ detail.taskStatusName }}</text>
+        </view>
+        <view class="image-wrap" v-if="imgSrc">
+            <u-image :src="imgSrc" width="150rpx" height="150rpx" radius="8" mode="aspectFill"
+                @click="preview"></u-image>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+    detail: {
+        type: Object,
+        default: () => ({})
+    },
+    type: {
+        type: String,
+        default: 'recycle'
+    }
+})
+
+const imgSrc = computed(() => props.detail.imageUrl || props.detail.imgPath || '')
+
+const copy = (text) => {
+    if (!text) return
+    uni.setClipboardData({ data: String(text) })
+}
+
+const preview = () => {
+    if (!imgSrc.value) return
+    uni.previewImage({ urls: [imgSrc.value] })
+}
+</script>
+
+<style lang="scss" scoped>
+.info-card {
+    background: #ffffff;
+    border-radius: 12rpx;
+    padding: 20rpx;
+}
+
+.row {
+    display: flex;
+    align-items: baseline;
+    margin-bottom: 12rpx;
+}
+
+.label {
+    font-size: 28rpx;
+    color: #666666;
+}
+
+.value {
+    font-size: 28rpx;
+    color: #333333;
+}
+
+.link {
+    font-size: 28rpx;
+    color: #1b77f0;
+}
+
+.sub {
+    margin-left: 16rpx;
+    font-size: 26rpx;
+    color: #22ac38;
+}
+
+.status {
+    font-size: 28rpx;
+    color: #22ac38;
+}
+
+.status.danger {
+    color: #ff4d4f;
+}
+
+.image-wrap {
+    margin: 16rpx 0;
+}
+</style>

+ 78 - 0
pages/order/components/track-form.vue

@@ -0,0 +1,78 @@
+<template>
+    <view class="track-form">
+        <view class="header">
+            <view class="line"></view>
+            <text class="title">新增跟进记录</text>
+        </view>
+        <view class="field">
+            <text class="label">任务详情</text>
+            <u-textarea v-model="content" placeholder="请输入" :height="120" :autoHeight="true"></u-textarea>
+        </view>
+        <view class="field">
+            <text class="label">上传图片</text>
+            <cy-upload :filename="files" @update:filename="files = $event" @update:loading="uploading = $event" />
+        </view>
+        <view class="actions">
+            <u-button type="primary" :loading="uploading" :disabled="uploading || submitting" @click="submit">跟进</u-button>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import CyUpload from '@/components/cy-upload/index.vue'
+
+const emit = defineEmits(['submit'])
+
+const content = ref('')
+const files = ref([])
+const uploading = ref(false)
+const submitting = ref(false)
+
+const submit = () => {
+    if (uploading.value || submitting.value) return
+    submitting.value = true
+    emit('submit', { content: content.value, files: files.value })
+    content.value = ''
+    files.value = []
+    submitting.value = false
+}
+</script>
+
+<style lang="scss" scoped>
+.track-form {
+    background: #ffffff;
+    border-radius: 12rpx;
+    padding: 24rpx;
+}
+.header {
+    display: flex;
+    align-items: center;
+    gap: 12rpx;
+    margin-bottom: 12rpx;
+}
+.line {
+    width: 8rpx;
+    height: 32rpx;
+    background: #22ac38;
+    border-radius: 4rpx;
+}
+.title {
+    font-size: 30rpx;
+    color: #333333;
+}
+.field {
+    margin-top: 16rpx;
+}
+.label {
+    display: block;
+    font-size: 28rpx;
+    color: #666666;
+    margin-bottom: 8rpx;
+}
+.actions {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 20rpx;
+}
+</style>

+ 101 - 0
pages/order/components/track-record.vue

@@ -0,0 +1,101 @@
+<template>
+    <view class="record-card">
+        <view class="title">
+            <view class="line"></view>
+            <text>任务跟踪记录</text>
+        </view>
+        <view v-for="(item, idx) in records" :key="idx" class="record-item">
+            <view class="item-header">
+                <text class="user">{{ item.user || '-' }}</text>
+                <text class="time">{{ item.time || '-' }}</text>
+            </view>
+            <view class="item-body">
+                <text class="content">{{ item.statusText || item.content || '-' }}</text>
+            </view>
+            <view v-if="getImgs(item).length" class="item-images">
+                <u-image
+                    v-for="(img, i) in getImgs(item)"
+                    :key="i"
+                    :src="img"
+                    width="160rpx"
+                    height="160rpx"
+                    radius="8"
+                    mode="aspectFill"
+                    @click="preview(getImgs(item), i)"
+                ></u-image>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+const props = defineProps({
+    records: {
+        type: Array,
+        default: () => []
+    }
+})
+
+const getImgs = (item) => {
+    const arr = item?.images || item?.files || item?.fileList || item?.pictures || item?.pics || []
+    if (!arr) return []
+    return Array.isArray(arr)
+        ? arr.map(v => typeof v === 'string' ? v : (v?.url || '')).filter(Boolean)
+        : []
+}
+
+const preview = (urls, index) => {
+    uni.previewImage({ urls, current: index })
+}
+</script>
+
+<style lang="scss" scoped>
+.record-card {
+    background: #ffffff;
+    border-radius: 12rpx;
+    padding: 20rpx;
+}
+.title {
+    display: flex;
+    align-items: center;
+    gap: 12rpx;
+    margin-bottom: 16rpx;
+}
+.line {
+    width: 8rpx;
+    height: 32rpx;
+    background: #22ac38;
+    border-radius: 4rpx;
+}
+.record-item {
+    padding: 20rpx 0;
+    border-top: 1rpx solid #f0f0f0;
+}
+.item-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 8rpx;
+}
+.user {
+    font-size: 28rpx;
+    color: #666666;
+}
+.time {
+    font-size: 26rpx;
+    color: #969696;
+}
+.item-body {
+    margin-top: 6rpx;
+}
+.content {
+    font-size: 28rpx;
+    color: #333333;
+}
+.item-images {
+    margin-top: 12rpx;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12rpx;
+}
+</style>

+ 103 - 0
pages/order/components/workorder-item.vue

@@ -0,0 +1,103 @@
+<template>
+    <view class="workorder-card" @tap="handleClick">
+        <view class="edit-icon">
+            <u-icon name="edit-pen" color="#1b77f0" size="18"></u-icon>
+        </view>
+        <view class="content">
+            <u-image :src="imgSrc" width="160rpx" height="190rpx" radius="8" mode="aspectFill"></u-image>
+            <view class="details">
+                <view class="row">
+                    <text class="label">快递单号:</text>
+                    <text class="value">{{ item.waybillCode || '-' }}</text>
+                </view>
+                <view class="row">
+                    <text class="label">任务类型:</text>
+                    <text class="value">{{ item.taskTypeName || item.taskType || '-' }}</text>
+                </view>
+                <view class="row">
+                    <text class="label">创建时间:</text>
+                    <text class="value">{{ item.createTime || '-' }}</text>
+                </view>
+                <view class="row" v-if="showDuration">
+                    <text class="label danger">处理时长:</text>
+                    <text class="value danger">{{ durationText }}</text>
+                </view>
+            </view>
+        </view>
+    </view>
+    </template>
+
+<script setup name="workorderItem">
+import { computed } from 'vue'
+
+const emit = defineEmits(['click'])
+
+const props = defineProps({
+    item: {
+        type: Object,
+        default: () => ({})
+    },
+    showDuration: {
+        type: Boolean,
+        default: false
+    }
+})
+
+const imgSrc = computed(() => {
+    return props.item.imageUrl || props.item.imgPath || '/static/img/book.png'
+})
+
+const durationText = computed(() => {
+    const sec = Number(props.item.waitingTime ?? props.item.duration ?? props.item.durationSec ?? 0)
+    const s = Math.max(0, Math.floor(sec))
+    const h = Math.floor(s / 3600)
+    const m = Math.floor((s % 3600) / 60)
+    const ss = s % 60
+    return `${h}小时${m}分${ss}秒`
+})
+
+const handleClick = () => {
+    emit('click', props.item)
+}
+</script>
+
+<style lang="scss" scoped>
+.workorder-card {
+    background: #ffffff;
+    border-radius: 12rpx;
+    padding: 20rpx;
+    box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
+    position: relative;
+}
+.edit-icon {
+    position: absolute;
+    right: 16rpx;
+    top: 16rpx;
+}
+.content {
+    display: flex;
+    gap: 20rpx;
+    align-items: flex-start;
+}
+.details {
+    flex: 1;
+}
+.row {
+    display: flex;
+    align-items: baseline;
+    margin-bottom: 10rpx;
+}
+.label {
+    font-size: 28rpx;
+    color: #666666;
+}
+.value {
+    font-size: 28rpx;
+    color: #333333;
+}
+.danger {
+    color: #ff4d4f;
+    font-weight: 500;
+}
+</style>
+

+ 187 - 91
pages/order/index.vue

@@ -1,73 +1,94 @@
 <template>
     <view class="order-statistics">
+        <u-skeleton :loading="loading" :rows="4" title></u-skeleton>
         <!-- 顶部统计卡片 -->
-        <view class="overview-card">
-            <view class="overview-grid">
-                <view class="grid-item">
-                    <text class="item-label">今日订单</text>
-                    <text class="item-value">{{
-                        orderStatData.todayOrderNum || 0
-                    }}</text>
-                </view>
-                <view class="grid-item">
-                    <text class="item-label">昨日订单</text>
-                    <text class="item-value">{{
-                        orderStatData.yesterdayOrderNum || 0
-                    }}</text>
-                </view>
-                <view class="grid-item">
-                    <text class="item-label">本月订单</text>
-                    <text class="item-value"
-                        >{{ formatOrderNum(orderStatData.monthOrderNum) || 0
-                        }}<text
-                            class="unit"
-                            v-if="orderStatData.monthOrderNum > 10000"
-                            >万</text
-                        ></text
-                    >
+        <view v-if="!loading">
+            <view class="overview-card">
+                <view class="overview-grid">
+                    <view class="grid-item">
+                        <text class="item-label">今日订单</text>
+                        <text class="item-value">{{ orderStatData.todayOrderNum || 0 }}</text>
+                    </view>
+                    <view class="grid-item">
+                        <text class="item-label">昨日订单</text>
+                        <text class="item-value">{{ orderStatData.yesterdayOrderNum || 0 }}</text>
+                    </view>
+                    <view class="grid-item">
+                        <text class="item-label">本月订单</text>
+                        <text class="item-value">{{ formatOrderNum(orderStatData.monthOrderNum) || 0 }}<text
+                                class="unit" v-if="orderStatData.monthOrderNum > 10000">万</text></text>
+                    </view>
                 </view>
             </view>
-        </view>
 
-        <!-- 订单状态网格 -->
-        <view class="status-grid">
-            <view
-                v-for="(item, index) in statusList"
-                :key="index"
-                class="status-item"
-                @tap="navigateToDetail(item.path)"
-            >
-                <view class="status-content">
-                    <text class="status-label">{{ item.label }}</text>
-                    <view class="status-value-wrap">
-                        <text class="status-value">{{
-                            orderStatData[item.key] || 0
-                        }}</text>
-                        <u-icon
-                            name="arrow-right"
-                            color="#999"
-                            size="14"
-                        ></u-icon>
+            <!-- 订单状态网格 -->
+            <view class="status-grid">
+                <view v-for="(item, index) in statusList" :key="index" class="status-item"
+                    @tap="navigateToDetail(item.path)">
+                    <view class="status-content">
+                        <text class="status-label">{{ item.label }}</text>
+                        <view class="status-value-wrap">
+                            <text class="status-value">{{
+                                orderStatData[item.key] || 0
+                                }}</text>
+                            <u-icon name="arrow-right" color="#999" size="14"></u-icon>
+                        </view>
+                    </view>
+                </view>
+            </view>
+            <view class="workorder-section">
+                <view class="workorder-card">
+                    <view class="workorder-header">
+                        <view class="header-left">
+                            <view class="dot"></view><text>回收工单</text>
+                        </view>
+                    </view>
+                    <view class="workorder-body">
+                        <view @tap="navigateToDetail('/pages/order/recycle/pending')">
+                            <text class="workorder-label">待我完成</text>
+                            <text class="workorder-value">{{
+                                workOrderStat.recycle.myPending || 0
+                                }}</text>
+                        </view>
+                        <view @tap="navigateToDetail('/pages/order/recycle/created')">
+                            <text class="workorder-label">我创建的</text>
+                            <text class="workorder-value">{{
+                                workOrderStat.recycle.myCreated || 0
+                                }}</text>
+                        </view>
+                    </view>
+                </view>
+                <view class="workorder-card">
+                    <view class="workorder-header">
+                        <view class="header-left">
+                            <view class="dot"></view><text>商城工单</text>
+                        </view>
+                    </view>
+                    <view class="workorder-body">
+                        <view @tap="navigateToDetail('/pages/order/mall/pending')">
+                            <text class="workorder-label">待我完成</text>
+                            <text class="workorder-value">{{
+                                workOrderStat.mall.myPending || 0
+                                }}</text>
+                        </view>
+                        <view @tap="navigateToDetail('/pages/order/mall/created')">
+                            <text class="workorder-label">我创建的</text>
+                            <text class="workorder-value">{{
+                                workOrderStat.mall.myCreated || 0
+                                }}</text>
+                        </view>
                     </view>
                 </view>
             </view>
-        </view>
 
-        <!-- 底部操作项 -->
-        <view class="bottom-actions">
-            <u-cell-group :border="false">
-                <u-cell
-                    title="收货统计"
-                    isLink
-                    @click="navigateToDetail('/pages/order/stat/receive-stat')"
-                ></u-cell>
-                <u-cell
-                    title="全部订单"
-                    isLink
-                    @click="navigateToDetail('/pages/order/stat/all')"
-                    :border="false"
-                ></u-cell>
-            </u-cell-group>
+            <!-- 底部操作项 -->
+            <view class="bottom-actions">
+                <u-cell-group :border="false">
+                    <u-cell title="收货统计" isLink @click="navigateToDetail('/pages/order/stat/receive-stat')"></u-cell>
+                    <u-cell title="全部订单" isLink @click="navigateToDetail('/pages/order/stat/all')"
+                        :border="false"></u-cell>
+                </u-cell-group>
+            </view>
         </view>
     </view>
 </template>
@@ -77,6 +98,7 @@ import { ref, onMounted, computed } from "vue";
 
 // 订单统计数据
 const orderStatData = ref({});
+const loading = ref(true);
 
 // 订单状态数据
 const statusList = computed(() => [
@@ -118,6 +140,11 @@ const statusList = computed(() => [
     },
 ]);
 
+const workOrderStat = ref({
+    recycle: { myPending: 0, myCreated: 0 },
+    mall: { myPending: 0, myCreated: 0 },
+});
+
 // 格式化订单数量(超过万的显示为X.XX万)
 const formatOrderNum = (num) => {
     if (!num) return 0;
@@ -129,35 +156,43 @@ const formatOrderNum = (num) => {
 
 // 获取订单统计数据
 const getOrderStatData = async () => {
-    uni.showLoading({
-        title: "加载中...",
-    });
+    try {
+        const res = await uni.$u.http.get("/app/ordersignstat/getPdaOrderStat");
+        if (res.code === 200) {
+            orderStatData.value = res.data || {};
+        }
+    } catch (e) { }
+};
 
-    uni.$u.http
-        .get("/app/ordersignstat/getPdaOrderStat")
-        .then((res) => {
-            if (res.code === 200) {
-                orderStatData.value = res.data;
-            }
-        })
-        .finally(() => {
-            uni.hideLoading();
-        });
+const getWorkOrderStat = async () => {
+    try {
+        const [recycleRes, mallRes] = await Promise.all([
+            uni.$u.http.get("/app/workorder/stat", { params: { type: "recycle" } }),
+            uni.$u.http.get("/app/workorder/stat", { params: { type: "mall" } }),
+        ]);
+        if (recycleRes.code === 200 && recycleRes.data) {
+            workOrderStat.value.recycle.myPending = recycleRes.data.myPending || 0;
+            workOrderStat.value.recycle.myCreated = recycleRes.data.myCreated || 0;
+        }
+        if (mallRes.code === 200 && mallRes.data) {
+            workOrderStat.value.mall.myPending = mallRes.data.myPending || 0;
+            workOrderStat.value.mall.myCreated = mallRes.data.myCreated || 0;
+        }
+    } catch (e) { }
 };
 
 // 页面跳转
 const navigateToDetail = (path) => {
-    if(path!='/pages/order/stat/receive-stat'){
-        return;
-    }
     uni.navigateTo({
         url: path,
     });
 };
 
 // 页面加载时获取数据
-onMounted(() => {
-    getOrderStatData();
+onMounted(async () => {
+    loading.value = true;
+    await Promise.all([getOrderStatData(), getWorkOrderStat()]);
+    loading.value = false;
 });
 </script>
 
@@ -168,13 +203,14 @@ page {
 </style>
 <style lang="scss" scoped>
 .order-statistics {
-    padding: 20rpx;
+    padding: 26rpx;
 
     .overview-card {
-        background: #ffffff;
+        background: linear-gradient(135deg, #22ac38 0%, #41c957 100%);
         border-radius: 16rpx;
-        padding: 30rpx 20rpx;
+        padding: 32rpx 24rpx;
         margin-bottom: 20rpx;
+        box-shadow: 0 12rpx 24rpx rgba(34, 172, 56, 0.15);
 
         .overview-grid {
             display: flex;
@@ -193,24 +229,25 @@ page {
                     transform: translateY(-50%);
                     height: 60%;
                     width: 2rpx;
-                    background: #eeeeee;
+                    background: rgba(255, 255, 255, 0.35);
                 }
 
                 .item-label {
-                    font-size: 32rpx;
-                    color: #666666;
+                    font-size: 30rpx;
+                    color: #ffffffcc;
                     margin-bottom: 12rpx;
                     display: block;
                 }
 
                 .item-value {
-                    font-size: 44rpx;
-                    color: #333333;
+                    font-size: 48rpx;
+                    color: #ffffff;
                     font-weight: 500;
 
                     .unit {
-                        font-size: 24rpx;
+                        font-size: 26rpx;
                         margin-left: 4rpx;
+                        color: #ffffffcc;
                     }
                 }
             }
@@ -232,12 +269,14 @@ page {
         .status-item {
             background: #ffffff;
             border-radius: 16rpx;
-            padding: 24rpx 12rpx;
+            padding: 26rpx 14rpx;
+            box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
+            border: 1rpx solid #f0f2f5;
 
             .status-content {
                 .status-label {
-                    font-size: 32rpx;
-                    color: #666666;
+                    font-size: 30rpx;
+                    color: #333333;
                     margin-bottom: 16rpx;
                     display: block;
                 }
@@ -248,7 +287,7 @@ page {
                     justify-content: space-between;
 
                     .status-value {
-                        font-size: 44rpx;
+                        font-size: 46rpx;
                         color: #333333;
                         font-weight: 500;
                     }
@@ -265,6 +304,63 @@ page {
         background: #ffffff;
         border-radius: 16rpx;
         overflow: hidden;
+        box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
+    }
+
+    .workorder-section {
+        display: grid;
+        grid-template-columns: 1fr;
+    }
+
+    .workorder-card {
+        background: #ffffff;
+        border-radius: 16rpx;
+        padding: 26rpx;
+        margin-bottom: 20rpx;
+        box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
+    }
+
+    .workorder-header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-size: 32rpx;
+        color: #22ac38;
+        margin-bottom: 16rpx;
+    }
+
+    .header-left {
+        display: flex;
+        align-items: center;
+    }
+
+    .dot {
+        width: 16rpx;
+        height: 16rpx;
+        border-radius: 50%;
+        background: #22ac38;
+        margin-right: 12rpx;
+    }
+
+    .workorder-body {
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        gap: 12rpx;
+        align-items: center;
+    }
+
+    .workorder-label {
+        font-size: 28rpx;
+        color: #666666;
+        text-align: center;
+    }
+
+    .workorder-value {
+        font-size: 44rpx;
+        color: #333333;
+        font-weight: 500;
+        text-align: center;
+        margin-left: 20rpx;
     }
 }
 </style>

+ 182 - 0
pages/order/mall/created.vue

@@ -0,0 +1,182 @@
+<template>
+    <view class="common-page" style="padding: 0;">
+        <view class="form-page">
+            <u-form ref="formRef" :model="form" :rules="rules" labelPosition="left" labelWidth="85px">
+                <u-form-item label="快递单号:" prop="waybillCode" required>
+                    <u-input v-model="form.waybillCode" placeholder="请输入快递单号" clearable></u-input>
+                </u-form-item>
+                <u-form-item label="承运商:" prop="carrier" required>
+                    <u-input :value="carrierText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                        @click="showCarrierPicker = true"></u-input>
+                </u-form-item>
+                <u-form-item label="验货状态:" prop="verifyStatus" required>
+                    <u-input :value="verifyText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                        @click="showVerifyPicker = true"></u-input>
+                </u-form-item>
+                <u-form-item label="任务类型:" prop="taskType" required>
+                    <u-input :value="taskTypeText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                        @click="showTaskTypePicker = true"></u-input>
+                </u-form-item>
+                <u-form-item label="任务详情:" prop="taskDetail" required>
+                    <u-textarea v-model="form.taskDetail" placeholder="请输入" :height="120"
+                        :autoHeight="true"></u-textarea>
+                </u-form-item>
+                <u-form-item label="上传图片:" prop="images" required>
+                    <cy-upload :filename="form.images" @update:filename="form.images = $event" />
+                </u-form-item>
+                <view class="divider"></view>
+                <u-form-item label="指派给:" prop="assignTo" required>
+                    <u-input :value="assignText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                        @click="showAssignPicker = true"></u-input>
+                </u-form-item>
+            </u-form>
+        </view>
+
+        <u-picker :show="showCarrierPicker" :columns="[carrierOptions]" title="选择承运商" @confirm="onPickCarrier"
+            @cancel="showCarrierPicker = false" @close="showCarrierPicker = false"></u-picker>
+        <u-picker :show="showVerifyPicker" :columns="[verifyOptions]" title="选择验货状态" @confirm="onPickVerify"
+            @cancel="showVerifyPicker = false" @close="showVerifyPicker = false"></u-picker>
+        <u-picker :show="showTaskTypePicker" :columns="[taskTypeOptions]" title="选择任务类型" @confirm="onPickTaskType"
+            @cancel="showTaskTypePicker = false" @close="showTaskTypePicker = false"></u-picker>
+        <u-picker :show="showAssignPicker" :columns="[assignOptions]" title="指派给" @confirm="onPickAssign"
+            @cancel="showAssignPicker = false" @close="showAssignPicker = false"></u-picker>
+
+        <view class="fixed-bottom">
+            <u-button type="warning" size="large" @click="onCancel">取消</u-button>
+            <u-button type="primary" size="large" :loading="submitting" @click="onSubmit">提交</u-button>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import CyUpload from '@/components/cy-upload/index.vue'
+
+const formRef = ref(null)
+const submitting = ref(false)
+
+const form = ref({
+    waybillCode: '',
+    carrier: '',
+    verifyStatus: '',
+    taskType: '',
+    taskDetail: '',
+    images: [],
+    assignTo: ''
+})
+
+const rules = {
+    waybillCode: [{ required: true, message: '请输入快递单号' }],
+    carrier: [{ required: true, message: '请选择承运商' }],
+    verifyStatus: [{ required: true, message: '请选择验货状态' }],
+    taskType: [{ required: true, message: '请选择任务类型' }],
+    taskDetail: [{ required: true, message: '请输入任务详情' }],
+    images: [{ type: 'array', required: true, min: 1, message: '请上传图片' }],
+    assignTo: [{ required: true, message: '请选择指派对象' }]
+}
+
+const carrierOptions = [
+    { text: '圆通', value: 'YTO' },
+    { text: '中通', value: 'ZTO' },
+    { text: '申通', value: 'STO' },
+    { text: '韵达', value: 'YUNDA' },
+    { text: '京东', value: 'JD' }
+]
+const verifyOptions = [
+    { text: '已验货', value: 'VERIFIED' },
+    { text: '未验货', value: 'UNVERIFIED' }
+]
+const taskTypeOptions = [
+    { text: '部分发货', value: 'PART_SHIP' },
+    { text: '少发', value: 'LESS_SHIP' },
+    { text: '破损', value: 'BROKEN' },
+    { text: '其他', value: 'OTHER' }
+]
+const assignOptions = [
+    { text: 'Agoni', value: 'Agoni' },
+    { text: '张三', value: 'ZhangSan' },
+    { text: '李四', value: 'LiSi' }
+]
+
+const showCarrierPicker = ref(false)
+const showVerifyPicker = ref(false)
+const showTaskTypePicker = ref(false)
+const showAssignPicker = ref(false)
+
+const carrierText = ref('')
+const verifyText = ref('')
+const taskTypeText = ref('')
+const assignText = ref('')
+
+const onPickCarrier = (e) => {
+    const cell = e.value?.[0]
+    form.value.carrier = cell?.value || ''
+    carrierText.value = cell?.text || ''
+    showCarrierPicker.value = false
+}
+const onPickVerify = (e) => {
+    const cell = e.value?.[0]
+    form.value.verifyStatus = cell?.value || ''
+    verifyText.value = cell?.text || ''
+    showVerifyPicker.value = false
+}
+const onPickTaskType = (e) => {
+    const cell = e.value?.[0]
+    form.value.taskType = cell?.value || ''
+    taskTypeText.value = cell?.text || ''
+    showTaskTypePicker.value = false
+}
+const onPickAssign = (e) => {
+    const cell = e.value?.[0]
+    form.value.assignTo = cell?.value || ''
+    assignText.value = cell?.text || ''
+    showAssignPicker.value = false
+}
+
+const onCancel = () => {
+    uni.navigateBack()
+}
+
+const onSubmit = async () => {
+    if (submitting.value) return
+    try {
+        await formRef.value?.validate?.()
+    } catch (e) {
+        return
+    }
+    submitting.value = true
+    const payload = { ...form.value, type: 'mall' }
+    try {
+        const res = await uni.$u.http.post('/app/workorder/create', payload)
+        if (res?.code === 200) {
+            uni.$u.toast('提交成功')
+            uni.navigateBack()
+        } else {
+            uni.$u.toast(res?.msg || '提交失败')
+        }
+    } catch (err) {
+        uni.$u.toast('网络错误,已本地保存')
+    } finally {
+        submitting.value = false
+    }
+}
+
+onLoad((options) => {
+    form.value.waybillCode = options?.waybillCode || ''
+    uni.setNavigationBarTitle({ title: '提交工单' })
+})
+</script>
+
+<style lang="scss" scoped>
+.form-page {
+    padding: 24rpx 24rpx 140rpx;
+    background: #ffffff;
+}
+
+.divider {
+    height: 16rpx;
+    background: #f4f5f5;
+    margin: 8rpx 0 16rpx;
+}
+</style>

+ 67 - 0
pages/order/mall/detail.vue

@@ -0,0 +1,67 @@
+<template>
+    <view class="common-page" style="padding: 0;padding-bottom: 130rpx;">
+        <InfoCard :detail="detail" type="mall" />
+
+        <TrackRecord :records="records" class="mt-20" />
+
+        <view class="fixed-bottom">
+            <u-button type="warning" size="large" @click="onEdit">编辑</u-button>
+            <u-button type="danger" size="large" @click="onVoid">作废</u-button>
+            <u-button type="primary" size="large" @click="onFinish">完成</u-button>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import InfoCard from '@/pages/order/components/info-card.vue'
+import TrackRecord from '@/pages/order/components/track-record.vue'
+
+const detail = ref({})
+const records = ref([])
+
+const getDetail = async (code) => {
+    try {
+        const res = await uni.$u.http.get('/app/workorder/detail', { params: { type: 'mall', waybillCode: code } })
+        if (res?.code === 200 && res?.data) {
+            detail.value = res.data
+            return
+        }
+    } catch (e) { }
+    detail.value = {
+        waybillCode: code || 'YT54454654564',
+        taskTypeName: '部分发货',
+        verifyStatusName: '已验货',
+        taskDetail: '少发一本',
+        createUser: 'zzz',
+        createTime: '2026-05-11 15:00:00',
+        imageUrl: '/static/img/book.png'
+    }
+    records.value = [
+        { time: '2025-02-11 16:00:00', user: 'aaa', statusText: '发货' }
+    ]
+}
+
+const onEdit = () => {
+    uni.$u.toast('进入编辑')
+}
+
+const onVoid = () => {
+    uni.$u.toast('已作废')
+}
+
+const onFinish = () => {
+    uni.$u.toast('任务已完成')
+}
+
+onLoad((options) => {
+    getDetail(options.waybillCode)
+})
+</script>
+
+<style lang="scss" scoped>
+.mt-20 {
+    margin-top: 20rpx;
+}
+</style>

+ 70 - 0
pages/order/mall/pending.vue

@@ -0,0 +1,70 @@
+<template>
+    <view class="common-page" style="padding: 0;">
+        <PageScroll
+            requestStr="/app/workorder/pending"
+            @updateList="updateList"
+            ref="scrollRef"
+            :otherParams="otherParams"
+            method="get"
+        >
+            <view class="list-con" v-if="dataList.length">
+                <WorkorderItem
+                    v-for="(cell, idx) in dataList"
+                    :key="idx"
+                    :item="cell"
+                    :showDuration="false"
+                    @click="goDetail(cell)"
+                    class="mt-20"
+                />
+            </view>
+        </PageScroll>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import PageScroll from '@/components/pageScroll/index.vue'
+import WorkorderItem from '../components/workorder-item.vue'
+
+const dataList = ref([])
+const scrollRef = ref(null)
+
+const otherParams = ref({
+    type: 'mall',
+    status: 'pending'
+})
+
+const updateList = (data, page) => {
+    const mock = [
+        { waybillCode: 'YT54454654564', taskTypeName: '部分发货', createTime: '2026-05-11 15:00:00' },
+        { waybillCode: 'YT54454654564', taskTypeName: '部分发货', createTime: '2026-05-11 15:00:00' },
+        { waybillCode: 'YT54454654564', taskTypeName: '部分发货', createTime: '2026-05-11 15:00:00' },
+        { waybillCode: 'YT54454654564', taskTypeName: '部分发货', createTime: '2026-05-11 15:00:00' },
+        { waybillCode: 'YT54454654564', taskTypeName: '部分发货', createTime: '2026-05-11 15:00:00' },
+    ]
+    dataList.value = Array.isArray(data) && data.length ? data : mock
+}
+
+const refreshList = () => {
+    scrollRef.value?.resetUpScroll()
+}
+
+const goDetail = (cell) => {
+    const code = cell?.waybillCode || ''
+    uni.navigateTo({
+        url: `/pages/order/mall/detail?waybillCode=${code}`
+    })
+}
+
+onShow(() => {
+    refreshList()
+})
+</script>
+
+<style lang="scss" scoped>
+.list-con {
+    padding: 10rpx 30rpx;
+    gap: 30rpx;
+}
+</style>

+ 121 - 0
pages/order/recycle/created.vue

@@ -0,0 +1,121 @@
+<template>
+    <view class="common-page" style="padding: 0;">
+        <view class="form-page">
+            <u-form ref="formRef" :model="form" :rules="rules" labelPosition="left" label-width="85px">
+                <u-form-item label="快递单号:" prop="waybillCode" required>
+                    <u-input v-model="form.waybillCode" placeholder="请输入快递单号" clearable></u-input>
+                </u-form-item>
+                <u-form-item label="订单编号:" prop="orderId" required>
+                    <u-input v-model="form.orderId" placeholder="请输入订单编号" clearable></u-input>
+                </u-form-item>
+                <u-form-item label="任务类型:" prop="taskType" required>
+                    <u-input :value="taskTypeText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                        @click="showTaskTypePicker = true"></u-input>
+                </u-form-item>
+                <u-form-item label="任务详情:" prop="taskDetail" required>
+                    <u-textarea v-model="form.taskDetail" placeholder="请输入" :height="120"
+                        :autoHeight="true"></u-textarea>
+                </u-form-item>
+                <u-form-item label="上传图片:" prop="images" required>
+                    <cy-upload :filename="form.images" @update:filename="form.images = $event" />
+                </u-form-item>
+            </u-form>
+        </view>
+
+        <u-picker :show="showTaskTypePicker" :columns="[taskTypeOptions]" title="选择任务类型" @confirm="onPickTaskType"
+            @cancel="showTaskTypePicker = false" @close="showTaskTypePicker = false"></u-picker>
+
+        <view class="fixed-bottom">
+            <u-button type="warning" size="large"  @click="onCancel">取消</u-button>
+            <u-button type="primary" size="large" :loading="submitting" @click="onSubmit">提交</u-button>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import CyUpload from '@/components/cy-upload/index.vue'
+
+const formRef = ref(null)
+const submitting = ref(false)
+
+const form = ref({
+    waybillCode: '',
+    orderId: '',
+    taskType: '',
+    taskDetail: '',
+    images: []
+})
+
+const rules = {
+    waybillCode: [{ required: true, message: '请输入快递单号' }],
+    orderId: [{ required: true, message: '请输入订单编号' }],
+    taskType: [{ required: true, message: '请选择任务类型' }],
+    taskDetail: [{ required: true, message: '请输入任务详情' }],
+    images: [{ type: 'array', required: true, min: 1, message: '请上传图片' }]
+}
+
+const taskTypeOptions = [
+    { text: '书单不符', value: 'MISMATCH' },
+    { text: '破损', value: 'BROKEN' },
+    { text: '缺件', value: 'LACK' },
+    { text: '其他', value: 'OTHER' }
+]
+
+const showTaskTypePicker = ref(false)
+const taskTypeText = ref('')
+const onPickTaskType = (e) => {
+    const cell = e.value?.[0]
+    form.value.taskType = cell?.value || ''
+    taskTypeText.value = cell?.text || ''
+    showTaskTypePicker.value = false
+}
+
+const onCancel = () => {
+    uni.navigateBack()
+}
+
+const onSubmit = async () => {
+    if (submitting.value) return
+    try {
+        await formRef.value?.validate?.()
+    } catch (e) {
+        return
+    }
+    submitting.value = true
+    const payload = { ...form.value, type: 'recycle' }
+    try {
+        const res = await uni.$u.http.post('/app/workorder/create', payload)
+        if (res?.code === 200) {
+            uni.$u.toast('提交成功')
+            uni.navigateBack()
+        } else {
+            uni.$u.toast(res?.msg || '提交失败')
+        }
+    } catch (err) {
+        uni.$u.toast('网络错误,已本地保存')
+    } finally {
+        submitting.value = false
+    }
+}
+
+onLoad((options) => {
+    form.value.waybillCode = options?.waybillCode || ''
+    form.value.orderId = options?.orderId || ''
+    uni.setNavigationBarTitle({ title: '提交工单' })
+})
+</script>
+
+<style lang="scss" scoped>
+.form-page {
+    padding: 24rpx 24rpx 140rpx;
+    background: #ffffff;
+}
+
+.divider {
+    height: 16rpx;
+    background: #f4f5f5;
+    margin: 20rpx 0;
+}
+</style>

+ 77 - 0
pages/order/recycle/detail.vue

@@ -0,0 +1,77 @@
+<template>
+    <view class="common-page"  style="padding: 0;padding-bottom: 130rpx;">
+        <InfoCard :detail="detail" type="recycle" />
+
+        <TrackForm @submit="onSubmitTrack" class="mt-20" />
+
+        <TrackRecord :records="records" class="mt-20" />
+
+        <view class="fixed-bottom">
+            <u-button type="warning" size="large" @click="onClaim">一键理赔</u-button>
+            <u-button type="primary" size="large" @click="onFinish">完成</u-button>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import InfoCard from '@/pages/order/components/info-card.vue'
+import TrackForm from '@/pages/order/components/track-form.vue'
+import TrackRecord from '@/pages/order/components/track-record.vue'
+
+const detail = ref({})
+const records = ref([])
+
+const getDetail = async (code) => {
+    try {
+        const res = await uni.$u.http.get('/app/workorder/detail', { params: { type: 'recycle', waybillCode: code } })
+        if (res?.code === 200 && res?.data) {
+            detail.value = res.data
+            return
+        }
+    } catch (e) {}
+    detail.value = {
+        waybillCode: code || 'YT54454654564',
+        orderId: '5622225',
+        taskTypeName: '书单不符',
+        taskTypeExt: '快速理赔',
+        orderStatusName: '已到货-待到货审核',
+        taskDetail: '书单不符',
+        createUser: 'zzz',
+        createTime: '2026-05-11 15:00:00',
+        imageUrl: '/static/img/book.png',
+        taskStatusName: '待处理'
+    }
+    records.value = [
+        { time: '2025-02-11 16:00:00', user: 'aaa', statusText: '完成' }
+    ]
+}
+
+const onSubmitTrack = (payload) => {
+    records.value.unshift({
+        time: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd hh:MM:ss'),
+        user: '我',
+        content: payload.content,
+        statusText: payload.content ? payload.content : '更新'
+    })
+}
+
+const onClaim = () => {
+    uni.$u.toast('已触发一键理赔')
+}
+
+const onFinish = () => {
+    uni.$u.toast('任务已完成')
+}
+
+onLoad((options) => {
+    getDetail(options.waybillCode)
+})
+</script>
+
+<style lang="scss" scoped>
+.mt-20 {
+    margin-top: 20rpx;
+}
+</style>

+ 140 - 0
pages/order/recycle/pending.vue

@@ -0,0 +1,140 @@
+<template>
+    <view class="common-page" style="padding: 0;">
+        <view class="filter-bar">
+            <view class="picker-trigger" @tap="pickerShow = true">
+                <text class="picker-label">{{ selectedTaskType?.text || '任务类型' }}</text>
+                <u-icon name="arrow-down" size="16" color="#666"></u-icon>
+            </view>
+            <u-button class="ml-10" type="primary" size="small" @click="onQuery">查询</u-button>
+            <u-button class="ml-10" size="small" @click="onReset">重置</u-button>
+        </view>
+
+        <u-picker
+            :show="pickerShow"
+            :columns="[taskTypeOptions]"
+            title="选择任务类型"
+            @confirm="onPickConfirm"
+            @cancel="pickerShow = false"
+            @close="pickerShow = false"
+        ></u-picker>
+
+        <PageScroll
+            requestStr="/app/workorder/pending"
+            @updateList="updateList"
+            ref="scrollRef"
+            :otherParams="otherParams"
+            method="get"
+            :diffHeight="150"
+        >
+            <view class="list-con" v-if="dataList.length">
+                <WorkorderItem
+                    v-for="(cell, idx) in dataList"
+                    :key="idx"
+                    :item="cell"
+                    :showDuration="true"
+                    @click="goDetail(cell)"
+                    class="mt-20"
+                />
+            </view>
+        </PageScroll>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import PageScroll from '@/components/pageScroll/index.vue'
+import WorkorderItem from '../components/workorder-item.vue'
+
+const dataList = ref([])
+const scrollRef = ref(null)
+const pickerShow = ref(false)
+const selectedTaskType = ref(null)
+
+const taskTypeOptions = [
+    { text: '全部', value: '' },
+    { text: '书单不符', value: 'MISMATCH' },
+    { text: '破损', value: 'BROKEN' },
+    { text: '缺件', value: 'LACK' },
+    { text: '其他', value: 'OTHER' },
+]
+
+const otherParams = ref({
+    type: 'recycle',
+    status: 'pending',
+    taskType: ''
+})
+
+const updateList = (data, page) => {
+    const mock = [
+        { waybillCode: 'YT54454654564', taskTypeName: '书单不符', createTime: '2026-05-11 15:00:00', waitingTime: 15 * 3600 + 5 * 60 + 26 },
+        { waybillCode: 'YT54454654564', taskTypeName: '书单不符', createTime: '2026-05-11 15:00:00', waitingTime: 15 * 3600 + 5 * 60 + 26 },
+        { waybillCode: 'YT54454654564', taskTypeName: '书单不符', createTime: '2026-05-11 15:00:00', waitingTime: 15 * 3600 + 5 * 60 + 26 },
+        { waybillCode: 'YT54454654564', taskTypeName: '书单不符', createTime: '2026-05-11 15:00:00', waitingTime: 15 * 3600 + 5 * 60 + 26 },
+        { waybillCode: 'YT54454654564', taskTypeName: '书单不符', createTime: '2026-05-11 15:00:00', waitingTime: 15 * 3600 + 5 * 60 + 26 },
+    ]
+    dataList.value = Array.isArray(data) && data.length ? data : mock
+}
+
+const refreshList = () => {
+    scrollRef.value?.resetUpScroll()
+}
+
+const onPickConfirm = (e) => {
+    const val = e?.value?.[0]
+    selectedTaskType.value = val
+    otherParams.value.taskType = val?.value || ''
+    pickerShow.value = false
+}
+
+const onQuery = () => {
+    refreshList()
+}
+const onReset = () => {
+    selectedTaskType.value = null
+    otherParams.value.taskType = ''
+    refreshList()
+}
+
+const goDetail = (cell) => {
+    const code = cell?.waybillCode || ''
+    uni.navigateTo({
+        url: `/pages/order/recycle/detail?waybillCode=${code}`
+    })
+}
+
+onShow(() => {
+    refreshList()
+})
+</script>
+
+<style lang="scss" scoped>
+.filter-bar {
+    display: flex;
+    align-items: center;
+    padding: 20rpx;
+    background-color: #ffffff;
+    gap: 16rpx;
+}
+.picker-trigger {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10rpx 20rpx;
+    border: 1rpx solid #e6e8eb;
+    border-radius: 8rpx;
+    min-width: 200px;
+}
+.picker-label {
+    font-size: 28rpx;
+    color: #333;
+}
+.list-con {
+    padding: 10rpx 30rpx;
+    gap: 30rpx;
+}
+.ml-10 {
+    margin-left: 10rpx;
+}
+</style>