gaoluyang
2026-03-04 7aa5e7a3923fd41ce6999f21496ab7ad0f8605a0
公司app
1.添加商机管理功能
2.app部署修改
已添加1个文件
已修改4个文件
1164 ■■■■■ 文件已修改
src/App.vue 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login.js 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/manifest.json 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/message.vue 940 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue
@@ -1,22 +1,96 @@
<template>
    <Splash v-if="showSplash" />
    <div v-else>
        <router-view />
    </div>
  <Splash v-if="showSplash" />
  <div v-else>
    <router-view />
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Splash from './components/Splash.vue'
  import { ref, onMounted } from "vue";
  import Splash from "./components/Splash.vue";
  import { confirmMessage } from "@/api/login.js";
const showSplash = ref(true)
onMounted(() => {
    setTimeout(() => {
        showSplash.value = false
    }, 5000)
})
  const showSplash = ref(true);
  onMounted(() => {
    setTimeout(() => {
      showSplash.value = false;
    }, 5000);
    // åˆå§‹åŒ–推送服务
    initPushService();
  });
  // åˆå§‹åŒ–推送服务(uni-push 1.0)
  const initPushService = () => {
    // #ifdef APP-PLUS
    console.log("开始初始化推送服务(uni-push 1.0)");
    if (typeof plus !== "undefined" && plus.push) {
      console.log("plus.push å­˜åœ¨:", plus.push);
      // èŽ·å–å®¢æˆ·ç«¯æŽ¨é€æ ‡è¯†
      console.log("使用 plus.push.getClientInfo èŽ·å–å®¢æˆ·ç«¯æ ‡è¯†");
      plus.push.getClientInfoAsync(info => {
        console.log("客户端推送标识:", info);
        uni.setStorageSync("clientid", info.clientid);
        // è¿™é‡Œå¯ä»¥å°†å®¢æˆ·ç«¯æ ‡è¯†å‘送到服务器
      });
      setTimeout(() => {
        console.log("使用 plus.push.getClientInfoAsync èŽ·å–å®¢æˆ·ç«¯æ ‡è¯†");
        plus.push.getClientInfoAsync(info => {
          console.log("客户端推送标识:", info);
          // è¿™é‡Œå¯ä»¥å°†å®¢æˆ·ç«¯æ ‡è¯†å‘送到服务器
        });
      }, 1000);
      // ç›‘听推送消息点击事件
      plus.push.addEventListener("click", handlePushClick, false);
      // ç›‘听推送消息接收事件
      plus.push.addEventListener("receive", handlePushReceive, false);
      console.log("推送服务注册成功");
    } else {
      console.log("推送服务不可用");
    }
    // #endif
  };
  // å¤„理推送消息点击事件
  const handlePushClick = msg => {
    console.log("点击推送消息:", msg);
    console.log("解析后:", msg.payload.noticeId);
    try {
      confirmMessage(msg.payload.noticeId, 1).then(res => {
        if (msg.payload.url) {
          if (msg.payload.url.indexOf("/") === 0) {
            uni.navigateTo({
              url: msg.payload.url,
            });
          } else {
            uni.navigateTo({
              url: "/" + msg.payload.url,
            });
          }
        }
      });
    } catch (error) {
      uni.showToast({
        title: "路径:" + msg.payload,
        icon: "none",
      });
      uni.showToast({
        title: "跳转失败:" + error.message,
        icon: "none",
      });
    }
    // è§£æžå¹¶å¤„理推送消息...
  };
  // å¤„理推送消息接收事件
  const handlePushReceive = msg => {
    console.log("收到推送消息:", msg);
    // å¤„理接收的推送消息...
  };
</script>
<style lang="scss">
@import "uview-plus/index.scss";
@import '@/static/scss/index.scss';
  @import "uview-plus/index.scss";
  @import "@/static/scss/index.scss";
