已添加5个文件
已修改22个文件
1891 ■■■■ 文件已修改
src/App.vue 200 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/deviceArea.js 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/environmentalMonitoring.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/paymentLedger.js 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/deliveryLedger.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/indicatorStats.js 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/oaWorkbench.js 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/manifest.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/attachment.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/components/formDia.vue 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/components/qrCodeFormDia.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/upload.vue 251 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inventoryManagement/environmentalMonitoring/index.vue 376 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/oa/ReimburseManage/reimburse-form/useFinReimburseForm.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/detail.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/finalInspection/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/materialInspection/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/qualityManagement/processInspection/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentLedger/detail.vue 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/receiptPaymentLedger/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/works.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/versionUpgrade.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue
@@ -1,111 +1,111 @@
<template>
  <Splash v-if="showSplash" />
  <div v-else>
    <router-view />
  </div>
    <Splash v-if="showSplash" />
    <view v-else>
        <router-view />
    </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import Splash from "./components/Splash.vue";
  import { confirmMessage } from "@/api/login.js";
import { ref, onMounted } from "vue";
import Splash from "./components/Splash.vue";
import { confirmMessage } from "@/api/login.js";
  const showSplash = ref(true);
const showSplash = ref(true);
  onMounted(() => {
    setTimeout(() => {
      showSplash.value = false;
    }, 5000);
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
};
    // åˆå§‹åŒ–推送服务,暂时注释,客户需要打开
    // 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);
// å¤„理推送消息点击事件
const handlePushClick = msg => {
    console.log("点击推送消息:", msg);
    console.log("解析后:", msg.payload.noticeId);
    try {
        if (msg.payload.needMarkRead) {
            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,
                        });
                    }
                }
            });
        } else {
            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",
        });
    }
    // è§£æžå¹¶å¤„理推送消息...
};
      // èŽ·å–å®¢æˆ·ç«¯æŽ¨é€æ ‡è¯†
      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 {
      if (msg.payload.needMarkRead) {
        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,
              });
            }
          }
        });
      } else {
        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);
    // å¤„理接收的推送消息...
  };
