2 İşlemeler 1ce7734eac ... 5abadf7d6a

Yazar SHA1 Mesaj Tarih
  Huanyi 5abadf7d6a 注释掉消息通知 1 hafta önce
  Huanyi d4c155b516 消息中心基本完成 1 hafta önce
37 değiştirilmiş dosya ile 814 ekleme ve 298 silme
  1. 0 2
      api/fulfiller/app.js
  2. 38 0
      api/system/notice.js
  3. 2 2
      manifest.json
  4. 95 17
      pages/home/index.vue
  5. 1 1
      pages/home/work-status/index.vue
  6. 3 7
      pages/login/index.vue
  7. 19 2
      pages/mine/index.vue
  8. 1 1
      pages/mine/level/index.vue
  9. 20 3
      pages/mine/message/detail/index.vue
  10. 56 6
      pages/mine/message/index.vue
  11. 91 39
      pages/mine/message/order/index.vue
  12. 89 47
      pages/mine/message/system/index.vue
  13. 5 9
      pages/mine/settings/about/agreement-detail/index.vue
  14. 3 3
      pages/mine/settings/auth/edit/index.vue
  15. 1 1
      pages/mine/settings/auth/index.vue
  16. 47 80
      pages/mine/settings/profile/index.vue
  17. 4 2
      pages/mine/wallet/index.vue
  18. 3 2
      pages/orders/anomaly/index.vue
  19. 57 28
      pages/orders/detail/index.vue
  20. 165 28
      pages/orders/index.vue
  21. 40 7
      pages/recruit/auth/index.vue
  22. 4 6
      pages/recruit/form/index.vue
  23. 2 2
      pages/recruit/qualifications/index.vue
  24. BIN
      unpackage/cache/apk/__UNI__76F5C47_cm.apk
  25. 1 1
      unpackage/cache/apk/apkurl
  26. 0 0
      unpackage/cache/apk/cmManifestCache.json
  27. 1 1
      unpackage/cache/wgt/__UNI__76F5C47/app-config-service.js
  28. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/app-service.js
  29. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/manifest.json
  30. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/home/index.css
  31. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/mine/settings/auth/edit/index.css
  32. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/mine/settings/profile/index.css
  33. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/orders/index.css
  34. 0 0
      unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/auth/index.css
  35. BIN
      unpackage/release/apk/__UNI__76F5C47__20260407145031.apk
  36. 49 0
      utils/index.js
  37. 17 1
      utils/request.js

+ 0 - 2
api/fulfiller/app.js

@@ -62,7 +62,6 @@ export function uploadFile(filePath) {
           if (data.code === 200) {
             resolve(data)
           } else {
-            uni.showToast({ title: data.msg || '上传失败', icon: 'none' })
             reject(data)
           }
         } catch (e) {
@@ -70,7 +69,6 @@ export function uploadFile(filePath) {
         }
       },
       fail: (err) => {
-        uni.showToast({ title: '上传失败', icon: 'none' })
         reject(err)
       }
     })

+ 38 - 0
api/system/notice.js

@@ -0,0 +1,38 @@
+/**
+ * 系统通知 API
+ * 此代码由AI生成
+ */
+import request from '@/utils/request'
+
+/**
+ * 获取我的通知列表
+ * @param {Object} query 查询参数
+ */
+export function listMyNotice(query) {
+    return request({
+        url: '/system/notice/myList',
+        method: 'GET',
+        params: query
+    })
+}
+
+/**
+ * 标记消息为已读
+ * @param {number|string} id 消息ID
+ */
+export function readNotice(id) {
+    return request({
+        url: `/system/notice/read/${id}`,
+        method: 'PUT'
+    })
+}
+
+/**
+ * 标记所有消息为已读
+ */
+export function readAllNotice() {
+    return request({
+        url: '/system/notice/readAll',
+        method: 'PUT'
+    })
+}

+ 2 - 2
manifest.json

@@ -2,8 +2,8 @@
     "name" : "履约守护",
     "appid" : "__UNI__76F5C47",
     "description" : "履约守护",
-    "versionName" : "1.1.1",
-    "versionCode" : 11,
+    "versionName" : "1.2.3",
+    "versionCode" : 23,
     "transformPx" : false,
     /* 5+App特有相关 */
     "app-plus" : {

+ 95 - 17
pages/home/index.vue

@@ -34,9 +34,10 @@
                         <text class="city-arrow">></text>
                     </view>
                 </view>
-                <!-- <view class="notification-box">
+                <!-- 此代码由AI生成 -->
+                <!-- <view class="notification-box" @click="navToMessage">
                     <image class="bell-img" src="/static/icons/bell.svg"></image>
-                    <view class="badge-count">2</view>
+                    <view class="badge-count" v-if="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</view>
                 </view> -->
             </view>
 
@@ -189,8 +190,8 @@
                             </template>
                         </view>
 
-                        <view class="remark-box" v-if="item.remark">
-                            <text>备注:{{ item.remark }}</text>
+                        <view class="remark-box">
+                            <text>备注:{{ item.remark || '-' }}</text>
                         </view>
                     </view>
                     <!-- action-btns class is in style.css but template used card-footer. style.css: .action-btns, index.vue: .card-footer -->
@@ -297,11 +298,15 @@
     <view class="modal-mask" v-if="showRejectModal">
         <view class="custom-modal">
             <text class="modal-title">拒绝接单</text>
-            <textarea class="reject-textarea" v-model="rejectReason" placeholder="请输入拒绝理由(必填)"
-                maxlength="100"></textarea>
+            <view class="textarea-container">
+                <textarea class="reject-textarea" v-model="rejectReason" placeholder="请输入拒绝理由(必填)"
+                    maxlength="100"></textarea>
+                <text class="char-count">{{ rejectReason.length }}/100</text>
+            </view>
             <view class="modal-btns mt-30">
                 <button class="modal-btn cancel" @click="closeRejectModal">取消</button>
-                <button class="modal-btn confirm" @click="confirmReject">提交</button>
+                <button class="modal-btn confirm" :class="{ 'disabled': !rejectReason.trim() }"
+                    @click="confirmReject">提交</button>
             </view>
         </view>
     </view>
@@ -342,6 +347,7 @@ import { listAllService } from '@/api/service/list'
 import { getAreaStationList } from '@/api/system/areaStation'
 import { isLoggedIn } from '@/utils/auth'
 import { reportGps } from '@/utils/gps'
+import { listMyNotice } from '@/api/system/notice'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
 export default {
@@ -386,7 +392,9 @@ export default {
                 reject: 0,
                 completed: 0,
                 price: 0
-            }
+            },
+            unreadCount: 0,
+            noticeTimer: null
         }
     },
     onPageScroll(e) {
@@ -410,8 +418,19 @@ export default {
             this.loadOrderStats()
             this.loadTaskList()
             this.loadServiceList() // 确保服务配置也是最新的
+            // 此代码由AI生成
+            // this.fetchUnreadNotice() // 获取未读消息
+            // this.startNoticePolling() // 开始轮询
         }
     },
+    onHide() {
+        // 此代码由AI生成
+        // this.stopNoticePolling()
+    },
+    onUnload() {
+        // 此代码由AI生成
+        // this.stopNoticePolling()
+    },
     async onPullDownRefresh() {
         this.checkWorkStatus();
 
@@ -499,6 +518,37 @@ export default {
                 console.error('获取订单统计失败:', err)
             }
         },