</style>
src/api/login.js
@@ -40,4 +40,67 @@
    method: 'get',
    params: params
  })
}
}
// èŽ·å–æœªè¿‡æœŸå…¬å‘Šæ•°é‡
export function noticesList(params) {
  return request({
    url: '/collaborativeApproval/notice/page',
    method: 'get',
    params: params
  })
}
// å‘送客户端推送标识到服务器
export function updateClientId(data) {
  return request({
    url: '/system/client/addOrUpdateClientId',
    method: 'post',
    data: data
  })
}
// æŸ¥è¯¢å…¬å‘Šåˆ—表
export function listNotice(query) {
  return request({
    url: '/system/notice/list',
    method: 'get',
    params: query
  })
}
// èŽ·å–æœªè¯»æ¶ˆæ¯æ•°é‡
export function getNoticeCount(consigneeId) {
  return request({
    url: '/system/notice/getCount',
    method: 'get',
    params: { consigneeId }
  })
}
// æ ‡è®°æ¶ˆæ¯ä¸ºå·²è¯»
export function markAsRead(noticeId, status) {
  return request({
    url: "/system/notice",
    method: "put",
    data: { noticeId, status },
  });
}
// ä¸€é”®æ ‡è®°æ‰€æœ‰æ¶ˆæ¯ä¸ºå·²è¯»
export function markAllAsRead() {
  return request({
    url: "/system/notice/readAll",
    method: "post",
  });
}
// ç¡®è®¤æ¶ˆæ¯
export function confirmMessage(noticeId, status) {
  return request({
    url: "/system/notice",
    method: "put",
    data: { noticeId, status },
  });
}
src/manifest.json
@@ -7,6 +7,10 @@
    "transformPx" : false,
    /* 5+App特有相关 */
    "app-plus" : {
        "compatible" : {
            "usingComponents" : true,
            "ignoreVersion" : true
        },
        "usingComponents" : true,
        "nvueStyleCompiler" : "uni-app",
        "compilerVersion" : 3,
@@ -17,7 +21,12 @@
            "delay" : 0
        },
        /* æ¨¡å—配置 */
        "modules" : {},
        "modules" : {
            "Camera" : {},
            "Barcode" : {},
            "Push" : {},
            "Maps" : {}
        },
        /* åº”用发布信息 */
        "distribute" : {
            /* android打包配置 */
@@ -37,15 +46,43 @@
                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
                    "<uses-feature android:name=\"android.hardware.camera\"/>",
                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
                    "<uses-permission android:name=\"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS\"/>",
                    "<uses-feature android:name=\"android.hardware.location\"/>",
                    "<uses-feature android:name=\"android.hardware.location.gps\"/>",
                    "<uses-feature android:name=\"android.hardware.location.network\"/>"
                ]
            },
            /* ios打包配置 */
            "ios" : {
                "dSYMs" : false
                "dSYMs" : false,
                "plist" : {
                    "NSLocationWhenInUseUsageDescription" : "需要获取您的位置信息来记录客户拜访地点",
                    "NSLocationAlwaysAndWhenInUseUsageDescription" : "需要获取您的位置信息来记录客户拜访地点"
                }
            },
            /* SDK配置 */
            "sdkConfigs" : {},
            "sdkConfigs" : {
                "push" : {
                    "unipush" : {
                        "icons" : {
                            "small" : {
                                "ldpi" : "D:/xindao/wenjian/img/logo/app.png"
                            }
                        },
                        "offline" : false
                    }
                },
                "maps" : {
                    "amap" : {
                        "name" : "amap_18330707920ae9zOwCD",
                        "appkey_ios" : "c2b4e3889ab4cb9468e9c8ae4f3ab53f",
                        "appkey_android" : "c2b4e3889ab4cb9468e9c8ae4f3ab53f"
                    }
                }
            },
            "icons" : {
                "android" : {
                    "hdpi" : "unpackage/res/icons/72x72.png",
src/pages.json
@@ -32,6 +32,12 @@
      }
    },
    {
      "path": "pages/message",
      "style": {
        "navigationBarTitleText": "消息中心"
      }
    },
    {
      "path": "pages/work",
      "style": {
        "navigationBarTitleText": "工作台"
@@ -639,6 +645,12 @@
        "text": "首页"
      },
      {
        "pagePath": "pages/message",
        "iconPath": "static/images/tabbar/work.png",
        "selectedIconPath": "static/images/tabbar/work_.png",
        "text": "消息"
      },
      {
        "pagePath": "pages/mine",
        "iconPath": "static/images/tabbar/mine.png",
        "selectedIconPath": "static/images/tabbar/mine_.png",
src/pages/message.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,940 @@
<template>
  <view class="message-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <!-- <PageHeader title="消息中心"
                :showBack="false" /> -->
    <!-- ç­›é€‰æ ‡ç­¾ -->
    <view class="tabs-container">
      <up-tabs v-model="activeTab"
               @change="handleTabChange"
               :list="tabList"
               :current="activeTab"
               itemStyle="width: 50%;height: 80rpx;"></up-tabs>
    </view>
    <!-- æ¶ˆæ¯åˆ—表 -->
    <scroll-view class="message-list"
                 scroll-y="true"
                 refresher-enabled="true"
                 :refresher-triggered="triggered"
                 :refresher-threshold="100"
                 refresher-background="#f5f7fa"
                 @refresherrefresh="onRefresh"
                 @scrolltolower="loadMore">
      <!-- åŠ è½½çŠ¶æ€ -->
      <view v-if="loading"
            class="loading-state">
        <text class="loading-text">加载中...</text>
      </view>
      <!-- æ¶ˆæ¯åˆ—表 -->
      <view v-else
            v-for="(item) in messageList"
            :key="item.id"
            class="message-item"
            :class="{
              'unread': !item.read,
              [getApprovalTypeClass(item.noticeTitle)]: true
            }">
        <view class="message-content">
          <view class="message-header">
            <text class="message-title">{{ item.noticeTitle }}</text>
            <text class="message-time">{{ formatTime(item.createTime) }}</text>
          </view>
          <text class="message-desc">{{ item.noticeContent }}</text>
          <view v-if="activeTab === 0"
                class="message-footer">
            <text class="message-view"
                  @click="goToDetail(item)">
              ç¡®è®¤æ¶ˆæ¯
            </text>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view v-if="!loading && messageList.length === 0"
            class="empty-state">
        <text class="empty-text">暂无消息</text>
      </view>
      <!-- åŠ è½½æ›´å¤šçŠ¶æ€ -->
      <view v-if="loadingMore"
            class="loading-more-state">
        <text class="loading-more-text">加载更多...</text>
      </view>
    </scroll-view>
  </view>
</template>
<script setup>
  import { ref, reactive, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { listNotice, confirmMessage, getNoticeCount } from "@/api/login.js";
  import useUserStore from "@/store/modules/user";
  // æ ‡ç­¾é¡µæ•°æ®
  const tabList = [
    { name: "未读", id: 0 },
    { name: "已读", id: 1 },
  ];
  // å½“前激活的标签
  const activeTab = ref(0);
  // æ¶ˆæ¯åˆ—表数据
  const messageList = ref([]);
  const loading = ref(false);
  const loadingMore = ref(false);
  const total = ref(0);
  const triggered = ref(false);
  // åˆ†é¡µå‚æ•°
  const page = reactive({
    current: 1,
    size: 10,
  });
  // æ ¼å¼åŒ–æ—¶é—´
  const formatTime = time => {
    if (!time) return "";
    const date = new Date(time);
    const Y = date.getFullYear();
    const M = String(date.getMonth() + 1).padStart(2, "0");
    const D = String(date.getDate()).padStart(2, "0");
    const h = String(date.getHours()).padStart(2, "0");
    const m = String(date.getMinutes()).padStart(2, "0");
    return `${Y}-${M}-${D} ${h}:${m}`;
  };
  // æ ¹æ®æ ‡é¢˜èŽ·å–å®¡æ‰¹ç±»åž‹å¯¹åº”çš„æ ·å¼ç±»å
  const getApprovalTypeClass = (title) => {
    if (!title) return 'type-default';
    const titleStr = title.toString().trim();
    if (titleStr.includes('采购审批') || titleStr.includes('采购')) {
      return 'type-purchase';
    } else if (titleStr.includes('报价审批') || titleStr.includes('报价')) {
      return 'type-quotation';
    } else if (titleStr.includes('发货审批') || titleStr.includes('发货')) {
      return 'type-shipment';
    } else if (titleStr.includes('公出') || titleStr.includes('公出管理')) {
      return 'type-business-trip';
    } else if (titleStr.includes('请假') || titleStr.includes('请假管理')) {
      return 'type-leave';
    } else if (titleStr.includes('出差') || titleStr.includes('出差管理')) {
      return 'type-travel';
    } else if (titleStr.includes('报销') || titleStr.includes('报销管理')) {
      return 'type-reimbursement';
    } else if (titleStr.includes('危险作业')) {
      return 'type-dangerous';
    }
    return 'type-default';
  };
  // æ ¹æ® noticeTitle åˆ¤æ–­è·³è½¬é¡µé¢
  const getJumpPathByTitle = (title, appJumpPath) => {
    if (!title) return null;
    const titleStr = title.toString().trim();
    // å°è¯•从 appJumpPath ä¸­æå– approveId
    let approveId = null;
    if (appJumpPath) {
      const queryIndex = appJumpPath.indexOf('?');
      if (queryIndex !== -1) {
        const queryString = appJumpPath.substring(queryIndex + 1);
        const params = queryString.split('&');
        params.forEach(param => {
          const [key, value] = param.split('=');
          if (key === 'approveId' && value) {
            approveId = decodeURIComponent(value);
          }
        });
      }
      // å¦‚果没有在查询参数中找到,尝试从路径中提取(如 /pages/xxx/xxx/123)
      if (!approveId) {
        const pathParts = appJumpPath.split('/');
        const lastPart = pathParts[pathParts.length - 1];
        if (lastPart && !lastPart.includes('?') && !lastPart.includes('.')) {
          approveId = lastPart;
        }
      }
    }
    // æ ¹æ®æ ‡é¢˜åˆ¤æ–­å®¡æ‰¹ç±»åž‹å’Œè·³è½¬è·¯å¾„
    if (titleStr.includes('采购审批') || titleStr.includes('采购')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '5');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=5`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index5`;
    } else if (titleStr.includes('报价审批') || titleStr.includes('报价')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '6');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=6`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index6`;
    } else if (titleStr.includes('发货审批') || titleStr.includes('发货')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '7');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=7`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index7`;
    } else if (titleStr.includes('公出') || titleStr.includes('公出管理')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '1');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=1`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index1`;
    } else if (titleStr.includes('请假') || titleStr.includes('请假管理')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '2');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=2`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index2`;
    } else if (titleStr.includes('出差') || titleStr.includes('出差管理')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '3');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=3`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index3`;
    } else if (titleStr.includes('报销') || titleStr.includes('报销管理')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '4');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=4`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index4`;
    } else if (titleStr.includes('危险作业')) {
      if (approveId) {
        uni.setStorageSync('approveId', approveId);
        uni.setStorageSync('approveType', '8');
        return `/pages/cooperativeOffice/collaborativeApproval/approve?approveType=8`;
      }
      return `/pages/cooperativeOffice/collaborativeApproval/index8`;
    }
    // å¦‚果都不匹配,尝试使用原始的 appJumpPath
    if (appJumpPath) {
      let jumpPath = appJumpPath;
      if (jumpPath.indexOf("/") !== 0) {
        jumpPath = "/" + jumpPath;
      }
      return jumpPath;
    }
    return null;
  };
  // æ›´æ–° tab æ è§’æ ‡
  const updateTabBarBadge = () => {
    if (userId.value) {
      getNoticeCount(userId.value)
        .then(res => {
          const count = res.data || 0;
          // æ›´æ–°tabbar的角标
          if (count > 0) {
            uni.setTabBarBadge({
              index: 1, // æ¶ˆæ¯æ ‡ç­¾é¡µçš„索引
              text: count.toString(),
            });
          } else {
            uni.removeTabBarBadge({
              index: 1,
            });
          }
        })
        .catch(error => {
          console.error("获取未读消息数量失败:", error);
        });
    }
  };
  // è·³è½¬åˆ°è¯¦æƒ…页
  const goToDetail = item => {
    confirmMessage(item.noticeId, 1).then(res => {
      if (res.code === 200) {
        // uni.showToast({ title: "确认成功", icon: "success" });
        loadMessages(false);
        // æ›´æ–° tab æ è§’æ ‡
        updateTabBarBadge();
        // æ ¹æ® noticeTitle åˆ¤æ–­è·³è½¬é¡µé¢
        const jumpPath = getJumpPathByTitle(item.noticeTitle, item.appJumpPath);
        if (jumpPath) {
          console.log('跳转路径:', jumpPath);
          console.log('消息标题:', item.noticeTitle);
          console.log('消息数据:', item);
          uni.navigateTo({
            url: jumpPath,
            fail: (err) => {
              console.error('跳转失败:', err);
              uni.showToast({
                title: "跳转失败,请检查路径",
                icon: "none",
              });
            }
          });
        } else {
          console.warn('无法根据标题判断跳转页面:', item.noticeTitle);
          uni.showToast({
            title: "无法跳转,请手动进入",
            icon: "none",
          });
        }
      } else {
        uni.showToast({ title: "确认失败", icon: "none" });
      }
    });
  };
  const userStore = useUserStore();
  const userId = ref("");
  const getUserId = () => {
    return userStore.getInfo().then(res => {
      console.log(res.user.userId, "res@@@@@@@@@@@2");
      userId.value = res.user.userId;
    });
  };
  // å¤„理标签页切换
  const handleTabChange = val => {
    console.log(val);
    activeTab.value = val.id;
    page.current = 1;
    loadMessages(false);
  };
  // åŠ è½½æ¶ˆæ¯åˆ—è¡¨
  const loadMessages = (append = false) => {
    if (append) {
      loadingMore.value = true;
    } else {
      loading.value = true;
    }
    // æž„建查询参数
    const params = {
      consigneeId: userId.value,
      current: page.current,
      size: page.size,
      status: activeTab.value,
    };
    console.log(params, "===========");
    return listNotice(params)
      .then(res => {
        const records = res?.data?.records || [];
        total.value = res?.data?.total || 0;
        if (append) {
          messageList.value = [...messageList.value, ...records];
        } else {
          messageList.value = records;
        }
      })
      .catch(error => {
        console.error("获取消息失败:", error);
        uni.showToast({ title: "获取消息失败,请重试", icon: "none" });
      })
      .finally(() => {
        loading.value = false;
        loadingMore.value = false;
      });
  };
  // åŠ è½½æ›´å¤š
  const loadMore = () => {
    console.log("===========");
    if (loading.value || loadingMore.value) return;
    if (messageList.value.length >= total.value) return;
    page.current++;
    loadMessages(true);
  };
  // ä¸‹æ‹‰åˆ·æ–°
  const onRefresh = () => {
    triggered.value = true;
    // é‡ç½®é¡µç 
    page.current = 1;
    // é‡æ–°åŠ è½½æ¶ˆæ¯
    loadMessages(false).finally(() => {
      // å…³é—­åˆ·æ–°çŠ¶æ€
      setTimeout(() => {
        triggered.value = false;
      }, 500);
    });
  };
  // é¡µé¢åŠ è½½æ—¶èŽ·å–æ¶ˆæ¯åˆ—è¡¨
  onMounted(() => {
    getUserId().then(() => {
      loadMessages();
      // æ›´æ–° tab æ è§’æ ‡
      updateTabBarBadge();
    });
  });
  // é¡µé¢æ˜¾ç¤ºæ—¶ä¹Ÿæ›´æ–°è§’标(从其他页面返回时)
  onShow(() => {
    if (userId.value) {
      updateTabBarBadge();
    }
  });
</script>
<style scoped lang="scss">
  // å…¨å±€å˜é‡
  $primary-color: #2979ff;
  $primary-light: #4a90e2;
  $success-color: #4cd964;
  $warning-color: #ff9500;
  $danger-color: #ff3b30;
  $text-primary: #1f2937;
  $text-secondary: #6b7280;
  $text-tertiary: #9ca3af;
  $bg-color: #f9fafb;
  $card-bg: #ffffff;
  $border-color: #e5e7eb;
  $shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
  $shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  $shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
  .message-page {
    min-height: 100vh;
    background: linear-gradient(180deg, #f9fafb 0%, #ffffff 100%);
    padding-bottom: 30rpx;
  }
  /* æ ‡ç­¾é¡µå®¹å™¨ */
  .tabs-container {
    background-color: #ffffff;
    margin-bottom: 12rpx;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
    position: sticky;
    top: 0;
    z-index: 10;
  }
  /* æ¶ˆæ¯åˆ—表 */
  .message-list {
    padding: 0 24rpx 24rpx;
    min-height: 600rpx;
    height: calc(100vh - 200rpx);
    box-sizing: border-box;
  }
  /* åŠ è½½çŠ¶æ€ */
  .loading-state {
    background-color: $card-bg;
    border-radius: 20rpx;
    box-shadow: $shadow-sm;
    text-align: center;
    padding: 120rpx 0;
    margin: 0 0 24rpx 0;
    border: 1px solid rgba(0, 0, 0, 0.05);
    width: 100%;
    box-sizing: border-box;
  }
  .loading-text {
    font-size: 14px;
    color: $text-tertiary;
    margin-top: 24rpx;
    font-weight: 500;
  }
  /* æ¶ˆæ¯é¡¹ */
  .message-item {
    display: flex;
    align-items: flex-start;
    background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
    border-radius: 20rpx;
    box-shadow: $shadow-sm;
    padding: 32rpx;
    margin: 0 0 24rpx 0;
    border: 1px solid rgba(0, 0, 0, 0.05);
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    overflow: hidden;
    width: 100%;
    box-sizing: border-box;
    &::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 6rpx;
      height: 100%;
      background: linear-gradient(180deg, $primary-color 0%, $primary-light 100%);
      opacity: 0;
      transition: opacity 0.3s ease;
    }
  }
  .message-item:active {
    transform: scale(0.98);
    box-shadow: $shadow-md;
  }
  .message-item.unread {
    &::before {
      opacity: 1;
    }
    .message-title {
      font-weight: 700;
    }
    .message-title::before {
      content: '';
      display: inline-block;
      width: 12rpx;
      height: 12rpx;
      border-radius: 50%;
      margin-right: 12rpx;
      vertical-align: middle;
      animation: pulse 2s infinite;
    }
  }
  // é‡‡è´­å®¡æ‰¹ - è“è‰²
  .message-item.type-purchase.unread {
    background: linear-gradient(135deg, #ffffff 0%, #e3f2fd 100%);
    border: 1px solid rgba(33, 150, 243, 0.2);
    box-shadow: 0 2px 8px rgba(33, 150, 243, 0.15);
    &::before {
      background: linear-gradient(180deg, #2196f3 0%, #1976d2 100%);
    }
    .message-title {
      color: #1976d2;
    }
    .message-title::before {
      background: #1976d2;
    }
    .message-view {
      background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
      box-shadow: 0 4rpx 12rpx rgba(33, 150, 243, 0.3);
    }
  }
  // æŠ¥ä»·å®¡æ‰¹ - ç»¿è‰²
  .message-item.type-quotation.unread {
    background: linear-gradient(135deg, #ffffff 0%, #e8f5e9 100%);
    border: 1px solid rgba(76, 175, 80, 0.2);
    box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
    &::before {
      background: linear-gradient(180deg, #4caf50 0%, #388e3c 100%);
    }
    .message-title {
      color: #388e3c;
    }
    .message-title::before {
      background: #388e3c;
    }
    .message-view {
      background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
      box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.3);
    }
  }
  // å‘货审批 - æ©™è‰²
  .message-item.type-shipment.unread {
    background: linear-gradient(135deg, #ffffff 0%, #fff3e0 100%);
    border: 1px solid rgba(255, 152, 0, 0.2);
    box-shadow: 0 2px 8px rgba(255, 152, 0, 0.15);
    &::before {
      background: linear-gradient(180deg, #ff9800 0%, #f57c00 100%);
    }
    .message-title {
      color: #f57c00;
    }
    .message-title::before {
      background: #f57c00;
    }
    .message-view {
      background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
      box-shadow: 0 4rpx 12rpx rgba(255, 152, 0, 0.3);
    }
  }
  // å…¬å‡ºç®¡ç† - ç´«è‰²
  .message-item.type-business-trip.unread {
    background: linear-gradient(135deg, #ffffff 0%, #f3e5f5 100%);
    border: 1px solid rgba(156, 39, 176, 0.2);
    box-shadow: 0 2px 8px rgba(156, 39, 176, 0.15);
    &::before {
      background: linear-gradient(180deg, #9c27b0 0%, #7b1fa2 100%);
    }
    .message-title {
      color: #7b1fa2;
    }
    .message-title::before {
      background: #7b1fa2;
    }
    .message-view {
      background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
      box-shadow: 0 4rpx 12rpx rgba(156, 39, 176, 0.3);
    }
  }
  // è¯·å‡ç®¡ç† - é’色
  .message-item.type-leave.unread {
    background: linear-gradient(135deg, #ffffff 0%, #e0f2f1 100%);
    border: 1px solid rgba(0, 150, 136, 0.2);
    box-shadow: 0 2px 8px rgba(0, 150, 136, 0.15);
    &::before {
      background: linear-gradient(180deg, #009688 0%, #00796b 100%);
    }
    .message-title {
      color: #00796b;
    }
    .message-title::before {
      background: #00796b;
    }
    .message-view {
      background: linear-gradient(135deg, #009688 0%, #00796b 100%);
      box-shadow: 0 4rpx 12rpx rgba(0, 150, 136, 0.3);
    }
  }
  // å‡ºå·®ç®¡ç† - çº¢è‰²
  .message-item.type-travel.unread {
    background: linear-gradient(135deg, #ffffff 0%, #ffebee 100%);
    border: 1px solid rgba(244, 67, 54, 0.2);
    box-shadow: 0 2px 8px rgba(244, 67, 54, 0.15);
    &::before {
      background: linear-gradient(180deg, #f44336 0%, #d32f2f 100%);
    }
    .message-title {
      color: #d32f2f;
    }
    .message-title::before {
      background: #d32f2f;
    }
    .message-view {
      background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
      box-shadow: 0 4rpx 12rpx rgba(244, 67, 54, 0.3);
    }
  }
  // æŠ¥é”€ç®¡ç† - ç²‰è‰²
  .message-item.type-reimbursement.unread {
    background: linear-gradient(135deg, #ffffff 0%, #fce4ec 100%);
    border: 1px solid rgba(233, 30, 99, 0.2);
    box-shadow: 0 2px 8px rgba(233, 30, 99, 0.15);
    &::before {
      background: linear-gradient(180deg, #e91e63 0%, #c2185b 100%);
    }
    .message-title {
      color: #c2185b;
    }
    .message-title::before {
      background: #c2185b;
    }
    .message-view {
      background: linear-gradient(135deg, #e91e63 0%, #c2185b 100%);
      box-shadow: 0 4rpx 12rpx rgba(233, 30, 99, 0.3);
    }
  }
  // å±é™©ä½œä¸š - æ·±çº¢è‰²
  .message-item.type-dangerous.unread {
    background: linear-gradient(135deg, #ffffff 0%, #ffebee 100%);
    border: 1px solid rgba(211, 47, 47, 0.3);
    box-shadow: 0 2px 8px rgba(211, 47, 47, 0.2);
    &::before {
      background: linear-gradient(180deg, #d32f2f 0%, #b71c1c 100%);
    }
    .message-title {
      color: #b71c1c;
    }
    .message-title::before {
      background: #b71c1c;
    }
    .message-view {
      background: linear-gradient(135deg, #d32f2f 0%, #b71c1c 100%);
      box-shadow: 0 4rpx 12rpx rgba(211, 47, 47, 0.4);
    }
  }
  // é»˜è®¤ç±»åž‹ - è“è‰²ï¼ˆåŽŸè‰²ï¼‰
  .message-item.type-default.unread {
    background: linear-gradient(135deg, #ffffff 0%, #f0f7ff 100%);
    border: 1px solid rgba(41, 121, 255, 0.15);
    box-shadow: 0 2px 8px rgba(41, 121, 255, 0.1);
    &::before {
      background: linear-gradient(180deg, $primary-color 0%, $primary-light 100%);
    }
    .message-title {
      color: $primary-color;
    }
    .message-title::before {
      background: $primary-color;
    }
  }
  @keyframes pulse {
    0%, 100% {
      opacity: 1;
      transform: scale(1);
    }
    50% {
      opacity: 0.6;
      transform: scale(1.2);
    }
  }
  /* æ¶ˆæ¯å†…容 */
  .message-content {
    flex: 1;
    position: relative;
    z-index: 1;
  }
  /* æ¶ˆæ¯å¤´éƒ¨ */
  .message-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 16rpx;
    gap: 16rpx;
  }
  .message-title {
    font-size: 32rpx;
    font-weight: 600;
    color: $text-primary;
    flex: 1;
    line-height: 1.5;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .message-time {
    font-size: 24rpx;
    color: $text-tertiary;
    white-space: nowrap;
    flex-shrink: 0;
    padding: 4rpx 12rpx;
    background: rgba(0, 0, 0, 0.03);
    border-radius: 12rpx;
  }
  /* æ¶ˆæ¯æè¿° */
  .message-desc {
    font-size: 28rpx;
    color: $text-secondary;
    line-height: 1.6;
    margin-bottom: 20rpx;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    max-height: 2.4em;
  }
  /* æ¶ˆæ¯åº•部 */
  .message-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    margin-top: 16rpx;
    padding-top: 16rpx;
    border-top: 1px solid rgba(0, 0, 0, 0.05);
  }
  .message-view {
    padding: 12rpx 32rpx;
    background: linear-gradient(135deg, $primary-color 0%, $primary-light 100%);
    color: #ffffff;
    border-radius: 24rpx;
    font-size: 26rpx;
    font-weight: 500;
    box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
    transition: all 0.3s ease;
    position: relative;
    overflow: hidden;
    &::before {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      width: 0;
      height: 0;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.3);
      transform: translate(-50%, -50%);
      transition: width 0.6s, height 0.6s;
    }
    &:active {
      transform: scale(0.95);
      box-shadow: 0 2rpx 8rpx rgba(41, 121, 255, 0.4);
      &::before {
        width: 300rpx;
        height: 300rpx;
      }
    }
  }
  /* ç©ºçŠ¶æ€ */
  .empty-state {
    background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
    border-radius: 20rpx;
    box-shadow: $shadow-sm;
    text-align: center;
    padding: 160rpx 40rpx;
    margin: 60rpx 0 0 0;
    border: 1px solid rgba(0, 0, 0, 0.05);
    width: 100%;
    box-sizing: border-box;
  }
  .empty-text {
    font-size: 28rpx;
    color: $text-tertiary;
    margin-top: 24rpx;
    font-weight: 500;
  }
  /* åŠ è½½æ›´å¤šçŠ¶æ€ */
  .loading-more-state {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 40rpx 0;
    margin-top: 20rpx;
  }
  .loading-more-text {
    font-size: 26rpx;
    color: $text-tertiary;
    margin-left: 12rpx;
  }
  /* åŠ¨ç”»æ•ˆæžœ */
  @keyframes fadeInUp {
    from {
      opacity: 0;
      transform: translateY(30rpx);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
  .message-item {
    animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1) backwards;
  }
  .message-item:nth-child(1) {
    animation-delay: 0.05s;
  }
  .message-item:nth-child(2) {
    animation-delay: 0.1s;
  }
  .message-item:nth-child(3) {
    animation-delay: 0.15s;
  }
  .message-item:nth-child(4) {
    animation-delay: 0.2s;
  }
  .message-item:nth-child(5) {
    animation-delay: 0.25s;
  }
  /* å“åº”式优化 */
  @media (prefers-color-scheme: dark) {
    .message-page {
      background: linear-gradient(180deg, #1f2937 0%, #111827 100%);
    }
    .tabs-container {
      background-color: #1f2937;
      border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    }
    .message-item {
      background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
      border-color: rgba(255, 255, 255, 0.1);
    }
    .message-item.unread {
      background: linear-gradient(135deg, #1f2937 0%, #1e3a5f 100%);
      border-color: rgba(41, 121, 255, 0.3);
    }
    .message-title {
      color: #f3f4f6;
    }
    .message-desc {
      color: #d1d5db;
    }
    .message-time {
      background: rgba(255, 255, 255, 0.1);
      color: #9ca3af;
    }
    .empty-state {
      background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
      border-color: rgba(255, 255, 255, 0.1);
    }
  }
</style>