// å¤„理推送消息接收事件
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/equipmentManagement/deviceArea.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
import request from "@/utils/request";
export function getDeviceAreaTree(params) {
  return request({
    url: "/device/area/tree",
    method: "get",
    params,
  });
}
export function getDeviceAreaTreeWithDevices(params) {
  return request({
    url: "/device/area/treeWithDevices",
    method: "get",
  });
}
export function getDeviceAreaPage(params) {
  return request({
    url: "/device/area/page",
    method: "get",
    params,
  });
}
export function getDeviceAreaDetail(id) {
  return request({
    url: `/device/area/${id}`,
    method: "get",
  });
}
export function addDeviceArea(data) {
  return request({
    url: "/device/area",
    method: "post",
    data,
  });
}
export function updateDeviceArea(data) {
  return request({
    url: "/device/area",
    method: "put",
    data,
  });
}
export function deleteDeviceArea(ids) {
  return request({
    url: "/device/area",
    method: "delete",
    data: ids,
  });
}
src/api/inventoryManagement/environmentalMonitoring.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
import request from "@/utils/request";
export const getEnvironmentalRealData = () => {
  return request({
    url: "/iot/getRealData",
    method: "get",
  });
};
src/api/procurementManagement/paymentLedger.js
@@ -1,20 +1,19 @@
// é‡‡è´­å°è´¦é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢
/** ä»˜æ¬¾å°è´¦ - ä¾›åº”商往来汇总 */
export function paymentLedgerList(query) {
  return request({
    url: "/purchase/paymentRegistration/supplierNameListPage",
    url: "/purchase/report/supplierTransactions",
    method: "get",
    params: query,
  });
}
// åˆ†é¡µæŸ¥è¯¢
export function paymentRecordList(supplierId) {
/** ä»˜æ¬¾å°è´¦ - ä¾›åº”商往来明细 */
export function paymentRecordList(params) {
  return request({
    url: "/purchase/paymentRegistration/supplierNameListPageDetails",
    url: "/purchase/report/supplierTransactionsDetails",
    method: "get",
    params: supplierId,
    params,
  });
}
src/api/salesManagement/deliveryLedger.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// å‘货台账页面接口
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢å‘货台账(根据客户ID)
export function deliveryLedgerListPage(query) {
  return request({
    url: "/shippingInfo/listPage",
    method: "get",
    params: query,
  });
}
src/api/salesManagement/indicatorStats.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
// æŒ‡æ ‡ç»Ÿè®¡é¡µé¢æŽ¥å£
import request from "@/utils/request";
// å¤´éƒ¨ç»Ÿè®¡æŽ¥å£
export function getTotalStatistics(query) {
  return request({
    url: "/metricStatistics/total",
    method: "get",
    params: query,
  });
}
// æŸ±çŠ¶å›¾æ•°æ®æŽ¥å£
export function getStatisticsTable(query) {
  return request({
    url: "/metricStatistics/statisticsTable",
    method: "get",
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥åˆ—表
export function customewTransactions(query) {
  return request({
    url: "/metricStatistics/customewTransactions",
    method: "get",
    params: query,
  });
}
// å®¢æˆ·å¾€æ¥æ˜Žç»†
export function customewTransactionsDetails(query) {
  return request({
    url: "/metricStatistics/customewTransactionsDetails",
    method: "get",
    params: query,
  });
}
src/config.js
@@ -1,7 +1,7 @@
// åº”用全局配置
const config = {
  baseUrl: "http://1.15.17.182:9048",
  fileUrl: "http://1.15.17.182:9049",
  baseUrl: "http://1.15.17.182:9073",
  fileUrl: "http://1.15.17.182:9072",
  // åº”用信息
  appInfo: {
    // åº”用名称
src/config/oaWorkbench.js
@@ -1,69 +1,8 @@
import { OA_NAV } from "./oaPaths.js";
/**
 * OA æ¨¡å—分组(工作台展示 / æ–‡æ¡£å¯¹ç…§ï¼‰
 * æ³¨ï¼šäººäº‹ç®¡ç†/假勤管理/报销管理/审批管理 å·²è¿ç§»è‡³å¯¹åº”业务模块
 */
export const OA_MODULES = [
  {
    key: "HrManage",
    name: "人事管理",
    children: [
      // { label: "员工档案", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.staffArchive },
      // { label: "员工合同", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.staffContract },
      { label: "转正申请", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.regularApply },
      { label: "调岗申请", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.transferApply },
      // { label: "离职申请", icon: "/static/images/icon/qingjiaguanli.svg", path: OA_NAV.resignApply },
      { label: "工作交接", icon: "/static/images/icon/gongchuguanli.svg", path: OA_NAV.workHandover },
      // { label: "岗位管理", icon: "/static/images/icon/gongxuguanli.svg", path: OA_NAV.postManage },
    ],
  },
  {
    key: "AttendManage",
    name: "假勤管理",
    children: [
      { label: "请假申请", icon: "/static/images/icon/qingjiaguanli.svg", path: OA_NAV.leaveApply },
      { label: "加班申请", icon: "/static/images/icon/dakaqiandao.svg", path: OA_NAV.overtimeApply },
    ],
  },
  {
    key: "ReimburseManage",
    name: "报销管理",
    children: [
      { label: "差旅报销", icon: "/static/images/icon/chuchaiguanli.svg", path: OA_NAV.travelReimburse },
      { label: "费用报销", icon: "/static/images/icon/baoxiaoguanli.svg", path: OA_NAV.costReimburse },
    ],
  },
  // {
  //   key: "ContractManage",
  //   name: "合同管理",
  //   children: [
  //     { label: "采购合同", icon: "/static/images/icon/caigoutaizhang.svg", path: OA_NAV.purchaseContract },
  //     { label: "销售合同", icon: "/static/images/icon/xiaoshoutaizhang.svg", path: OA_NAV.saleContract },
  //   ],
  // },
  {
    key: "ApproveManage",
    name: "审批管理",
    children: [
      { label: "审批列表", icon: "/static/images/icon/xietongshenpi.svg", path: OA_NAV.approveList },
      { label: "审批模板", icon: "/static/images/icon/guizhangzhidu.svg", path: OA_NAV.approveTemplate },
    ],
  },
  // {
  //   key: "EnterpriseNews",
  //   name: "企业新闻",
  //   children: [
  //     { label: "企业新闻", icon: "/static/images/icon/zhishiku.svg", path: OA_NAV.enterpriseNews },
  //   ],
  // },
  // {
  //   key: "NoticeAnnouncement",
  //   name: "公告通知",
  //   children: [
  //     { label: "公告通知", icon: "/static/images/icon/tongzhigonggao.svg", path: OA_NAV.noticeAnnouncement },
  //   ],
  // },
];
export const OA_MODULES = [];
/** å·¥ä½œå°æ‰å¹³èœå•(纯前端配置) */
export const OA_WORKBENCH_ITEMS = OA_MODULES.flatMap(module =>
src/manifest.json
@@ -1,6 +1,6 @@
{
    "name" : "信息管理Pro-OA",
    "appid" : "__UNI__751174B",
    "name" : "新疆大罗素马铃薯",
    "appid" : "__UNI__21B3383",
    "description" : "",
    "versionName" : "1.1.5",
    "versionCode" : 100,
src/pages.json
@@ -922,6 +922,13 @@
      }
    },
    {
      "path": "pages/inventoryManagement/environmentalMonitoring/index",
      "style": {
        "navigationBarTitleText": "环境检测",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/safeProduction/safeQualifications/index",
      "style": {
        "navigationBarTitleText": "规程与资质",
@@ -1148,7 +1155,7 @@
    {
      "path": "pages/qualityManagement/materialInspection/index",
      "style": {
        "navigationBarTitleText": "原材料检验",
        "navigationBarTitleText": "原料检验",
        "navigationStyle": "custom"
      }
    },
src/pages/inspectionUpload/attachment.vue
@@ -5,29 +5,11 @@
                @back="goBack" />
    <!-- é¡µé¢å†…容 -->
    <view class="attachment-content">
      <!-- åˆ†ç±»æ ‡ç­¾é¡µ -->
      <view class="attachment-tabs">
        <view class="tab-item"
              :class="{ active: currentViewType === 'before' }"
              @click="switchViewType('before')">
          ç”Ÿäº§å‰ ({{ getAttachmentsByType(0).length }})
        </view>
        <view class="tab-item"
              :class="{ active: currentViewType === 'after' }"
              @click="switchViewType('after')">
          ç”Ÿäº§ä¸­ ({{ getAttachmentsByType(1).length }})
        </view>
        <view class="tab-item"
              :class="{ active: currentViewType === 'issue' }"
              @click="switchViewType('issue')">
          ç”Ÿäº§åŽ ({{ getAttachmentsByType(2).length }})
        </view>
      </view>
      <!-- å½“前分类的附件列表 -->
      <!-- é™„件列表 -->
      <view class="attachment-list-container">
        <view v-if="getCurrentViewAttachments().length > 0"
        <view v-if="attachmentList.length > 0"
              class="attachment-list">
          <view v-for="(file, index) in getCurrentViewAttachments()"
          <view v-for="(file, index) in attachmentList"
                :key="index"
                class="attachment-item"
                @click="previewAttachment(file)">
@@ -52,7 +34,7 @@
        </view>
        <view v-else
              class="attachment-empty">
          <text class="empty-text">该分类暂无附件</text>
          <text class="empty-text">暂无附件</text>
        </view>
      </view>
    </view>
@@ -96,9 +78,6 @@
  // é™„件列表
  const attachmentList = ref([]);
  // å½“前查看类型
  const currentViewType = ref("before"); // 'before', 'after', 'issue'
  // è§†é¢‘预览相关状态
  const showVideoDialog = ref(false);
  const currentVideoFile = ref(null);
@@ -122,46 +101,18 @@
    }
  });
  // åŠ è½½é™„ä»¶æ•°æ®
  // åŠ è½½é™„ä»¶æ•°æ®ï¼ˆç»Ÿä¸€ä½¿ç”¨ commonFileListVO)
  const loadAttachments = () => {
    const task = taskInfo.value;
    if (!task) return;
    attachmentList.value = [];
    // åŽç«¯åæ˜¾å­—段 (VO优先)
    const beforeList = Array.isArray(task?.commonFileListBeforeVO)
      ? task.commonFileListBeforeVO
      : Array.isArray(task?.commonFileListBefore)
      ? task.commonFileListBefore
      : [];
    const duringList = Array.isArray(task?.commonFileListVO)
    const fileList = Array.isArray(task?.commonFileListVO)
      ? task.commonFileListVO
      : Array.isArray(task?.commonFileListAfter)
      ? task.commonFileListAfter
      : []; // å…¼å®¹æ—§é€»è¾‘或命名不一致
    const afterList = Array.isArray(task?.commonFileListAfterVO)
      ? task.commonFileListAfterVO
      : Array.isArray(task?.commonFileListIssue)
      ? task.commonFileListIssue
      : [];
    // å¦‚æžœ VO éƒ½æ²¡æœ‰ï¼Œå°è¯•从 commonFileList è¿‡æ»¤
    const allList = Array.isArray(task?.commonFileList)
      ? task.commonFileList
      : [];
    const finalBefore =
      beforeList.length > 0 ? beforeList : allList.filter(f => f?.type === 10);
    const finalDuring =
      duringList.length > 0 ? duringList : allList.filter(f => f?.type === 11);
    const finalAfter =
      afterList.length > 0 ? afterList : allList.filter(f => f?.type === 12);
    const mapToViewFile = (file, viewType) => {
      // å…¼å®¹ previewURL, previewUrl, url, downloadURL, downloadUrl
    attachmentList.value = fileList.map(file => {
      const rawUrl =
        file?.previewURL ||
        file?.previewUrl ||
@@ -173,20 +124,14 @@
      return {
        ...file,
        type: viewType,
        name:
          file?.originalFilename || file?.bucketFilename || file?.name || "附件",
        name: file?.originalFilename || file?.bucketFilename || file?.name || "附件",
        bucketFilename: file?.bucketFilename || file?.name,
        originalFilename: file?.originalFilename || file?.name,
        url: u,
        downloadUrl: u,
        size: file?.byteSize || file?.size || 0,
      };
    };
    attachmentList.value.push(...finalBefore.map(f => mapToViewFile(f, 0)));
    attachmentList.value.push(...finalDuring.map(f => mapToViewFile(f, 1)));
    attachmentList.value.push(...finalAfter.map(f => mapToViewFile(f, 2)));
    });
  };
  // å°†åŽç«¯è¿”回的文件地址规范成可访问URL
@@ -220,30 +165,6 @@
    uni.navigateBack();
  };
  // åˆ‡æ¢æŸ¥çœ‹ç±»åž‹
  const switchViewType = type => {
    currentViewType.value = type;
  };
  // æ ¹æ®type获取对应分类的附件
  const getAttachmentsByType = typeValue => {
    return attachmentList.value.filter(file => file.type === typeValue) || [];
  };
  // èŽ·å–å½“å‰æŸ¥çœ‹ç±»åž‹çš„é™„ä»¶
  const getCurrentViewAttachments = () => {
    switch (currentViewType.value) {
      case "before":
        return getAttachmentsByType(0);
      case "after":
        return getAttachmentsByType(1);
      case "issue":
        return getAttachmentsByType(2);
      default:
        return [];
    }
  };
  // åˆ¤æ–­æ˜¯å¦ä¸ºå›¾ç‰‡æ–‡ä»¶
  const isImageFile = file => {
    if (file.contentType && file.contentType.startsWith("image/")) {
@@ -259,7 +180,7 @@
  // é¢„览附件
  const previewAttachment = file => {
    if (isImageFile(file)) {
      const imageUrls = getCurrentViewAttachments()
      const imageUrls = attachmentList.value
        .filter(f => isImageFile(f))
        .map(f => f.url || f.downloadUrl);
@@ -309,32 +230,6 @@
  .attachment-content {
    padding: 15px;
  }
  /* æ ‡ç­¾é¡µæ ·å¼ */
  .attachment-tabs {
    display: flex;
    background: #fff;
    border-radius: 12px;
    margin-bottom: 15px;
    padding: 4px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .tab-item {
    flex: 1;
    text-align: center;
    padding: 12px 8px;
    font-size: 14px;
    color: #666;
    border-radius: 8px;
    transition: all 0.3s ease;
  }
  .tab-item.active {
    background: #409eff;
    color: #fff;
    font-weight: 500;
  }
  /* é™„件列表样式 */
src/pages/inspectionUpload/components/formDia.vue
@@ -1,7 +1,7 @@
<template>
  <u-popup
    v-model="dialogVisitable"
    mode="center"
  <u-popup
    v-model="dialogVisitable"
    mode="center"
    :round="10"
    :closeable="true"
    @close="cancel"
@@ -10,23 +10,23 @@
      <view class="popup-header">
        <text class="popup-title">巡检记录上传</text>
      </view>
      <view class="upload-container">
        <!-- å¼‚常状态选择 -->
        <view class="form-container">
          <view class="title">巡检状态</view>
          <view class="exception-section">
            <view class="exception-options">
              <view
                class="exception-option"
              <view
                class="exception-option"
                :class="{ active: hasException === false }"
                @click="setExceptionStatus(false)"
              >
                <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon>
                <text class="option-text">正常</text>
              </view>
              <view
                class="exception-option"
              <view
                class="exception-option"
                :class="{ active: hasException === true }"
                @click="setExceptionStatus(true)"
              >
@@ -48,54 +48,23 @@
            :customStyle="{ padding: '10px', backgroundColor: '#f5f5f5' }"
          />
        </view>
        <!-- ä¸Šä¼ åŒºåŸŸï¼ˆä»…在异常时显示) -->
        <template v-if="hasException === true">
          <view class="form-container">
            <view class="title">生产前</view>
            <u-upload
              :fileList="beforeModelValue"
              @afterRead="afterRead"
              @delete="deleteFile"
              name="before"
              multiple
              :maxCount="10"
              :maxSize="5 * 1024 * 1024"
              accept="image/*"
              :previewFullImage="true"
            ></u-upload>
          </view>
          <view class="form-container">
            <view class="title">生产后</view>
            <u-upload
              :fileList="afterModelValue"
              @afterRead="afterRead"
              @delete="deleteFile"
              name="after"
              multiple
              :maxCount="10"
              :maxSize="5 * 1024 * 1024"
              accept="image/*"
              :previewFullImage="true"
            ></u-upload>
          </view>
          <view class="form-container">
            <view class="title">生产问题</view>
            <u-upload
              :fileList="issueModelValue"
              @afterRead="afterRead"
              @delete="deleteFile"
              name="issue"
              multiple
              :maxCount="10"
              :maxSize="5 * 1024 * 1024"
              accept="image/*"
              :previewFullImage="true"
            ></u-upload>
          </view>
        </template>
        <view class="form-container" v-if="hasException === true">
          <view class="title">上传附件</view>
          <text class="upload-count">已上传: {{ fileList.length }}个文件</text>
          <u-upload
            :fileList="fileList"
            @afterRead="afterRead"
            @delete="deleteFile"
            name="attachment"
            multiple
            :maxCount="30"
            :maxSize="5 * 1024 * 1024"
            accept="image/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
        <!-- æ­£å¸¸çŠ¶æ€æç¤º -->
        <view class="form-container normal-tip" v-if="hasException === false">
@@ -103,7 +72,7 @@
          <text class="tip-text">设备运行正常,无需上传照片</text>
        </view>
      </view>
      <view class="popup-footer">
        <u-button @click="cancel" :customStyle="{ marginRight: '10px' }">取消</u-button>
        <u-button type="primary" @click="submitForm">保存</u-button>
@@ -121,9 +90,7 @@
const emit = defineEmits(['closeDia'])
const dialogVisitable = ref(false)
const beforeModelValue = ref([])
const afterModelValue = ref([])
const issueModelValue = ref([])
const fileList = ref([])
const infoData = ref(null)
// å¼‚常状态:null=未选择, false=正常, true=异常
@@ -134,13 +101,13 @@
// è®¡ç®—上传URL
const uploadFileUrl = computed(() => {
  let baseUrl = '';
  if (process.env.VUE_APP_BASE_API) {
    baseUrl = process.env.VUE_APP_BASE_API;
  } else {
    baseUrl = config.baseUrl;
  }
  return baseUrl + '/file/upload';
})
@@ -203,29 +170,12 @@
// æ–‡ä»¶ä¸Šä¼ å¤„理
const afterRead = (event) => {
  const { name, file } = event
  // æ ¹æ®ä¸Šä¼ ç±»åž‹è®¾ç½®ä¸åŒçš„type值
  let typeValue = 10 // é»˜è®¤å€¼
  if (name === 'before') {
    typeValue = 10 // ç”Ÿäº§å‰
  } else if (name === 'after') {
    typeValue = 11 // ç”Ÿäº§ä¸­
  } else if (name === 'issue') {
    typeValue = 12 // ç”Ÿäº§åŽ
  }
  const { file } = event
  const files = Array.isArray(file) ? file : [file]
  Promise.resolve().then(async () => {
    for (const f of files) {
      const uploaded = await uploadSingleFile(f, typeValue)
      if (name === 'before') {
        beforeModelValue.value.push(uploaded)
      } else if (name === 'after') {
        afterModelValue.value.push(uploaded)
      } else if (name === 'issue') {
        issueModelValue.value.push(uploaded)
      }
      const uploaded = await uploadSingleFile(f, 10)
      fileList.value.push(uploaded)
    }
    uni.showToast({ title: '上传成功', icon: 'success' })
  }).catch((err) => {
@@ -236,15 +186,8 @@
// åˆ é™¤æ–‡ä»¶
const deleteFile = (event) => {
  const { name, index } = event
  if (name === 'before') {
    beforeModelValue.value.splice(index, 1)
  } else if (name === 'after') {
    afterModelValue.value.splice(index, 1)
  } else if (name === 'issue') {
    issueModelValue.value.splice(index, 1)
  }
  const { index } = event
  fileList.value.splice(index, 1)
}
// è®¾ç½®å¼‚常状态
@@ -266,8 +209,7 @@
    // å¦‚果是异常状态,检查是否有上传文件
    if (hasException.value === true) {
      const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length
      if (totalFiles === 0) {
      if (fileList.value.length === 0) {
        uni.showToast({
          title: '请上传异常照片',
          icon: 'none'
@@ -284,28 +226,17 @@
      }
    }
    let arr = []
    if (beforeModelValue.value.length > 0) {
      arr.push(...beforeModelValue.value.map(item => ({ ...item, statusType: 0 })))
    }
    if (afterModelValue.value.length > 0) {
      arr.push(...afterModelValue.value.map(item => ({ ...item, statusType: 1 })))
    }
    if (issueModelValue.value.length > 0) {
      arr.push(...issueModelValue.value.map(item => ({ ...item, statusType: 2 })))
    }
    // æäº¤æ•°æ®
    infoData.value.storageBlobDTO = arr
    infoData.value.commonFileListDTO = fileList.value
    infoData.value.hasException = hasException.value
    infoData.value.exceptionDescription = exceptionDescription.value
    await submitInspectionRecord({ ...infoData.value })
    uni.showToast({
      title: '提交成功',
      icon: 'success'
    })
    cancel()
  } catch (error) {
    console.error('提交失败:', error)
@@ -320,11 +251,9 @@
const openDialog = async (row) => {
  infoData.value = row
  dialogVisitable.value = true
  // æ¸…空之前的数据
  beforeModelValue.value = []
  afterModelValue.value = []
  issueModelValue.value = []
  fileList.value = []
  hasException.value = null
  exceptionDescription.value = ''
}
@@ -367,10 +296,18 @@
.form-container {
  margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0;
  }
}
.upload-count {
  display: block;
  font-size: 12px;
  color: #999;
  margin-bottom: 8px;
  padding-left: 10px;
}
.title {
@@ -381,7 +318,7 @@
  padding-left: 10px;
  position: relative;
  margin: 6px 0 10px;
  &::before {
    content: "";
    position: absolute;
@@ -423,12 +360,12 @@
  cursor: pointer;
  transition: all 0.3s;
  background-color: #fff;
  &.active {
    border-color: #1890ff;
    background-color: #e6f7ff;
  }
  &:active {
    opacity: 0.8;
  }
@@ -450,7 +387,7 @@
  background-color: #f6ffed;
  border: 1px dashed #b7eb8f;
  border-radius: 8px;
  .tip-text {
    margin-top: 15px;
    font-size: 14px;
src/pages/inspectionUpload/components/qrCodeFormDia.vue
@@ -40,16 +40,15 @@
            </u-input>
          </u-form-item>
          
          <u-form-item label="附件" prop="storageBlobDTO" labelWidth="80">
          <u-form-item label="附件" prop="commonFileListDTO" labelWidth="80">
            <view class="upload-container">
              <ImageUpload
                v-model="form.storageBlobDTO"
                v-model="form.commonFileListDTO"
                :limit="10"
                :fileSize="50"
                :fileType="['jpg', 'jpeg', 'png', 'mp4', 'mov']"
                :maxVideoDuration="60"
                :statusType="0"
                @update:modelValue="handleStorageBlobUpdate"
                @update:modelValue="handleFileListUpdate"
              />
            </view>
          </u-form-item>
@@ -116,7 +115,7 @@
  scannerName: '',
  scannerId: '',
  scanTime: '',
  storageBlobDTO: [],
  commonFileListDTO: [],
  qrCode: {
    id: ''
  }
@@ -144,9 +143,9 @@
  }
})
// å¤„理storageBlobDTO数据更新
const handleStorageBlobUpdate = (value) => {
  form.storageBlobDTO = value || []
// å¤„理commonFileListDTO数据更新
const handleFileListUpdate = (value) => {
  form.commonFileListDTO = value || []
}
// èŽ·å–å½“å‰ä½ç½®
@@ -259,7 +258,7 @@
  form.deviceName = row.deviceName || ''
  form.location = row.location || ''
  form.qrCodeId = row.qrCodeId
  form.storageBlobDTO = []
  form.commonFileListDTO = []
  console.log('弹框表单数据:', form)
  
  // å¼ºåˆ¶æ›´æ–°è§†å›¾
@@ -308,8 +307,8 @@
      scannerName: form.scannerName,
      scannerId: form.scannerId,
      scanTime: form.scanTime,
      storageBlobDTO: form.storageBlobDTO.map(file => ({
        id: file.id, // æ·»åŠ id字段
      commonFileListDTO: form.commonFileListDTO.map(file => ({
        id: file.id,
        url: file.url,
        bucketFilename: file.bucketFilename || file.name,
        downloadUrl: file.downloadUrl || file.url,
src/pages/inspectionUpload/upload.vue
@@ -57,33 +57,16 @@
                  maxlength="500"
                  placeholder="请描述异常情况..." />
      </view>
      <!-- åˆ†ç±»æ ‡ç­¾é¡µï¼ˆä»…在异常时显示) -->
      <!-- ä¸Šä¼ åŒºåŸŸï¼ˆä»…在异常时显示) -->
      <view class="section-card"
            v-if="hasException === true">
        <view class="upload-tabs">
          <view class="tab-item"
                :class="{ active: currentUploadType === 'before' }"
                @click="switchUploadType('before')">
            ç”Ÿäº§å‰
          </view>
          <view class="tab-item"
                :class="{ active: currentUploadType === 'after' }"
                @click="switchUploadType('after')">
            ç”Ÿäº§ä¸­
          </view>
          <view class="tab-item"
                :class="{ active: currentUploadType === 'issue' }"
                @click="switchUploadType('issue')">
            ç”Ÿäº§åŽ
          </view>
        </view>
        <!-- å½“前分类的上传区域 -->
        <view class="section-title">上传附件</view>
        <view class="upload-area">
          <view class="upload-buttons">
            <u-button type="primary"
                      @click="chooseMedia('image')"
                      :loading="uploading"
                      :disabled="getCurrentFiles().length >= uploadConfig.limit"
                      :disabled="fileList.length >= uploadConfig.limit"
                      :customStyle="{ marginRight: '10px', flex: 1 }">
              <u-icon name="camera"
                      size="18"
@@ -94,7 +77,7 @@
            <u-button type="success"
                      @click="chooseMedia('video')"
                      :loading="uploading"
                      :disabled="getCurrentFiles().length >= uploadConfig.limit"
                      :disabled="fileList.length >= uploadConfig.limit"
                      :customStyle="{ flex: 1 }">
              <uni-icons type="videocam"
                         size="18"
@@ -110,10 +93,10 @@
                             :showText="true"
                             activeColor="#409eff"></u-line-progress>
          </view>
          <!-- å½“前分类的文件列表 -->
          <view v-if="getCurrentFiles().length > 0"
          <!-- æ–‡ä»¶åˆ—表 -->
          <view v-if="fileList.length > 0"
                class="file-list">
            <view v-for="(file, index) in getCurrentFiles()"
            <view v-for="(file, index) in fileList"
                  :key="index"
                  class="file-item">
              <view class="file-preview-container">
@@ -129,7 +112,6 @@
                             style="margin-right: 5px"></uni-icons>
                  <text class="video-text">视频</text>
                </view>
                <!-- åˆ é™¤æŒ‰é’® -->
                <view class="delete-btn"
                      @click="removeFile(index)">
                  <u-icon name="close"
@@ -143,18 +125,13 @@
              </view>
            </view>
          </view>
          <view v-if="getCurrentFiles().length === 0"
          <view v-if="fileList.length === 0"
                class="empty-state">
            <text>请选择要上传的{{ getUploadTypeText() }}图片或视频</text>
            <text>请选择要上传的图片或视频</text>
          </view>
        </view>
        <!-- ç»Ÿè®¡ä¿¡æ¯ -->
        <view class="upload-summary">
          <text class="summary-text">
            ç”Ÿäº§å‰: {{ beforeModelValue.length }}个文件 |
            ç”Ÿäº§ä¸­: {{ afterModelValue.length }}个文件 |
            ç”Ÿäº§åŽ: {{ issueModelValue.length }}个文件
          </text>
          <text class="summary-text">已上传: {{ fileList.length }}个文件</text>
        </view>
      </view>
      <!-- æ­£å¸¸çŠ¶æ€æç¤º -->
@@ -197,13 +174,8 @@
  const uploading = ref(false);
  const uploadProgress = ref(0);
  // ä¸‰ä¸ªåˆ†ç±»çš„上传状态
  const beforeModelValue = ref([]); // ç”Ÿäº§å‰
  const afterModelValue = ref([]); // ç”Ÿäº§ä¸­
  const issueModelValue = ref([]); // ç”Ÿäº§åŽ
  // å½“前激活的上传类型
  const currentUploadType = ref("before"); // 'before', 'after', 'issue'
  // ç»Ÿä¸€çš„æ–‡ä»¶åˆ—表
  const fileList = ref([]);
  // å¼‚常状态
  const hasException = ref(null); // null: æœªé€‰æ‹©, true: å­˜åœ¨å¼‚常, false: æ­£å¸¸
@@ -232,73 +204,37 @@
        const info = JSON.parse(decodeURIComponent(options.taskInfo));
        taskInfo.value = info;
        // å›žæ˜¾é€»è¾‘:从 taskInfo ä¸­æ¢å¤å·²ä¸Šä¼ çš„æ–‡ä»¶
        const mapFiles = list => {
          if (!list || !Array.isArray(list)) return [];
          return list.map(item => {
            // å¤„理 URL,去除可能的空格
            const finalUrl = (item.url || item.previewURL || "").trim();
            // è‡ªåŠ¨æŽ¨æ–­æ–‡ä»¶ç±»åž‹
            let fileType = item.type;
            if (!fileType && item.contentType) {
              fileType = item.contentType.startsWith("video") ? "video" : "image";
            } else if (!fileType) {
              fileType = "image"; // é»˜è®¤å›¾ç‰‡
            }
        // å›žæ˜¾é€»è¾‘:从 commonFileListVO æ¢å¤å·²ä¸Šä¼ çš„æ–‡ä»¶
        const fileVOList = Array.isArray(info.commonFileListVO) ? info.commonFileListVO : [];
        fileList.value = fileVOList.map(item => {
          const finalUrl = (item.url || item.previewURL || "").trim();
          let fileType = item.type;
          if (!fileType && item.contentType) {
            fileType = item.contentType.startsWith("video") ? "video" : "image";
          } else if (!fileType) {
            fileType = "image";
          }
          return {
            ...item,
            url: finalUrl,
            name: item.name || item.originalFilename,
            tempId: item.tempId || item.id || item.tempFileId,
            size: item.size || item.byteSize || 0,
            type: fileType,
            status: "success",
          };
        });
            return {
              ...item,
              url: finalUrl,
              name: item.name || item.originalFilename,
              tempId: item.tempId || item.id || item.tempFileId,
              size: item.size || item.byteSize || 0, // æ˜ å°„大小字段
              type: fileType,
              status: "success",
            };
          });
        };
        // ä¿®æ­£å­—段映射:BeforeVO(生产前), VO(生产中), AfterVO(生产后)
        if (
          info.commonFileListBeforeVO &&
          Array.isArray(info.commonFileListBeforeVO)
        ) {
          beforeModelValue.value = mapFiles(info.commonFileListBeforeVO);
        }
        console.log(beforeModelValue.value, "beforeModelValue");
        if (info.commonFileListVO && Array.isArray(info.commonFileListVO)) {
          afterModelValue.value = mapFiles(info.commonFileListVO);
        }
        if (
          info.commonFileListAfterVO &&
          Array.isArray(info.commonFileListAfterVO)
        ) {
          issueModelValue.value = mapFiles(info.commonFileListAfterVO);
        }
        // å¦‚果有异常描述,也恢复
        if (info.abnormalDescription) {
          abnormalDescription.value = info.abnormalDescription;
        }
        // å¦‚果有异常状态,也恢复
        if (info.hasException !== undefined && info.hasException !== null) {
          hasException.value = info.hasException;
        } else if (
          info.inspectionResult !== undefined &&
          info.inspectionResult !== null
        ) {
          // 0-异常,1-正常
        } else if (info.inspectionResult !== undefined && info.inspectionResult !== null) {
          hasException.value = String(info.inspectionResult) === "0";
        }
        // è‡ªåŠ¨å…œåº•ï¼šå¦‚æžœå­˜åœ¨å·²ä¸Šä¼ æ–‡ä»¶ï¼Œåˆ™å¿…ç„¶æ˜¯å¼‚å¸¸çŠ¶æ€ï¼Œç¡®ä¿ UI æ­£å¸¸æ˜¾ç¤º
        if (
          !hasException.value &&
          (beforeModelValue.value.length > 0 ||
            afterModelValue.value.length > 0 ||
            issueModelValue.value.length > 0)
        ) {
        if (!hasException.value && fileList.value.length > 0) {
          hasException.value = true;
        }
      } catch (e) {
@@ -310,39 +246,6 @@
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // åˆ‡æ¢ä¸Šä¼ ç±»åž‹
  const switchUploadType = type => {
    currentUploadType.value = type;
  };
  // èŽ·å–å½“å‰åˆ†ç±»çš„æ–‡ä»¶åˆ—è¡¨
  const getCurrentFiles = () => {
    switch (currentUploadType.value) {
      case "before":
        return beforeModelValue.value || [];
      case "after":
        return afterModelValue.value || [];
      case "issue":
        return issueModelValue.value || [];
      default:
        return [];
    }
  };
  // èŽ·å–ä¸Šä¼ ç±»åž‹æ–‡æœ¬
  const getUploadTypeText = () => {
    switch (currentUploadType.value) {
      case "before":
        return "生产前";
      case "after":
        return "生产中";
      case "issue":
        return "生产后";
      default:
        return "";
    }
  };
  // è®¾ç½®å¼‚常状态
@@ -360,14 +263,8 @@
        inspector: taskInfo.value?.inspector,
        hasException: hasException.value,
        inspectionResult: hasException.value ? 0 : 1, // 0-异常,1-正常
        commonFileListBeforeDTO: beforeModelValue.value,
        commonFileListDTO: afterModelValue.value,
        commonFileListAfterDTO: issueModelValue.value,
        uploadedFiles: {
          before: beforeModelValue.value,
          after: afterModelValue.value,
          issue: issueModelValue.value,
        },
        commonFileListDTO: fileList.value,
        uploadedFiles: fileList.value,
      };
      uni.setStorageSync("repairTaskInfo", JSON.stringify(taskData));
@@ -398,11 +295,7 @@
      // å¦‚果是异常状态,检查是否有上传文件和描述
      if (hasException.value === true) {
        const totalFiles =
          beforeModelValue.value.length +
          afterModelValue.value.length +
          issueModelValue.value.length;
        if (totalFiles === 0) {
        if (fileList.value.length === 0) {
          uni.showToast({
            title: "请上传异常照片",
            icon: "none",
@@ -425,17 +318,10 @@
        mask: true,
      });
      // æŒ‰ç…§é€»è¾‘合并所有分类的文件用于提取ID
      const allFiles = [
        ...beforeModelValue.value,
        ...afterModelValue.value,
        ...issueModelValue.value,
      ];
      // ä¼ ç»™åŽç«¯çš„临时文件ID列表
      let tempFileIds = [];
      if (allFiles.length > 0) {
        tempFileIds = allFiles
      if (fileList.value.length > 0) {
        tempFileIds = fileList.value
          .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
          .filter(v => v !== undefined && v !== null && v !== "");
      }
@@ -443,9 +329,7 @@
      // æäº¤æ•°æ®
      const submitData = {
        ...taskInfo.value,
        commonFileListBeforeDTO: beforeModelValue.value, // ç”Ÿäº§å‰
        commonFileListDTO: afterModelValue.value, // ç”Ÿäº§ä¸­
        commonFileListAfterDTO: issueModelValue.value, // ç”Ÿäº§åŽ
        commonFileListDTO: fileList.value,
        hasException: hasException.value,
        inspectionResult: hasException.value ? 0 : 1, // 0-异常,1-正常
        abnormalDescription: abnormalDescription.value,
@@ -498,7 +382,7 @@
  // æ‹ç…§/拍视频
  const chooseMedia = type => {
    if (getCurrentFiles().length >= uploadConfig.limit) {
    if (fileList.value.length >= uploadConfig.limit) {
      uni.showToast({
        title: `最多只能选择${uploadConfig.limit}个文件`,
        icon: "none",
@@ -506,7 +390,7 @@
      return;
    }
    const remaining = uploadConfig.limit - getCurrentFiles().length;
    const remaining = uploadConfig.limit - fileList.value.length;
    // ä¼˜å…ˆä½¿ç”¨ chooseMedia
    if (typeof uni.chooseMedia === "function") {
@@ -612,7 +496,7 @@
        Authorization: `Bearer ${token}`,
      },
      formData: {
        type: getTabType(),
        type: 10,
      },
      success: res => {
        try {
@@ -638,14 +522,7 @@
              status: "success",
            };
            // æ ¹æ®å½“前类型添加到对应数组
            if (currentUploadType.value === "before") {
              beforeModelValue.value.push(uploadedFile);
            } else if (currentUploadType.value === "after") {
              afterModelValue.value.push(uploadedFile);
            } else if (currentUploadType.value === "issue") {
              issueModelValue.value.push(uploadedFile);
            }
            fileList.value.push(uploadedFile);
            uni.showToast({ title: "上传成功", icon: "success" });
          } else {
@@ -670,23 +547,9 @@
    });
  };
  // èŽ·å–type值
  const getTabType = () => {
    switch (currentUploadType.value) {
      case "before":
        return 10;
      case "after":
        return 11;
      case "issue":
        return 12;
      default:
        return 10;
    }
  };
  // åˆ é™¤æ–‡ä»¶
  const removeFile = index => {
    const files = getCurrentFiles();
    const files = fileList.value;
    files.splice(index, 1);
  };
</script>
@@ -810,30 +673,6 @@
    outline: none;
    border-color: #409eff;
    background: #fff;
  }
  /* åˆ†ç±»æ ‡ç­¾é¡µ */
  .upload-tabs {
    display: flex;
    gap: 10px;
    margin-bottom: 15px;
  }
  .tab-item {
    flex: 1;
    padding: 10px;
    text-align: center;
    background: #f5f5f5;
    border-radius: 6px;
    font-size: 13px;
    color: #666;
    cursor: pointer;
    transition: all 0.3s;
  }
  .tab-item.active {
    background: #409eff;
    color: #fff;
  }
  /* ä¸Šä¼ åŒºåŸŸ */
src/pages/inventoryManagement/environmentalMonitoring/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,376 @@
<template>
  <view class="environment-page">
    <PageHeader title="环境监测" @back="goBack" />
    <scroll-view scroll-y class="page-scroll">
      <view class="summary-card">
        <view class="summary-header">
          <text class="summary-title">实时环境概览</text>
          <text class="summary-desc">每15秒自动刷新一次设备环境数据</text>
        </view>
        <view v-if="deviceCards.length" class="summary-grid">
          <view class="summary-item">
            <text class="summary-label">设备数量</text>
            <text class="summary-value">{{ deviceCards.length }}</text>
          </view>
          <view class="summary-item">
            <text class="summary-label">平均温度</text>
            <text class="summary-value">{{ averageMetrics.temperature }}</text>
          </view>
          <view class="summary-item">
            <text class="summary-label">平均湿度</text>
            <text class="summary-value">{{ averageMetrics.humidity }}</text>
          </view>
          <view class="summary-item">
            <text class="summary-label">平均光照</text>
            <text class="summary-value">{{ averageMetrics.light }}</text>
          </view>
        </view>
        <view v-else class="empty-block">
          <text class="empty-text">暂无环境数据</text>
        </view>
      </view>
      <view class="section-block">
        <view class="section-header">
          <text class="section-title">设备监测列表</text>
        </view>
        <view v-if="deviceCards.length" class="device-list">
          <view
            v-for="item in deviceCards"
            :key="item.name"
            class="device-card"
          >
            <view class="device-card__header">
              <view class="device-base">
                <text class="device-name">{{ item.name }}</text>
                <text class="device-meta">设备编码:{{ item.guid || "--" }}</text>
                <text class="device-meta">存放位置:{{ item.storageLocation || "--" }}</text>
              </view>
              <text class="device-tag">在线监测</text>
            </view>
            <view class="metrics-grid">
              <view
                v-for="metric in metricConfig"
                :key="metric.key"
                class="metric-item"
              >
                <text class="metric-label">{{ metric.label }}</text>
                <text class="metric-value" :style="{ color: metric.color }">
                  {{ item[metric.key] }}
                </text>
              </view>
            </view>
          </view>
        </view>
        <view v-else class="empty-block">
          <text class="empty-text">暂无设备监测信息</text>
        </view>
      </view>
    </scroll-view>
  </view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onHide, onShow, onUnload } from "@dcloudio/uni-app";
import PageHeader from "@/components/PageHeader.vue";
import { getEnvironmentalRealData } from "@/api/inventoryManagement/environmentalMonitoring";
const POLL_INTERVAL = 17000;
const TEMP_MARK_1 = "\u2103";
const TEMP_MARK_2 = "\u00B0C";
const TEMP_UNIT = TEMP_MARK_2;
const latestDevices = ref([]);
let pollTimer = null;
const metricConfig = [
  { key: "temperature", label: "温度", color: "#ff7a59", unit: TEMP_UNIT },
  { key: "humidity", label: "湿度", color: "#1ea7fd", unit: "%RH" },
  { key: "co2", label: "CO2", color: "#12c48b", unit: "ppm" },
  { key: "light", label: "光照", color: "#8b5cf6", unit: "Lux" },
];
const extractNumericValue = (rawValue) => {
  const matched = String(rawValue ?? "").match(/-?\d+(\.\d+)?/);
  return matched ? Number(matched[0]) : 0;
};
const normalizeMetricObject = (source, index) => {
  const normalized = {
    name: source?.deviceName || source?.name || source?.deviceNo || `设备${index + 1}`,
    guid: source?.guid || source?.deviceGuid || source?.id || "",
    deviceCode: source?.deviceCode || source?.deviceNo || source?.code || "",
    storageLocation: source?.storageLocation || "",
    temperature: 0,
    humidity: 0,
    co2: 0,
    light: 0,
  };
  Object.entries(source || {}).forEach(([key, value]) => {
    const rawText = String(value ?? "");
    if (rawText.includes(TEMP_MARK_1) || rawText.includes(TEMP_MARK_2)) {
      normalized.temperature = extractNumericValue(rawText);
      return;
    }
    if (rawText.includes("%RH")) {
      normalized.humidity = extractNumericValue(rawText);
      return;
    }
    if (rawText.includes("ppm")) {
      normalized.co2 = extractNumericValue(rawText);
      return;
    }
    if (rawText.includes("Lux")) {
      normalized.light = extractNumericValue(rawText);
      return;
    }
    if (key === "temperature") {
      normalized.temperature = extractNumericValue(rawText);
    } else if (key === "humidity") {
      normalized.humidity = extractNumericValue(rawText);
    } else if (key === "co2") {
      normalized.co2 = extractNumericValue(rawText);
    } else if (key === "light") {
      normalized.light = extractNumericValue(rawText);
    }
  });
  return normalized;
};
const formatMetric = (value, unit) => `${Number(value || 0).toFixed(2)}${unit}`;
const deviceCards = computed(() =>
  latestDevices.value.map((item) => ({
    name: item.name,
    guid: item.guid,
    deviceCode: item.deviceCode,
    storageLocation: item.storageLocation,
    temperature: formatMetric(item.temperature, TEMP_UNIT),
    humidity: formatMetric(item.humidity, "%RH"),
    co2: formatMetric(item.co2, "ppm"),
    light: formatMetric(item.light, "Lux"),
  }))
);
const averageMetrics = computed(() => {
  if (!latestDevices.value.length) {
    return {
      temperature: `0.00${TEMP_UNIT}`,
      humidity: "0.00%RH",
      light: "0.00Lux",
    };
  }
  const total = latestDevices.value.reduce(
    (result, item) => {
      result.temperature += Number(item.temperature || 0);
      result.humidity += Number(item.humidity || 0);
      result.light += Number(item.light || 0);
      return result;
    },
    { temperature: 0, humidity: 0, light: 0 }
  );
  const count = latestDevices.value.length;
  return {
    temperature: formatMetric(total.temperature / count, TEMP_UNIT),
    humidity: formatMetric(total.humidity / count, "%RH"),
    light: formatMetric(total.light / count, "Lux"),
  };
});
const fetchRealData = async () => {
  try {
    const res = await getEnvironmentalRealData();
    const dataList = Array.isArray(res?.data) ? res.data : [];
    latestDevices.value = dataList.map((item, index) => normalizeMetricObject(item, index));
  } catch (error) {
    latestDevices.value = [];
  }
};
const clearPolling = () => {
  if (pollTimer) {
    clearInterval(pollTimer);
    pollTimer = null;
  }
};
const startPolling = () => {
  clearPolling();
  fetchRealData();
  pollTimer = setInterval(fetchRealData, POLL_INTERVAL);
};
const goBack = () => {
  uni.navigateBack();
};
onShow(() => {
  startPolling();
});
onHide(() => {
  clearPolling();
});
onUnload(() => {
  clearPolling();
});
</script>
<style scoped lang="scss">
.environment-page {
  min-height: 100vh;
  background: #f4f7fb;
}
.page-scroll {
  height: calc(100vh - 88rpx);
  padding: 24rpx;
  box-sizing: border-box;
}
.summary-card,
.section-block {
  background: #ffffff;
  border-radius: 24rpx;
  padding: 28rpx;
  box-shadow: 0 8rpx 24rpx rgba(31, 54, 88, 0.06);
}
.section-block {
  margin-top: 24rpx;
}
.summary-header,
.section-header {
  margin-bottom: 24rpx;
}
.summary-title,
.section-title {
  display: block;
  font-size: 32rpx;
  font-weight: 600;
  color: #1d344f;
}
.summary-desc {
  display: block;
  margin-top: 12rpx;
  font-size: 24rpx;
  color: #7b8aa0;
}
.summary-grid,
.metrics-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 20rpx;
}
.summary-item {
  width: calc(50% - 10rpx);
  padding: 24rpx;
  border-radius: 20rpx;
  background: linear-gradient(135deg, #f8fbff 0%, #eef5ff 100%);
  box-sizing: border-box;
}
.summary-label,
.metric-label {
  display: block;
  font-size: 24rpx;
  color: #7b8aa0;
}
.summary-value,
.metric-value {
  display: block;
  margin-top: 14rpx;
  font-size: 30rpx;
  font-weight: 600;
  color: #1d344f;
}
.device-list {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}
.device-card {
  padding: 24rpx;
  border-radius: 20rpx;
  background: #f8fbff;
}
.device-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 20rpx;
  gap: 20rpx;
}
.device-base {
  min-width: 0;
  flex: 1;
}
.device-name {
  display: block;
  font-size: 30rpx;
  font-weight: 600;
  color: #1d344f;
}
.device-meta {
  display: block;
  margin-top: 8rpx;
  font-size: 22rpx;
  color: #7b8aa0;
  word-break: break-all;
}
.device-tag {
  flex-shrink: 0;
  padding: 8rpx 16rpx;
  border-radius: 999rpx;
  background: rgba(18, 196, 139, 0.12);
  font-size: 22rpx;
  color: #12c48b;
}
.metric-item {
  width: calc(50% - 10rpx);
  padding: 20rpx;
  border-radius: 16rpx;
  background: #ffffff;
  box-sizing: border-box;
}
.empty-block {
  padding: 60rpx 0;
  text-align: center;
}
.empty-text {
  font-size: 26rpx;
  color: #98a6b9;
}
</style>
src/pages/oa/ReimburseManage/reimburse-form/useFinReimburseForm.js
@@ -34,7 +34,14 @@
  getTravelStandardByTier,
} from "../_utils/travelReimburseUtils.js";
const userStore = useUserStore();
// å»¶è¿Ÿåˆå§‹åŒ– userStore,避免在模块加载时调用
let userStore = null;
function getUserStore() {
  if (!userStore) {
    userStore = useUserStore();
  }
  return userStore;
}
function buildOverBudgetWarnings(f, detailTotal, hotelLimit, transportLimit, mealLimit) {
  const warnings = [];
@@ -198,14 +205,15 @@
  /** æ–°å¢žæ—¶é»˜è®¤å¸¦å‡ºå½“前登录人,减少选人步骤 */
  function tryApplyCurrentUser() {
    if (modeRef.value === "edit" || form.applicantId) return;
    const id = userStore.id;
    const store = getUserStore();
    const id = store.id;
    if (!id) return;
    let u = userById(id);
    if (!u) {
      u = {
        userId: id,
        nickName: userStore.nickName,
        userName: userStore.name,
        nickName: store.nickName,
        userName: store.name,
      };
    }
    fillApplicantFromUser(u);
src/pages/procurementManagement/paymentLedger/detail.vue
@@ -3,27 +3,7 @@
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="供应商往来详情"
                @back="goBack" />
    <!-- ç»Ÿè®¡ä¿¡æ¯ -->
    <view class="summary-info"
          v-if="tableData.length > 0">
      <view class="summary-item">
        <text class="summary-label">总记录数</text>
        <text class="summary-value">{{ tableData.length }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">开票总金额</text>
        <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">回款总金额</text>
        <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">应收总金额</text>
        <text class="summary-value danger">{{ formatAmount(unReceiptTotal) }}</text>
      </view>
    </view>
    <!-- å›žæ¬¾è®°å½•明细列表 -->
    <!-- é‡‡è´­å°è´¦æ˜Žç»† -->
    <view class="detail-list"
          v-if="tableData.length > 0">
      <view v-for="(item, index) in tableData"
@@ -36,98 +16,98 @@
                       size="16"
                       color="#ffffff"></up-icon>
            </view>
            <text class="item-index">{{ index + 1 }}</text>
            <text class="item-index">{{ item.purchaseContractNumber }}</text>
          </view>
          <view class="item-date">{{ item.happenTime }}</view>
          <view class="item-status">
            <text class="status-tag"
                  :class="getReceiptStatusClass(item.status)">{{ receiptStatusText[item.status] || '未知状态' }}</text>
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">发票金额(元)</text>
            <text class="detail-value">{{ formatAmount(item.invoiceAmount) }}</text>
            <text class="detail-label">合同签订日期</text>
            <text class="detail-value">{{ item.executionDate }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">付款金额(元)</text>
            <text class="detail-value highlight">{{ formatAmount(item.currentPaymentAmount) }}</text>
            <text class="detail-label">项目名称</text>
            <text class="detail-value">{{ item.projectName }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">应付金额(元)</text>
            <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">发生日期</text>
            <text class="detail-value">{{ item.paymentDate }}</text>
            <text class="detail-label">合同金额(元)</text>
            <text class="detail-value">{{ formatAmount(item.contractAmount) }}</text>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无回款记录</text>
      <text>暂无采购台账记录</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, computed, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import {
    paymentLedgerList,
    paymentRecordList,
  } from "@/api/procurementManagement/paymentLedger";
  import { ref, onMounted } from "vue";
  import { gePurchaseListPage } from "@/api/procurementManagement/invoiceEntry.js";
  // å®¢æˆ·ä¿¡æ¯
  const supplierId = ref("");
  const supplierName = ref("");
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  const invoiceTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.invoiceAmount) || 0);
    }, 0);
  });
  // æ”¶è´§çŠ¶æ€æ–‡æœ¬æ˜ å°„
  const receiptStatusText = {
    1: '待收货',
    2: '收货中',
    3: '已收货'
  };
  const receiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.receiptAmount) || 0);
    }, 0);
  });
  const unReceiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.unReceiptAmount) || 0);
    }, 0);
  });
  // æ”¶è´§çŠ¶æ€æ ‡ç­¾æ ·å¼
  const getReceiptStatusClass = (status) => {
    const classMap = {
      1: 'status-info',
      2: 'status-warning',
      3: 'status-success'
    };
    return classMap[status] || 'status-info';
  };
  // è¿”回上一页
  const goBack = () => {
    uni.removeStorageSync("supplierId");
    uni.removeStorageSync("supplierName");
    uni.navigateBack();
  };
  // èŽ·å–é¡µé¢å‚æ•°
  const getPageParams = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–ä¾›åº”å•†ID
    const storedSupplierId = uni.getStorageSync("supplierId");
    if (storedSupplierId) {
      supplierId.value = storedSupplierId;
    const storedSupplierName = uni.getStorageSync("supplierName");
    if (storedSupplierName) {
      supplierName.value = storedSupplierName;
    }
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    if (!supplierId.value) {
    if (!supplierName.value) {
      uni.showToast({
        title: "客户信息缺失",
        title: "供应商信息缺失",
        icon: "error",
      });
      return;
    }
    showLoadingToast("加载中...");
    paymentRecordList({ supplierId: supplierId.value })
    gePurchaseListPage({ supplierName: supplierName.value, current: 1, size: 1000 })
      .then(res => {
        tableData.value = res.data;
        let orders = [];
        if (res.data) {
          if (Array.isArray(res.data)) {
            orders = res.data;
          } else if (res.data.records && Array.isArray(res.data.records)) {
            orders = res.data.records;
          }
        }
        tableData.value = orders;
        closeToast();
      })
      .catch(() => {
@@ -173,46 +153,6 @@
  .u-divider {
    margin: 0 !important;
  }
  .summary-info {
    background: #ffffff;
    margin: 20px 20px 0 20px;
    border-radius: 12px;
    padding: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .summary-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .summary-label {
    font-size: 14px;
    color: #666;
  }
  .summary-value {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
  .summary-value.highlight {
    color: #2979ff;
    font-weight: 600;
  }
  .summary-value.danger {
    color: #ff4757;
    font-weight: 600;
  }
  .detail-list {
@@ -262,6 +202,32 @@
    color: #666;
  }
  .item-status {
    display: flex;
    align-items: center;
  }
  .status-tag {
    font-size: 12px;
    padding: 2px 8px;
    border-radius: 4px;
  }
  .status-success {
    color: #67c23a;
    background: rgba(103, 194, 58, 0.1);
  }
  .status-warning {
    color: #e6a23c;
    background: rgba(230, 162, 60, 0.1);
  }
  .status-info {
    color: #909399;
    background: rgba(144, 147, 153, 0.1);
  }
  .item-details {
    padding: 16px 0;
  }
@@ -289,16 +255,6 @@
    text-align: right;
    flex: 1;
    margin-left: 16px;
  }
  .detail-value.highlight {
    color: #2979ff;
    font-weight: 500;
  }
  .detail-value.danger {
    color: #ff4757;
    font-weight: 500;
  }
  .no-data {
src/pages/procurementManagement/paymentLedger/index.vue
@@ -45,15 +45,7 @@
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">合同金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.invoiceAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">付款金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.paymentAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">应付金额(元)</text>
              <text class="detail-value highlight danger">{{ formattedNumber(item.payableAmount) }}</text>
              <text class="detail-value">{{ formattedNumber(item.contractAmounts) }}</text>
            </view>
          </view>
        </view>
@@ -132,7 +124,7 @@
const rowClickMethod = (row) => {
  // ä½¿ç”¨ uni.setStorageSync å­˜å‚¨ä¾›åº”商信息
  uni.setStorageSync('supplierId', row.supplierId);
  uni.setStorageSync('supplierName', row.supplierName);
  // è·³è½¬åˆ°å›žæ¬¾è®°å½•明细页面
  uni.navigateTo({
    url: '/pages/procurementManagement/paymentLedger/detail'
src/pages/qualityManagement/finalInspection/index.vue
@@ -121,7 +121,7 @@
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState"
                      :disabled="!!item.inspectState"
                      @click.stop="startInspection(item)">
              ç¼–辑
            </u-button>
@@ -134,7 +134,7 @@
            <u-button type="success"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState"
                      :disabled="!!item.inspectState"
                      @click.stop="submitInspection(item)">
              æäº¤
            </u-button>
@@ -149,7 +149,7 @@
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState || item.checkName !== ''"
                      :disabled="!!item.inspectState || item.checkName !== ''"
                      @click.stop="assignInspector(item)">
              åˆ†é…æ£€éªŒå‘˜
            </u-button>
src/pages/qualityManagement/materialInspection/index.vue
@@ -1,7 +1,7 @@
<template>
  <view class="material-inspection-page">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="原材料检验"
    <PageHeader title="原料检验"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
@@ -121,7 +121,7 @@
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState"
                      :disabled="!!item.inspectState"
                      @click.stop="startInspection(item)">
              ç¼–辑
            </u-button>
@@ -134,7 +134,7 @@
            <u-button type="success"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState"
                      :disabled="!!item.inspectState"
                      @click.stop="submitInspection(item)">
              æäº¤
            </u-button>
@@ -149,7 +149,7 @@
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState || item.checkName !== ''"
                      :disabled="!!item.inspectState || item.checkName !== ''"
                      @click.stop="assignInspector(item)">
              åˆ†é…æ£€éªŒå‘˜
            </u-button>
src/pages/qualityManagement/processInspection/index.vue
@@ -121,7 +121,7 @@
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState"
                      :disabled="!!item.inspectState"
                      @click.stop="startInspection(item)">
              ç¼–辑
            </u-button>
@@ -134,7 +134,7 @@
            <u-button type="success"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState"
                      :disabled="!!item.inspectState"
                      @click.stop="submitInspection(item)">
              æäº¤
            </u-button>
@@ -149,7 +149,7 @@
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.inspectState || item.checkName !== ''"
                      :disabled="!!item.inspectState || item.checkName !== ''"
                      @click.stop="assignInspector(item)">
              åˆ†é…æ£€éªŒå‘˜
            </u-button>
src/pages/sales/receiptPaymentLedger/detail.vue
@@ -3,27 +3,7 @@
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="客户往来详情"
                @back="goBack" />
    <!-- ç»Ÿè®¡ä¿¡æ¯ -->
    <view class="summary-info"
          v-if="tableData.length > 0">
      <view class="summary-item">
        <text class="summary-label">总记录数</text>
        <text class="summary-value">{{ tableData.length }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">开票总金额</text>
        <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">回款总金额</text>
        <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">应收总金额</text>
        <text class="summary-value danger">{{ formatAmount(unReceiptTotal) }}</text>
      </view>
    </view>
    <!-- å›žæ¬¾è®°å½•明细列表 -->
    <!-- å‘货台账明细 -->
    <view class="detail-list"
          v-if="tableData.length > 0">
      <view v-for="(item, index) in tableData"
@@ -36,66 +16,59 @@
                       size="16"
                       color="#ffffff"></up-icon>
            </view>
            <text class="item-index">{{ index + 1 }}</text>
            <text class="item-index">{{ item.salesContractNo }}</text>
          </view>
          <view class="item-date">{{ item.happenTime }}</view>
          <view class="item-status">
            <text class="status-tag"
                  :class="getStatusClass(item.status)">{{ item.status || '--' }}</text>
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">开票金额(元)</text>
            <text class="detail-value">{{ formatAmount(item.invoiceTotal) }}</text>
            <text class="detail-label">客户名称</text>
            <text class="detail-value">{{ item.customerName }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">回款金额(元)</text>
            <text class="detail-value highlight">{{ formatAmount(item.receiptPaymentAmount) }}</text>
            <text class="detail-label">产品名称</text>
            <text class="detail-value">{{ item.productName }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">应收金额(元)</text>
            <text class="detail-value danger">{{ formatAmount(item.unReceiptPaymentAmount) }}</text>
            <text class="detail-label">规格型号</text>
            <text class="detail-value">{{ item.specificationModel }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">发生日期</text>
            <text class="detail-value">{{ item.receiptPaymentDate }}</text>
            <text class="detail-label">发货时间</text>
            <text class="detail-value">{{ item.shippingDate }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">发货数量</text>
            <text class="detail-value">{{ item.totalQuantity }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">出库单号</text>
            <text class="detail-value">{{ item.outboundBatches }}</text>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无回款记录</text>
      <text>暂无发货记录</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, computed, onMounted } from "vue";
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { customerInteractions } from "@/api/salesManagement/receiptPayment.js";
  import { deliveryLedgerListPage } from "@/api/salesManagement/deliveryLedger.js";
  // å®¢æˆ·ä¿¡æ¯
  const customerId = ref("");
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  const invoiceTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.invoiceTotal) || 0);
    }, 0);
  });
  const receiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.receiptPaymentAmount) || 0);
    }, 0);
  });
  const unReceiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.unReceiptPaymentAmount) || 0);
    }, 0);
  });
  // è¿”回上一页
  const goBack = () => {
@@ -105,7 +78,6 @@
  // èŽ·å–é¡µé¢å‚æ•°
  const getPageParams = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–å®¢æˆ·ID
    const storedCustomerId = uni.getStorageSync("customerId");
    if (storedCustomerId) {
      customerId.value = storedCustomerId;
@@ -123,15 +95,21 @@
    }
    showLoadingToast("加载中...");
    const param = {
    deliveryLedgerListPage({
      customerId: customerId.value,
      current: -1,
      size: -1,
    };
    customerInteractions(param)
      current: 1,
      size: 1000,
    })
      .then(res => {
        tableData.value = res.data;
        let orders = [];
        if (res.data) {
          if (Array.isArray(res.data)) {
            orders = res.data;
          } else if (res.data.records && Array.isArray(res.data.records)) {
            orders = res.data.records;
          }
        }
        tableData.value = orders;
        closeToast();
      })
      .catch(() => {
@@ -143,9 +121,15 @@
      });
  };
  // æ ¼å¼åŒ–金额
  const formatAmount = amount => {
    return amount ? parseFloat(amount).toFixed(2) : "0.00";
  // å‘货状态样式
  const getStatusClass = (status) => {
    const statusMap = {
      '已发货': 'status-success',
      '审核通过': 'status-success',
      '审核中': 'status-warning',
      '审核不通过': 'status-danger',
    };
    return statusMap[status] || 'status-info';
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
@@ -183,46 +167,6 @@
  .u-divider {
    margin: 0 !important;
  }
  .summary-info {
    background: #ffffff;
    margin: 20px 20px 0 20px;
    border-radius: 12px;
    padding: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  .summary-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .summary-label {
    font-size: 14px;
    color: #666;
  }
  .summary-value {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
  .summary-value.highlight {
    color: #2979ff;
    font-weight: 600;
  }
  .summary-value.danger {
    color: #ff4757;
    font-weight: 600;
  }
  .detail-list {
@@ -272,6 +216,37 @@
    color: #666;
  }
  .item-status {
    display: flex;
    align-items: center;
  }
  .status-tag {
    font-size: 12px;
    padding: 2px 8px;
    border-radius: 4px;
  }
  .status-success {
    color: #67c23a;
    background: rgba(103, 194, 58, 0.1);
  }
  .status-warning {
    color: #e6a23c;
    background: rgba(230, 162, 60, 0.1);
  }
  .status-danger {
    color: #f56c6c;
    background: rgba(245, 108, 108, 0.1);
  }
  .status-info {
    color: #909399;
    background: rgba(144, 147, 153, 0.1);
  }
  .item-details {
    padding: 16px 0;
  }
@@ -301,19 +276,10 @@
    margin-left: 16px;
  }
  .detail-value.highlight {
    color: #2979ff;
    font-weight: 500;
  }
  .detail-value.danger {
    color: #ff4757;
    font-weight: 500;
  }
  .no-data {
    padding: 40px 0;
    text-align: center;
    color: #999;
  }
</style>
src/pages/sales/receiptPaymentLedger/index.vue
@@ -47,16 +47,8 @@
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">开票金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.invoiceTotal) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">回款金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.receiptPaymentAmount) }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">应收金额(元)</text>
              <text class="detail-value highlight danger">{{ formattedNumber(item.unReceiptPaymentAmount) }}</text>
              <text class="detail-label">合同金额(元)</text>
              <text class="detail-value">{{ formattedNumber(item.contractAmounts) }}</text>
            </view>
          </view>
        </view>
@@ -72,7 +64,7 @@
<script setup>
  import { onMounted, ref, reactive, toRefs } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger";
  import { customewTransactions } from "@/api/salesManagement/indicatorStats.js";
  const tableData = ref([]);
@@ -106,7 +98,7 @@
  const getList = () => {
    showLoadingToast("加载中...");
    invoiceLedgerSalesAccount({ ...searchForm.value, ...page })
    customewTransactions({ ...searchForm.value, ...page })
      .then(res => {
        tableData.value = res.data.records;
        closeToast();
@@ -139,7 +131,7 @@
  const rowClickMethod = row => {
    // ä½¿ç”¨ uni.setStorageSync å­˜å‚¨å®¢æˆ·ä¿¡æ¯
    uni.setStorageSync("customerId", row.id);
    uni.setStorageSync("customerId", row.customerId);
    // è·³è½¬åˆ°å›žæ¬¾è®°å½•明细页面
    uni.navigateTo({
      url: "/pages/sales/receiptPaymentLedger/detail",
src/pages/works.vue
@@ -328,14 +328,17 @@
<script setup>
  import { ref, onMounted, nextTick, reactive, computed } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { userLoginFacotryList } from "@/api/login";
  import { getProductWorkOrderById } from "@/api/productionManagement/productionReporting";
  import { createVersionUpgradeChecker } from "@/utils/versionUpgrade";
  import DownloadProgressMask from "@/components/DownloadProgressMask.vue";
  import { OA_WORKBENCH_ITEMS } from "@/config/oaWorkbench.js";
  import modal from "@/plugins/modal";
  import useUserStore from "@/store/modules/user";
  const userStore = useUserStore();
  const { triggerVersionCheck } = createVersionUpgradeChecker({ logPrefix: "[version-works]" });
  const show = ref(false);
  const factoryList = ref([]);
  const factoryListTem = ref([]);
@@ -495,6 +498,10 @@
      icon: "/static/images/icon/kucunguanli.svg",
      label: "库存管理",
    },
    {
      icon: "/static/images/icon/weixianzuoye.svg",
      label: "环境检测",
    },
  ]);
  const humanResourcesItems = reactive([
@@ -510,11 +517,26 @@
      icon: "/static/images/icon/hetongguanli.svg",
      label: "合同管理",
    },
    {
      icon: "/static/images/icon/hetongguanli.svg",
      label: "转正申请",
      path: "/pages/oa/HrManage/regular-apply/index",
    },
    {
      icon: "/static/images/icon/renyuanxinzi.svg",
      label: "调岗申请",
      path: "/pages/oa/HrManage/transfer-apply/index",
    },
    {
      icon: "/static/images/icon/gongchuguanli.svg",
      label: "工作交接",
      path: "/pages/oa/HrManage/work-handover/index",
    },
  ]);
  const qualityItems = reactive([
    {
      icon: "/static/images/icon/yuancailiaojianyan.svg",
      label: "原材料检验",
      label: "原料检验",
    },
    {
      icon: "/static/images/icon/guochengjianyan.svg",
@@ -593,6 +615,36 @@
    {
      icon: "/static/images/icon/kehubaifang.svg",
      label: "客户拜访",
    },
    {
      icon: "/static/images/icon/qingjiaguanli.svg",
      label: "请假申请",
      path: "/pages/oa/AttendManage/leave-apply/index",
    },
    {
      icon: "/static/images/icon/dakaqiandao.svg",
      label: "加班申请",
      path: "/pages/oa/AttendManage/overtime-apply/index",
    },
    {
      icon: "/static/images/icon/chuchaiguanli.svg",
      label: "差旅报销",
      path: "/pages/oa/ReimburseManage/travel-reimburse/index",
    },
    {
      icon: "/static/images/icon/baoxiaoguanli.svg",
      label: "费用报销",
      path: "/pages/oa/ReimburseManage/cost-reimburse/index",
    },
    {
      icon: "/static/images/icon/xietongshenpi.svg",
      label: "审批列表",
      path: "/pages/oa/ApproveManage/approve-list/index",
    },
    {
      icon: "/static/images/icon/guizhangzhidu.svg",
      label: "审批模板",
      path: "/pages/oa/ApproveManage/approve-template/index",
    },
  ]);
@@ -1018,7 +1070,7 @@
          url: "/pages/humanResources/contractManagement/index",
        });
        break;
      case "原材料检验":
      case "原料检验":
        uni.navigateTo({
          url: "/pages/qualityManagement/materialInspection/index",
        });
@@ -1038,6 +1090,11 @@
          url: "/pages/inventoryManagement/stockManagement/index",
        });
        break;
      case "环境检测":
        uni.navigateTo({
          url: "/pages/inventoryManagement/environmentalMonitoring/index",
        });
        break;
      case "反馈登记":
        uni.navigateTo({
          url: "/pages/customerService/feedbackRegistration/index",
src/store/modules/user.ts
@@ -23,10 +23,9 @@
    currentLoginTime: "",
    roles: Array(),
    permissions: [],
    routers: [], // è·¯ç”±æƒé™æ•°æ®
    routers: [],
  }),
  actions: {
    // éƒ¨é—¨ç™»å½•
    loginCheckFactory(userInfo: any) {
      const userName = userInfo.userName.trim();
      const password = userInfo.password;
@@ -34,7 +33,7 @@
      return new Promise((resolve, reject) => {
        loginCheckFactory(userName, password, factoryId)
          .then((res: any) => {
            const token = res.token || res.data?.token;
            const token = res.token || (res.data && res.data.token);
            if (token) {
              setToken(token);
              this.token = token;
@@ -48,7 +47,6 @@
          });
      });
    },
    // èŽ·å–ç”¨æˆ·ä¿¡æ¯
    getInfo() {
      return new Promise((resolve, reject) => {
        getInfo()
@@ -80,7 +78,6 @@
          });
      });
    },
    // é€€å‡ºç³»ç»Ÿ
    logOut() {
      return new Promise<null>((resolve, reject) => {
        logout()
@@ -109,7 +106,6 @@
      const seconds = String(now.getSeconds()).padStart(2, "0"); // ç§’数补零
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    // èŽ·å–è·¯ç”±æƒé™
    getRouters() {
      return new Promise((resolve, reject) => {
        getRoutersApi()
src/utils/versionUpgrade.js
@@ -26,12 +26,12 @@
}
async function getCurrentVersion(logPrefix) {
  let currentVersion = config?.appInfo?.version || "1.0.0";
  let currentVersion = (config && config.appInfo && config.appInfo.version) || "1.0.0";
  console.log(`${logPrefix} å¼€å§‹èŽ·å–å½“å‰ç‰ˆæœ¬ï¼Œé»˜è®¤å€¼:`, currentVersion);
  // #ifdef MP-WEIXIN
  try {
    const accountInfo = uni.getAccountInfoSync();
    if (accountInfo?.miniProgram?.version) {
    if (accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.version) {
      currentVersion = accountInfo.miniProgram.version;
      console.log(`${logPrefix} å½“前环境=MP-WEIXIN,版本=`, currentVersion);
      return currentVersion;
@@ -45,14 +45,14 @@
    // APP-PLUS ä¸‹ï¼Œplus.runtime.version ä¸æ˜¯ä¸šåŠ¡ç‰ˆæœ¬å·ï¼ˆç»å¸¸æ˜¯è¿è¡Œæ—¶/SDK版本),
    // è¿™é‡Œæ”¹ç”¨ getProperty å–系统层面的 app version。
    // @ts-ignore
    if (plus?.runtime?.getProperty) {
    if (plus && plus.runtime && plus.runtime.getProperty) {
      // @ts-ignore
      const appid = plus.runtime.appid;
      const appInfo = await new Promise((resolve) => {
        // @ts-ignore
        plus.runtime.getProperty(appid, (info) => resolve(info || {}));
      });
      const v = appInfo?.version || appInfo?.versionName || appInfo?.appVersion || "";
      const v = (appInfo && appInfo.version) || (appInfo && appInfo.versionName) || (appInfo && appInfo.appVersion) || "";
      if (v) {
        currentVersion = String(v);
        console.log(`${logPrefix} å½“前环境=APP-PLUS,版本=`, currentVersion);
@@ -90,7 +90,7 @@
    },
    (err) => {
      console.log(`${logPrefix} å®‰è£…失败:`, err);
      uni.showToast({ title: err?.message || "安装更新包失败", icon: "none" });
      uni.showToast({ title: (err && err.message) || "安装更新包失败", icon: "none" });
    }
  );
  // #endif
@@ -101,7 +101,7 @@
  if (!u) return "";
  // å·²ç»æ˜¯ç»å¯¹åœ°å€ï¼Œç›´æŽ¥è¿”回
  if (/^https?:\/\//i.test(u)) return u;
  const base = String(config?.fileUrl || config?.baseUrl || "").replace(/\/+$/, "");
  const base = String((config && config.fileUrl) || (config && config.baseUrl) || "").replace(/\/+$/, "");
  const path = u.startsWith("/") ? u : `/${u}`;
  return `${base}${path}`;
}
vite.config.js
@@ -7,6 +7,10 @@
        build: {
            minify: true,
            outDir: 'dist',
            target: 'es2015',
        },
        esbuild: {
            target: 'es2015',
        },
        server: {
            port: '80'
@@ -20,9 +24,14 @@
        css: {
            preprocessorOptions: {
                scss: {
                    api: 'modern-compiler'
                    api: 'legacy'
                }
            }
        }
        },
        optimizeDeps: {
            esbuildOptions: {
                target: 'es2015',
            },
        },
    }
})
})