瀏覽代碼

整改基本完成

Huanyi 1 月之前
父節點
當前提交
cabd8cc82b
共有 43 個文件被更改,包括 1279 次插入470 次删除
  1. 17 22
      .idea/workspace.xml
  2. 13 2
      api/fulfiller.js
  3. 33 1
      manifest.json
  4. 1 8
      pages/home/index.vue
  5. 19 4
      pages/home/logic.js
  6. 35 7
      pages/home/style.css
  7. 2 0
      pages/mine/index.vue
  8. 6 6
      pages/mine/settings/profile/index.vue
  9. 10 1
      pages/mine/style.css
  10. 171 43
      pages/orders/detail-logic.js
  11. 225 3
      pages/orders/detail-style.css
  12. 39 15
      pages/orders/detail.vue
  13. 4 8
      pages/orders/index.vue
  14. 41 3
      pages/orders/logic.js
  15. 6 5
      pages/orders/style.css
  16. 二進制
      unpackage/cache/apk/__UNI__76F5C47_cm.apk
  17. 1 1
      unpackage/cache/apk/apkurl
  18. 0 0
      unpackage/cache/apk/cmManifestCache.json
  19. 1 1
      unpackage/cache/wgt/__UNI__76F5C47/app-config-service.js
  20. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/app-service.js
  21. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/manifest.json
  22. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/home/index.css
  23. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/mine/index.css
  24. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/orders/detail.css
  25. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/orders/index.css
  26. 1 1
      unpackage/dist/build/app-plus/app-config-service.js
  27. 0 0
      unpackage/dist/build/app-plus/app-service.js
  28. 1 0
      unpackage/dist/build/app-plus/manifest.json
  29. 0 0
      unpackage/dist/build/app-plus/pages/home/index.css
  30. 0 0
      unpackage/dist/build/app-plus/pages/mine/index.css
  31. 0 0
      unpackage/dist/build/app-plus/pages/orders/detail.css
  32. 0 0
      unpackage/dist/build/app-plus/pages/orders/index.css
  33. 0 8
      unpackage/dist/cache/.vite/deps/_metadata.json
  34. 0 3
      unpackage/dist/cache/.vite/deps/package.json
  35. 349 296
      unpackage/dist/dev/app-plus/app-service.js
  36. 32 0
      unpackage/dist/dev/app-plus/manifest.json
  37. 31 7
      unpackage/dist/dev/app-plus/pages/home/index.css
  38. 9 1
      unpackage/dist/dev/app-plus/pages/mine/index.css
  39. 198 3
      unpackage/dist/dev/app-plus/pages/orders/detail.css
  40. 5 5
      unpackage/dist/dev/app-plus/pages/orders/index.css
  41. 二進制
      unpackage/release/apk/__UNI__76F5C47__20260312144943.apk
  42. 1 1
      utils/config.js
  43. 28 15
      utils/request.js

+ 17 - 22
.idea/workspace.xml

@@ -4,24 +4,7 @@
     <option name="autoReloadType" value="SELECTIVE" />
   </component>
   <component name="ChangeListManager">