+        async fetchUnreadNotice() {
+            if (!isLoggedIn()) return;
+            try {
+                const res = await listMyNotice({
+                    pageNum: 1,
+                    pageSize: 1,
+                    readFlag: false
+                });
+                this.unreadCount = Number(res.total) || 0;
+            } catch (err) {
+                console.error('获取未读消息失败:', err);
+            }
+        },
+        startNoticePolling() {
+            this.stopNoticePolling();
+            // 每5秒轮询一次未读消息
+            this.noticeTimer = setInterval(() => {
+                this.fetchUnreadNotice();
+            }, 5000);
+        },
+        stopNoticePolling() {
+            if (this.noticeTimer) {
+                clearInterval(this.noticeTimer);
+                this.noticeTimer = null;
+            }
+        },
+        navToMessage() {
+            uni.navigateTo({
+                url: '/pages/mine/message/index'
+            });
+        },
         checkWorkStatus() {
             const status = uni.getStorageSync('workStatus');
             if (status) {
@@ -577,7 +627,7 @@ export default {
                 this.loadOrderStats();
             } catch (err) {
                 console.error('拒绝接单失败:', err);
-                uni.showToast({ title: '操作失败', icon: 'none' });
+                uni.showToast({ title: err.message || err.msg || '拒绝接单失败', icon: 'none' });
             } finally {
                 uni.hideLoading();
             }
@@ -602,7 +652,7 @@ export default {
                 this.loadOrderStats()
             } catch (err) {
                 console.error('接单失败:', err)
-                uni.showToast({ title: '接单失败', icon: 'none' })
+                uni.showToast({ title: err.message || err.msg || '接单失败', icon: 'none' });
             }
         },
         openNavigation(item, pointType) {
@@ -698,7 +748,7 @@ export default {
                 this.taskList = (res.rows || []).map(item => this.transformOrder(item))
             } catch (err) {
                 console.error('获取订单列表失败:', err)
-                uni.showToast({ title: '加载失败', icon: 'none' })
+                uni.showToast({ title: err.message || err.msg || '加载失败', icon: 'none' })
                 this.taskList = []
             }
         },
@@ -1939,17 +1989,45 @@ page {
     align-self: flex-end;
 }
 
-/* 拒绝接单弹窗输入框 */
+/* 拒绝接单弹窗输入区域 */
+.textarea-container {
+    padding: 0 4rpx;
+    width: 100%;
+    position: relative;
+    margin-bottom: 20rpx;
+}
+
 .reject-textarea {
     width: 100%;
-    height: 200rpx;
-    background-color: #F8F8F8;
-    border-radius: 12rpx;
+    height: 240rpx;
+    background-color: #F9F9F9;
+    border: 1rpx solid #E0E0E0;
+    border-radius: 16rpx;
     padding: 24rpx;
+    padding-bottom: 60rpx;
     font-size: 28rpx;
-    line-height: 1.5;
+    line-height: 1.6;
     box-sizing: border-box;
-    margin-bottom: 20rpx;
+    transition: all 0.3s;
+}
+
+.reject-textarea:focus {
+    border-color: #FF9800;
+    background-color: #fff;
+}
+
+.char-count {
+    position: absolute;
+    right: 44rpx;
+    bottom: 24rpx;
+    font-size: 22rpx;
+    color: #999;
+}
+
+.modal-btn.confirm.disabled {
+    background: #FFD180;
+    box-shadow: none;
+    opacity: 0.8;
 }
 
 .mt-30 {

+ 1 - 1
pages/home/work-status/index.vue

@@ -86,7 +86,7 @@ export default {
             } catch (err) {
                 uni.hideLoading();
                 console.error('切换状态失败:', err);
-                uni.showToast({ title: '操作失败,请重试', icon: 'none' });
+                uni.showToast({ title: err.message || '操作失败', icon: 'none' });
             } finally {
                 this.loading = false;
             }

+ 3 - 7
pages/login/index.vue

@@ -202,13 +202,9 @@ export default {
       try {
         uni.showLoading({ title: '加载中...' });
         const res = await getAgreement(id);
-        if (res.code === 200 && res.data) {
-          this.agreementTitle = res.data.title;
-          this.agreementContent = res.data.content;
-          this.showAgreementModal = true;
-        } else {
-          uni.showToast({ title: res.msg || '获取协议失败', icon: 'none' });
-        }
+        this.agreementTitle = res.data.title;
+        this.agreementContent = res.data.content;
+        this.showAgreementModal = true;
       } catch (err) {
         console.error('获取协议详情失败:', err);
       } finally {

+ 19 - 2
pages/mine/index.vue

@@ -96,11 +96,12 @@
 
         <!-- 菜单列表 -->
         <view class="menu-list">
+            <!-- 此代码由AI生成 -->
             <!-- <view class="menu-item" @click="navToNotification">
                 <image class="menu-icon" src="/static/icons/bell_linear.svg"></image>
                 <text class="menu-text">消息中心</text>
                 <view class="menu-right">
-                    <view class="red-dot"></view>
+                    <view class="red-dot" v-if="totalUnread > 0"></view>
                     <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
                 </view>
             </view> -->
@@ -190,6 +191,7 @@ import { getMyProfile } from '@/api/fulfiller/fulfiller'
 import { listAllLevelConfigs } from '@/api/fulfiller/levelConfig'
 import { getCustomerServiceSetting } from '@/api/system/customerServiceSetting'
 import { clearAuth, isLoggedIn } from '@/utils/auth'
+import { listMyNotice } from '@/api/system/notice'
 import customTabbar from '@/components/custom-tabbar/index.vue'
 
 export default {
@@ -210,7 +212,8 @@ export default {
                 enterpriseWechatLink: '',
                 startServiceTime: '',
                 endServiceTime: ''
-            }
+            },
+            totalUnread: 0
         }
     },
     computed: {
@@ -227,9 +230,23 @@ export default {
             this.loadProfile()
             this.loadLevelConfigs()
             this.fetchCustomerServiceSetting()
+            // 此代码由AI生成
+            // this.fetchUnreadCount()
         }
     },
     methods: {
+        async fetchUnreadCount() {
+            try {
+                const res = await listMyNotice({
+                    readFlag: false,
+                    pageNum: 1,
+                    pageSize: 1
+                });
+                this.totalUnread = Number(res.total) || 0;
+            } catch (err) {
+                console.error('获取总未读数失败:', err);
+            }
+        },
         async loadProfile() {
             if (this.profileLoading) return
             this.profileLoading = true

+ 1 - 1
pages/mine/level/index.vue

@@ -155,7 +155,7 @@ export default {
                 }
             } catch (err) {
                 console.error('初始化等级页面失败:', err);
-                uni.showToast({ title: '数据加载失败', icon: 'none' });
+                uni.showToast({ title: err.message || '数据加载失败', icon: 'none' });
             } finally {
                 this.pageLoading = false;
                 uni.hideLoading();

+ 20 - 3
pages/mine/message/detail/index.vue

@@ -11,11 +11,11 @@
         <view class="nav-placeholder"></view>
 
         <view class="detail-content">
-            <text class="detail-title">账号审核通过</text>
-            <text class="detail-time">2023-11-01 10:00</text>
+            <text class="detail-title">{{ notice.title }}</text>
+            <text class="detail-time">{{ formatTime(notice.time) }}</text>
             
             <view class="detail-body">
-                <text>尊敬的用户,您的健康认证资料已通过平台审核。作为履约者,您现在可以正常接收并处理订单。请确保您熟读平台规则,遵守交通法规,安全配送。祝您工作愉快!</text>
+                <text>{{ notice.content }}</text>
             </view>
             
             <view class="detail-footer">
@@ -28,8 +28,25 @@
 </template>
 
 <script>
+import { formatTime } from '@/utils/index'
+
 export default {
+    data() {
+        return {
+            notice: {
+                title: '消息详情',
+                content: '',
+                time: ''
+            }
+        }
+    },
+    onLoad(options) {
+        if (options.title) this.notice.title = options.title;
+        if (options.content) this.notice.content = options.content;
+        if (options.time) this.notice.time = options.time;
+    },
     methods: {
+        formatTime,
         navBack() {
             uni.navigateBack();
         }

+ 56 - 6
pages/mine/message/index.vue

@@ -23,14 +23,14 @@
             <view class="message-item" @click="navToOrderMsg">
                 <view class="icon-wrapper">
                     <image class="msg-icon" src="/static/icons/icon_order_msg.svg"></image>
-                    <view class="red-dot-badge"></view>
+                    <view class="red-dot-badge" v-if="orderUnread > 0"></view>
                 </view>
                 <view class="content-wrapper">
                     <view class="top-row">
                         <text class="msg-title">订单消息</text>
-                        <text class="msg-time">5分钟前</text>
+                        <text class="msg-time" v-if="latestOrderMsg">{{ formatTime(latestOrderMsg.createTime) }}</text>
                     </view>
-                    <text class="msg-preview">你收到一个站长手动派单的新订单</text>
+                    <text class="msg-preview text-ellipsis">{{ latestOrderMsg ? latestOrderMsg.content : '暂无新订单消息' }}</text>
                 </view>
             </view>
 
@@ -38,13 +38,14 @@
             <view class="message-item" @click="navToSystemMsg">
                 <view class="icon-wrapper">
                     <image class="msg-icon" src="/static/icons/icon_system_msg.svg"></image>
+                    <view class="red-dot-badge" v-if="systemUnread > 0"></view>
                 </view>
                 <view class="content-wrapper">
                     <view class="top-row">
                         <text class="msg-title">系统消息</text>
-                        <text class="msg-time">7天前</text>
+                        <text class="msg-time" v-if="latestSystemMsg">{{ formatTime(latestSystemMsg.createTime) }}</text>
                     </view>
-                    <text class="msg-preview">你的健康证明认证审核已通过。</text>
+                    <text class="msg-preview text-ellipsis">{{ latestSystemMsg ? latestSystemMsg.content : '暂无新系统消息' }}</text>
                 </view>
             </view>
         </view>
@@ -53,11 +54,60 @@
 </template>
 
 <script>
+import { listMyNotice } from '@/api/system/notice'
+import { formatTime } from '@/utils/index'
+
 export default {
     data() {
-        return {}
+        return {
+            orderUnread: 0,
+            systemUnread: 0,
+            latestOrderMsg: null,
+            latestSystemMsg: null
+        }
+    },
+    onShow() {
+        this.loadMessageSummary();
     },
     methods: {
+        formatTime,
+        async loadMessageSummary() {
+            try {
+                // 加载订单消息概览 (type=0)
+                const orderRes = await listMyNotice({
+                    type: 0,
+                    pageNum: 1,
+                    pageSize: 1
+                });
+                this.latestOrderMsg = orderRes.rows && orderRes.rows.length > 0 ? orderRes.rows[0] : null;
+                
+                const orderUnreadRes = await listMyNotice({
+                    type: 0,
+                    readFlag: false,
+                    pageNum: 1,
+                    pageSize: 1
+                });
+                this.orderUnread = Number(orderUnreadRes.total) || 0;
+
+                // 加载系统消息概览 (type=1) - 假设系统消息是1
+                const systemRes = await listMyNotice({
+                    type: 1,
+                    pageNum: 1,
+                    pageSize: 1
+                });
+                this.latestSystemMsg = systemRes.rows && systemRes.rows.length > 0 ? systemRes.rows[0] : null;
+
+                const systemUnreadRes = await listMyNotice({
+                    type: 1,
+                    readFlag: false,
+                    pageNum: 1,
+                    pageSize: 1
+                });
+                this.systemUnread = Number(systemUnreadRes.total) || 0;
+            } catch (err) {
+                console.error('加载消息概览失败:', err);
+            }
+        },
         navBack() {
             uni.navigateBack();
         },

+ 91 - 39
pages/mine/message/order/index.vue

@@ -10,65 +10,117 @@
         </view>
         <view class="nav-placeholder"></view>
 
-        <view class="msg-group">
-            <view class="date-label">2099-12-28</view>
+        <view class="msg-group" v-for="(group, date) in groupedNotices" :key="date">
+            <view class="date-label">{{ date }}</view>
             
-            <!-- 消息卡片 1 -->
-            <view class="msg-card">
+            <view class="msg-card" v-for="item in group" :key="item.id" @click="handleMarkRead(item)">
                 <view class="card-header">
-                    <text class="card-title">站长手动派单</text>
-                    <view class="red-dot"></view>
+                    <text class="card-title">{{ item.title }}</text>
+                    <view class="red-dot" v-if="!item.readFlag"></view>
                 </view>
                 <view class="card-body">
-                    <text class="msg-text">你收到一个新订单,请及时查看并接单。</text>
+                    <text class="msg-text">{{ item.content }}</text>
                 </view>
-                <view class="card-footer">
-                    <text class="order-id">订单: 2099091503521</text>
-                    <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
-                </view>
-            </view>
-
-            <!-- 消息卡片 2 -->
-            <view class="msg-card">
-                <view class="card-header">
-                    <text class="card-title">系统自动派单</text>
-                </view>
-                <view class="card-body">
-                    <text class="msg-text">你收到一个新订单,请及时查看并接单。</text>
-                </view>
-                <view class="card-footer">
-                    <text class="order-id">订单: 2099091503523</text>
+                <view class="card-footer" @click.stop="navToOrder(item)">
+                    <text class="order-id">查看详情</text>
                     <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
                 </view>
             </view>
         </view>
 
-        <view class="msg-group">
-            <view class="date-label">2099-12-27</view>
-            
-            <!-- 消息卡片 3 -->
-            <view class="msg-card">
-                <view class="card-header">
-                    <text class="card-title">系统取消派单</text>
-                </view>
-                <view class="card-body">
-                    <text class="msg-text">订单由于超时未接单已被系统取消。</text>
-                </view>
-                <view class="card-footer">
-                    <text class="order-id">订单: 2099091503111</text>
-                    <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
-                </view>
-            </view>
+        <!-- No data -->
+        <view class="empty-state" v-if="Object.keys(groupedNotices).length === 0 && !loading">
+            <text class="empty-text">暂无消息记录</text>
         </view>
+        <uni-load-more :status="loadMoreStatus" v-if="notices.length > 0"></uni-load-more>
 
     </view>
 </template>
 
 <script>
+import { listMyNotice, readNotice } from '@/api/system/notice'
+import { formatTime } from '@/utils/index'
+
 export default {
+    data() {
+        return {
+            notices: [],
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                type: 0 // 订单消息
+            },
+            total: 0,
+            loading: false,
+            loadMoreStatus: 'more' // more, loading, noMore
+        }
+    },
+    computed: {
+        groupedNotices() {
+            const groups = {};
+            this.notices.forEach(item => {
+                const date = formatTime(item.createTime, 'yyyy-MM-dd');
+                if (!groups[date]) {
+                    groups[date] = [];
+                }
+                groups[date].push(item);
+            });
+            return groups;
+        }
+    },
+    onShow() {
+        this.refreshList();
+    },
+    onPullDownRefresh() {
+        this.refreshList();
+    },
+    onReachBottom() {
+        if (this.notices.length >= this.total) return;
+        this.queryParams.pageNum++;
+        this.loadNotices();
+    },
     methods: {
+        async refreshList() {
+            this.queryParams.pageNum = 1;
+            this.notices = [];
+            await this.loadNotices();
+            uni.stopPullDownRefresh();
+        },
+        async loadNotices() {
+            if (this.loading) return;
+            this.loading = true;
+            this.loadMoreStatus = 'loading';
+            try {
+                const res = await listMyNotice(this.queryParams);
+                this.notices = [...this.notices, ...(res.rows || [])];
+                this.total = Number(res.total);
+                this.loadMoreStatus = this.notices.length >= this.total ? 'noMore' : 'more';
+            } catch (err) {
+                console.error('获取消息失败:', err);
+                uni.showToast({ title: '加载失败', icon: 'none' });
+            } finally {
+                this.loading = false;
+            }
+        },
+        async handleMarkRead(item) {
+            if (item.readFlag) return;
+            try {
+                await readNotice(item.id);
+                item.readFlag = true;
+            } catch (err) {
+                console.error('标记已读失败:', err);
+            }
+        },
         navBack() {
             uni.navigateBack();
+        },
+        navToOrder(item) {
+            this.handleMarkRead(item);
+            // 这里根据业务需求跳转。通常是跳转到订单列表中的待接单或者是具体的详情?
+            // 目前先跳回首页任务大厅。
+            uni.switchTab({
+                url: '/pages/home/index'
+            });
         }
     }
 }

+ 89 - 47
pages/mine/message/system/index.vue

@@ -11,75 +11,117 @@
         </view>
         <view class="nav-placeholder"></view>
 
-        <view class="sys-msg-list">
-            <view class="date-label">2023-11-01</view>
-
-            <!-- 消息卡片 1 -->
-            <view class="sys-card" @click="navToDetail">
-                <view class="sys-header">
-                    <text class="sys-title">账号审核通过</text>
-                    <view class="red-dot"></view>
-                </view>
-                <view class="sys-content">
-                    <text class="sys-text">恭喜,您的健康证已通过审核,现在可以开始接单了。</text>
-                </view>
-                <view class="sys-footer">
-                    <text class="sys-time">10:00</text>
-                    <view class="check-more">
-                        <text>查看详情</text>
-                        <image class="arrow-icon-small" src="/static/icons/chevron_right.svg"></image>
-                    </view>
-                </view>
-            </view>
-
-            <!-- 消息卡片 2 -->
-            <view class="sys-card">
+        <view class="sys-msg-list" v-for="(group, date) in groupedNotices" :key="date">
+            <view class="date-label">{{ date }}</view>
+            
+            <view class="sys-card" v-for="item in group" :key="item.id" @click="handleMarkRead(item)">
                 <view class="sys-header">
-                    <text class="sys-title">活动奖励到账</text>
+                    <text class="sys-title">{{ item.title }}</text>
+                    <view class="red-dot" v-if="!item.readFlag"></view>
                 </view>
                 <view class="sys-content">
-                    <text class="sys-text">您参与的“新手启航”活动奖励金 ¥50 已发放到您的账户。</text>
+                    <text class="sys-text">{{ item.content }}</text>
                 </view>
-                <view class="sys-footer">
-                    <text class="sys-time">09:15</text>
+                <view class="sys-footer" @click.stop="navToDetail(item)">
+                    <text class="sys-time">{{ formatTime(item.createTime, 'HH:mm') }}</text>
                     <view class="check-more">
                         <text>查看详情</text>
                         <image class="arrow-icon-small" src="/static/icons/chevron_right.svg"></image>
                     </view>
                 </view>
             </view>
+        </view>
 
-            <view class="date-label">2023-10-30</view>
-
-            <!-- 消息卡片 3 -->
-            <view class="sys-card">
-                <view class="sys-header">
-                    <text class="sys-title">系统维护通知</text>
-                </view>
-                <view class="sys-content">
-                    <text class="sys-text">平台将于 11月5日 凌晨 02:00-04:00 进行系统维护,届时将无法接单。</text>
-                </view>
-                <view class="sys-footer">
-                    <text class="sys-time">18:30</text>
-                    <view class="check-more">
-                        <text>查看详情</text>
-                        <image class="arrow-icon-small" src="/static/icons/chevron_right.svg"></image>
-                    </view>
-                </view>
-            </view>
+        <!-- No data -->
+        <view class="empty-state" v-if="Object.keys(groupedNotices).length === 0 && !loading">
+            <text class="empty-text">暂无消息记录</text>
         </view>
+        <uni-load-more :status="loadMoreStatus" v-if="notices.length > 0"></uni-load-more>
     </view>
 </template>
 
 <script>
+import { listMyNotice, readNotice } from '@/api/system/notice'
+import { formatTime } from '@/utils/index'
+
 export default {
+    data() {
+        return {
+            notices: [],
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                type: 1 // 系统消息
+            },
+            total: 0,
+            loading: false,
+            loadMoreStatus: 'more'
+        }
+    },
+    computed: {
+        groupedNotices() {
+            const groups = {};
+            this.notices.forEach(item => {
+                const date = formatTime(item.createTime, 'yyyy-MM-dd');
+                if (!groups[date]) {
+                    groups[date] = [];
+                }
+                groups[date].push(item);
+            });
+            return groups;
+        }
+    },
+    onShow() {
+        this.refreshList();
+    },
+    onPullDownRefresh() {
+        this.refreshList();
+    },
+    onReachBottom() {
+        if (this.notices.length >= this.total) return;
+        this.queryParams.pageNum++;
+        this.loadNotices();
+    },
     methods: {
+        formatTime,
+        async refreshList() {
+            this.queryParams.pageNum = 1;
+            this.notices = [];
+            await this.loadNotices();
+            uni.stopPullDownRefresh();
+        },
+        async loadNotices() {
+            if (this.loading) return;
+            this.loading = true;
+            this.loadMoreStatus = 'loading';
+            try {
+                const res = await listMyNotice(this.queryParams);
+                this.notices = [...this.notices, ...(res.rows || [])];
+                this.total = Number(res.total);
+                this.loadMoreStatus = this.notices.length >= this.total ? 'noMore' : 'more';
+            } catch (err) {
+                console.error('获取消息失败:', err);
+                uni.showToast({ title: '加载失败', icon: 'none' });
+            } finally {
+                this.loading = false;
+            }
+        },
+        async handleMarkRead(item) {
+            if (item.readFlag) return;
+            try {
+                await readNotice(item.id);
+                item.readFlag = true;
+            } catch (err) {
+                console.error('标记已读失败:', err);
+            }
+        },
         navBack() {
             uni.navigateBack();
         },
-        navToDetail() {
+        navToDetail(item) {
+            this.handleMarkRead(item);
             uni.navigateTo({
-                url: '/pages/mine/message/detail/index'
+                url: `/pages/mine/message/detail/index?id=${item.id}&title=${item.title}&content=${item.content}&time=${item.createTime}`
             });
         }
     }

+ 5 - 9
pages/mine/settings/about/agreement-detail/index.vue

@@ -47,17 +47,13 @@ export default {
             uni.showLoading({ title: '加载中...' });
             try {
                 const res = await getAgreement(this.id);
-                if (res.code === 200 && res.data) {
-                    this.title = res.data.title;
-                    this.content = res.data.content;
-                    // 设置原生导航栏标题(作为兜底)
-                    uni.setNavigationBarTitle({ title: this.title });
-                } else {
-                    uni.showToast({ title: res.msg || '获取失败', icon: 'none' });
-                }
+                this.title = res.data.title;
+                this.content = res.data.content;
+                // 设置原生导航栏标题(作为兜底)
+                uni.setNavigationBarTitle({ title: this.title });
             } catch (err) {
                 console.error('获取协议详情失败:', err);
-                uni.showToast({ title: '网络错误', icon: 'none' });
+                uni.showToast({ title: err.message || '获取协议详情失败', icon: 'none' });
             } finally {
                 uni.hideLoading();
             }

+ 3 - 3
pages/mine/settings/auth/edit/index.vue

@@ -175,7 +175,7 @@ export default {
       } catch (e) {
         uni.hideLoading()
         console.error('加载认证信息失败', e)
-        uni.showToast({ title: '加载失败', icon: 'none' })
+        uni.showToast({ title: e.message || '加载失败', icon: 'none' })
       }
     },
     navBack() {
@@ -207,7 +207,7 @@ export default {
           } catch (err) {
             uni.hideLoading()
             console.error('上传身份证图片失败:', err)
-            uni.showToast({ title: '上传失败', icon: 'none' })
+            uni.showToast({ title: err.message || '上传失败', icon: 'none' })
           }
         }
       })
@@ -332,7 +332,7 @@ export default {
       } catch (err) {
         uni.hideLoading()
         console.error('提交失败:', err)
-        uni.showToast({ title: '提交失败', icon: 'none' })
+        uni.showToast({ title: err.message || '提交失败', icon: 'none' })
       }
     }
   }

+ 1 - 1
pages/mine/settings/auth/index.vue

@@ -132,7 +132,7 @@ export default {
                 }
             } catch (e) {
                 console.error('加载认证信息失败', e)
-                uni.showToast({ title: '加载失败', icon: 'none' })
+                uni.showToast({ title: e.message || '加载失败', icon: 'none' })
             }
         },
         maskIdCard(idCard) {

+ 47 - 80
pages/mine/settings/profile/index.vue

@@ -29,10 +29,6 @@
         </view>
 
         <view class="group-card">
-            <!-- <view class="list-item">
-                <text class="item-title">工作类型</text>
-                <view class="tag-blue-outline">{{ userInfo.workType }}</view>
-            </view> -->
             <view class="list-item" @click="showStatusPicker">
                 <text class="item-title">工作状态</text>
                 <view class="item-right">
@@ -101,8 +97,8 @@
 </template>
 
 <script>
-// 引入 API @author steelwei
-import { getMyProfile, updateAvatar, updateName, updateStatus, updateCity } from '@/api/fulfiller/fulfiller'
+// 引入 API
+import { getMyProfile, updateAvatar, updateStatus, updateCity } from '@/api/fulfiller/fulfiller'
 import { uploadFile } from '@/api/fulfiller/app'
 import { getAreaStationList } from '@/api/system/areaStation'
 
@@ -139,44 +135,40 @@ export default {
         uni.$off('updateName');
     },
     methods: {
-        // 加载用户信息 @author steelwei
+        // 加载用户信息
         async loadUserInfo() {
             uni.showLoading({ title: '加载中...' });
             try {
                 const res = await getMyProfile();
-                if (res.code === 200) {
-                    const data = res.data;
-                    this.userInfo = {
-                        name: data.realName || data.name,
-                        workType: data.workType === 'full_time' ? '全职' : '兼职',
-                        workStatus: this.formatStatus(data.status),
-                        city: data.cityName || '',
-                        avatar: data.avatarUrl || '/static/touxiang.png',
-                        stationName: data.stationName || '',
-                        stationFullName: '加载中...'
-                    };
-
-                    // 异步查找完整路径
-                    if (data.stationId) {
-                        if (this.fullTree.length === 0) {
-                            await this.loadAreaStationTree();
-                        }
-                        this.userInfo.stationFullName = this.findStationFullName(data.stationId, this.fullTree) || data.stationName || '未分配站点';
-                    } else {
-                        this.userInfo.stationFullName = '未分配站点';
+                const data = res.data;
+                this.userInfo = {
+                    name: data.realName || data.name,
+                    workType: data.workType === 'full_time' ? '全职' : '兼职',
+                    workStatus: this.formatStatus(data.status),
+                    city: data.cityName || '',
+                    avatar: data.avatarUrl || '/static/touxiang.png',
+                    stationName: data.stationName || '',
+                    stationFullName: '加载中...'
+                };
+
+                // 异步查找完整路径
+                if (data.stationId) {
+                    if (this.fullTree.length === 0) {
+                        await this.loadAreaStationTree();
                     }
+                    this.userInfo.stationFullName = this.findStationFullName(data.stationId, this.fullTree) || data.stationName || '未分配站点';
                 } else {
-                    uni.showToast({ title: res.msg || '加载失败', icon: 'none' });
+                    this.userInfo.stationFullName = '未分配站点';
                 }
             } catch (error) {
                 console.error('加载用户信息失败:', error);
-                uni.showToast({ title: '网络错误', icon: 'none' });
+                uni.showToast({ title: error.message || error.msg || '加载信息失败', icon: 'none' });
             } finally {
                 uni.hideLoading();
             }
         },
 
-        // 格式化状态 @author steelwei
+        // 格式化状态
         formatStatus(status) {
             const statusMap = {
                 'busy': '接单中',
@@ -186,7 +178,7 @@ export default {
             return statusMap[status] || status;
         },
 
-        // 查找站点完整路径 (城市/区域/站点) @author steelwei
+        // 查找站点完整路径 (城市/区域/站点)
         findStationFullName(stationId, tree) {
             if (!stationId || !tree || tree.length === 0) return '';
 
@@ -214,7 +206,7 @@ export default {
             uni.navigateBack({ delta: 1 });
         },
 
-        // 修改头像 @author steelwei
+        // 修改头像
         changeAvatar() {
             uni.chooseImage({
                 count: 1,
@@ -225,21 +217,15 @@ export default {
                     uni.showLoading({ title: '上传中...' });
                     try {
                         const uploadRes = await uploadFile(tempFilePath);
-                        if (uploadRes.code === 200) {
-                            const { url, ossId } = uploadRes.data;
-
-                            // 调用接口更新头像 @author steelwei
-                            const result = await updateAvatar(ossId);
-                            if (result.code === 200) {
-                                this.userInfo.avatar = url;
-                                uni.showToast({ title: '修改成功', icon: 'success' });
-                            } else {
-                                uni.showToast({ title: result.msg || '修改失败', icon: 'none' });
-                            }
-                        }
+                        const { url, ossId } = uploadRes.data;
+
+                        // 调用接口更新头像
+                        await updateAvatar(ossId);
+                        this.userInfo.avatar = url;
+                        uni.showToast({ title: '修改成功', icon: 'success' });
                     } catch (error) {
                         console.error('修改头像失败:', error);
-                        uni.showToast({ title: '上传失败', icon: 'none' });
+                        uni.showToast({ title: error.message || error.msg || '上传失败', icon: 'none' });
                     } finally {
                         uni.hideLoading();
                     }
@@ -261,7 +247,7 @@ export default {
             this.isStatusPickerShow = false;
         },
 
-        // 选择状态 @author steelwei
+        // 选择状态
         async selectStatus(statusText) {
             const statusMap = {
                 '接单中': 'busy',
@@ -270,22 +256,18 @@ export default {
             const status = statusMap[statusText];
 
             try {
-                const res = await updateStatus(status);
-                if (res.code === 200) {
-                    this.userInfo.workStatus = statusText;
-                    uni.showToast({ title: '状态已更新', icon: 'success' });
-                } else {
-                    uni.showToast({ title: res.msg || '修改失败', icon: 'none' });
-                }
+                await updateStatus(status);
+                this.userInfo.workStatus = statusText;
+                uni.showToast({ title: '状态已更新', icon: 'success' });
             } catch (error) {
                 console.error('修改状态失败:', error);
-                uni.showToast({ title: '网络错误', icon: 'none' });
+                uni.showToast({ title: error.message || error.msg || '状态更新失败', icon: 'none' });
             } finally {
                 this.closeStatusPicker();
             }
         },
 
-        // 城市和站点级联选择器 @author steelwei
+        // 城市和站点级联选择器
         async showCityPicker() {
             this.isCityPickerShow = true;
             if (this.fullTree.length === 0) {
@@ -317,6 +299,7 @@ export default {
             } catch (err) {
                 console.error('加载站点数据失败:', err);
                 this.fullTree = [];
+                uni.showToast({ title: err.message || err.msg || '获取站点失败', icon: 'none' });
             } finally {
                 uni.hideLoading();
             }
@@ -352,7 +335,7 @@ export default {
                 this.currentCityList = parent ? parent.children : [];
             }
         },
-        // 确认城市与站点选择 @author steelwei
+        // 确认城市与站点选择
         async confirmCity() {
             if (this.selectedPathway.length === 0) {
                 uni.showToast({ title: '请选择站点', icon: 'none' });
@@ -369,19 +352,15 @@ export default {
             };
 
             try {
-                const res = await updateCity(reqData);
-                if (res.code === 200) {
-                    this.userInfo.stationFullName = fullName;
-                    uni.showToast({ title: '修改成功', icon: 'success' });
-                    this.closeCityPicker();
-                    // 重置以便下一次打开
-                    this.selectedPathway = [];
-                } else {
-                    uni.showToast({ title: res.msg || '修改失败', icon: 'none' });
-                }
+                await updateCity(reqData);
+                this.userInfo.stationFullName = fullName;
+                uni.showToast({ title: '修改成功', icon: 'success' });
+                this.closeCityPicker();
+                // 重置以便下一次打开
+                this.selectedPathway = [];
             } catch (error) {
                 console.error('修改失败:', error);
-                uni.showToast({ title: '网络错误', icon: 'none' });
+                uni.showToast({ title: error.message || error.msg || '站点更新失败', icon: 'none' });
             }
         }
     }
@@ -422,7 +401,6 @@ page {
 
 .header-title {
     font-size: 28rpx;
-    /* 14pt */
     font-weight: bold;
     color: #333;
 }
@@ -473,7 +451,6 @@ page {
 
 .user-avatar {
     width: 64rpx;
-    /* Smaller avatar */
     height: 64rpx;
     border-radius: 50%;
 }
@@ -489,15 +466,6 @@ page {
     font-weight: 500;
 }
 
-.tag-blue-outline {
-    font-size: 24rpx;
-    color: #2979FF;
-    border: 1px solid #2979FF;
-    padding: 4rpx 20rpx;
-    border-radius: 30rpx;
-    background-color: #fff;
-}
-
 /* Popup Styles */
 .popup-mask {
     position: fixed;
@@ -517,7 +485,6 @@ page {
     border-top-left-radius: 20rpx;
     border-top-right-radius: 20rpx;
     padding-bottom: 30rpx;
-    /* Safe area */
 }
 
 .popup-title {
@@ -569,7 +536,7 @@ page {
     font-weight: bold;
 }
 
-/* 级联城市选择器(与我要加入页面一致) */
+/* 级联城市选择器 */
 .picker-body {
     display: flex;
     height: 500rpx;

+ 4 - 2
pages/mine/wallet/index.vue

@@ -132,8 +132,9 @@ export default {
                     this.balance = (res.data.balance / 100).toFixed(2);
                     this.pendingBalance = (res.data.pendingBalance / 100).toFixed(2);
                 }
-            } catch (error) {
-                console.error('获取余额数据失败', error);
+            } catch (err) {
+                console.error('获取余额数据失败', err);
+                uni.showToast({ title: err.message || err.msg || '获取余额失败', icon: 'none' });
             }
         },
         async fetchList(reset = false) {
@@ -181,6 +182,7 @@ export default {
                 }
             } catch (error) {
                 console.error('获取列表数据失败', error);
+                uni.showToast({ title: error.message || error.msg || '获取账单失败', icon: 'none' });
             } finally {
                 this.loading = false;
             }

+ 3 - 2
pages/orders/anomaly/index.vue

@@ -138,6 +138,7 @@ export default {
                 }
             } catch (err) {
                 console.error('获取异常类型字典失败:', err)
+                uni.showToast({ title: err.message || err.msg || '获取异常类型失败', icon: 'none' });
             }
         },
         // 打开类型选择器
@@ -177,7 +178,7 @@ export default {
                     } catch (err) {
                         uni.hideLoading();
                         console.error('上传失败:', err);
-                        uni.showToast({ title: '上传失败', icon: 'none' });
+                        uni.showToast({ title: err.message || err.msg || '上传失败', icon: 'none' });
                     }
                 }
             });
@@ -213,7 +214,7 @@ export default {
             } catch (err) {
                 uni.hideLoading();
                 console.error('异常上报失败:', err);
-                uni.showToast({ title: '提交失败,请重试', icon: 'none' });
+                uni.showToast({ title: err.message || err.msg || '提交失败', icon: 'none' });
             }
         }
     }

+ 57 - 28
pages/orders/detail/index.vue

@@ -122,7 +122,7 @@
                         <image class="si-icon outline custom-icon-file" src="/static/icons/file.svg"></image>
                         <view class="si-content">
                             <text class="si-label">备注</text>
-                            <text class="si-val">{{ orderDetail.remark || '' }}</text>
+                            <text class="si-val">{{ orderDetail.remark || '-' }}</text>
                         </view>
                     </view>
                     <!-- <view class="si-row record-history-row" @tap.stop="openAnomalyModal">
@@ -590,7 +590,7 @@ export default {
                 await this.loadOrderLogs()
             } catch (err) {
                 console.error('获取订单详情失败:', err)
-                uni.showToast({ title: '加载失败', icon: 'none' })
+                uni.showToast({ title: err.message || err.msg || '加载失败', icon: 'none' })
             }
         },
         async loadOrderLogs() {
@@ -639,7 +639,7 @@ export default {
                 toLat: order.toLat, toLng: order.toLng,
                 customerPhone: order.contactPhoneNumber || '',
                 ownerName: order.contact || '',
-                serviceContent: '', remark: '',
+                serviceContent: order.remark || '', remark: order.remark || '',
                 orderNo: order.code || 'T' + order.id,
                 createTime: order.serviceTime || '',
                 nursingSummary: order.nursingSummary || '',
@@ -781,28 +781,54 @@ export default {
         openUploadModal() { this.modalMediaList = []; this.modalRemark = ''; this.showUploadModal = true },
         closeUploadModal() { this.showUploadModal = false },
         handleConfirmUpload() { this.confirmUploadModal() },
-        async chooseModalMedia() {
-            uni.chooseMedia({
-                count: 5 - this.modalMediaList.length,
-                mediaType: ['image', 'video'],
-                sourceType: ['album', 'camera'],
-                success: async (res) => {
-                    uni.showLoading({ title: '上传中...', mask: true });
-                    try {
-                        for (const file of res.tempFiles) {
-                            const uploadRes = await uploadFile(file.tempFilePath);
-                            if (uploadRes.code === 200) {
-                                this.modalMediaList.push({
-                                    url: uploadRes.data.url, ossId: uploadRes.data.ossId,
-                                    localPath: file.tempFilePath, mediaType: file.fileType,
-                                    thumb: file.thumbTempFilePath
-                                });
-                            }
-                        }
-                        uni.hideLoading(); uni.showToast({ title: '上传成功', icon: 'success' });
-                    } catch (err) { uni.hideLoading(); uni.showToast({ title: '上传失败', icon: 'none' }) }
-                },
-                fail: (err) => { console.error('选择媒体文件失败:', err) }
+        chooseModalMedia() {
+            uni.showActionSheet({
+                itemList: ['选择图片', '选择视频'],
+                success: (asRes) => {
+                    if (asRes.tapIndex === 0) {
+                        uni.chooseImage({
+                            count: 5 - this.modalMediaList.length,
+                            sizeType: ['compressed'],
+                            sourceType: ['album', 'camera'],
+                            success: async (res) => {
+                                uni.showLoading({ title: '上传中...', mask: true });
+                                try {
+                                    for (const filePath of res.tempFilePaths) {
+                                        const uploadRes = await uploadFile(filePath);
+                                        if (uploadRes.code === 200) {
+                                            this.modalMediaList.push({
+                                                url: uploadRes.data.url, ossId: uploadRes.data.ossId,
+                                                localPath: filePath, mediaType: 'image'
+                                            });
+                                        }
+                                    }
+                                    uni.hideLoading(); uni.showToast({ title: '上传成功', icon: 'success' });
+                                } catch (err) { uni.hideLoading(); uni.showToast({ title: err.message || err.msg || '上传失败', icon: 'none' }) }
+                            },
+                            fail: (err) => { console.error('选择图片失败:', err) }
+                        });
+                    } else if (asRes.tapIndex === 1) {
+                        uni.chooseVideo({
+                            sourceType: ['album', 'camera'],
+                            compressed: true,
+                            success: async (res) => {
+                                uni.showLoading({ title: '上传中...', mask: true });
+                                try {
+                                    const uploadRes = await uploadFile(res.tempFilePath);
+                                    if (uploadRes.code === 200) {
+                                        this.modalMediaList.push({
+                                            url: uploadRes.data.url, ossId: uploadRes.data.ossId,
+                                            localPath: res.tempFilePath, mediaType: 'video',
+                                            thumb: res.thumbTempFilePath || res.tempFilePath
+                                        });
+                                    }
+                                    uni.hideLoading(); uni.showToast({ title: '上传成功', icon: 'success' });
+                                } catch (err) { uni.hideLoading(); uni.showToast({ title: err.message || err.msg || '上传失败', icon: 'none' }) }
+                            },
+                            fail: (err) => { console.error('选择视频失败:', err) }
+                        });
+                    }
+                }
             });
         },
         removeModalMedia(index) { this.modalMediaList.splice(index, 1) },
@@ -825,7 +851,7 @@ export default {
                 await this.loadOrderDetail();
             } catch (err) {
                 uni.hideLoading(); console.error('打卡失败:', err);
-                uni.showToast({ title: '打卡失败,请重试', icon: 'none' });
+                uni.showToast({ title: err.message || err.msg || '打卡失败', icon: 'none' });
             }
         },
         copyOrderNo() {
@@ -857,10 +883,13 @@ export default {
                 if (res.code === 200) {
                     uni.showToast({ title: '小结已提交', icon: 'success' });
                     this.closeSumModal(); await this.loadOrderDetail();
-                } else { uni.showToast({ title: res.msg || '提交失败', icon: 'none' }) }
+                } else {
+                    // 对于业务失败,如果 request.js 没弹,这里补一下,但要用原信息
+                    uni.showToast({ title: res.msg || '提交小结失败', icon: 'none' })
+                }
             } catch (err) {
                 uni.hideLoading(); console.error('提交宠护小结失败:', err);
-                uni.showToast({ title: '提交失败,请重试', icon: 'none' });
+                uni.showToast({ title: err.message || err.msg || '提交小结失败', icon: 'none' });
             }
         },
         isVideo(url) {

+ 165 - 28
pages/orders/index.vue

@@ -144,8 +144,8 @@
                     </view>
 
                     <!-- 备注 -->
-                    <view class="remark-box" v-if="item.remark">
-                        <text>备注:{{ item.remark }}</text>
+                    <view class="remark-box">
+                        <text>备注:{{ item.remark || '-' }}</text>
                     </view>
                 </view><!-- End of card-body -->
 
@@ -267,6 +267,22 @@
             <view class="nav-sheet-item cancel" @click="closeNavModal">取消</view>
         </view>
     </view>
+    <!-- 取消订单确认弹窗 -->
+    <view class="modal-mask" v-if="showCancelModal">
+        <view class="custom-modal">
+            <text class="modal-title">取消订单</text>
+            <view class="textarea-container">
+                <textarea class="reject-textarea" v-model="cancelReason" placeholder="请输入取消原因(必填)"
+                    maxlength="100"></textarea>
+                <text class="char-count">{{ cancelReason.length }}/100</text>
+            </view>
+            <view class="modal-btns mt-30">
+                <button class="modal-btn cancel" @click="closeCancelModal">再想想</button>
+                <button class="modal-btn confirm" :class="{ 'disabled': !cancelReason.trim() }"
+                    @click="confirmCancel">确认取消</button>
+            </view>
+        </view>
+    </view>
     <custom-tabbar currentPath="pages/orders/index"></custom-tabbar>
 </template>
 
@@ -305,7 +321,10 @@ export default {
             navTargetPointType: '',
             activeCallItem: null,
             showRemarkInput: false,
-            remarkText: ''
+            remarkText: '',
+            showCancelModal: false,
+            cancelReason: '',
+            currentOrder: null
         }
     },
     created() {
@@ -356,6 +375,7 @@ export default {
                 this.typeFilterOptions = ['全部类型', ...this.serviceList.map(s => s.name)]
             } catch (err) {
                 console.error('获取服务类型失败:', err)
+                uni.showToast({ title: err.message || err.msg || '获取服务失败', icon: 'none' })
             }
         },
         async loadOrders() {
@@ -378,6 +398,7 @@ export default {
             } catch (err) {
                 console.error('获取订单列表失败:', err)
                 this.allOrderList = []
+                uni.showToast({ title: err.message || err.msg || '加载订单失败', icon: 'none' })
             }
         },
         transformOrder(order, tabIndex) {
@@ -757,32 +778,42 @@ export default {
             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();
-                        }
-                    }
-                }
-            });
+            this.currentOrder = item;
+            this.cancelReason = '';
+            this.showCancelModal = true;
+        },
+        closeCancelModal() {
+            this.showCancelModal = false;
+            this.currentOrder = null;
+        },
+        async confirmCancel() {
+            if (!this.cancelReason.trim()) {
+                uni.showToast({ title: '请输入取消原因', icon: 'none' });
+                return;
+            }
+            try {
+                uni.showLoading({ title: '取消中...', mask: true });
+                await cancelOrderApi({
+                    orderId: this.currentOrder.id,
+                    reason: this.cancelReason
+                });
+                uni.showToast({ title: '订单已取消', icon: 'success' });
+                this.showCancelModal = false;
+                this.currentOrder = null;
+                // 延时刷新列表,防止提示框闪现
+                setTimeout(() => {
+                    this.loadOrders();
+                }, 1000);
+            } catch (err) {
+                console.error('取消订单失败:', err);
+                uni.showToast({ title: err.message || err.msg || '取消失败', icon: 'none' });
+            } finally {
+                uni.hideLoading();
+            }
         }
     }
 }
@@ -1368,9 +1399,9 @@ page {
     background-color: #F8F8F8;
     padding: 15rpx 20rpx;
     border-radius: 8rpx;
-    font-size: 26rpx;
+    font-size: 24rpx;
     color: #666;
-    margin-bottom: 30rpx;
+    margin-bottom: 20rpx;
 }
 
 .action-btns {
@@ -1852,4 +1883,110 @@ page {
     z-index: 900;
     background: transparent;
 }
+
+/* 全局通用对话框样式 (复用首页思路) */
+.modal-mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.5);
+    z-index: 5000;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.custom-modal {
+    width: 600rpx;
+    background-color: #fff;
+    border-radius: 24rpx;
+    padding: 40rpx 50rpx;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.modal-title {
+    font-size: 36rpx;
+    font-weight: bold;
+    color: #333;
+    margin-bottom: 30rpx;
+}
+
+.modal-btns {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+}
+
+.modal-btn {
+    width: 45%;
+    height: 80rpx;
+    line-height: 80rpx;
+    border-radius: 40rpx;
+    font-size: 30rpx;
+    font-weight: bold;
+    margin: 0;
+}
+
+.modal-btn::after {
+    border: none;
+}
+
+.modal-btn.cancel {
+    background-color: #F5F5F5;
+    color: #999;
+}
+
+.modal-btn.confirm {
+    background: linear-gradient(90deg, #FF9800 0%, #FF5722 100%);
+    color: #fff;
+    box-shadow: 0 5rpx 15rpx rgba(255, 87, 34, 0.3);
+}
+
+.textarea-container {
+    padding: 0 4rpx;
+    width: 100%;
+    position: relative;
+    margin-bottom: 20rpx;
+}
+
+.reject-textarea {
+    width: 100%;
+    height: 240rpx;
+    background-color: #F9F9F9;
+    border: 1rpx solid #E0E0E0;
+    border-radius: 16rpx;
+    padding: 24rpx;
+    padding-bottom: 60rpx;
+    font-size: 28rpx;
+    line-height: 1.6;
+    box-sizing: border-box;
+    transition: all 0.3s;
+}
+
+.reject-textarea:focus {
+    border-color: #FF9800;
+    background-color: #fff;
+}
+
+.char-count {
+    position: absolute;
+    right: 44rpx;
+    bottom: 24rpx;
+    font-size: 22rpx;
+    color: #999;
+}
+
+.modal-btn.confirm.disabled {
+    background: #FFD180;
+    box-shadow: none;
+    opacity: 0.8;
+}
+
+.mt-30 {
+    margin-top: 30rpx;
+}
 </style>

+ 40 - 7
pages/recruit/auth/index.vue

@@ -13,7 +13,7 @@
 
       <!-- 真实姓名 -->
       <view class="form-item">
-        <text class="label">真实姓名</text>
+        <text class="label"><text class="required">*</text>真实姓名</text>
         <view class="gray-input-box">
           <input class="input-area" type="text" v-model="formData.name" placeholder="证件姓名"
             placeholder-class="input-placeholder" />
@@ -22,7 +22,7 @@
 
       <!-- 证件号码 -->
       <view class="form-item">
-        <text class="label">证件号码</text>
+        <text class="label"><text class="required">*</text>证件号码</text>
         <view class="gray-input-box">
           <input class="input-area" type="idcard" v-model="formData.idNumber" placeholder="身份证号"
             placeholder-class="input-placeholder" />
@@ -31,7 +31,7 @@
 
       <!-- 有效日期 -->
       <view class="form-item">
-        <text class="label">有效日期</text>
+        <text class="label"><text class="required">*</text>有效日期</text>
         <view class="gray-input-box" @click="openPicker">
           <text class="input-area" :class="{ 'input-placeholder': !formData.expiryDate }">
             {{ formData.expiryDate || '选择有效结束期限' }}
@@ -64,7 +64,7 @@
           <text class="upload-text">点击上传</text>
         </template>
       </view>
-      <text class="card-label">证件带照片面</text>
+      <text class="card-label"><text class="required">*</text>证件带照片面</text>
     </view>
 
     <!-- 身份证反面 -->
@@ -81,7 +81,7 @@
           <text class="upload-text">点击上传</text>
         </template>
       </view>
-      <text class="card-label">证件国徽面</text>
+      <text class="card-label"><text class="required">*</text>证件国徽面</text>
     </view>
 
     <!-- 底部按钮 -->
@@ -203,11 +203,38 @@ export default {
             if (side === 'front') this.idCardFrontOssId = uploadRes.data.ossId;
             else this.idCardBackOssId = uploadRes.data.ossId;
             uni.hideLoading(); this.saveAuthData();
-          } catch (err) { uni.hideLoading(); console.error('上传身份证图片失败:', err); }
+          } catch (err) { uni.hideLoading(); console.error('上传身份证图片失败:', err); uni.showToast({ title: err.message || err.msg || '上传失败', icon: 'none' }); }
         }
       });
     },
     goToQualifications() {
+      // 必填项校验
+      if (!this.formData.name.trim()) {
+        uni.showToast({ title: '请输入真实姓名', icon: 'none' });
+        return;
+      }
+      if (!this.formData.idNumber.trim()) {
+        uni.showToast({ title: '请输入证件号码', icon: 'none' });
+        return;
+      }
+      // 简单的身份证格式校验建议(此处仅做非空,如果需要正则可以添加)
+      if (this.formData.idNumber.length < 15) {
+        uni.showToast({ title: '请输入正确的证件号码', icon: 'none' });
+        return;
+      }
+      if (!this.formData.expiryDate) {
+        uni.showToast({ title: '请选择有效日期', icon: 'none' });
+        return;
+      }
+      if (!this.idCardFrontOssId) {
+        uni.showToast({ title: '请上传身份证照片面', icon: 'none' });
+        return;
+      }
+      if (!this.idCardBackOssId) {
+        uni.showToast({ title: '请上传身份证国徽面', icon: 'none' });
+        return;
+      }
+
       this.saveAuthData();
       try {
         const stored = uni.getStorageSync('recruit_form_data');
@@ -259,12 +286,18 @@ page {
 }
 
 .label {
-  width: 160rpx;
+  width: 170rpx;
   font-size: 26rpx;
   font-weight: bold;
   color: #333;
 }
 
+.required {
+  color: #FF5252;
+  margin-right: 4rpx;
+  font-weight: bold;
+}
+
 .input-area {
   flex: 1;
   font-size: 26rpx;

+ 4 - 6
pages/recruit/form/index.vue

@@ -243,7 +243,7 @@ export default {
   methods: {
     async loadAreaStationData() {
       try { const res = await getAreaStationList(); this.fullStationData = res.data || []; }
-      catch (err) { console.error('加载站点列表失败:', err); }
+      catch (err) { console.error('加载站点列表失败:', err); uni.showToast({ title: err.message || err.msg || '获取站点失败', icon: 'none' }); }
     },
     restoreFormData() {
       try {
@@ -287,7 +287,7 @@ export default {
       try {
         const res = await listAllService();
         this.serviceTypes = (res.data || []).map(item => ({ id: item.id, name: item.name }));
-      } catch (err) { console.error('加载服务类型失败:', err); this.serviceTypes = []; }
+      } catch (err) { console.error('加载服务类型失败:', err); this.serviceTypes = []; uni.showToast({ title: err.message || err.msg || '获取服务失败', icon: 'none' }); }
     },
     toggleService(item) {
       const idx = this.formData.serviceType.indexOf(item.id);
@@ -324,10 +324,8 @@ export default {
       try {
         uni.showLoading({ title: '加载中...' });
         const res = await getAgreement(3);
-        if (res.code === 200 && res.data) {
-          this.agreementTitle = res.data.title; this.agreementContent = res.data.content; this.showPrivacy = true;
-        } else { uni.showToast({ title: res.msg || '获取协议失败', icon: 'none' }); }
-      } catch (err) { console.error('获取协议详情失败:', err); }
+        this.agreementTitle = res.data.title; this.agreementContent = res.data.content; this.showPrivacy = true;
+      } catch (err) { console.error('获取协议详情失败:', err); uni.showToast({ title: err.message || err.msg || '获取协议失败', icon: 'none' }); }
       finally { uni.hideLoading(); }
     },
     goToAuth() {

+ 2 - 2
pages/recruit/qualifications/index.vue

@@ -78,7 +78,7 @@ export default {
           for (const tempPath of res.tempFilePaths) {
             this.qualifications[serviceName].push(tempPath); this.$forceUpdate();
             try { const uploadRes = await uploadFile(tempPath); this.qualOssIds[serviceName].push(uploadRes.data.ossId); this.saveQualData(); }
-            catch (err) { console.error('上传资质图片失败:', err); }
+            catch (err) { console.error('上传资质图片失败:', err); uni.showToast({ title: err.message || err.msg || '上传失败', icon: 'none' }); }
           }
         }
       });
@@ -120,7 +120,7 @@ export default {
         const p = recruitData.mobile || '';
         uni.removeStorageSync('recruit_form_data'); uni.removeStorageSync('recruit_auth_data'); uni.removeStorageSync('recruit_qual_data');
         uni.reLaunch({ url: `/pages/recruit/success/index?station=${s}&name=${n}&phone=${p}` });
-      } catch (err) { uni.hideLoading(); console.error('提交申请失败:', err); }
+      } catch (err) { uni.hideLoading(); console.error('提交申请失败:', err); uni.showToast({ title: err.message || err.msg || '提交失败', icon: 'none' }); }
     }
   }
 }

BIN
unpackage/cache/apk/__UNI__76F5C47_cm.apk


+ 1 - 1
unpackage/cache/apk/apkurl

@@ -1 +1 @@
-https://app.liuyingyong.cn/build/download/97c4ff90-28e2-11f1-a884-4dc006ef45c3
+https://app.liuyingyong.cn/build/download/83f35700-2e70-11f1-8ab7-6ffc0f58920c

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/apk/cmManifestCache.json


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 1
unpackage/cache/wgt/__UNI__76F5C47/app-config-service.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/app-service.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/manifest.json


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/home/index.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/mine/settings/auth/edit/index.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/mine/settings/profile/index.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/orders/index.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
unpackage/cache/wgt/__UNI__76F5C47/pages/recruit/auth/index.css


BIN
unpackage/release/apk/__UNI__76F5C47__20260330181001.apk → unpackage/release/apk/__UNI__76F5C47__20260407145031.apk


+ 49 - 0
utils/index.js

@@ -0,0 +1,49 @@
+/**
+ * 通用工具类
+ * 此代码由AI生成
+ */
+
+/**
+ * 格式化时间
+ * @param {string|Date} time 
+ * @param {string} pattern 
+ */
+export function formatTime(time, pattern = 'yyyy-MM-dd HH:mm:ss') {
+  if (!time) return ''
+  const date = new Date(time)
+  const o = {
+    'M+': date.getMonth() + 1,
+    'd+': date.getDate(),
+    'H+': date.getHours(),
+    'm+': date.getMinutes(),
+    's+': date.getSeconds()
+  }
+  if (/(y+)/.test(pattern)) {
+    pattern = pattern.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+  }
+  for (const k in o) {
+    if (new RegExp('(' + k + ')').test(pattern)) {
+      pattern = pattern.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
+    }
+  }
+  return pattern
+}
+
+/**
+ * 日期格式化展示(5分钟前,今天 HH:mm 等)
+ */
+export function displayTime(time) {
+  if (!time) return ''
+  const date = new Date(time)
+  const now = new Date()
+  const diff = now.getTime() - date.getTime()
+  
+  if (diff < 60000) return '刚刚'
+  if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前'
+  if (now.toDateString() === date.toDateString()) return '今天 ' + formatTime(date, 'HH:mm')
+  
+  const yesterday = new Date(now.getTime() - 86400000)
+  if (yesterday.toDateString() === date.toDateString()) return '昨天 ' + formatTime(date, 'HH:mm')
+  
+  return formatTime(date, 'MM-dd HH:mm')
+}

+ 17 - 1
utils/request.js

@@ -88,10 +88,26 @@ export default function request(options = {}) {
           return reject(new Error(errorMsg))
         }
 
+        // 账号禁用判断:如果获取个人信息的接口返回禁用状态,强制退出
+        if (url === '/fulfiller/fulfiller/my' && result) {
+          if (result.status === 'disabled' || result.status === '1' || result.status === 1) {
+            clearAuth()
+            uni.showModal({
+              title: '提示',
+              content: '您的账号已被禁用',
+              showCancel: false,
+              success: () => {
+                uni.reLaunch({ url: '/pages/login/index' })
+              }
+            })
+            return reject(new Error('账号已被禁用'))
+          }
+        }
+
         resolve(res.data)
       },
       fail: (err) => {
-        uni.showToast({ title: '网络异常,请检查网络状态', icon: 'none' })
+        uni.showToast({ title: err.errMsg || '网络请求失败', icon: 'none' })
         reject(err)
       }
     })

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor