소스 검색

feat(work-order): 添加工单模块功能及优化现有逻辑

1. 新增历史工单页面,支持查看工单详情和操作
2. 优化工单创建、编辑、完成、作废和重开功能
3. 调整工单列表和详情页接口调用和数据展示
4. 完善图片预览和工单状态显示逻辑
5. 修复页面跳转和参数传递问题
6. 更新工单统计接口和字段映射
ylong 3 주 전
부모
커밋
bfdb8ac0be

BIN
.DS_Store


+ 4 - 4
components/pageScroll/index.vue

@@ -88,7 +88,7 @@ const getList = (mescroll) => {
         })
 
         methods.then(res => {
-            emit('update:total', res.total)
+            emit('update:total', res.total || res.data.total || 0)
             resolve(res)
         })
     })
@@ -96,10 +96,10 @@ const getList = (mescroll) => {
 
 // 上拉加载的回调: 其中num:当前页 从1开始, size:每页数据条数,默认10
 const upCallback = (mescroll) => {
-    console.log(mescroll, 'mescroll')
     if (!props.requestStr) return;
     getList(mescroll).then(res => {
-        const curPageData = res.rows || res.data || [] // 当前页数据
+        console.log(res, 'res')
+        const curPageData = res.rows || res.data?.rows || res.data || [] // 当前页数据
         if (mescroll.num == 1) list.value = []; // 第一页需手动制空列表
         list.value = list.value.concat(curPageData); //追加新数据
 
@@ -111,7 +111,7 @@ const upCallback = (mescroll) => {
         //mescroll.endByPage(curPageData.length, totalPage); //必传参数(当前页的数据个数, 总页数)
 
         //方法二(推荐): 后台接口有返回列表的总数据量 totalSize
-        mescroll.endBySize(curPageData.length, res.total); //必传参数(当前页的数据个数, 总数据量)
+        mescroll.endBySize(curPageData.length, res.total || res.data.total || 0); //必传参数(当前页的数据个数, 总数据量)
 
         //方法三(推荐): 您有其他方式知道是否有下一页 hasNext
         //mescroll.endSuccess(curPageData.length, hasNext); //必传参数(当前页的数据个数, 是否有下一页true/false)

+ 6 - 0
pages.json

@@ -334,6 +334,12 @@
 					"style": {
 						"navigationBarTitleText": "回收工单"
 					}
+				},
+				{
+					"path": "work-order/history",
+					"style": {
+						"navigationBarTitleText": "历史工单"
+					}
 				}
 			]
 		},

+ 9 - 1
pages/index/detail/components/OrderInfo.vue

@@ -37,7 +37,7 @@
                         detail.waybillCode }})</text></text>
             </view>
             <view class="info-item">
-                <text class="label">发件人<text style="color: #e99d42">(所有单)</text></text>
+                <text class="label">发件人<text style="color: #e99d42;cursor: pointer;" @click="handleUserOrders">(所有单)</text></text>
 
                 <view class="content flex flex-a-c flex-j-c">
                     <text class="content-text" @click="copyToClipboard(detail.sendMobile)">复制</text>
@@ -274,6 +274,14 @@ function handleSmsSuccess() {
     });
     uni.$u.ttsModule.speak("短信发送成功");
 }
+
+// 跳转到用户所有单页面
+function handleUserOrders() {
+    playGlobalSound();
+    uni.navigateTo({
+        url: `/pages/index/detail/user-orders?userId=${props.detail.userId}`
+    });
+}
 </script>
 
 <style lang="scss" scoped>

+ 24 - 151
pages/index/index.vue

@@ -17,11 +17,27 @@
                 </view>
             </view>
         </view>
+
+        <!-- 工单操作列表 -->
+        <view class="section" v-if="workOrderOperations.length > 0">
+            <view class="section-title">工单</view>
+            <view class="grid-container">
+                <view
+                    class="grid-item"
+                    v-for="op in workOrderOperations"
+                    :key="op.path"
+                    @click="handleNavigation(op.path)"
+                    :class="op.type"
+                >
+                    {{ op.name }}
+                </view>
+            </view>
+        </view>
     </view>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from "vue";
+import { ref } from "vue";
 import { onLoad } from "@dcloudio/uni-app";
 
 //点击全局音效
@@ -121,168 +137,25 @@ const formatName = (name) => {
     return result;
 };
 
-// 快递操作列表
-const expressOperations = ref([
-    {
-        name: "中转\n\r签收",
-        path: "/pages/index/express/transfer-sign",
-        type: "primary",
-    },
-    {
-        name: "到仓\n\r签收",
-        path: "/pages/index/express/warehouse-sign",
-        type: "warning",
-    },
-    {
-        name: "重量\n\r修改",
-        path: "/pages/index/express/weight-modify",
-        type: "primary",
-    },
-    {
-        name: "快递\n\r验收",
-        path: "/pages/index/express/quick-check",
-        type: "warning",
-    },
-    {
-        name: "路由签收\n\r异常",
-        path: "/pages/index/express/route-exception",
-        type: "primary",
-    },
-    {
-        name: "快递\n\r拆包",
-        path: "/pages/index/express/quick-unpack",
-        type: "warning",
-    },
-]);
 
-// 审核操作列表
-const auditOperations = ref([
-    {
-        name: "确认\n\r收货",
-        path: "/pages/index/audit/confirm-receipt",
-        type: "primary",
-    },
-    {
-        name: "扫书\n\r查单",
-        path: "/pages/index/audit/scan-order",
-        type: "warning",
-    },
-    {
-        name: "根据快递单\n\r或订单",
-        path: "/pages/index/audit/express-order",
-        type: "warning",
-    },
-    {
-        name: "根据\n\r发件人",
-        path: "/pages/index/audit/sender",
-        type: "primary",
-    },
-]);
 
-// WMS操作列表
-const wmsOperations = ref([
-    {
-        name: "中等\n\r入库",
-        path: "/pages/index/wms/medium-in",
-        type: "primary",
-    },
-    {
-        name: "良品\n\r入库",
-        path: "/pages/index/wms/good-in",
-        type: "warning",
-    },
-    {
-        name: "次品\n\r入库",
-        path: "/pages/index/wms/secondary-in",
-        type: "primary",
-    },
-    {
-        name: "不良\n\r入库",
-        path: "/pages/index/wms/bad-in",
-        type: "warning",
-    },
-    {
-        name: "不良\n\r出库",
-        path: "/pages/index/wms/bad-out",
-        type: "primary",
-    },
-    {
-        name: "不良\n\r下架",
-        path: "/pages/index/wms/bad-off",
-        type: "warning",
-    },
-    {
-        name: "订单\n\r查询",
-        path: "/pages/index/wms/order-query",
-        type: "primary",
-    },
-    {
-        name: "库位\n\r订单",
-        path: "/pages/index/wms/location-order",
-        type: "warning",
-    },
-    {
-        name: "快速盘点",
-        path: "/pages/index/wms/speedy-check",
-        type: "primary",
-        span: 24,
-    },
-]);
 
-// 统计操作列表
-const statisticsOperations = ref([
-    {
-        name: "审核统计",
-        path: "/pages/index/statistic/audit",
-        type: "warning",
-        span: 24,
-    },
-    {
-        name: "售后\n\r统计",
-        path: "/pages/index/statistic/after-sale",
-        type: "primary",
-    },
-    {
-        name: "打包\n\r统计",
-        path: "/pages/index/statistic/package",
-        type: "warning",
-    },
-]);
-//线下核单
-const offlineOperations = ref([
-    {
-        name: "线下\n\r核单",
-        path: "/pages/index/offline/check-order",
-        type: "primary",
-    },
-    {
-        name: "核单\n\r记录",
-        path: "/pages/index/offline/check-record",
-        type: "warning",
-    },
-]);
 
-//录入信息
-const entryOperations = ref([
+
+// 工单操作列表
+const workOrderOperations = ref([
     {
-        name: "扫码\n\r查书",
-        path: "/pages/index/entry/scan-book",
+        name: "回收工单",
+        path: "/pages/index/work-order/recycle",
         type: "primary",
     },
     {
-        name: "录入\n\r书籍重量",
-        path: "/pages/index/entry/book-weight",
+        name: "卖书工单",
+        path: "/pages/index/work-order/mall",
         type: "warning",
     },
-    {
-        name: "商品档案推送",
-        path: "/pages/index/entry/commodity-scan",
-        type: "primary",
-        span: 24,
-    },
 ]);
 
-
 // 页面跳转方法
 const handleNavigation = (path) => {
     uni.navigateTo({

+ 383 - 0
pages/index/work-order/history.vue

@@ -0,0 +1,383 @@
+<template>
+    <view class="common-page" style="padding: 0;">
+        <view class="header">
+            <u-navbar title="历史工单" :border="false" fixed safe-area-inset-top>
+                <template #left>
+                    <u-icon name="arrow-left" color="#333333" size="20" @click="goBack"></u-icon>
+                </template>
+            </u-navbar>
+        </view>
+
+        <view class="content" v-if="workOrderDetail">
+            <!-- 工单信息 -->
+            <view class="info-section">
+                <view class="info-row">
+                    <text class="label">快递单号:</text>
+                    <text class="value">{{ workOrderDetail.waybillCode }}</text>
+                </view>
+                <view class="info-row" v-if="workOrderDetail.orderId">
+                    <text class="label">订单编号:</text>
+                    <text class="value link-text" @click="goToOrderDetail">{{ workOrderDetail.orderId }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">任务类型:</text>
+                    <text class="value">{{ workOrderDetail.taskTypeName || '-' }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">验货状态:</text>
+                    <text class="value">{{ workOrderDetail.inspectionStatusName || '-' }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">任务详情:</text>
+                    <text class="value">{{ workOrderDetail.deatil || '-' }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">创建人:</text>
+                    <text class="value">{{ workOrderDetail.createUserName || '-' }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">创建时间:</text>
+                    <text class="value">{{ formatTime(workOrderDetail.createTime) }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">任务状态:</text>
+                    <text class="value status-text" :class="'status-' + workOrderDetail.status">{{ getStatusText(workOrderDetail.status) }}</text>
+                </view>
+
+                <view class="info-row">
+                    <text class="label">指派人:</text>
+                    <text class="value">{{  workOrderDetail.handleUsers.map(item => item.userName).join(',') || '-' }}</text>
+                </view>
+                
+                <!-- 图片展示 -->
+                <view class="image-list" v-if="workOrderDetail.imgInfo && workOrderDetail.imgInfo.imgUrlList.length > 0">
+                    <image 
+                        v-for="(img, index) in workOrderDetail.imgInfo.imgUrlList" 
+                        :key="index"
+                        :src="img" 
+                        mode="aspectFill"
+                        class="work-order-image"
+                        @click="previewImage(index)"
+                    ></image>
+                </view>
+            </view>
+        </view>
+
+        <!-- 底部操作按钮 -->
+        <view class="fixed-bottom" v-if="showButtons">
+            <template v-if="isCreator">
+                <!-- 创建人按钮 -->
+                <template v-if="workOrderDetail.status === 1 || workOrderDetail.status === 2 || workOrderDetail.status === 3">
+                    <!-- 待处理、处理中、仓库处理状态 -->
+                    <u-button type="warning" size="large" text="编辑" @click="handleEdit"></u-button>
+                    <u-button type="primary" size="large" text="完成" @click="handleFinish"></u-button>
+                    <u-button type="error" size="large" text="作废" @click="handleCancel"></u-button>
+                </template>
+                <template v-else-if="workOrderDetail.status === 4 || workOrderDetail.status === 5">
+                    <!-- 已完成、已作废状态 -->
+                    <u-button type="primary" size="large" text="重开" @click="handleReopen"></u-button>
+                </template>
+            </template>
+            
+            <template v-else>
+                <!-- 被指派人按钮 -->
+                <template v-if="workOrderDetail.status === 1 || workOrderDetail.status === 2 || workOrderDetail.status === 3">
+                    <!-- 未处理、处理中、仓库处理状态 -->
+                    <u-button type="warning" size="large" text="编辑" @click="handleEdit"></u-button>
+                    <u-button type="primary" size="large" text="完成" @click="handleFinish"></u-button>
+                </template>
+                <!-- 已完成、已作废状态无操作按钮 -->
+            </template>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+
+const workOrderId = ref('')
+const orderType = ref('') // 1-卖书 2-回收
+const waybillCode = ref('')
+const orderId = ref('')
+const workOrderDetail = ref(null)
+const currentUserId = ref(null)
+
+// 判断是否为创建人
+const isCreator = computed(() => {
+    if (!workOrderDetail.value || !currentUserId.value) return false
+    return workOrderDetail.value.createUserId === currentUserId.value
+})
+
+// 是否显示按钮
+const showButtons = computed(() => {
+    if (!workOrderDetail.value) return false
+    const status = workOrderDetail.value.status
+    // 状态:1-待处理 2-处理中 3-仓库处理 4-完成 5-作废
+    return [1, 2, 3, 4, 5].includes(status)
+})
+
+// 获取状态文本
+const getStatusText = (status) => {
+    const statusMap = {
+        1: '待处理',
+        2: '处理中',
+        3: '仓库处理',
+        4: '已完成',
+        5: '已作废'
+    }
+    return statusMap[status] || '未知状态'
+}
+
+// 格式化时间
+const formatTime = (time) => {
+    if (!time) return '-'
+    return time.replace('T', ' ').substring(0, 19)
+}
+
+// 获取工单详情
+const getWorkOrderDetail = async () => {
+    try {
+        uni.showLoading({ title: '加载中...' })
+        const res = await uni.$u.http.get('/app/workOrder/getWorkOrderDetail', {
+            params: { workOrderId: workOrderId.value }
+        })
+        
+        if (res.code === 200 && res.data) {
+            workOrderDetail.value = res.data
+        } else {
+            uni.$u.toast(res.msg || '获取工单详情失败')
+        }
+    } catch (error) {
+        console.error(error)
+        uni.$u.toast('网络错误')
+    } finally {
+        uni.hideLoading()
+    }
+}
+
+// 预览图片
+const previewImage = (index) => {
+    if (workOrderDetail.value?.imgInfo) {
+        const urls = Array.isArray(workOrderDetail.value.imgInfo) ? workOrderDetail.value.imgInfo : workOrderDetail.value.imgInfo.split(',')
+        if (urls.length > 0) {
+            uni.previewImage({
+                urls: urls,
+                current: index
+            })
+        }
+    }
+}
+
+// 返回上一页
+const goBack = () => {
+    uni.navigateBack()
+}
+
+// 跳转到订单详情
+const goToOrderDetail = () => {
+    if (!workOrderDetail.value?.orderId) return
+    uni.navigateTo({
+        url: `/pages/index/detail/index?id=${workOrderDetail.value.orderId}`
+    })
+}
+
+// 编辑工单
+const handleEdit = () => {
+    uni.navigateTo({
+        url: `/pages/order/${orderType.value == 1 ? 'mall' : 'recycle'}/created?waybillCode=${waybillCode.value}&orderId=${orderId.value}&workOrderId=${workOrderId.value}&mode=edit`
+    })
+}
+
+// 完成工单
+const handleFinish = async () => {
+    uni.showModal({
+        title: '提示',
+        content: '确定要完成该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    uni.showLoading({ title: '提交中...' })
+                    const result = await uni.$u.http.post('/app/workOrder/finish', {
+                        ids: [workOrderId.value]
+                    })
+                    
+                    if (result.code === 200) {
+                        uni.$u.ttsModule.speak('提交成功')
+                        uni.$u.toast('操作成功')
+                        getWorkOrderDetail()
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (error) {
+                    console.error(error)
+                    uni.$u.toast('网络错误')
+                } finally {
+                    uni.hideLoading()
+                }
+            }
+        }
+    })
+}
+
+// 作废工单
+const handleCancel = async () => {
+    uni.showModal({
+        title: '提示',
+        content: '确定要作废该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    uni.showLoading({ title: '提交中...' })
+                    const result = await uni.$u.http.post('/app/workOrder/cancel', {
+                        id: workOrderId.value
+                    })
+                    
+                    if (result.code === 200) {
+                        uni.$u.ttsModule.speak('操作成功')
+                        uni.$u.toast('操作成功')
+                        getWorkOrderDetail()
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (error) {
+                    console.error(error)
+                    uni.$u.toast('网络错误')
+                } finally {
+                    uni.hideLoading()
+                }
+            }
+        }
+    })
+}
+
+// 重开工单(重新打开已完成的工单)
+const handleReopen = async () => {
+    uni.showModal({
+        title: '提示',
+        content: '确定要重开该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    uni.showLoading({ title: '提交中...' })
+                    const result = await uni.$u.http.post('/app/workOrder/reopen', {
+                        id: workOrderId.value
+                    })
+                    
+                    if (result.code === 200) {
+                        uni.$u.ttsModule.speak('操作成功')
+                        uni.$u.toast('操作成功')
+                        getWorkOrderDetail()
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (error) {
+                    console.error(error)
+                    uni.$u.toast('网络错误')
+                } finally {
+                    uni.hideLoading()
+                }
+            }
+        }
+    })
+}
+
+onLoad((options) => {
+    workOrderId.value = options.workOrderId
+    orderType.value = options.type
+    waybillCode.value = options.waybillCode || ''
+    orderId.value = options.orderId || ''
+    
+    // 获取当前用户ID(这里需要从用户信息中获取,暂时使用固定值或从缓存读取)
+    const userInfo = uni.getStorageSync('userInfo') || {}
+    currentUserId.value = userInfo.userId || null
+    
+    // 加载工单详情
+    getWorkOrderDetail()
+})
+</script>
+
+<style lang="scss" scoped>
+.content {
+    padding-top: 12px;
+    border-radius: 0;
+    /* #ifdef APP-PLUS */
+    padding-top: 100px;
+    /* #endif */
+}
+
+.info-section {
+    background-color: #ffffff;
+    padding: 20px;
+    margin-bottom: 12px;
+}
+
+.info-row {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 16px;
+    line-height: 1.5;
+
+    &:last-child {
+        margin-bottom: 0;
+    }
+}
+
+.label {
+    color: #666;
+    font-size: 28rpx;
+    width: 160rpx;
+    flex-shrink: 0;
+}
+
+.value {
+    color: #333;
+    font-size: 28rpx;
+    flex: 1;
+    word-break: break-all;
+}
+
+.link-text {
+    color: #2979ff;
+    cursor: pointer;
+    
+    &:active {
+        opacity: 0.7;
+    }
+}
+
+.status-text {
+    &.status-1 {
+        color: #e6a23c; // 待处理 - 橙色
+    }
+
+    &.status-2 {
+        color: #409eff; // 处理中 - 蓝色
+    }
+
+    &.status-3 {
+        color: #909399; // 仓库处理 - 灰色
+    }
+
+    &.status-4 {
+        color: #67c23a; // 已完成 - 绿色
+    }
+
+    &.status-5 {
+        color: #f56c6c; // 已作废 - 红色
+    }
+}
+
+.image-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20rpx;
+    margin-top: 20rpx;
+}
+
+.work-order-image {
+    width: 180rpx;
+    height: 180rpx;
+    border-radius: 8rpx;
+}
+</style>

+ 31 - 16
pages/index/work-order/mall.vue

@@ -4,17 +4,12 @@
         <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' ? '扫描/输入订单号' : '扫描/输入物流单号'"
+                        placeholder="扫描/输入物流单号"
                         border="surround"
                         clearable
                     >
@@ -37,25 +32,45 @@ import { ref, onUnmounted } from "vue";
 import { onLoad, onShow } from "@dcloudio/uni-app";
 
 const formData = ref({
-    searchType: "2",
     search: "",
 });
 
 // 处理查询
-const handleSubmit = () => {
+const handleSubmit = async () => {
     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);
+    
+    try {
+        const res = await uni.$u.http.get("/app/workOrder/scanWorkOrder", {
+            params: {
+                waybillCode: formData.value.search
+            }
+        });
+        
+        if (res.code === 200) {
+            if (res.data && res.data.id) {
+                // 存在历史工单,跳转到历史工单页面
+                uni.navigateTo({
+                    url: `/pages/index/work-order/history?workOrderId=${res.data.id}&type=1&waybillCode=${res.data.waybillCode || formData.value.search}&orderId=${res.data.orderId || ''}`,
+                });
+            } else {
+                // 不存在历史工单,跳转到创建工单页面,携带回填信息
+                const orderId = res.data?.orderId || '';
+                const waybillCode = res.data?.waybillCode || formData.value.search;
+                const expressType = res.data?.expressType || '';
+                uni.navigateTo({
+                    url: `/pages/order/mall/created?waybillCode=${waybillCode}&orderId=${orderId}&expressType=${expressType}&readonly=1`,
+                });
+            }
         } else {
-            let text = formData.value.search + "订单不存在";
-            uni.$u.ttsModule.speak(text);
+            uni.$u.toast(res.msg || "查询失败");
         }
-    });
+    } catch (error) {
+        console.error(error);
+        uni.$u.toast("查询失败");
+    }
 };
 
 // 处理扫码
@@ -86,4 +101,4 @@ onUnmounted(() => {
 
 <style lang="scss" scoped>
 @import "../components/common.scss";
-</style>
+</style>

+ 34 - 9
pages/index/work-order/recycle.vue

@@ -42,20 +42,45 @@ const formData = ref({
 });
 
 // 处理查询
-const handleSubmit = () => {
+const handleSubmit = async () => {
     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);
+    
+    try {
+        const res = await uni.$u.http.get("/app/workOrder/scanWorkOrder", {
+            params: {
+                waybillCode: formData.value.searchType === '2' ? formData.value.search : undefined,
+                orderId: formData.value.searchType === '1' ? formData.value.search : undefined
+            }
+        });
+        
+        if (res.code === 200) {
+            const reqWaybillCode = formData.value.searchType === '2' ? formData.value.search : '';
+            const reqOrderId = formData.value.searchType === '1' ? formData.value.search : '';
+            
+            if (res.data && res.data.id) {
+                // 存在历史工单,跳转到历史工单页面
+                uni.navigateTo({
+                    url: `/pages/index/work-order/history?workOrderId=${res.data.id}&type=2&waybillCode=${res.data.waybillCode}&orderId=${res.data.orderId}`,
+                });
+            } else {
+                // 不存在历史工单,跳转到创建工单页面,携带回填信息
+                const orderId = res.data?.orderId || reqOrderId;
+                const waybillCode = res.data?.waybillCode || reqWaybillCode;
+                const expressType = res.data?.expressType || '';
+                uni.navigateTo({
+                    url: `/pages/order/recycle/created?waybillCode=${waybillCode}&orderId=${orderId}&expressType=${expressType}&readonly=1`,
+                });
+            }
         } else {
-            let text = formData.value.search + "订单不存在";
-            uni.$u.ttsModule.speak(text);
+            uni.$u.toast(res.msg || "查询失败");
         }
-    });
+    } catch (error) {
+        console.error(error);
+        uni.$u.toast("查询失败");
+    }
 };
 
 // 处理扫码
@@ -86,4 +111,4 @@ onUnmounted(() => {
 
 <style lang="scss" scoped>
 @import "../components/common.scss";
-</style>
+</style>

+ 2 - 4
pages/my/components/orderItem.vue

@@ -38,9 +38,7 @@
 
         <!-- 操作按钮 -->
         <view class="action-buttons">
-            <u-button size="small" type="primary" @click="handleAudit" :disabled="item.status != 8 || item.status != 9"
-                >到货审核</u-button
-            >
+            <u-button size="small" type="primary" @click="handleAudit">到货审核</u-button>
             <u-button size="small" @click="handleView">查看</u-button>
         </view>
     </view>
@@ -52,7 +50,7 @@ import { ref, computed } from "vue";
 const props = defineProps({
     item: {
         type: Object,
-        default: () => {},
+        default: () => { },
     },
 });
 

+ 8 - 2
pages/order/components/info-card.vue

@@ -62,8 +62,14 @@ const copy = (text) => {
 }
 
 const preview = () => {
-    if (!imgSrc.value) return
-    uni.previewImage({ urls: [imgSrc.value] })
+    if (props.detail.imageUrls && props.detail.imageUrls.length > 0) {
+        uni.previewImage({
+            urls: props.detail.imageUrls,
+            current: 0
+        })
+    } else if (imgSrc.value) {
+        uni.previewImage({ urls: [imgSrc.value] })
+    }
 }
 </script>
 

+ 4 - 4
pages/order/components/track-record.vue

@@ -6,11 +6,11 @@
         </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>
+                <text class="user">{{ item.createUserName || '-' }}</text>
+                <text class="time">{{ item.createTime || '-' }}</text>
             </view>
             <view class="item-body">
-                <text class="content">{{ item.statusText || item.content || '-' }}</text>
+                <text class="content">{{ item.remark || '-' }} {{ item.newContent || '-' }}</text>
             </view>
             <view v-if="getImgs(item).length" class="item-images">
                 <u-image
@@ -37,7 +37,7 @@ const props = defineProps({
 })
 
 const getImgs = (item) => {
-    const arr = item?.images || item?.files || item?.fileList || item?.pictures || item?.pics || []
+    const arr = item?.images || item?.files || item?.fileList || item?.pictures || item?.newContent || []
     if (!arr) return []
     return Array.isArray(arr)
         ? arr.map(v => typeof v === 'string' ? v : (v?.url || '')).filter(Boolean)

+ 47 - 6
pages/order/components/workorder-item.vue

@@ -4,7 +4,7 @@
             <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>
+            <u-image :src="imgSrc" width="160rpx" height="190rpx" radius="8" mode="aspectFill" @click="previewImage"></u-image>
             <view class="details">
                 <view class="row">
                     <text class="label">快递单号:</text>
@@ -28,7 +28,7 @@
     </template>
 
 <script setup name="workorderItem">
-import { computed } from 'vue'
+import { computed, ref, onMounted, onUnmounted } from 'vue'
 
 const emit = defineEmits(['click'])
 
@@ -43,22 +43,63 @@ const props = defineProps({
     }
 })
 
+const currentTime = ref(Date.now())
+
+// 每秒更新当前时间
+let timer = null
+
+onMounted(() => {
+    timer = setInterval(() => {
+        currentTime.value = Date.now()
+    }, 100)
+})
+
+onUnmounted(() => {
+    if (timer) {
+        clearInterval(timer)
+    }
+})
+
 const imgSrc = computed(() => {
-    return props.item.imageUrl || props.item.imgPath || '/static/img/book.png'
+    // 优先使用 imgInfo.imgUrlList
+    if (props.item.imgInfo && props.item.imgInfo.imgUrlList && props.item.imgInfo.imgUrlList[0]) {
+        return props.item.imgInfo.imgUrlList[0]
+    }
+    // 然后使用其他图片字段
+    return props.item.imgUrl || 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 createTime = new Date(props.item.createTime).getTime()
+    if (!createTime) return '0 小时 0 分 0 秒 0'
+    
+    const diff = Math.max(0, currentTime.value - createTime)
+    const ms = diff % 1000
+    const s = Math.floor(diff / 1000)
     const h = Math.floor(s / 3600)
     const m = Math.floor((s % 3600) / 60)
     const ss = s % 60
-    return `${h}小时${m}分${ss}秒`
+    return `${h}小时${m}分${ss}秒${ms}`
 })
 
 const handleClick = () => {
     emit('click', props.item)
 }
+
+const previewImage = () => {
+    // 优先使用 imgInfo.imgUrlList
+    if (props.item.imgInfo && props.item.imgInfo.imgUrlList && props.item.imgInfo.imgUrlList.length > 0) {
+        uni.previewImage({
+            urls: props.item.imgInfo.imgUrlList,
+            current: 0
+        })
+    } else if (imgSrc.value && imgSrc.value !== '/static/img/book.png') {
+        uni.previewImage({
+            urls: [imgSrc.value]
+        })
+    }
+}
 </script>
 
 <style lang="scss" scoped>

+ 10 - 7
pages/order/index.vue

@@ -167,18 +167,21 @@ const getOrderStatData = async () => {
 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" } }),
+            uni.$u.http.get("/app/workOrder/statistics", { params: { type: 2 } }), // type=2: 收书工单
+            uni.$u.http.get("/app/workOrder/statistics", { params: { type: 1 } }), // type=1: 卖书工单
         ]);
         if (recycleRes.code === 200 && recycleRes.data) {
-            workOrderStat.value.recycle.myPending = recycleRes.data.myPending || 0;
-            workOrderStat.value.recycle.myCreated = recycleRes.data.myCreated || 0;
+            // 映射字段:needFinishNum -> myPending, createNum -> myCreated
+            workOrderStat.value.recycle.myPending = recycleRes.data.needFinishNum || 0;
+            workOrderStat.value.recycle.myCreated = recycleRes.data.createNum || 0;
         }
         if (mallRes.code === 200 && mallRes.data) {
-            workOrderStat.value.mall.myPending = mallRes.data.myPending || 0;
-            workOrderStat.value.mall.myCreated = mallRes.data.myCreated || 0;
+            workOrderStat.value.mall.myPending = mallRes.data.needFinishNum || 0;
+            workOrderStat.value.mall.myCreated = mallRes.data.createNum || 0;
         }
-    } catch (e) { }
+    } catch (e) {
+        console.error("获取工单统计失败:", e);
+    }
 };
 
 // 页面跳转

+ 360 - 43
pages/order/mall/created.vue

@@ -3,18 +3,27 @@
         <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>
+                    <template v-if="isReadonly && form.waybillCode">
+                        <text class="readonly-text">{{ form.waybillCode }}</text>
+                    </template>
+                    <u-input v-else v-model="form.waybillCode" placeholder="请输入快递单号" clearable></u-input>
                 </u-form-item>
+                <!-- <u-form-item label="订单编号:" prop="orderId" v-if="form.orderId || isReadonly">
+                    <template v-if="isReadonly && form.orderId">
+                        <text class="clickable-text" @click="goToOrderDetail">{{ form.orderId }}</text>
+                    </template>
+                    <u-input v-else v-model="form.orderId" 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-input v-model="carrierText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                        @click="!isReadonly && (showCarrierPicker = true)"></u-input>
                 </u-form-item>
                 <u-form-item label="验货状态:" prop="verifyStatus" required>
-                    <u-input :value="verifyText" readonly suffixIcon="arrow-down" placeholder="请选择"
+                    <u-input v-model="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="请选择"
+                    <u-input v-model="taskTypeText" readonly suffixIcon="arrow-down" placeholder="请选择"
                         @click="showTaskTypePicker = true"></u-input>
                 </u-form-item>
                 <u-form-item label="任务详情:" prop="taskDetail" required>
@@ -25,9 +34,9 @@
                     <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 label="指派给:" prop="assignTo">
+                    <u-input v-model="assignText" readonly suffixIcon="arrow-down" placeholder="请选择 (非必填)"
+                        @click="openAssignPicker"></u-input>
                 </u-form-item>
             </u-form>
         </view>
@@ -38,8 +47,33 @@
             @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>
+        <!-- 指派给选择器 - 使用 popup + checkbox 实现多选 -->
+        <u-popup v-model:show="showAssignPicker" mode="bottom" :round="20">
+            <view class="assign-picker-content">
+                <view class="assign-header">
+                    <text class="title">选择指派人</text>
+                    <view class="actions">
+                        <text class="cancel-btn" @click="showAssignPicker = false">取消</text>
+                        <text class="confirm-btn" @click="confirmAssign">确定</text>
+                    </view>
+                </view>
+                <view class="assign-body">
+                    <view 
+                        v-for="(item, index) in assignOptions" 
+                        :key="index"
+                        class="assign-item"
+                        @click="toggleAssign(index)"
+                    >
+                        <text class="item-text">{{ item.text }}</text>
+                        <view 
+                            class="custom-checkbox"
+                            :class="{ checked: tempAssignIndexes.includes(index) }"
+                        >
+                        </view>
+                    </view>
+                </view>
+            </view>
+        </u-popup>
 
         <view class="fixed-bottom">
             <u-button type="warning" size="large" @click="onCancel">取消</u-button>
@@ -58,51 +92,87 @@ const submitting = ref(false)
 
 const form = ref({
     waybillCode: '',
-    carrier: '',
+    orderId: '',
+    expressType: '',
     verifyStatus: '',
     taskType: '',
     taskDetail: '',
     images: [],
-    assignTo: ''
+    assignTo: []
 })
 
 const rules = {
     waybillCode: [{ required: true, message: '请输入快递单号' }],
-    carrier: [{ required: true, message: '请选择承运商' }],
+    expressType: [{ 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: '请选择指派对象' }]
+    assignTo: [{ required: false, message: '请选择指派对象' }]
 }
 
-const carrierOptions = [
-    { text: '圆通', value: 'YTO' },
-    { text: '中通', value: 'ZTO' },
-    { text: '申通', value: 'STO' },
-    { text: '韵达', value: 'YUNDA' },
-    { text: '京东', value: 'JD' }
-]
+const carrierOptions = ref([])
 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' }
+    { text: '已验货', value: 1 },
+    { text: '未验货', value: 0 }
 ]
+const taskTypeOptions = ref([])
+const assignOptions = ref([{ text: '默认(不指派)', value: '' }])
+
+// 获取快递公司字典
+const getCarrierOptions = async () => {
+    try {
+        const res = await uni.$u.http.get('/system/dict/data/type/shop_order_express_name')
+        if (res.code === 200 && res.data) {
+            carrierOptions.value = res.data.map(item => ({
+                text: item.dictLabel,
+                value: item.dictValue
+            }))
+        }
+    } catch (e) {
+        console.error('获取快递公司失败', e)
+    }
+}
+
+// 获取任务类型字典
+const getTaskTypeOptions = async () => {
+    try {
+        const res = await uni.$u.http.get('/system/dict/data/type/sell_task_type')
+        if (res.code === 200 && res.data) {
+            taskTypeOptions.value = res.data.map(item => ({
+                text: item.dictLabel,
+                value: parseInt(item.dictValue)
+            }))
+        }
+    } catch (e) {
+        console.error('获取任务类型失败', e)
+    }
+}
+
+// 获取指派人列表
+const getAssignOptions = async () => {
+    try {
+        const res = await uni.$u.http.get('/system/user/list', {
+            params: {
+                pageSize: 1000
+            }
+        })
+        if (res.code === 200 && res.rows) {
+            assignOptions.value = res.rows.map(item => ({
+                text: item.nickName,
+                value: item.userId
+            }))
+        }
+    } catch (e) {
+        console.error('获取指派人失败', e)
+    }
+}
 
 const showCarrierPicker = ref(false)
 const showVerifyPicker = ref(false)
 const showTaskTypePicker = ref(false)
 const showAssignPicker = ref(false)
+// 临时存储选中的索引数组
+const tempAssignIndexes = ref([])
 
 const carrierText = ref('')
 const verifyText = ref('')
@@ -111,33 +181,135 @@ const assignText = ref('')
 
 const onPickCarrier = (e) => {
     const cell = e.value?.[0]
-    form.value.carrier = cell?.value || ''
+    form.value.expressType = cell?.value || ''
     carrierText.value = cell?.text || ''
     showCarrierPicker.value = false
+    console.log('选择承运商:', form.value.expressType, carrierText.value)
 }
 const onPickVerify = (e) => {
     const cell = e.value?.[0]
     form.value.verifyStatus = cell?.value || ''
     verifyText.value = cell?.text || ''
     showVerifyPicker.value = false
+    console.log('选择验货状态:', form.value.verifyStatus, verifyText.value)
 }
 const onPickTaskType = (e) => {
     const cell = e.value?.[0]
     form.value.taskType = cell?.value || ''
     taskTypeText.value = cell?.text || ''
     showTaskTypePicker.value = false
+    console.log('选择任务类型:', form.value.taskType, taskTypeText.value)
+}
+
+// 打开指派人选择器时,初始化临时选中状态
+const openAssignPicker = () => {
+    if (isReadonly.value) return
+    // 根据当前已选择的值,初始化 tempAssignIndexes
+    tempAssignIndexes.value = []
+    if (form.value.assignTo && form.value.assignTo.length > 0) {
+        assignOptions.value.forEach((item, index) => {
+            if (form.value.assignTo.includes(item.value)) {
+                tempAssignIndexes.value.push(index)
+            }
+        })
+    }
+    showAssignPicker.value = true
+    console.log('打开选择器,已选中索引:', tempAssignIndexes.value)
 }
 const onPickAssign = (e) => {
-    const cell = e.value?.[0]
-    form.value.assignTo = cell?.value || ''
-    assignText.value = cell?.text || ''
+    // 这个方法不再使用,改用 toggleAssign 和 confirmAssign
+}
+
+// 切换选中状态
+const toggleAssign = (index) => {
+    const idx = tempAssignIndexes.value.indexOf(index)
+    if (idx > -1) {
+        tempAssignIndexes.value.splice(idx, 1)
+    } else {
+        tempAssignIndexes.value.push(index)
+    }
+}
+
+// 确认选择
+const confirmAssign = () => {
+    const selectedList = []
+    tempAssignIndexes.value.forEach(index => {
+        selectedList.push(assignOptions.value[index])
+    })
+    
+    form.value.assignTo = selectedList.map(item => item.value)
+    assignText.value = selectedList.map(item => item.text).join(',')
     showAssignPicker.value = false
+    console.log('选择指派人:', form.value.assignTo, assignText.value)
 }
 
 const onCancel = () => {
     uni.navigateBack()
 }
 
+const goToOrderDetail = () => {
+    if (!form.value.orderId) return
+    uni.navigateTo({
+        url: `/pages/index/detail/index?id=${form.value.orderId}`
+    })
+}
+
+const workOrderId = ref('')
+const isEdit = ref(false)
+const isReadonly = ref(false)
+
+const onLoadDetail = async (id) => {
+    try {
+        const res = await uni.$u.http.get('/app/workOrder/getWorkOrderDetail', {
+            params: { workOrderId: id }
+        })
+        if (res.code === 200 && res.data) {
+            const data = res.data
+            form.value.waybillCode = data.waybillCode || ''
+            form.value.orderId = data.orderId || ''
+            form.value.taskDetail = data.deatil || data.taskDetail || ''
+            form.value.images = data.imgInfo?.imgUrlList.length ? data.imgInfo.imgUrlList : []
+            
+            // 加载任务类型字典
+            await getTaskTypeOptions()
+
+            // 回显任务类型
+            if (data.taskType) {
+                form.value.taskType = data.taskType
+                const matchedType = taskTypeOptions.value.find(item => item.value === data.taskType)
+                if (matchedType) {
+                    taskTypeText.value = matchedType.text
+                }
+            }
+            // 回显指派人(多选)
+            if (data.handleUsers && data.handleUsers.length > 0) {
+                form.value.assignTo = data.handleUsers.map(user => user.userId)
+                assignText.value = data.handleUsers.map(user => user.userName).join(',')
+            }
+            if (data.inspectionStatus !== undefined && data.inspectionStatus !== null) {
+                form.value.verifyStatus = data.inspectionStatus
+                const matchedVerify = verifyOptions.find(item => item.value === data.inspectionStatus)
+                if (matchedVerify) {
+                    verifyText.value = matchedVerify.text
+                }
+            }
+            // 回显承运商
+            if (data.expressType) {
+                form.value.expressType = data.expressType
+                // 从字典中查找对应的文本
+                const matchedCarrier = carrierOptions.value.find(item => {
+                    return item.value === data.expressType
+                })
+                if (matchedCarrier) {
+                    carrierText.value = matchedCarrier.text
+                }
+            }
+        }
+    } catch (e) {
+        console.error(e)
+    }
+}
+
 const onSubmit = async () => {
     if (submitting.value) return
     try {
@@ -146,9 +318,27 @@ const onSubmit = async () => {
         return
     }
     submitting.value = true
-    const payload = { ...form.value, type: 'mall' }
+    const payload = {
+        taskType: form.value.taskType || 9,
+        inspectionStatus: form.value.verifyStatus ?? 0,
+        deatil: form.value.taskDetail,
+        expressType: form.value.expressType || 0,
+        waybillCode: form.value.waybillCode,
+        orderId: form.value.orderId || '',
+        type: 1, // 工单类型:1-卖书
+        imgInfo: form.value.images.length > 0 ? form.value.images : [],
+        handleUsers: form.value.assignTo.length > 0 ? form.value.assignTo : []
+    }
+
+    if (isEdit.value) {
+        payload.id = workOrderId.value
+        payload.updateType = 1
+    }
+
+    const apiUrl = isEdit.value ? '/app/workOrder/update' : '/app/workOrder/createWorkOrder'
+
     try {
-        const res = await uni.$u.http.post('/app/workorder/create', payload)
+        const res = await uni.$u.http.post(apiUrl, payload)
         if (res?.code === 200) {
             uni.$u.toast('提交成功')
             uni.navigateBack()
@@ -164,7 +354,35 @@ const onSubmit = async () => {
 
 onLoad((options) => {
     form.value.waybillCode = options?.waybillCode || ''
-    uni.setNavigationBarTitle({ title: '提交工单' })
+    form.value.orderId = options?.orderId || ''
+
+    if (options?.readonly == 1) {
+        isReadonly.value = true
+        // 尝试回显承运商
+        if (options?.expressType !== undefined && options?.expressType !== null) {
+            form.value.expressType = parseInt(options.expressType)
+            // 从字典中查找对应的文本
+            const matchedCarrier = carrierOptions.value.find(item => item.value === options.expressType)
+            if (matchedCarrier) {
+                carrierText.value = matchedCarrier.text
+            }
+        }
+    }
+
+    if (options?.mode === 'edit' && options?.workOrderId) {
+        isEdit.value = true
+        isReadonly.value = true
+        workOrderId.value = options.workOrderId
+        uni.setNavigationBarTitle({ title: '编辑工单' })
+        onLoadDetail(options.workOrderId)
+    } else {
+        uni.setNavigationBarTitle({ title: '提交工单' })
+    }
+
+    // 加载字典数据
+    getCarrierOptions()
+    getTaskTypeOptions()
+    getAssignOptions()
 })
 </script>
 
@@ -177,6 +395,105 @@ onLoad((options) => {
 .divider {
     height: 16rpx;
     background: #f4f5f5;
-    margin: 8rpx 0 16rpx;
+    margin: 20rpx 0;
+}
+
+// 指派人多选选择器样式
+.assign-picker-content {
+    padding: 0 24rpx;
+    background: #fff;
+    border-radius: 20rpx 20rpx 0 0;
+    
+    .assign-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 24rpx 0;
+        border-bottom: 1px solid #eee;
+        
+        .title {
+            font-size: 32rpx;
+            font-weight: bold;
+            color: #333;
+        }
+        
+        .actions {
+            display: flex;
+            gap: 24rpx;
+            
+            .cancel-btn {
+                font-size: 28rpx;
+                color: #666;
+                padding: 8rpx 16rpx;
+            }
+            
+            .confirm-btn {
+                font-size: 28rpx;
+                color: #2979ff;
+                font-weight: 500;
+                padding: 8rpx 16rpx;
+            }
+        }
+    }
+    
+    .assign-body {
+        max-height: 600rpx;
+        overflow-y: auto;
+        padding: 16rpx 0;
+        
+        .assign-item {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 24rpx 16rpx;
+            border-bottom: 1px solid #f5f5f5;
+            
+            &:active {
+                background: #f8f9fa;
+            }
+            
+            .item-text {
+                font-size: 30rpx;
+                color: #333;
+            }
+            
+            .custom-checkbox {
+                width: 40rpx;
+                height: 40rpx;
+                border: 2px solid #c8c9cc;
+                border-radius: 8rpx;
+                position: relative;
+                transition: all 0.2s;
+                
+                &.checked {
+                    background: #2979ff;
+                    border-color: #2979ff;
+                    
+                    &::after {
+                        content: '✓';
+                        position: absolute;
+                        top: 50%;
+                        left: 50%;
+                        transform: translate(-50%, -50%);
+                        color: #fff;
+                        font-size: 28rpx;
+                        font-weight: bold;
+                    }
+                }
+            }
+        }
+    }
+}
+
+.readonly-text {
+    font-size: 28rpx;
+    color: #333;
+    line-height: 60rpx;
+}
+
+.clickable-text {
+    font-size: 28rpx;
+    color: #2979ff;
+    line-height: 60rpx;
 }
 </style>

+ 143 - 25
pages/order/mall/detail.vue

@@ -2,61 +2,179 @@
     <view class="common-page" style="padding: 0;padding-bottom: 130rpx;">
         <InfoCard :detail="detail" type="mall" />
 
-        <TrackRecord :records="records" class="mt-20" />
+        <TrackRecord v-if="records.length > 0" :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 class="fixed-bottom" v-if="showButtons">
+            <u-button v-if="canEdit" type="warning" size="large" @click="onEdit">编辑</u-button>
+            <u-button v-if="canVoid" type="error" size="large" @click="onVoid">作废</u-button>
+            <u-button v-if="canFinish" type="primary" size="large" @click="onFinish">完成</u-button>
+            <u-button v-if="canReopen" type="primary" size="large" @click="onReopen">重开</u-button>
         </view>
     </view>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, computed } 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 workOrderId = ref('')
 
-const getDetail = async (code) => {
+// 获取当前用户信息
+const currentUserId = ref(uni.getStorageSync('userInfo')?.userId || '')
+
+// 计算按钮显示逻辑
+const canEdit = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    const isAssignee = detail.value.handleUsers?.some(user => user.userId === currentUserId.value)
+    
+    if (isCreator) {
+        return status === 1 || status === 2 || status === 3 // 待处理、处理中、仓库处理
+    } else if (isAssignee) {
+        return status === 1 || status === 2 || status === 3 // 待处理、处理中、仓库处理
+    }
+    return false
+})
+
+const canVoid = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    return isCreator && (status === 1 || status === 2 || status === 3) // 待处理、处理中、仓库处理
+})
+
+const canFinish = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    const isAssignee = detail.value.handleUsers?.some(user => user.userId === currentUserId.value)
+    return (isCreator || isAssignee) && (status === 1 || status === 2 || status === 3) // 待处理、处理中、仓库处理
+})
+
+const canReopen = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    return isCreator && (status === 4 || status === 5) // 已完成、已作废
+})
+
+const showButtons = computed(() => {
+    return canEdit.value || canVoid.value || canFinish.value || canReopen.value
+})
+
+const getDetail = async (id) => {
     try {
-        const res = await uni.$u.http.get('/app/workorder/detail', { params: { type: 'mall', waybillCode: code } })
+        const res = await uni.$u.http.get(`/app/workOrder/getWorkOrderDetail`, {
+            params: { workOrderId: id }
+        })
         if (res?.code === 200 && res?.data) {
-            detail.value = res.data
+            const data = res.data;
+            detail.value = {
+                ...data,
+                waybillCode: data.waybillCode || '',
+                taskTypeName: data.taskTypeName || '',
+                verifyStatusName: data.inspectionStatusName || '', // 验货状态
+                taskDetail: data.deatil || data.taskDetail || '',
+                createUser: data.createUserName || '',
+                createUserId: data.createUserId || '',
+                createTime: data.createTime || '',
+                imageUrl: (data.imgInfo && data.imgInfo.imgUrlList && data.imgInfo.imgUrlList[0]) || data.imageUrl || '/static/img/book.png',
+                imageUrls: (data.imgInfo && data.imgInfo.imgUrlList) || [],
+                status: data.status,
+                handleUsers: data.handleUsers || []
+            }
+            records.value = data.changeRecords || []
             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'
+    } catch (e) {
+        console.error(e)
     }
-    records.value = [
-        { time: '2025-02-11 16:00:00', user: 'aaa', statusText: '发货' }
-    ]
 }
 
 const onEdit = () => {
-    uni.$u.toast('进入编辑')
+    uni.navigateTo({
+        url: `/pages/order/mall/created?workOrderId=${workOrderId.value}&mode=edit&waybillCode=${detail.value.waybillCode}&readonly=1`
+    })
 }
 
 const onVoid = () => {
-    uni.$u.toast('已作废')
+    uni.showModal({
+        title: '提示',
+        content: '确定要作废该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    const result = await uni.$u.http.post('/app/workOrder/cancel', { id: workOrderId.value })
+                    if (result.code === 200) {
+                        uni.$u.toast('已作废')
+                        setTimeout(() => {
+                            uni.navigateBack()
+                        }, 1000)
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (e) {
+                    console.error(e)
+                }
+            }
+        }
+    })
 }
 
 const onFinish = () => {
-    uni.$u.toast('任务已完成')
+    uni.showModal({
+        title: '提示',
+        content: '确定要完成该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    const result = await uni.$u.http.post('/app/workOrder/finish', { ids: [workOrderId.value] })
+                    if (result.code === 200) {
+                        uni.$u.toast('已完成')
+                        setTimeout(() => {
+                            uni.navigateBack()
+                        }, 1000)
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (e) {
+                    console.error(e)
+                }
+            }
+        }
+    })
+}
+
+const onReopen = () => {
+    uni.showModal({
+        title: '提示',
+        content: '确定要重开该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    // 这里需要调用重开工单的接口
+                    // 假设接口是 /app/workOrder/reopen
+                    const result = await uni.$u.http.post('/app/workOrder/reopen', { id: workOrderId.value })
+                    if (result.code === 200) {
+                        uni.$u.toast('已重开')
+                        setTimeout(() => {
+                            uni.navigateBack()
+                        }, 1000)
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (e) {
+                    console.error(e)
+                }
+            }
+        }
+    })
 }
 
 onLoad((options) => {
-    getDetail(options.waybillCode)
+    workOrderId.value = options.id
+    getDetail(options.id)
 })
 </script>
 