-    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="1.完成app样式调整">
-      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/manifest.json" beforeDir="false" afterPath="$PROJECT_DIR$/manifest.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages.json" beforeDir="false" afterPath="$PROJECT_DIR$/pages.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages/home/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/home/index.vue" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages/home/logic.js" beforeDir="false" afterPath="$PROJECT_DIR$/pages/home/logic.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages/mine/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/mine/index.vue" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages/mine/logic.js" beforeDir="false" afterPath="$PROJECT_DIR$/pages/mine/logic.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages/orders/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/orders/index.vue" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pages/orders/logic.js" beforeDir="false" afterPath="$PROJECT_DIR$/pages/orders/logic.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/app-config-service.js" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/app-config-service.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/app-service.js" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/app-service.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/manifest.json" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/manifest.json" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/home/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/home/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/mine/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/mine/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/orders/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/orders/index.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/recruit/form.css" beforeDir="false" afterPath="$PROJECT_DIR$/unpackage/dist/dev/app-plus/pages/recruit/form.css" afterDir="false" />
-    </list>
+    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="接单成功,异常上报成功,UI初步调整,订单详情优化" />
     <list id="6ae23f6a-53fe-4817-a1d7-106bcf184c18" name="New changelist" comment="不想提交的文件" />
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -63,6 +46,7 @@
   "keyToString": {
     "RunOnceActivity.ShowReadmeOnStart": "true",
     "RunOnceActivity.git.unshallow": "true",
+    "RunOnceActivity.typescript.service.memoryLimit.init": "true",
     "git-widget-placeholder": "dev/shenliang",
     "kotlin-language-version-configured": "true",
     "last_opened_file_path": "D:/windsurfProject/petSystem/pet-system-fulfiller-app",
@@ -79,8 +63,7 @@
   <component name="SharedIndexes">
     <attachedChunks>
       <set>
-        <option value="bundled-jdk-9823dce3aa75-a94e463ab2e7-intellij.indexing.shared.core-IU-243.26053.27" />
-        <option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-IU-243.26053.27" />
+        <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-WS-253.31033.133" />
       </set>
     </attachedChunks>
   </component>
@@ -99,6 +82,7 @@
       <workItem from="1773058524158" duration="373000" />
       <workItem from="1773140880066" duration="1007000" />
       <workItem from="1773142765254" duration="647000" />
+      <workItem from="1773252784481" duration="88000" />
     </task>
     <task id="LOCAL-00001" summary="1.完成app端履约者入驻相关功能开发&#10;2.完成app端履约者登录功能开发&#10;3.完成履约者个人中心功能开发">
       <option name="closed" value="true" />
@@ -140,19 +124,30 @@
       <option name="project" value="LOCAL" />
       <updated>1773058548412</updated>
     </task>
-    <option name="localTasksCounter" value="6" />
+    <task id="LOCAL-00006" summary="接单成功,异常上报成功,UI初步调整,订单详情优化">
+      <option name="closed" value="true" />
+      <created>1773252869966</created>
+      <option name="number" value="00006" />
+      <option name="presentableId" value="LOCAL-00006" />
+      <option name="project" value="LOCAL" />
+      <updated>1773252869966</updated>
+    </task>
+    <option name="localTasksCounter" value="7" />
     <servers />
   </component>
   <component name="TypeScriptGeneratedFilesManager">
     <option name="version" value="3" />
   </component>
   <component name="VcsManagerConfiguration">
+    <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="CHECK_NEW_TODO" value="false" />
     <MESSAGE value="1.完成app端履约者入驻相关功能开发&#10;2.完成app端履约者登录功能开发&#10;3.完成履约者个人中心功能开发" />
     <MESSAGE value="完成修改认证页面" />
     <MESSAGE value="1.完成app登录调整" />
     <MESSAGE value="1" />
     <MESSAGE value="不想提交的文件" />
     <MESSAGE value="1.完成app样式调整" />
-    <option name="LAST_COMMIT_MESSAGE" value="1.完成app样式调整" />
+    <MESSAGE value="接单成功,异常上报成功,UI初步调整,订单详情优化" />
+    <option name="LAST_COMMIT_MESSAGE" value="接单成功,异常上报成功,UI初步调整,订单详情优化" />
   </component>
 </project>

+ 13 - 2
api/fulfiller.js

@@ -136,7 +136,7 @@ export function getMyRewardLog(params) {
 
 /**
  * 修改头像
- * @param {string} avatar - 头像URL
+ * @param {string} avatar - 头像ossId
  * @author steelwei
  */
 export function updateAvatar(avatar) {
@@ -320,7 +320,7 @@ export function getOrderLogs(orderId) {
 
 /**
  * 订单打卡
- * @param {Object} data - { orderId, photos, content, type, title }
+ * @param {Object} data - { orderId, photos, content, step, title, startFlag, endFlag }
  */
 export function clockIn(data) {
   return request({
@@ -352,3 +352,14 @@ export function getAnomalyList(orderId) {
     method: 'GET'
   })
 }
+/**
+ * 提交宠护小结
+ * @param {Object} data - { orderId, content }
+ */
+export function submitNursingSummary(data) {
+  return request({
+    url: '/order/subOrder/nursingSummary',
+    method: 'PUT',
+    data
+  })
+}

+ 33 - 1
manifest.json

@@ -45,7 +45,39 @@
                 "dSYMs" : false
             },
             /* SDK配置 */
-            "sdkConfigs" : {}
+            "sdkConfigs" : {},
+            "icons" : {
+                "android" : {
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            }
         }
     },
     /* 快应用特有相关 */

+ 1 - 8
pages/home/index.vue

@@ -123,7 +123,7 @@
         </view>
 
         <!-- 列表容器 -->
-        <block v-if="workStatus === 'working'">
+        <view class="task-list-container">
             <!-- 任务列表 -->
             <view class="task-list">
                 <view class="task-card" v-for="(item, index) in taskList" :key="index" @click="goToDetail(item)">
@@ -199,13 +199,6 @@
                 <!-- Padding for safe area/tabbar -->
                 <view style="height: 120rpx;"></view>
             </view>
-        </block>
-
-        <!-- 休息中空状态 -->
-        <view class="empty-state" v-else>
-            <image class="empty-icon" src="/static/empty-rest.png" mode="aspectFit"></image>
-            <text class="empty-text">当前处于休息中,暂时无法接单</text>
-            <button class="start-work-btn" @click="startWork">开工接单</button>
         </view>
         <!-- 自定义确认弹窗 -->
         <view class="modal-mask" v-if="showConfirmModal">

+ 19 - 4
pages/home/logic.js

@@ -1,5 +1,6 @@
 import { getMyProfile, getPendingOrders, acceptOrder, getOrderCount } from '@/api/fulfiller'
 import { getServiceList } from '@/api/service'
+import { rejectOrderApi } from '@/api/order/subOrder/index'
 import { isLoggedIn } from '@/utils/auth'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
@@ -165,14 +166,28 @@ export default {
             this.showRejectModal = false;
             this.currentOrder = null;
         },
-        confirmReject() {
+        async confirmReject() {
             if (!this.rejectReason.trim()) {
                 uni.showToast({ title: '请输入拒绝理由', icon: 'none' });
                 return;
             }
-            // Add actual API call here
-            uni.showToast({ title: '已拒绝接单', icon: 'success' });
-            this.showRejectModal = false;
+            if (!this.currentOrder?.id) return
+            try {
+                uni.showLoading({ title: '提交中...', mask: true });
+                await rejectOrderApi({
+                    orderId: this.currentOrder.id,
+                    rejectReason: this.rejectReason
+                });
+                uni.showToast({ title: '已拒绝接单', icon: 'success' });
+                this.showRejectModal = false;
+                this.currentOrder = null;
+                this.loadTaskList();
+            } catch (err) {
+                console.error('拒绝接单失败:', err);
+                uni.showToast({ title: '操作失败', icon: 'none' });
+            } finally {
+                uni.hideLoading();
+            }
         },
         openAcceptModal(item) {
             this.currentOrder = item;

+ 35 - 7
pages/home/style.css

@@ -22,7 +22,7 @@ page {
     top: 0;
     left: 0;
     width: 100%;
-    height: 310rpx;
+    height: 360rpx;
     /* Reduced by another 20rpx */
     background: linear-gradient(180deg, #FFE0B2 0%, #FFF3E0 100%);
     border-bottom-left-radius: 60rpx;
@@ -39,7 +39,7 @@ page {
     width: 100%;
     z-index: 100;
     padding-top: var(--status-bar-height);
-    height: 88rpx;
+    height: 100rpx;
     display: flex;
     align-items: center;
     justify-content: center;
@@ -55,7 +55,7 @@ page {
 .header-section {
     position: relative;
     z-index: 2;
-    padding: 88rpx 30rpx 0;
+    padding: 140rpx 30rpx 0;
 }
 
 /* 用户信息 */
@@ -227,7 +227,7 @@ page {
 /* 任务大厅标题栏 - 吸顶容器 */
 .task-header {
     position: sticky;
-    top: calc(88rpx + var(--status-bar-height));
+    top: calc(100rpx + var(--status-bar-height));
     z-index: 90;
     margin-top: 0;
     margin-bottom: 10rpx;
@@ -264,9 +264,8 @@ page {
 
 .left-title .title {
     font-size: 28rpx;
-    font-weight: 900;
+    font-weight: bold;
     color: #333;
-    font-style: italic;
 }
 
 .left-title .count {
@@ -420,7 +419,7 @@ page {
     padding: 15rpx 20rpx;
     display: flex;
     align-items: center;
-    margin-bottom: 20rpx;
+    margin-bottom: 30rpx;
 }
 
 .pet-avatar {
@@ -529,11 +528,40 @@ page {
     width: 0;
 }
 
+.addr-title-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
+
 .addr-title {
     font-size: 28rpx;
     font-weight: bold;
     color: #333;
     margin-bottom: 6rpx;
+    flex: 1;
+}
+
+.phone-call-btn {
+    width: 48rpx;
+    height: 48rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #E8F5E9;
+    border-radius: 50%;
+    margin-left: 10rpx;
+    transition: transform 0.1s;
+}
+
+.phone-call-btn:active {
+    transform: scale(0.9);
+    background-color: #C8E6C9;
+}
+
+.phone-icon-item {
+    width: 28rpx;
+    height: 28rpx;
 }
 
 .addr-desc {

+ 2 - 0
pages/mine/index.vue

@@ -118,6 +118,8 @@
         <!-- 退出登录 -->
         <!-- 退出登录 -->
         <view class="logout-btn" @click="logout">退出登录</view>
+        <view class="dev-tip">本APP部分功能开发当中,请只进行修改个人信息、修改设置、完成订单流程操作</view>
+
 
         <!-- 联系客服弹窗 -->
         <view class="service-popup-mask" v-if="showServicePopup" @click="closeServicePopup">

+ 6 - 6
pages/mine/settings/profile/index.vue

@@ -28,10 +28,10 @@
         </view>
 
         <view class="group-card">
-            <view class="list-item">
+           <!-- <view class="list-item">
                 <text class="item-title">工作类型</text>
                 <view class="tag-blue-outline">{{ userInfo.workType }}</view>
-            </view>
+            </view> -->
             <view class="list-item" @click="showStatusPicker">
                 <text class="item-title">工作状态</text>
                 <view class="item-right">
@@ -203,12 +203,12 @@ export default {
                     try {
                         const uploadRes = await uploadFile(tempFilePath);
                         if (uploadRes.code === 200) {
-                            const avatarUrl = uploadRes.data.url;
+                            const { url, ossId } = uploadRes.data;
                             
-                            // 调用接口更新头像
-                            const result = await updateAvatar(avatarUrl);
+                            // 调用接口更新头像 @author steelwei
+                            const result = await updateAvatar(ossId);
                             if (result.code === 200) {
-                                this.userInfo.avatar = avatarUrl;
+                                this.userInfo.avatar = url;
                                 uni.showToast({ title: '修改成功', icon: 'success' });
                             } else {
                                 uni.showToast({ title: result.msg || '修改失败', icon: 'none' });

+ 10 - 1
pages/mine/style.css

@@ -53,7 +53,7 @@ page {
 .header-section {
     position: relative;
     z-index: 2;
-    padding: 60rpx 30rpx 0;
+    padding: 120rpx 30rpx 0;
 }
 
 .title-bar {
@@ -371,6 +371,15 @@ page {
     box-shadow: 0 5rpx 15rpx rgba(0, 0, 0, 0.03);
 }
 
+/* 开发提示文字 */
+.dev-tip {
+    margin: 40rpx 60rpx;
+    font-size: 24rpx;
+    color: #999;
+    text-align: center;
+    line-height: 1.6;
+}
+
 /* 联系客服弹窗样式 */
 .service-popup-mask {
     position: fixed;

+ 171 - 43
pages/orders/detail-logic.js

@@ -1,4 +1,4 @@
-import { getOrderInfo, getOrderLogs, uploadFile, clockIn, getAnomalyList } from '@/api/fulfiller'
+import { getOrderInfo, getOrderLogs, uploadFile, clockIn, getAnomalyList, submitNursingSummary } from '@/api/fulfiller'
 import { getServiceList, getServiceDetail } from '@/api/service'
 import { getDictDataByType } from '@/api/system/dict/index'
 import { getPetDetail } from '@/api/archieves/pet/index'
@@ -11,6 +11,7 @@ export default {
             orderType: 1,
             orderStatus: 2,
             serviceId: null, // 当前订单的服务类型ID
+            serviceMode: null, // 当前订单的服务模式 (0: 喂遛/洗护, 1: 接送)
             petId: null, // 当前订单关联的宠物ID
             petDetail: null, // 宠物档案详情
 
@@ -34,13 +35,14 @@ export default {
                 serviceTag: '',
                 startLocation: '',
                 startAddress: '',
-                endLocation: '',
                 endAddress: '',
+                customerPhone: '',
                 serviceContent: '',
                 remark: '',
                 orderNo: '',
                 createTime: '',
-                progressLogs: []
+                progressLogs: [],
+                nursingSummary: '' // 宠护小结
             },
 
             serviceList: [],
@@ -56,14 +58,18 @@ export default {
             showSumModal: false,
             sumContent: '',
             sumDate: '',
-            sumSigner: '张*哥',
+            sumSigner: '未知',
 
             showPetRemarkInput: false,
             petRemarkText: '',
 
             showAnomalyModal: false,
             anomalyList: [],
-            anomalyTypeDict: []
+            anomalyTypeDict: [],
+
+            // 媒体预览相关
+            videoPlayerShow: false,
+            videoPlayerUrl: ''
         }
     },
     computed: {
@@ -122,8 +128,6 @@ export default {
         try {
             // 先加载字典
             await this.loadAnomalyTypeDict()
-            // 先获取服务列表(用于匹配服务类型名称等)
-            await this.loadServiceList()
             // 获取订单详情(内部会拿到 serviceId,然后加载服务详情获取 clockInRemark)
             await this.loadOrderDetail()
         } finally {
@@ -143,18 +147,24 @@ export default {
          * 根据服务类型ID获取服务详情,解析 clockInRemark 为打卡步骤
          */
         async loadServiceDetail(serviceId) {
+            console.log('开始加载服务详情, ID:', serviceId)
             try {
                 const res = await getServiceDetail(serviceId)
                 const serviceInfo = res.data
-                if (serviceInfo && serviceInfo.clockInRemark) {
-                    try {
-                        const parsed = JSON.parse(serviceInfo.clockInRemark)
-                        if (Array.isArray(parsed) && parsed.length > 0) {
-                            this.clockInSteps = parsed
-                            console.log('解析打卡步骤:', this.clockInSteps)
+                console.log('服务详情响应结果:', serviceInfo)
+                if (serviceInfo) {
+                    this.serviceMode = serviceInfo.mode
+                    console.log('当前服务模式(mode):', this.serviceMode)
+                    if (serviceInfo.clockInRemark) {
+                        try {
+                            const parsed = JSON.parse(serviceInfo.clockInRemark)
+                            if (Array.isArray(parsed) && parsed.length > 0) {
+                                this.clockInSteps = parsed
+                                console.log('解析打卡步骤:', this.clockInSteps)
+                            }
+                        } catch (parseErr) {
+                            console.error('解析 clockInRemark 失败:', parseErr)
                         }
-                    } catch (parseErr) {
-                        console.error('解析 clockInRemark 失败:', parseErr)
                     }
                 }
             } catch (err) {
@@ -181,10 +191,13 @@ export default {
                 this.serviceId = order.service
                 this.petId = order.usrPet || null
                 this.transformOrderData(order)
+                console.log('解析出的 serviceId:', this.serviceId)
 
                 // 根据订单的服务类型ID获取服务详情(含 clockInRemark)
                 if (this.serviceId) {
                     await this.loadServiceDetail(this.serviceId)
+                } else {
+                    console.warn('订单中未找到 service 字段,无法加载服务步骤')
                 }
 
                 // 加载宠物档案详情
@@ -230,7 +243,7 @@ export default {
                         this.currentStep = stepIndex + 1
                     } else {
                         // 兜底:直接按 step 值推算
-                        this.currentStep = latestStep
+                        this.currentStep = Number(latestStep)
                     }
                 } else {
                     this.currentStep = 0
@@ -273,14 +286,23 @@ export default {
                 startAddress: order.fromAddress || '',
                 endLocation: (order.contact || '') + ' ' + (order.contactPhoneNumber || ''),
                 endAddress: order.toAddress || '',
+                customerPhone: order.contactPhoneNumber || '',
+                ownerName: order.contact || '', // 宠主姓名(默认使用客户姓名)
                 serviceContent: '',
                 remark: '',
                 orderNo: order.code || 'T' + order.id,
                 createTime: order.serviceTime || '',
+                nursingSummary: order.nursingSummary || '',
+                fulfillerName: order.fulfillerName || '', // 履约者/护宠师姓名
                 progressLogs: [
                     { status: '您已接单', time: order.serviceTime || '' }
                 ]
             }
+
+            // 更新签名
+            if (this.orderDetail.fulfillerName) {
+                this.sumSigner = this.orderDetail.fulfillerName
+            }
         },
         /**
          * 根据宠物ID获取宠物档案详情
@@ -295,6 +317,7 @@ export default {
                     this.orderDetail.petAvatar = pet.avatarUrl || '/static/dog.png'
                     this.orderDetail.petName = pet.name || this.orderDetail.petName
                     this.orderDetail.petBreed = pet.breed || this.orderDetail.petBreed
+                    this.orderDetail.ownerName = pet.ownerName || this.orderDetail.ownerName // 如果宠物档案有宠主姓名,则覆盖
                     console.log('宠物档案:', pet)
                 }
             } catch (err) {
@@ -355,7 +378,7 @@ export default {
                 this.currentStep = 0
             } else if (this.orderStatus === 3) {
                 this.currentStep = 1
-            } else if (this.orderStatus === 5) {
+            } else if (this.orderStatus === 4) {
                 this.currentStep = this.steps.length - 1
             } else {
                 this.currentStep = 0
@@ -426,7 +449,7 @@ export default {
             this.currentPetInfo.petLogs.unshift({
                 date: date,
                 content: this.petRemarkText,
-                recorder: '张*哥'
+                recorder: this.orderDetail.fulfillerName || '未知'
             });
             this.closePetRemarkInput();
             uni.showToast({ title: '备注已添加', icon: 'success' });
@@ -437,7 +460,8 @@ export default {
             });
         },
         callPhone() {
-            uni.makePhoneCall({ phoneNumber: '18900008451' });
+            const phoneNum = this.orderDetail.customerPhone || '18900008451'
+            uni.makePhoneCall({ phoneNumber: phoneNum });
         },
         openNavigation(type) {
             this.navTargetPointType = type;
@@ -477,36 +501,45 @@ export default {
         },
         async chooseModalMedia() {
             console.log('chooseModalMedia被调用');
-            uni.chooseImage({
+            // 使用 uni.chooseMedia 支持图片和视频
+            uni.chooseMedia({
                 count: 5 - this.modalMediaList.length,
+                mediaType: ['image', 'video'],
+                sourceType: ['album', 'camera'],
                 success: async (res) => {
-                    console.log('选择图片成功,文件路径:', res.tempFilePaths);
-                    uni.showLoading({ title: '上传中...' });
+                    console.log('选择媒体文件成功:', res.tempFiles);
+                    uni.showLoading({ title: '上传中...', mask: true });
                     try {
-                        for (const filePath of res.tempFilePaths) {
-                            console.log('上传文件:', filePath);
+                        for (const file of res.tempFiles) {
+                            const filePath = file.tempFilePath;
+                            const fileType = file.fileType; // 'image' or 'video'
+                            console.log('开始上传文件:', filePath, '类型:', fileType);
+
                             const uploadRes = await uploadFile(filePath);
-                            console.log('上传响应:', uploadRes);
+                            console.log('服务器响应:', uploadRes);
+
                             if (uploadRes.code === 200) {
                                 this.modalMediaList.push({
                                     url: uploadRes.data.url,
                                     ossId: uploadRes.data.ossId,
-                                    localPath: filePath
+                                    localPath: filePath,
+                                    mediaType: fileType,
+                                    thumb: file.thumbTempFilePath // 视频缩略图(如果有)
                                 });
-                                console.log('上传成功,url:', uploadRes.data.url);
+                                console.log('媒体文件添加成功');
                             }
                         }
                         uni.hideLoading();
-                        console.log('当前modalMediaList:', this.modalMediaList);
                         uni.showToast({ title: '上传成功', icon: 'success' });
                     } catch (err) {
                         uni.hideLoading();
-                        console.error('上传失败:', err);
+                        console.error('上传失败详情:', err);
                         uni.showToast({ title: '上传失败', icon: 'none' });
                     }
                 },
                 fail: (err) => {
-                    console.error('选择图片失败:', err);
+                    console.error('选择媒体文件失败:', err);
+                    // 某些平台如果不兼容 chooseMedia,由开发者决定是否回退到 chooseImage/chooseVideo
                 }
             });
         },
@@ -543,8 +576,10 @@ export default {
                     orderId: this.orderId,
                     photos: ossIds,
                     content: this.modalRemark || '',
-                    type: clockInType,
-                    title: this.currentTaskTitle
+                    step: clockInType,
+                    title: this.currentTaskTitle,
+                    startFlag: Number(clockInType) === 1,
+                    endFlag: Number(this.currentStep) === this.steps.length - 1
                 };
 
                 console.log('打卡数据:', clockInData);
@@ -570,15 +605,24 @@ export default {
             });
         },
         openSumModal() {
-            // 初始化日期
-            const now = new Date();
-            const y = now.getFullYear();
-            const m = String(now.getMonth() + 1).padStart(2, '0');
-            const d = String(now.getDate()).padStart(2, '0');
-            this.sumDate = `${y}/${m}/${d}`;
+            // 初始化日期:优先使用订单中的服务时间,如果没有则使用当前时间
+            let displayDate = '';
+            if (this.orderDetail.time) {
+                // 如果是带时间的字符串,只取日期部分
+                displayDate = this.orderDetail.time.split(' ')[0].replace(/-/g, '/');
+            } else {
+                const now = new Date();
+                const y = now.getFullYear();
+                const m = String(now.getMonth() + 1).padStart(2, '0');
+                const d = String(now.getDate()).padStart(2, '0');
+                displayDate = `${y}/${m}/${d}`;
+            }
+            this.sumDate = displayDate;
 
-            // 预设服务内容模板
-            if (!this.sumContent) {
+            // 优先使用后端返回的小结,如果没有则判断本地是否有输入,都没有则使用预设服务内容模板
+            if (this.orderDetail.nursingSummary) {
+                this.sumContent = this.orderDetail.nursingSummary;
+            } else if (!this.sumContent) {
                 this.sumContent =
                     '1. 精神/身体状态:\n' +
                     '2. 进食/饮水:\n' +
@@ -592,13 +636,97 @@ export default {
         closeSumModal() {
             this.showSumModal = false;
         },
-        submitSumModal() {
+        async submitSumModal() {
             if (!this.sumContent.trim()) {
                 uni.showToast({ title: '请填写服务内容', icon: 'none' });
                 return;
             }
-            this.closeSumModal();
-            uni.showToast({ title: '小结已提交', icon: 'success' });
+
+            uni.showLoading({ title: '提交中...', mask: true });
+            try {
+                const res = await submitNursingSummary({
+                    orderId: this.orderId,
+                    content: this.sumContent
+                });
+
+                uni.hideLoading();
+                if (res.code === 200) {
+                    uni.showToast({ title: '小结已提交', icon: 'success' });
+                    this.closeSumModal();
+                    // 重新加载订单详情以获取最新的已保存数据
+                    await this.loadOrderDetail();
+                } else {
+                    uni.showToast({ title: res.msg || '提交失败', icon: 'none' });
+                }
+            } catch (err) {
+                uni.hideLoading();
+                console.error('提交宠护小结失败:', err);
+                uni.showToast({ title: '提交失败,请重试', icon: 'none' });
+            }
+        },
+        /**
+         * 检查是否为视频
+         */
+        isVideo(url) {
+            if (!url) return false;
+            const videoExts = ['.mp4', '.mov', '.m4v', '.3gp', '.avi', '.wmv'];
+            const lowerUrl = url.toLowerCase();
+            return videoExts.some(ext => lowerUrl.includes(ext));
+        },
+        /**
+         * 获取视频封面图 (第一帧)
+         * 兼容阿里云、腾讯云等主流 OSS
+         */
+        getVideoPoster(url) {
+            if (!this.isVideo(url)) return url;
+            
+            // 已经带了处理逻辑的直接返回
+            if (url.includes('?x-oss-process') || url.includes('?ci-process') || url.includes('?vframe')) {
+                return url;
+            }
+            
+            // 兼容性尝试:
+            // 1. 阿里云 OSS 截图: ?x-oss-process=video/snapshot,t_1,f_jpg,w_300,m_fast
+            // 2. 腾讯云 COS 截图: ?ci-process=snapshot&time=1
+            // 3. 七牛云 截图: ?vframe/jpg/offset/1
+            
+            // 默认拼接多个参数(实际使用中通常只会生效一种,根据具体后端使用的服务决定)
+            // 这里为了通用,先猜测阿里的
+            const aliyun = `?x-oss-process=video/snapshot,t_1,f_jpg,w_300,m_fast`;
+            const tencent = `?ci-process=snapshot&time=1`;
+            
+            // 如果后端没有特殊说明,我们根据域名简单判断或直接拼接(部分OSS支持第一个 ? 后的参数)
+            if (url.includes('myqcloud.com')) {
+                return url + tencent;
+            }
+            
+            return url + aliyun;
+        },
+        /**
+         * 统一预览媒体
+         */
+        previewMedia(medias, currentIdx) {
+            const url = medias[currentIdx];
+            if (this.isVideo(url)) {
+                // 如果是视频,播放视频
+                this.videoPlayerUrl = url;
+                this.videoPlayerShow = true;
+            } else {
+                // 如果是图片,预览图片(过滤掉数组中的视频)
+                const imageUrls = medias.filter(m => !this.isVideo(m));
+                // 调整当前图片的索引
+                const currentImgUrl = url;
+                const newIdx = imageUrls.indexOf(currentImgUrl);
+                
+                uni.previewImage({
+                    current: newIdx >= 0 ? newIdx : 0,
+                    urls: imageUrls
+                });
+            }
+        },
+        closeVideoPlayer() {
+            this.videoPlayerShow = false;
+            this.videoPlayerUrl = '';
         }
     }
 }

+ 225 - 3
pages/orders/detail-style.css

@@ -858,9 +858,10 @@ page {
 
 .um-remark-hint {
     display: block;
-    font-size: 24rpx;
-    color: #FF9800;
-    margin-top: 12rpx;
+    font-size: 22rpx;
+    color: #999;
+    margin-top: 16rpx;
+    line-height: 1.4;
 }
 
 .um-grid {
@@ -901,6 +902,26 @@ page {
     color: #fff;
     border-radius: 50%;
     font-size: 28rpx;
+    z-index: 10;
+}
+
+.um-video-badge {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(0, 0, 0, 0.2);
+    z-index: 5;
+}
+
+.play-icon-small {
+    width: 48rpx;
+    height: 48rpx;
+    opacity: 0.9;
 }
 
 .um-add {
@@ -1659,4 +1680,205 @@ page {
 .empty-text {
     font-size: 26rpx;
     color: #999;
+}
+
+/* 媒体项容器 */
+.tl-media-item {
+    position: relative;
+    width: 140rpx;
+    height: 140rpx;
+}
+
+/* 视频占位样式(不取第一帧,直接用图标) */
+.tl-video-placeholder {
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(135deg, #444 0%, #222 100%);
+    border-radius: 8rpx;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+
+.tl-video-placeholder.miniaturized {
+    border-radius: 8rpx;
+    overflow: hidden;
+}
+
+.tl-video-label {
+    color: #fff;
+    font-size: 20rpx;
+    margin-top: 50rpx;
+    opacity: 0.8;
+}
+
+.tl-video-label.small {
+    margin-top: 35rpx;
+    font-size: 18rpx;
+}
+
+.tl-play-icon {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 60rpx;
+    height: 60rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(255, 255, 255, 0.2);
+    border: 2rpx solid #fff;
+    border-radius: 50%;
+}
+
+.tl-play-icon::after {
+    content: '';
+    display: block;
+    width: 0;
+    height: 0;
+    border-top: 15rpx solid transparent;
+    border-bottom: 15rpx solid transparent;
+    border-left: 20rpx solid #fff;
+    margin-left: 6rpx;
+}
+
+.tl-play-icon.small {
+    width: 40rpx;
+    height: 40rpx;
+}
+
+.tl-play-icon.small::after {
+    border-top: 10rpx solid transparent;
+    border-bottom: 10rpx solid transparent;
+    border-left: 14rpx solid #fff;
+    margin-left: 4rpx;
+}
+
+/* 视频播放弹窗 */
+.video-player-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.9);
+    z-index: 999;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.video-player-content {
+    position: relative;
+    width: 100%;
+    height: 600rpx;
+}
+
+.v-player {
+    width: 100%;
+    height: 100%;
+}
+
+.v-close {
+    position: absolute;
+    top: -80rpx;
+    right: 40rpx;
+    width: 60rpx;
+    height: 60rpx;
+    background-color: rgba(255, 255, 255, 0.2);
+    color: #fff;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 40rpx;
+}
+
+/* 异常列表媒体样式 */
+.am-photo-item {
+    position: relative;
+    width: 150rpx;
+    height: 150rpx;
+    border-radius: 8rpx;
+    overflow: hidden;
+}
+
+.am-photo {
+    width: 150rpx;
+    height: 150rpx;
+    border-radius: 8rpx;
+}
+
+.am-video-poster.miniaturized {
+    width: 100%;
+    height: 100%;
+    position: relative;
+}
+
+.tl-play-icon.small {
+    width: 40rpx;
+    height: 40rpx;
+}
+
+.tl-play-icon.small::after {
+    border-top: 10rpx solid transparent;
+    border-bottom: 10rpx solid transparent;
+    border-left: 14rpx solid #fff;
+    margin-left: 4rpx;
+}
+
+/* 导航选择弹窗 */
+.nav-modal-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.6);
+    z-index: 1000;
+    display: flex;
+    align-items: flex-end;
+}
+
+.nav-action-sheet {
+    width: 100%;
+    background-color: #fff;
+    border-radius: 30rpx 30rpx 0 0;
+    padding-bottom: constant(safe-area-inset-bottom);
+    padding-bottom: env(safe-area-inset-bottom);
+    overflow: hidden;
+}
+
+.nav-sheet-title {
+    display: block;
+    text-align: center;
+    font-size: 26rpx;
+    color: #999;
+    padding: 30rpx 0;
+    border-bottom: 1px solid #f5f5f5;
+}
+
+.nav-sheet-item {
+    display: block;
+    text-align: center;
+    font-size: 32rpx;
+    color: #333;
+    padding: 35rpx 0;
+    border-bottom: 1px solid #f5f5f5;
+    background-color: #fff;
+}
+
+.nav-sheet-item:active {
+    background-color: #f9f9f9;
+}
+
+.nav-sheet-item.cancel {
+    color: #999;
+}
+
+.nav-sheet-gap {
+    height: 12rpx;
+    background-color: #f5f5f5;
 }

+ 39 - 15
pages/orders/detail.vue

@@ -130,13 +130,13 @@
                         <text class="si-val">{{ orderDetail.remark || '无' }}</text>
                     </view>
                 </view>
-                <view class="si-row record-history-row" @tap.stop="openAnomalyModal">
+                <!-- <view class="si-row record-history-row" @tap.stop="openAnomalyModal">
                     <view class="si-content">
                         <text class="si-label">异常记录</text>
                         <text class="si-val">查看历史异常记录</text>
                     </view>
                     <image class="record-arrow" src="/static/icons/right_arrow_orange.svg"></image>
-                </view>
+                </view> -->
             </view>
 
             <!-- 打卡任务 -->
@@ -188,11 +188,17 @@
                                 <text class="tl-status">{{ log.status }}</text>
                                 <text class="tl-time">{{ log.time }}</text>
                             </view>
-                            <!-- 图片展示 -->
+                            <!-- 媒体(图片/视频)展示 -->
                             <view class="tl-medias" v-if="log.medias && log.medias.length > 0">
-                                <image class="tl-img" v-for="(img, midx) in log.medias" :key="midx" :src="img"
-                                    mode="aspectFill">
-                                </image>
+                                <view class="tl-media-item" v-for="(media, midx) in log.medias" :key="midx"
+                                    @click="previewMedia(log.medias, midx)">
+                                    <image v-if="!isVideo(media)" class="tl-img" :src="media" mode="aspectFill"></image>
+                                    <view v-else class="tl-video-placeholder">
+                                        <view class="tl-play-icon">
+                                        </view>
+                                        <text class="tl-video-label">视频</text>
+                                    </view>
+                                </view>
                             </view>
                             <!-- 备注展示 -->
                             <view class="tl-remark" v-if="log.remark">
@@ -210,7 +216,7 @@
         <view class="bottom-action-bar">
             <view class="action-left">
                 <button class="action-btn outline grey-outline" @click="goToAnomaly">异常上报</button>
-                <button class="action-btn outline orange-outline" @click="openSumModal">宠护小结</button>
+                <button class="action-btn outline orange-outline" @click="openSumModal" v-if="serviceMode === 0">宠护小结</button>
             </view>
             <view class="action-right">
                 <button class="action-btn primary" @click="openUploadModal" v-if="currentStep < steps.length">{{
@@ -330,12 +336,15 @@
             <view class="upload-modal-content" @click.stop>
                 <view class="um-header">
                     <text class="um-title">上传图或视频 ({{ modalMediaList.length }}/5)</text>
-                    <text class="um-remark-hint" v-if="currentClockIn && currentClockIn.remark">要求:{{ currentClockIn.remark }}</text>
+                    <text class="um-remark-hint">{{ currentTaskDesc }}</text>
                 </view>
                 <view class="um-body">
                     <view class="um-grid">
                         <view class="um-item" v-for="(img, idx) in modalMediaList" :key="idx">
-                            <image class="um-preview" :src="img.url || img.localPath || img" mode="aspectFill"></image>
+                            <image class="um-preview" :src="img.thumb || img.url || img.localPath || img" mode="aspectFill"></image>
+                            <view class="um-video-badge" v-if="img.mediaType === 'video'">
+                                <image class="play-icon-small" src="/static/icons/play_circle.svg"></image>
+                            </view>
                             <view class="um-del" @click="removeModalMedia(idx)">×</view>
                         </view>
                         <view class="um-add" @click="chooseModalMedia" v-if="modalMediaList.length < 5">
@@ -369,17 +378,17 @@
                         </view>
                         <view class="sum-meta-row">
                             <text class="sum-meta-label">宠主姓名:</text>
-                            <text class="sum-meta-val">{{ orderDetail.ownerName || '张**' }}</text>
+                            <text class="sum-meta-val">{{ orderDetail.ownerName || '未知' }}</text>
                         </view>
                         <view class="sum-section-title">宠物信息</view>
                         <view class="sum-pet-card">
                             <image class="sum-pet-avatar" :src="orderDetail.petAvatar" mode="aspectFill"></image>
                             <view class="sum-pet-info">
                                 <view class="sum-pet-name-row">
-                                    <text class="sum-pet-name">{{ orderDetail.petName }}</text>
-                                    <text class="sum-pet-breed">品种: {{ orderDetail.petBreed }}</text>
+                                    <text class="sum-pet-name">{{ orderDetail.petName || '未知' }}</text>
+                                    <text class="sum-pet-breed">品种: {{ orderDetail.petBreed || '未知' }}</text>
                                 </view>
-                                <text class="sum-pet-remark">{{ orderDetail.petNotes || '喜欢坐车,有点晗车,请注意通风。' }}</text>
+                                <text class="sum-pet-remark">{{ orderDetail.petNotes || '暂无备注' }}</text>
                             </view>
                         </view>
                         <view class="sum-section-title">服务内容记录</view>
@@ -417,7 +426,14 @@
                         </view>
                         <text class="am-item-content">{{ item.content }}</text>
                         <view class="am-item-photos" v-if="item.photos && item.photos.length > 0">
-                            <image class="am-photo" v-for="(photoUrl, pIdx) in item.photoUrls" :key="pIdx" :src="photoUrl" mode="aspectFill"></image>
+                            <view class="am-photo-item" v-for="(photoUrl, pIdx) in item.photoUrls" :key="pIdx" @click="previewMedia(item.photoUrls, pIdx)">
+                                <image v-if="!isVideo(photoUrl)" class="am-photo" :src="photoUrl" mode="aspectFill"></image>
+                                <view v-else class="tl-video-placeholder miniaturized">
+                                    <view class="tl-play-icon small">
+                                    </view>
+                                    <text class="tl-video-label small">视频</text>
+                                </view>
+                            </view>
                         </view>
                         <view class="am-audit-box" v-if="item.status !== 0">
                             <view class="am-audit-header">
@@ -427,7 +443,15 @@
                             <text class="am-audit-remark">{{ item.auditRemark || '无' }}</text>
                         </view>
                     </view>
-                </scroll-view>
+        </scroll-view>
+        </view>
+        </view>
+
+        <!-- 视频播放弹窗 -->
+        <view class="video-player-mask" v-if="videoPlayerShow" @click="closeVideoPlayer">
+            <view class="video-player-content" @click.stop>
+                <video class="v-player" :src="videoPlayerUrl" autoplay controls></video>
+                <view class="v-close" @click="closeVideoPlayer">×</view>
             </view>
         </view>
         </template>

+ 4 - 8
pages/orders/index.vue

@@ -14,7 +14,7 @@
             <!-- 搜索栏 -->
             <view class="search-bar">
                 <view class="search-input-box">
-                    <input class="search-input" placeholder="搜索地址/电话/姓名" placeholder-class="ph-style" />
+                    <input class="search-input" v-model="searchContent" placeholder="搜索地址/电话/姓名" placeholder-class="ph-style" />
                 </view>
             </view>
 
@@ -149,16 +149,12 @@
                 <!-- 按钮组 -->
                 <view class="action-btns" v-if="['接单', '到达', '出发', '开始', '送达', '结束'].includes(item.statusText)">
                     <view class="action-left">
-                        <button class="btn normal" @click.stop="toggleCallMenu(item)">一键拨号</button>
-                        <view class="call-popover" v-if="activeCallItem === item" @click.stop>
-                            <view class="call-pop-item" @click="doCall('merchant')">联系商家</view>
-                            <view class="call-pop-item" @click="doCall('customer')">联系客户</view>
-                        </view>
+                        <button class="btn normal" @click.stop="doCall('customer', item)">拨号</button>
                     </view>
                     <view class="action-right">
+                        <button class="btn normal danger" v-if="item.status === 2" @click.stop="handleCancelOrder(item)">取消</button>
                         <button class="btn normal" @click.stop="reportAbnormal(item)">异常上报</button>
-                        <button class="btn primary" @click.stop="mainAction(item)">{{ getMainActionText(item)
-                            }}</button>
+                        <button class="btn primary" @click.stop="mainAction(item)">打卡</button>
                     </view>
                 </view>
             </view>

+ 41 - 3
pages/orders/logic.js

@@ -1,5 +1,6 @@
 import { getMyOrders } from '@/api/fulfiller'
 import { getServiceList } from '@/api/service'
+import { cancelOrderApi } from '@/api/order/subOrder/index'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
 export default {
@@ -42,6 +43,8 @@ export default {
     },
     onShow() {
         uni.hideTabBar()
+        // 此处不需要重复调用,因为逻辑可能在onLoad已处理,
+        // 或者如果需要每次进入都刷新,可以保留,但需确保顺序
         this.loadOrders()
     },
     async onPullDownRefresh() {
@@ -58,6 +61,10 @@ export default {
         },
         currentTypeFilterIdx() {
             this.loadOrders()
+        },
+        searchContent() {
+            // 搜索内容变化时,自动重新加载订单
+            this.loadOrders()
         }
     },
     computed: {
@@ -77,7 +84,7 @@ export default {
         },
         async loadOrders() {
             try {
-                const statusMap = { 0: 2, 1: 3, 2: 5, 3: null }
+                const statusMap = { 0: 2, 1: 3, 2: 4, 3: 5 }
                 const serviceId = this.currentTypeFilterIdx > 0 ? this.serviceList[this.currentTypeFilterIdx - 1]?.id : undefined
                 const params = {
                     status: statusMap[this.currentTab],
@@ -117,6 +124,7 @@ export default {
             }
             return {
                 id: order.id,
+                status: order.status, // 保存原始 status 用于判断权限
                 type: isRoundTrip ? 1 : 2,
                 typeText: serviceText,
                 typeIcon: serviceIcon,
@@ -132,6 +140,7 @@ export default {
                 startDistance: '0km',
                 endLocation: (order.customerName || '') + ' ' + (order.customerPhone || ''),
                 endAddress: order.toAddress || '',
+                customerPhone: order.customerPhone || '',
                 endDistance: '0km',
                 serviceContent: order.remark || '',
                 remark: order.remark || ''
@@ -210,12 +219,13 @@ export default {
         closeCallMenu() {
             this.activeCallItem = null;
         },
-        doCall(type) {
+        doCall(type, item) {
             let phoneNum = ''
+            const targetItem = item || this.activeCallItem
             if (type === 'merchant') {
                 phoneNum = '18900008451'
             } else if (type === 'customer') {
-                phoneNum = '13800000001'
+                phoneNum = targetItem?.customerPhone || '13800000001'
             }
             if (phoneNum) {
                 uni.makePhoneCall({ phoneNumber: phoneNum })
@@ -337,6 +347,34 @@ export default {
             });
             uni.showToast({ title: '备注已添加', icon: 'success' });
             this.closeRemarkInput();
+        },
+        /**
+         * 取消订单处理逻辑
+         * @param {Object} item - 订单项
+         */
+        handleCancelOrder(item) {
+            uni.showModal({
+                title: '提示',
+                content: '确认是否取消这个订单?',
+                success: async (res) => {
+                    if (res.confirm) {
+                        try {
+                            uni.showLoading({ title: '取消中...', mask: true });
+                            await cancelOrderApi({ orderId: item.id });
+                            uni.showToast({ title: '订单已取消', icon: 'success' });
+                            // 延时刷新列表,防止提示框闪现
+                            setTimeout(() => {
+                                this.loadOrders();
+                            }, 1500);
+                        } catch (err) {
+                            console.error('取消订单失败:', err);
+                            uni.showToast({ title: '取消失败', icon: 'none' });
+                        } finally {
+                            uni.hideLoading();
+                        }
+                    }
+                }
+            });
         }
     }
 }

+ 6 - 5
pages/orders/style.css

@@ -29,11 +29,7 @@ page {
     background-color: #F8F8F8;
     display: flex;
     flex-direction: column;
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
+    min-height: 100vh;
 }
 
 /* 状态 Tabs */
@@ -652,6 +648,11 @@ page {
     border: none;
 }
 
+.btn.normal.danger {
+    background-color: #FFF2F0;
+    color: #F5222D;
+}
+
 /* 宠物档案弹窗 */
 .pet-modal-mask {
     position: fixed;

二進制
unpackage/cache/apk/__UNI__76F5C47_cm.apk


+ 1 - 1
unpackage/cache/apk/apkurl

@@ -1 +1 @@
-https://app.liuyingyong.cn/build/download/bc056990-1d12-11f1-8857-5dd7a04169c7
+https://app.liuyingyong.cn/build/download/a6788ca0-1ddf-11f1-a284-c712c017342c

File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/apk/cmManifestCache.json


File diff suppressed because it is too large
+ 1 - 1
unpackage/cache/wgt/__UNI__76F5C47/app-config-service.js


File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/app-service.js


File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/manifest.json


File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/home/index.css


File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/mine/index.css


File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/orders/detail.css


File diff suppressed because it is too large
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/orders/index.css


File diff suppressed because it is too large
+ 1 - 1
unpackage/dist/build/app-plus/app-config-service.js


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/build/app-plus/app-service.js


+ 1 - 0
unpackage/dist/build/app-plus/manifest.json

@@ -130,6 +130,7 @@
           "selectedIconPath": "/static/tabbar/mine-active.svg"
         }
       ],
+      "custom": true,
       "backgroundColor": "#ffffff",
       "selectedIndex": 0,
       "shown": true

File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/build/app-plus/pages/home/index.css


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/build/app-plus/pages/mine/index.css


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/build/app-plus/pages/orders/detail.css


File diff suppressed because it is too large
+ 0 - 0
unpackage/dist/build/app-plus/pages/orders/index.css


+ 0 - 8
unpackage/dist/cache/.vite/deps/_metadata.json

@@ -1,8 +0,0 @@
-{
-  "hash": "fd71fe9a",
-  "configHash": "b19c648e",
-  "lockfileHash": "e3b0c442",
-  "browserHash": "262e9795",
-  "optimized": {},
-  "chunks": {}
-}

+ 0 - 3
unpackage/dist/cache/.vite/deps/package.json

@@ -1,3 +0,0 @@
-{
-  "type": "module"
-}

File diff suppressed because it is too large
+ 349 - 296
unpackage/dist/dev/app-plus/app-service.js


+ 32 - 0
unpackage/dist/dev/app-plus/manifest.json

@@ -41,6 +41,38 @@
     "nvueStyleCompiler": "uni-app",
     "compilerVersion": 3,
     "distribute": {
+      "icons": {
+        "android": {
+          "hdpi": "unpackage/res/icons/72x72.png",
+          "xhdpi": "unpackage/res/icons/96x96.png",
+          "xxhdpi": "unpackage/res/icons/144x144.png",
+          "xxxhdpi": "unpackage/res/icons/192x192.png"
+        },
+        "ios": {
+          "appstore": "unpackage/res/icons/1024x1024.png",
+          "ipad": {
+            "app": "unpackage/res/icons/76x76.png",
+            "app@2x": "unpackage/res/icons/152x152.png",
+            "notification": "unpackage/res/icons/20x20.png",
+            "notification@2x": "unpackage/res/icons/40x40.png",
+            "proapp@2x": "unpackage/res/icons/167x167.png",
+            "settings": "unpackage/res/icons/29x29.png",
+            "settings@2x": "unpackage/res/icons/58x58.png",
+            "spotlight": "unpackage/res/icons/40x40.png",
+            "spotlight@2x": "unpackage/res/icons/80x80.png"
+          },
+          "iphone": {
+            "app@2x": "unpackage/res/icons/120x120.png",
+            "app@3x": "unpackage/res/icons/180x180.png",
+            "notification@2x": "unpackage/res/icons/40x40.png",
+            "notification@3x": "unpackage/res/icons/60x60.png",
+            "settings@2x": "unpackage/res/icons/58x58.png",
+            "settings@3x": "unpackage/res/icons/87x87.png",
+            "spotlight@2x": "unpackage/res/icons/80x80.png",
+            "spotlight@3x": "unpackage/res/icons/120x120.png"
+          }
+        }
+      },
       "google": {
         "permissions": [
           "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",

+ 31 - 7
unpackage/dist/dev/app-plus/pages/home/index.css

@@ -64,7 +64,7 @@ body {
     top: 0;
     left: 0;
     width: 100%;
-    height: 9.6875rem;
+    height: 11.25rem;
     /* Reduced by another 20rpx */
     background: linear-gradient(180deg, #FFE0B2 0%, #FFF3E0 100%);
     border-bottom-left-radius: 1.875rem;
@@ -80,7 +80,7 @@ body {
     width: 100%;
     z-index: 100;
     padding-top: var(--status-bar-height);
-    height: 2.75rem;
+    height: 3.125rem;
     display: flex;
     align-items: center;
     justify-content: center;
@@ -94,7 +94,7 @@ body {
 .header-section {
     position: relative;
     z-index: 2;
-    padding: 2.75rem 0.9375rem 0;
+    padding: 4.375rem 0.9375rem 0;
 }
 /* 用户信息 */
 .user-info {
@@ -245,7 +245,7 @@ body {
 .task-header {
     position: -webkit-sticky;
     position: sticky;
-    top: calc(2.75rem + var(--status-bar-height));
+    top: calc(3.125rem + var(--status-bar-height));
     z-index: 90;
     margin-top: 0;
     margin-bottom: 0.3125rem;
@@ -278,9 +278,8 @@ body {
 }
 .left-title .title {
     font-size: 0.875rem;
-    font-weight: 900;
+    font-weight: bold;
     color: #333;
-    font-style: italic;
 }
 .left-title .count {
     font-size: 0.75rem;
@@ -412,7 +411,7 @@ body {
     padding: 0.46875rem 0.625rem;
     display: flex;
     align-items: center;
-    margin-bottom: 0.625rem;
+    margin-bottom: 0.9375rem;
 }
 .pet-avatar {
     width: 2.5rem;
@@ -505,11 +504,36 @@ body {
     padding-right: 0.625rem;
     width: 0;
 }
+.addr-title-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
 .addr-title {
     font-size: 0.875rem;
     font-weight: bold;
     color: #333;
     margin-bottom: 0.1875rem;
+    flex: 1;
+}
+.phone-call-btn {
+    width: 1.5rem;
+    height: 1.5rem;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #E8F5E9;
+    border-radius: 50%;
+    margin-left: 0.3125rem;
+    transition: transform 0.1s;
+}
+.phone-call-btn:active {
+    transform: scale(0.9);
+    background-color: #C8E6C9;
+}
+.phone-icon-item {
+    width: 0.875rem;
+    height: 0.875rem;
 }
 .addr-desc {
     font-size: 0.75rem;

+ 9 - 1
unpackage/dist/dev/app-plus/pages/mine/index.css

@@ -93,7 +93,7 @@ body {
 .header-section {
     position: relative;
     z-index: 2;
-    padding: 1.875rem 0.9375rem 0;
+    padding: 3.75rem 0.9375rem 0;
 }
 .title-bar {
     text-align: center;
@@ -366,6 +366,14 @@ body {
     font-weight: 500;
     box-shadow: 0 0.15625rem 0.46875rem rgba(0, 0, 0, 0.03);
 }
+/* 开发提示文字 */
+.dev-tip {
+    margin: 1.25rem 1.875rem;
+    font-size: 0.75rem;
+    color: #999;
+    text-align: center;
+    line-height: 1.6;
+}
 /* 联系客服弹窗样式 */
 .service-popup-mask {
     position: fixed;

+ 198 - 3
unpackage/dist/dev/app-plus/pages/orders/detail.css

@@ -736,9 +736,10 @@ body {
 }
 .um-remark-hint {
     display: block;
-    font-size: 0.75rem;
-    color: #FF9800;
-    margin-top: 0.375rem;
+    font-size: 0.6875rem;
+    color: #999;
+    margin-top: 0.5rem;
+    line-height: 1.4;
 }
 .um-grid {
     display: flex;
@@ -774,6 +775,24 @@ body {
     color: #fff;
     border-radius: 50%;
     font-size: 0.875rem;
+    z-index: 10;
+}
+.um-video-badge {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(0, 0, 0, 0.2);
+    z-index: 5;
+}
+.play-icon-small {
+    width: 1.5rem;
+    height: 1.5rem;
+    opacity: 0.9;
 }
 .um-add {
     width: 4.0625rem;
@@ -1427,3 +1446,179 @@ body {
     font-size: 0.8125rem;
     color: #999;
 }
+/* 媒体项容器 */
+.tl-media-item {
+    position: relative;
+    width: 4.375rem;
+    height: 4.375rem;
+}
+/* 视频占位样式(不取第一帧,直接用图标) */
+.tl-video-placeholder {
+    width: 100%;
+    height: 100%;
+    background: linear-gradient(135deg, #444 0%, #222 100%);
+    border-radius: 0.25rem;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+.tl-video-placeholder.miniaturized {
+    border-radius: 0.25rem;
+    overflow: hidden;
+}
+.tl-video-label {
+    color: #fff;
+    font-size: 0.625rem;
+    margin-top: 1.5625rem;
+    opacity: 0.8;
+}
+.tl-video-label.small {
+    margin-top: 1.09375rem;
+    font-size: 0.5625rem;
+}
+.tl-play-icon {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 1.875rem;
+    height: 1.875rem;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(255, 255, 255, 0.2);
+    border: 0.0625rem solid #fff;
+    border-radius: 50%;
+}
+.tl-play-icon::after {
+    content: '';
+    display: block;
+    width: 0;
+    height: 0;
+    border-top: 0.46875rem solid transparent;
+    border-bottom: 0.46875rem solid transparent;
+    border-left: 0.625rem solid #fff;
+    margin-left: 0.1875rem;
+}
+.tl-play-icon.small {
+    width: 1.25rem;
+    height: 1.25rem;
+}
+.tl-play-icon.small::after {
+    border-top: 0.3125rem solid transparent;
+    border-bottom: 0.3125rem solid transparent;
+    border-left: 0.4375rem solid #fff;
+    margin-left: 0.125rem;
+}
+/* 视频播放弹窗 */
+.video-player-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.9);
+    z-index: 999;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.video-player-content {
+    position: relative;
+    width: 100%;
+    height: 18.75rem;
+}
+.v-player {
+    width: 100%;
+    height: 100%;
+}
+.v-close {
+    position: absolute;
+    top: -2.5rem;
+    right: 1.25rem;
+    width: 1.875rem;
+    height: 1.875rem;
+    background-color: rgba(255, 255, 255, 0.2);
+    color: #fff;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 1.25rem;
+}
+/* 异常列表媒体样式 */
+.am-photo-item {
+    position: relative;
+    width: 4.6875rem;
+    height: 4.6875rem;
+    border-radius: 0.25rem;
+    overflow: hidden;
+}
+.am-photo {
+    width: 4.6875rem;
+    height: 4.6875rem;
+    border-radius: 0.25rem;
+}
+.am-video-poster.miniaturized {
+    width: 100%;
+    height: 100%;
+    position: relative;
+}
+.tl-play-icon.small {
+    width: 1.25rem;
+    height: 1.25rem;
+}
+.tl-play-icon.small::after {
+    border-top: 0.3125rem solid transparent;
+    border-bottom: 0.3125rem solid transparent;
+    border-left: 0.4375rem solid #fff;
+    margin-left: 0.125rem;
+}
+/* 导航选择弹窗 */
+.nav-modal-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.6);
+    z-index: 1000;
+    display: flex;
+    align-items: flex-end;
+}
+.nav-action-sheet {
+    width: 100%;
+    background-color: #fff;
+    border-radius: 0.9375rem 0.9375rem 0 0;
+    padding-bottom: constant(safe-area-inset-bottom);
+    padding-bottom: env(safe-area-inset-bottom);
+    overflow: hidden;
+}
+.nav-sheet-title {
+    display: block;
+    text-align: center;
+    font-size: 0.8125rem;
+    color: #999;
+    padding: 0.9375rem 0;
+    border-bottom: 1px solid #f5f5f5;
+}
+.nav-sheet-item {
+    display: block;
+    text-align: center;
+    font-size: 1rem;
+    color: #333;
+    padding: 1.09375rem 0;
+    border-bottom: 1px solid #f5f5f5;
+    background-color: #fff;
+}
+.nav-sheet-item:active {
+    background-color: #f9f9f9;
+}
+.nav-sheet-item.cancel {
+    color: #999;
+}
+.nav-sheet-gap {
+    height: 0.375rem;
+    background-color: #f5f5f5;
+}

+ 5 - 5
unpackage/dist/dev/app-plus/pages/orders/index.css

@@ -73,11 +73,7 @@ body {
     background-color: #F8F8F8;
     display: flex;
     flex-direction: column;
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
+    min-height: 100vh;
 }
 
 /* 状态 Tabs */
@@ -617,6 +613,10 @@ body {
     box-shadow: 0 0.125rem 0.375rem rgba(255, 87, 34, 0.2);
     border: none;
 }
+.btn.normal.danger {
+    background-color: #FFF2F0;
+    color: #F5222D;
+}
 
 /* 宠物档案弹窗 */
 .pet-modal-mask {

二進制
unpackage/release/apk/__UNI__76F5C47__20260311142252.apk → unpackage/release/apk/__UNI__76F5C47__20260312144943.apk


+ 1 - 1
utils/config.js

@@ -4,7 +4,7 @@
  */
 
 // API 基础地址(开发环境)
-export const BASE_URL = 'http://192.168.0.104:8080'
+export const BASE_URL = 'http://192.168.1.118:8080'
 
 // 履约者App客户端ID(需要在 sys_client 表中配置)
 export const CLIENT_ID = '3'

+ 28 - 15
utils/request.js

@@ -47,11 +47,30 @@ export default function request(options = {}) {
       data,
       header: headers,
       success: (res) => {
+        console.log(res)
         const statusCode = res.statusCode
-        const result = res.data
+        const code = res.data.code
+        const msg = res.data.msg
+        const result = res.data.data
 
         // HTTP 状态码错误
-        if (statusCode === 401) {
+        // if (statusCode === 401) {
+        //   // Token 过期或未登录
+        //   clearAuth()
+        //   uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' })
+        //   setTimeout(() => {
+        //     uni.reLaunch({ url: '/pages/login/login' })
+        //   }, 1500)
+        //   return reject(new Error('未授权'))
+        // }
+
+        if (statusCode !== 200) {
+          const msg = msg || `请求失败(${statusCode})`
+          uni.showToast({ title: msg, icon: 'none' })
+          return reject(new Error(msg))
+        }
+
+        if (code === 401) {
           // Token 过期或未登录
           clearAuth()
           uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' })
@@ -61,20 +80,14 @@ export default function request(options = {}) {
           return reject(new Error('未授权'))
         }
 
-        if (statusCode !== 200) {
-          const msg = result?.msg || `请求失败(${statusCode})`
-          uni.showToast({ title: msg, icon: 'none' })
-          return reject(new Error(msg))
-        }
-
-        // 业务状态码
-        if (result.code !== undefined && result.code !== 200) {
-          const msg = result.msg || '操作失败'
-          uni.showToast({ title: msg, icon: 'none' })
-          return reject(new Error(msg))
-        }
+        // // 业务状态码
+        // if (result.code !== undefined && result.code !== 200) {
+        //   const msg = result.msg || '操作失败'
+        //   uni.showToast({ title: msg, icon: 'none' })
+        //   return reject(new Error(msg))
+        // }
 
-        resolve(result)
+        resolve(res.data)
       },
       fail: (err) => {
         uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' })

Some files were not shown because too many files changed in this diff