+ 7 - 13
pages/order/mall/pending.vue

@@ -1,7 +1,7 @@
 <template>
     <view class="common-page" style="padding: 0;">
         <PageScroll
-            requestStr="/app/workorder/pending"
+            requestStr="/app/workOrder/getMyHandleWorkOrder"
             @updateList="updateList"
             ref="scrollRef"
             :otherParams="otherParams"
@@ -31,19 +31,13 @@ const dataList = ref([])
 const scrollRef = ref(null)
 
 const otherParams = ref({
-    type: 'mall',
-    status: 'pending'
+    type: 1 // 工单类型:1-卖书
 })
 
 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
+    // 如果data是响应对象,提取rows
+    const rows = data?.rows || data
+    dataList.value = Array.isArray(rows) && rows.length ? rows : []
 }
 
 const refreshList = () => {
@@ -51,9 +45,9 @@ const refreshList = () => {
 }
 
 const goDetail = (cell) => {
-    const code = cell?.waybillCode || ''
+    const id = cell?.id || ''
     uni.navigateTo({
-        url: `/pages/order/mall/detail?waybillCode=${code}`
+        url: `/pages/order/mall/detail?id=${id}`
     })
 }
 

+ 123 - 17
pages/order/recycle/created.vue

@@ -3,21 +3,27 @@
         <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>
+                    <template v-if="isReadonly && form.waybillCode">
+                        <text class="readonly-text">{{ form.waybillCode }}</text>
+                    </template>
+                    <u-input v-else 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>
+                    <template v-if="isReadonly && form.orderId">
+                        <text class="clickable-text" @click="goToOrderDetail">{{ form.orderId }}</text>
+                    </template>
+                    <u-input v-else 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="请选择"
+                    <u-input v-model="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 label="上传图片:" prop="imgInfo" required>
+                    <cy-upload :filename="form.imgInfo" @update:filename="form.imgInfo = $event" />
                 </u-form-item>
             </u-form>
         </view>
@@ -26,7 +32,7 @@
             @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="warning" size="large" @click="onCancel">取消</u-button>
             <u-button type="primary" size="large" :loading="submitting" @click="onSubmit">提交</u-button>
         </view>
     </view>
@@ -45,7 +51,7 @@ const form = ref({
     orderId: '',
     taskType: '',
     taskDetail: '',
-    images: []
+    imgInfo: []
 })
 
 const rules = {
@@ -53,19 +59,30 @@ const rules = {
     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 taskTypeOptions = ref([])
 
 const showTaskTypePicker = ref(false)
 const taskTypeText = ref('')
+
+// 获取任务类型字典
+const getTaskTypeOptions = async () => {
+    try {
+        const res = await uni.$u.http.get('/system/dict/data/type/task_type')
+        if (res.code === 200 && res.data) {
+            taskTypeOptions.value = res.data.map(item => ({
+                text: item.dictLabel,
+                value: parseInt(item.dictValue)
+            }))
+        }
+    } catch (e) {
+        console.error('获取任务类型失败', e)
+    }
+}
+
 const onPickTaskType = (e) => {
+    console.log(e)
     const cell = e.value?.[0]
     form.value.taskType = cell?.value || ''
     taskTypeText.value = cell?.text || ''
@@ -76,6 +93,47 @@ const onCancel = () => {
     uni.navigateBack()
 }
 
+const goToOrderDetail = () => {
+    if (!form.value.orderId) return
+    uni.navigateTo({
+        url: `/pages/index/detail/index?id=${form.value.orderId}`
+    })
+}
+
+const workOrderId = ref('')
+const isEdit = ref(false)
+const isReadonly = ref(false)
+const expressTypeFromScan = ref(0)
+
+const onLoadDetail = async (id) => {
+    try {
+        const res = await uni.$u.http.get('/app/workOrder/getWorkOrderDetail', {
+            params: { workOrderId: id }
+        })
+        if (res.code === 200 && res.data) {
+            const data = res.data
+            form.value.waybillCode = data.waybillCode || ''
+            form.value.orderId = data.orderId || ''
+            form.value.taskDetail = data.deatil || data.taskDetail || ''
+            form.value.imgInfo = data.imgInfo?.imgUrlList?.length ? data.imgInfo.imgUrlList : []
+
+            // 加载任务类型字典
+            await getTaskTypeOptions()
+
+            // 回显任务类型
+            if (data.taskType) {
+                form.value.taskType = data.taskType
+                const matchedType = taskTypeOptions.value.find(item => item.value === data.taskType)
+                if (matchedType) {
+                    taskTypeText.value = matchedType.text
+                }
+            }
+        }
+    } catch (e) {
+        console.error(e)
+    }
+}
+
 const onSubmit = async () => {
     if (submitting.value) return
     try {
@@ -84,9 +142,26 @@ const onSubmit = async () => {
         return
     }
     submitting.value = true
-    const payload = { ...form.value, type: 'recycle' }
+    const payload = {
+        taskType: form.value.taskType,
+        inspectionStatus: 0, // 默认未验货
+        deatil: form.value.taskDetail,
+        waybillCode: form.value.waybillCode,
+        orderId: form.value.orderId,
+        type: 2, // 工单类型:2-收书
+        imgInfo: form.value.imgInfo,
+        handleUsers: []
+    }
+
+    if (isEdit.value) {
+        payload.id = workOrderId.value
+        payload.updateType = 1
+    }
+
+    const apiUrl = isEdit.value ? '/app/workOrder/update' : '/app/workOrder/createWorkOrder'
+
     try {
-        const res = await uni.$u.http.post('/app/workorder/create', payload)
+        const res = await uni.$u.http.post(apiUrl, payload)
         if (res?.code === 200) {
             uni.$u.toast('提交成功')
             uni.navigateBack()
@@ -103,7 +178,26 @@ const onSubmit = async () => {
 onLoad((options) => {
     form.value.waybillCode = options?.waybillCode || ''
     form.value.orderId = options?.orderId || ''
-    uni.setNavigationBarTitle({ title: '提交工单' })
+    if (options?.expressType) {
+        expressTypeFromScan.value = parseInt(options.expressType)
+    }
+
+    if (options?.readonly == 1) {
+        isReadonly.value = true
+    }
+
+    if (options?.mode === 'edit' && options?.workOrderId) {
+        isEdit.value = true
+        isReadonly.value = true
+        workOrderId.value = options.workOrderId
+        uni.setNavigationBarTitle({ title: '编辑工单' })
+        onLoadDetail(options.workOrderId)
+    } else {
+        uni.setNavigationBarTitle({ title: '提交工单' })
+    }
+
+    // 加载任务类型字典
+    getTaskTypeOptions()
 })
 </script>
 
@@ -118,4 +212,16 @@ onLoad((options) => {
     background: #f4f5f5;
     margin: 20rpx 0;
 }
+
+.readonly-text {
+    font-size: 28rpx;
+    color: #333;
+    line-height: 60rpx;
+}
+
+.clickable-text {
+    font-size: 28rpx;
+    color: #2979ff;
+    line-height: 60rpx;
+}
 </style>

+ 181 - 35
pages/order/recycle/detail.vue

@@ -1,20 +1,22 @@
 <template>
-    <view class="common-page"  style="padding: 0;padding-bottom: 130rpx;">
-        <InfoCard :detail="detail" type="recycle" />
+    <view class="common-page" style="padding: 0;padding-bottom: 130rpx;">
+        <InfoCard :detail="detail" type="recycle" @clickImage="previewImage" />
 
         <TrackForm @submit="onSubmitTrack" class="mt-20" />
 
-        <TrackRecord :records="records" class="mt-20" />
+        <TrackRecord v-if="records.length > 0" :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 class="fixed-bottom" v-if="showButtons">
+            <u-button v-if="canEdit" type="warning" size="large" @click="onClaim">编辑</u-button>
+            <u-button v-if="canVoid" type="error" size="large" @click="onVoid">作废</u-button>
+            <u-button v-if="canFinish" type="primary" size="large" @click="onFinish">完成</u-button>
+            <u-button v-if="canReopen" type="primary" size="large" @click="onReopen">重开</u-button>
         </view>
     </view>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, computed } 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'
@@ -22,51 +24,195 @@ import TrackRecord from '@/pages/order/components/track-record.vue'
 
 const detail = ref({})
 const records = ref([])
+const workOrderId = ref('')
 
-const getDetail = async (code) => {
+// 获取当前用户信息
+const currentUserId = ref(uni.getStorageSync('userInfo')?.userId || '')
+
+// 计算按钮显示逻辑
+const canEdit = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    const isAssignee = detail.value.handleUsers?.some(user => user.userId === currentUserId.value)
+
+    if (isCreator) {
+        return status === 1 || status === 2 || status === 3 // 待处理、处理中、已完成、已作废
+    } else if (isAssignee) {
+        return status === 1 || status === 2 || status === 3 // 待处理、处理中、已完成、已作废
+    }
+    return false
+})
+
+const canVoid = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    return isCreator && (status === 1 || status === 2 || status === 3) // 待处理、处理中、已完成、已作废
+})
+
+const canFinish = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    const isAssignee = detail.value.handleUsers?.some(user => user.userId === currentUserId.value)
+    return (isCreator || isAssignee) && (status === 1 || status === 2 || status === 3) // 待处理、处理中、已完成、已作废
+})
+
+const canReopen = computed(() => {
+    const status = detail.value.status
+    const isCreator = currentUserId.value === detail.value.createUserId
+    return isCreator && (status === 4 || status === 5) // 已完成、已作废
+})
+
+const showButtons = computed(() => {
+    return canEdit.value || canVoid.value || canFinish.value || canReopen.value
+})
+
+const getDetail = async (id) => {
     try {
-        const res = await uni.$u.http.get('/app/workorder/detail', { params: { type: 'recycle', waybillCode: code } })
+        const res = await uni.$u.http.get(`/app/workOrder/getWorkOrderDetail`, {
+            params: { workOrderId: id }
+        })
         if (res?.code === 200 && res?.data) {
-            detail.value = res.data
+            const data = res.data;
+            detail.value = {
+                ...data,
+                waybillCode: data.waybillCode || '',
+                orderId: data.orderId || '',
+                taskTypeName: data.taskTypeName || '',
+                taskTypeExt: data.taskTypeExt || '',
+                orderStatusName: data.statusName || '',
+                taskDetail: data.deatil || data.taskDetail || '',
+                createUser: data.createUserName || '',
+                createUserId: data.createUserId || '',
+                createTime: data.createTime || '',
+                imageUrl: (data.imgInfo && data.imgInfo.imgUrlList && data.imgInfo.imgUrlList[0]) || data.imageUrl || '/static/img/book.png',
+                imageUrls: (data.imgInfo && data.imgInfo.imgUrlList) || [],
+                taskStatusName: data.statusName || '',
+                status: data.status,
+                handleUsers: data.handleUsers || []
+            }
+            records.value = data.changeRecords
             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: '待处理'
+    } catch (e) {
+        console.error(e)
     }
-    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 onSubmitTrack = async (payload) => {
+    try {
+        // 调用编辑工单接口,更新任务详情
+        const result = await uni.$u.http.post('/app/workOrder/update', {
+            id: workOrderId.value,
+            updateType: 1, // 1-更新任务详情
+            deatil: payload.content,
+            imgInfo: payload.files || []
+        })
+        
+        if (result.code === 200) {
+            uni.$u.toast('跟进成功')
+            // 重新获取工单详情
+            await getDetail(workOrderId.value)
+        } else {
+            uni.$u.toast(result.msg || '跟进失败')
+        }
+    } catch (e) {
+        console.error(e)
+        uni.$u.toast('网络错误')
+    }
 }
 
 const onClaim = () => {
-    uni.$u.toast('已触发一键理赔')
+    uni.navigateTo({
+        url: `/pages/order/recycle/created?workOrderId=${workOrderId.value}&mode=edit&waybillCode=${detail.value.waybillCode}&orderId=${detail.value.orderId}&readonly=1`
+    })
+}
+
+const onVoid = () => {
+    uni.showModal({
+        title: '提示',
+        content: '确定要作废该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    const result = await uni.$u.http.post('/app/workOrder/cancel', { id: workOrderId.value })
+                    if (result.code === 200) {
+                        uni.$u.toast('已作废')
+                        setTimeout(() => {
+                            uni.navigateBack()
+                        }, 1000)
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (e) {
+                    console.error(e)
+                }
+            }
+        }
+    })
 }
 
 const onFinish = () => {
-    uni.$u.toast('任务已完成')
+    uni.showModal({
+        title: '提示',
+        content: '确定要完成该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    const result = await uni.$u.http.post('/app/workOrder/finish', { ids: [workOrderId.value] })
+                    if (result.code === 200) {
+                        uni.$u.toast('已完成')
+                        setTimeout(() => {
+                            uni.navigateBack()
+                        }, 1000)
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (e) {
+                    console.error(e)
+                }
+            }
+        }
+    })
+}
+
+const onReopen = () => {
+    uni.showModal({
+        title: '提示',
+        content: '确定要重开该工单吗?',
+        success: async (res) => {
+            if (res.confirm) {
+                try {
+                    // 这里需要调用重开工单的接口
+                    // 假设接口是 /app/workOrder/reopen
+                    const result = await uni.$u.http.post('/app/workOrder/reopen', { id: workOrderId.value })
+                    if (result.code === 200) {
+                        uni.$u.toast('已重开')
+                        setTimeout(() => {
+                            uni.navigateBack()
+                        }, 1000)
+                    } else {
+                        uni.$u.toast(result.msg || '操作失败')
+                    }
+                } catch (e) {
+                    console.error(e)
+                }
+            }
+        }
+    })
+}
+
+const previewImage = () => {
+    if (detail.value.imageUrls && detail.value.imageUrls.length > 0) {
+        uni.previewImage({
+            urls: detail.value.imageUrls,
+            current: 0
+        })
+    }
 }
 
 onLoad((options) => {
-    getDetail(options.waybillCode)
+    workOrderId.value = options.id
+    getDetail(options.id)
 })
 </script>
 

+ 40 - 48
pages/order/recycle/pending.vue

@@ -9,33 +9,13 @@
             <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>
+        <u-picker :show="pickerShow" :columns="[taskTypeOptions]" title="选择任务类型" @confirm="onPickConfirm"
+            @cancel="pickerShow = false" @close="pickerShow = false"></u-picker>
+
+        <PageScroll requestStr="/app/workOrder/getMyHandleWorkOrder" @updateList="updateList" ref="scrollRef"
+            :otherParams="otherParams" method="get" :diffHeight="150">
+            <WorkorderItem v-for="(cell, idx) in dataList" :key="idx" :item="cell" :showDuration="true"
+                @click="goDetail(cell)" class="mt-20" />
         </PageScroll>
     </view>
 </template>
@@ -51,29 +31,35 @@ 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 taskTypeOptions = ref([])
+
+// 获取任务类型字典
+const getTaskTypeOptions = async () => {
+    try {
+        const res = await uni.$u.http.get('/system/dict/data/type/task_type')
+        if (res.code === 200 && res.data) {
+            // 添加"全部"选项
+            taskTypeOptions.value = [
+                { text: '全部', value: '' }
+            ].concat(res.data.map(item => ({
+                text: item.dictLabel,
+                value: parseInt(item.dictValue)
+            })))
+        }
+    } catch (e) {
+        console.error('获取任务类型失败', e)
+    }
+}
 
 const otherParams = ref({
-    type: 'recycle',
-    status: 'pending',
-    taskType: ''
+    type: 2, // 工单类型:2-收书
+    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 updateList = (data) => {
+    console.log(data, 'data')
+    // 如果data是响应对象,提取rows
+    dataList.value = data
 }
 
 const refreshList = () => {
@@ -97,13 +83,15 @@ const onReset = () => {
 }
 
 const goDetail = (cell) => {
-    const code = cell?.waybillCode || ''
+    const id = cell?.id || ''
     uni.navigateTo({
-        url: `/pages/order/recycle/detail?waybillCode=${code}`
+        url: `/pages/order/recycle/detail?id=${id}`
     })
 }
 
 onShow(() => {
+    // 加载任务类型字典
+    getTaskTypeOptions()
     refreshList()
 })
 </script>
@@ -116,6 +104,7 @@ onShow(() => {
     background-color: #ffffff;
     gap: 16rpx;
 }
+
 .picker-trigger {
     flex: 1;
     display: flex;
@@ -126,14 +115,17 @@ onShow(() => {
     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;
 }