已添加3个文件
已修改13个文件
8620 ■■■■■ 文件已修改
src/api/equipmentManagement/upkeep.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementLedger.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salesLedger.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/fileList.vue 562 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 491 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/components/formDia.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/index.vue 546 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/detail.vue 517 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementLedger/detail.vue 2237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/procurementLedger/index.vue 384 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/detail.vue 1883 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/goOut.vue 657 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/index.vue 386 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/out.vue 407 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/sales/salesAccount/view.vue 337 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/upkeep.js
@@ -70,3 +70,30 @@
    method: "delete",
  });
};
// æŸ¥è¯¢ä¿å…»ä»»åŠ¡é™„ä»¶åˆ—è¡¨
export function listMaintenanceTaskFiles(query) {
  return request({
    url: "/maintenanceTaskFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žä¿å…»ä»»åС附件
export function addMaintenanceTaskFile(data) {
  return request({
    url: "/maintenanceTaskFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤ä¿å…»ä»»åС附件
export function delMaintenanceTaskFile(id) {
  return request({
    url: "/maintenanceTaskFile/del",
    method: "delete",
    data: Array.isArray(id) ? id : [id],
  });
}
src/api/procurementManagement/procurementLedger.js
@@ -72,3 +72,10 @@
    method: "get",
  });
}
export function approveProcessGetInfo(query) {
    return request({
        url: '/approveProcess/get',
        method: 'get',
        params: query,
    })
}
src/api/salesManagement/salesLedger.js
@@ -125,3 +125,13 @@
    params: query
  })
}
// æ–°å¢žå‘货信息
export function addShippingInfo(data) {
  return request({
    url: "/shippingInfo/add",
    method: "post",
    data,
  });
}
src/pages.json
@@ -58,6 +58,20 @@
      }
    },
    {
      "path": "pages/sales/salesAccount/out",
      "style": {
        "navigationBarTitleText": "发货状态",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/sales/salesAccount/goOut",
      "style": {
        "navigationBarTitleText": "发货",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/sales/salesAccount/detail",
      "style": {
        "navigationBarTitleText": "修改台账",
@@ -541,6 +555,13 @@
      }
    },
    {
      "path": "pages/equipmentManagement/upkeep/fileList",
      "style": {
        "navigationBarTitleText": "保养文件管理",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/equipmentManagement/inspection/index",
      "style": {
        "navigationBarTitleText": "设备巡检",
src/pages/equipmentManagement/upkeep/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,562 @@
<template>
  <view class="file-list-page">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <PageHeader title="附件管理"
                @back="goBack" />
    <!-- é™„件列表 -->
    <view class="file-list-container">
      <view v-if="fileList.length > 0"
            class="file-list">
        <view v-for="(file, index) in fileList"
              :key="file.id || index"
              class="file-item">
          <!-- æ–‡ä»¶å›¾æ ‡ -->
          <!-- <view class="file-icon"
                :class="getFileIconClass(file.fileType)">
            <up-icon :name="getFileIcon(file.fileType)"
                     size="24"
                     color="#ffffff" />
          </view> -->
          <!-- æ–‡ä»¶ä¿¡æ¯ -->
          <view class="file-info">
            <text class="file-name">{{ file.name }}</text>
            <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} Â· {{ file.uploadTime || file.createTime }}</text> -->
          </view>
          <!-- æ“ä½œæŒ‰é’® -->
          <view class="file-actions">
            <!-- <u-button size="small"
                      type="primary"
                      plain
                      @click="previewFile(file)">预览</u-button> -->
            <u-button size="small"
                      type="info"
                      plain
                      @click="downloadFile(file)">下载并预览</u-button>
            <u-button size="small"
                      type="error"
                      plain
                      @click="confirmDelete(file, index)">删除</u-button>
          </view>
        </view>
      </view>
      <!-- ç©ºçŠ¶æ€ -->
      <view v-else
            class="empty-state">
        <up-icon name="document"
                 size="64"
                 color="#c0c4cc" />
        <text class="empty-text">暂无附件</text>
      </view>
    </view>
    <!-- <a rel="nofollow"
       id="downloadLink"
       href="#"
       style="display:none;">下载文本文件</a> -->
    <!-- ä¸Šä¼ æŒ‰é’® -->
    <view class="upload-button"
          @click="chooseFile">
      <up-icon name="plus"
               size="24"
               color="#ffffff" />
      <text class="upload-text">上传附件</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import config from "@/config";
  import { getToken } from "@/utils/auth";
  // import { saveAs } from "file-saver";
  import {
    listMaintenanceTaskFiles,
    addMaintenanceTaskFile,
    delMaintenanceTaskFile,
  } from "@/api/equipmentManagement/upkeep";
  import { blobValidate } from "@/utils/ruoyi";
  // é™„件列表
  const fileList = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // const request = axios.create({
  //   baseURL: "URL.com",
  //   adapter: axiosAdapterUniapp,
  // });
  // èŽ·å–æ–‡ä»¶å›¾æ ‡
  const getFileIcon = fileType => {
    const iconMap = {
      doc: "document",
      docx: "document",
      xls: "grid",
      xlsx: "grid",
      pdf: "document",
      ppt: "copy",
      pptx: "copy",
      txt: "document",
      jpg: "image",
      jpeg: "image",
      png: "image",
      gif: "image",
      zip: "folder",
      rar: "folder",
    };
    return iconMap[fileType.toLowerCase()] || "document";
  };
  // èŽ·å–æ–‡ä»¶å›¾æ ‡æ ·å¼ç±»
  const getFileIconClass = fileType => {
    const colorMap = {
      doc: "blue",
      docx: "blue",
      xls: "green",
      xlsx: "green",
      pdf: "red",
      ppt: "orange",
      pptx: "orange",
      txt: "gray",
      jpg: "purple",
      jpeg: "purple",
      png: "purple",
      gif: "purple",
      zip: "yellow",
      rar: "yellow",
    };
    return colorMap[fileType.toLowerCase()] || "gray";
  };
  // æ ¼å¼åŒ–文件大小
  const formatFileSize = bytes => {
    if (bytes === 0) return "0 B";
    const k = 1024;
    const sizes = ["B", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  };
  // é€‰æ‹©æ–‡ä»¶
  const chooseFile = () => {
    uni.chooseImage({
      count: 9,
      sizeType: ["original", "compressed"],
      sourceType: ["album", "camera"],
      success: res => {
        console.log(res, "选择图片成功");
        uploadFiles(res.tempFiles);
      },
      fail: err => {
        console.error("选择图片失败:", err);
        showToast("选择文件失败");
      },
    });
    // uni.chooseFile({
    //   count: 9,
    //   extension: [
    //     ".doc",
    //     ".docx",
    //     ".xls",
    //     ".xlsx",
    //     ".pdf",
    //     ".ppt",
    //     ".pptx",
    //     ".txt",
    //     ".jpg",
    //     ".jpeg",
    //     ".png",
    //     ".gif",
    //     ".zip",
    //     ".rar",
    //   ],
    //   success: res => {
    //     console.log(res, "选择文件成功");
    //     uploadFiles(res.tempFiles);
    //   },
    //   fail: err => {
    //     showToast("选择文件失败");
    //   },
    // });
  };
  // ä¸Šä¼ æ–‡ä»¶
  const uploadFiles = tempFiles => {
    console.log(tempFiles, "上传文件1");
    tempFiles.forEach((tempFile, index) => {
      // æ˜¾ç¤ºä¸Šä¼ ä¸­æç¤º
      uni.showLoading({
        title: "上传中...",
        mask: true,
      });
      console.log(tempFile, "上传文件2");
      // 1. ç›´æŽ¥ä½¿ç”¨ uni.uploadFile ä¸Šä¼ æ–‡ä»¶
      uni.uploadFile({
        url: config.baseUrl + "/file/upload",
        filePath: tempFile.path,
        name: "file",
        header: {
          Authorization: "Bearer " + getToken(),
        },
        success: uploadRes => {
          uni.hideLoading();
          console.log(uploadRes, "上传文件3");
          try {
            const res = JSON.parse(uploadRes.data);
            console.log(res, "上传文件4");
            if (res.code === 200) {
              // 2. æå–文件信息
              const fileName = tempFile.name
                ? tempFile.name
                : tempFile.path.split("/").pop();
              // const fileType = fileName.split(".").pop();
              // 3. æž„造保存文件信息的参数
              const saveData = {
                name: fileName,
                deviceMaintenanceId: upkeepId.value,
                url: res.data.tempPath || "",
              };
              console.log(saveData, "保存文件信息参数");
              // 4. è°ƒç”¨ addRuleFile æŽ¥å£ä¿å­˜æ–‡ä»¶ä¿¡æ¯
              addMaintenanceTaskFile(saveData)
                .then(addRes => {
                  if (addRes.code === 200) {
                    // 5. æ·»åŠ åˆ°æ–‡ä»¶åˆ—è¡¨
                    const newFile = {
                      ...addRes.data,
                      uploadTime: new Date().toLocaleString(),
                    };
                    // fileList.value.push(newFile);
                    getFileList();
                    showToast("上传成功");
                  } else {
                    showToast("保存文件信息失败");
                  }
                })
                .catch(err => {
                  console.error("保存文件信息失败:", err);
                  showToast("保存文件信息失败");
                });
            } else {
              showToast("文件上传失败");
            }
          } catch (e) {
            console.error("解析上传结果失败:", e);
            showToast("上传失败");
          }
        },
        fail: err => {
          uni.hideLoading();
          console.error("上传失败:", err);
          showToast("上传失败");
        },
      });
    });
  };
  // ä¸‹è½½æ–‡ä»¶
  const downloadFile = file => {
    var url =
      config.baseUrl +
      "/common/download?fileName=" +
      encodeURIComponent(file.url) +
      "&delete=true";
    console.log(url, "url");
    uni
      .downloadFile({
        url: url,
        responseType: "blob",
        header: { Authorization: "Bearer " + getToken() },
      })
      .then(res => {
        let osType = uni.getStorageSync("deviceInfo").osName;
        let filePath = res.tempFilePath;
        if (osType === "ios") {
          uni.openDocument({
            filePath: filePath,
            showMenu: true,
            success: res => {},
            fail: err => {
              console.log("uni.openDocument--fail");
              reject(err);
            },
          });
        } else {
          uni.saveFile({
            tempFilePath: filePath,
            success: fileRes => {
              uni.showToast({
                icon: "none",
                mask: true,
                title:
                  "文件已保存:Android/data/uni.UNI720216F/apps/__UNI__720216F/" +
                  fileRes.savedFilePath, //保存路径
                duration: 3000,
              });
              setTimeout(() => {
                //打开文档查看
                uni.openDocument({
                  filePath: fileRes.savedFilePath,
                  success: function (res) {},
                });
              }, 1000);
            },
            fail: err => {
              console.log("uni.save--fail");
              reject(err);
            },
          });
        }
        // const isBlob = blobValidate(res.data);
        // if (isBlob) {
        //   const blob = new Blob([res.data], { type: "text/plain" });
        //   const url = URL.createObjectURL(blob);
        //   const downloadLink = document.getElementById("downloadLink");
        //   downloadLink.href = url;
        //   downloadLink.download = file.name;
        //   downloadLink.click();
        //   showToast("下载成功");
        // } else {
        //   showToast("下载失败");
        // }
      })
      .catch(err => {
        console.error("下载失败:", err);
        showToast("下载失败");
      });
  };
  // ç¡®è®¤åˆ é™¤
  const confirmDelete = (file, index) => {
    uni.showModal({
      title: "删除确认",
      content: `确定要删除附件 "${file.name}" å—?`,
      success: res => {
        if (res.confirm) {
          deleteFile(file.id, index);
        }
      },
    });
  };
  // åˆ é™¤æ–‡ä»¶
  const deleteFile = (fileId, index) => {
    uni.showLoading({
      title: "删除中...",
      mask: true,
    });
    delMaintenanceTaskFile([fileId])
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          // fileList.value.splice(index, 1);
          getFileList();
          showToast("删除成功");
        } else {
          showToast("删除失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("删除失败");
      });
  };
  // æ˜¾ç¤ºæç¤º
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  const rulesRegulationsManagementId = ref("");
  const upkeepId = ref("");
  // é¡µé¢åŠ è½½æ—¶
  onMounted(() => {
    // ä»Ž API èŽ·å–é™„ä»¶åˆ—è¡¨
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å– rulesRegulationsManagementId
    rulesRegulationsManagementId.value = uni.getStorageSync(
      "rulesRegulationsManagement"
    );
    upkeepId.value = uni.getStorageSync("upkeepId");
    getFileList();
  });
  // èŽ·å–é™„ä»¶åˆ—è¡¨
  const getFileList = () => {
    uni.showLoading({
      title: "加载中...",
      mask: true,
    });
    listMaintenanceTaskFiles({
      current: 1,
      size: 100,
      deviceMaintenanceId: upkeepId.value,
      rulesRegulationsManagementId: upkeepId.value,
    })
      .then(res => {
        uni.hideLoading();
        if (res.code === 200) {
          fileList.value = res.data.records || [];
        } else {
          showToast("获取附件列表失败");
        }
      })
      .catch(err => {
        uni.hideLoading();
        showToast("获取附件列表失败");
      });
  };
</script>
<style scoped lang="scss">
  @import "../../../styles/sales-common.scss";
  .file-list-page {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 100rpx;
  }
  .file-list-container {
    padding: 20rpx;
  }
  .file-list {
    background: #ffffff;
    border-radius: 8rpx;
    overflow: hidden;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .file-item {
    display: flex;
    align-items: center;
    padding: 20rpx;
    border-bottom: 1rpx solid #f0f0f0;
    &:last-child {
      border-bottom: none;
    }
  }
  .file-icon {
    width: 56rpx;
    height: 56rpx;
    border-radius: 8rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 20rpx;
    &.blue {
      background: #409eff;
    }
    &.green {
      background: #67c23a;
    }
    &.red {
      background: #f56c6c;
    }
    &.orange {
      background: #e6a23c;
    }
    &.gray {
      background: #909399;
    }
    &.purple {
      background: #909399;
    }
    &.yellow {
      background: #e6a23c;
    }
  }
  .file-info {
    flex: 1;
    min-width: 0;
  }
  .file-name {
    display: block;
    font-size: 16px;
    color: #303133;
    margin-bottom: 8rpx;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .file-meta {
    display: block;
    font-size: 12px;
    color: #909399;
  }
  .file-actions {
    display: flex;
    gap: 12rpx;
  }
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 100rpx 0;
    background: #ffffff;
    border-radius: 8rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  }
  .empty-text {
    font-size: 14px;
    color: #909399;
    margin-top: 20rpx;
  }
  .upload-button {
    position: fixed;
    bottom: 40rpx;
    right: 40rpx;
    width: 130rpx;
    height: 130rpx;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
    z-index: 1000;
  }
  .upload-text {
    font-size: 10px;
    color: #ffffff;
    margin-top: 4rpx;
  }
  .upload-progress {
    padding: 40rpx 0;
  }
  .upload-progress-text {
    display: block;
    text-align: center;
    margin-top: 20rpx;
    font-size: 14px;
    color: #606266;
  }
</style>
src/pages/equipmentManagement/upkeep/index.vue
@@ -1,44 +1,50 @@
<template>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="设备保养" @back="goBack" />
    <PageHeader title="设备保养"
                @back="goBack" />
    <!-- æœç´¢åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input
            class="search-text"
            placeholder="请输入设备名称搜索"
            v-model="searchKeyword"
            @change="getList"
            clearable
          />
          <up-input class="search-text"
                    placeholder="请输入设备名称搜索"
                    v-model="searchKeyword"
                    @change="getList"
                    clearable />
        </view>
        <view class="filter-button" @click="getList">
          <up-icon name="search" size="24" color="#999"></up-icon>
        <view class="filter-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- è®¾å¤‡ä¿å…»åˆ—表 -->
    <view class="ledger-list" v-if="upkeepList.length > 0">
      <view v-for="(item, index) in upkeepList" :key="index">
        <view class="ledger-item" @click="toggleSelection(item)">
    <view class="ledger-list"
          v-if="upkeepList.length > 0">
      <view v-for="(item, index) in upkeepList"
            :key="index">
        <view class="ledger-item"
              @click="toggleSelection(item)">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">设备名称:{{ item.deviceName }}</text>
            </view>
            <view class="status-tag">
              <u-tag v-if="item.status === 1" type="success">完结</u-tag>
              <u-tag v-if="item.status === 0" type="error">待保养</u-tag>
              <u-tag v-if="item.status === 1"
                     type="success">完结</u-tag>
              <u-tag v-if="item.status === 0"
                     type="error">待保养</u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">规格型号</text>
@@ -67,267 +73,282 @@
            <view class="detail-row">
              <text class="detail-label">保养结果</text>
              <view class="detail-value">
              <u-tag v-if="item.maintenanceResult === 1" type="success" size="mini">
                å®Œå¥½
              </u-tag>
              <u-tag v-if="item.maintenanceResult === 0" type="error" size="mini">
                ç»´ä¿®
              </u-tag>
              <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
            </view>
                <u-tag v-if="item.maintenanceResult === 1"
                       type="success"
                       size="mini">
                  å®Œå¥½
                </u-tag>
                <u-tag v-if="item.maintenanceResult === 0"
                       type="error"
                       size="mini">
                  ç»´ä¿®
                </u-tag>
                <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text>
              </view>
            </view>
          </view>
          <!-- æŒ‰é’®åŒºåŸŸ -->
          <view class="action-buttons">
            <u-button
              type="primary"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click.stop="edit(item.id)"
            >
            <u-button type="primary"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click.stop="edit(item.id)">
              ç¼–辑
            </u-button>
            <u-button
              type="warning"
              size="small"
              class="action-btn"
              :disabled="item.status === 1"
              @click.stop="addMaintain(item.id)"
            >
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click.stop="addMaintain(item.id)">
              ä¿å…»
            </u-button>
            <u-button
              type="error"
              size="small"
              plain
              class="action-btn"
              @click.stop="delUpkeepByIds(item.id)"
            >
            <u-button type="error"
                      size="small"
                      plain
                      class="action-btn"
                      @click.stop="delUpkeepByIds(item.id)">
              åˆ é™¤
            </u-button>
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      @click.stop="addFile(item.id)">
              é™„ä»¶
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
    <view v-else
          class="no-data">
      <text>暂无设备保养数据</text>
    </view>
    <!-- æµ®åŠ¨æ–°å¢žæŒ‰é’® -->
    <view class="fab-button" @click="addPlan">
      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
    <view class="fab-button"
          @click="addPlan">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getUpkeepPage, delUpkeep } from '@/api/equipmentManagement/upkeep'
import useUserStore from "@/store/modules/user"
// æ˜¾ç¤ºæç¤ºä¿¡æ¯
const showToast = (message) => {
  uni.showToast({
    title: message,
    icon: 'none'
  })
};
import dayjs from "dayjs"
  import { ref, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { getUpkeepPage, delUpkeep } from "@/api/equipmentManagement/upkeep";
  import useUserStore from "@/store/modules/user";
  // æ˜¾ç¤ºæç¤ºä¿¡æ¯
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import dayjs from "dayjs";
const userStore = useUserStore()
  const userStore = useUserStore();
// æœç´¢å…³é”®è¯
const searchKeyword = ref('')
  // æœç´¢å…³é”®è¯
  const searchKeyword = ref("");
// è®¾å¤‡ä¿å…»æ•°æ®
const upkeepList = ref([])
  // è®¾å¤‡ä¿å…»æ•°æ®
  const upkeepList = ref([]);
// å¤šé€‰åˆ—表
const multipleList = ref([])
  // å¤šé€‰åˆ—表
  const multipleList = ref([]);
// è¿”回上一页
const goBack = () => {
  uni.navigateBack()
}
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
// æ ¼å¼åŒ–日期
const formatDate = (dateStr) => {
  if (!dateStr) return ''
  return dayjs(dateStr).format("YYYY-MM-DD")
}
  // æ ¼å¼åŒ–日期
  const formatDate = dateStr => {
    if (!dateStr) return "";
    return dayjs(dateStr).format("YYYY-MM-DD");
  };
// æ ¼å¼åŒ–日期时间
const formatDateTime = (dateStr) => {
  if (!dateStr) return ''
  return dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss")
}
  // æ ¼å¼åŒ–日期时间
  const formatDateTime = dateStr => {
    if (!dateStr) return "";
    return dayjs(dateStr).format("YYYY-MM-DD HH:mm:ss");
  };
// æŸ¥è¯¢åˆ—表
const getList = () => {
  showLoadingToast('加载中...')
  const params = {
    current: -1,
    size: -1,
    deviceName: searchKeyword.value || undefined
  }
  getUpkeepPage(params)
    .then((res) => {
      // å¦‚æžœres.data不是数组,设置为空数组
      upkeepList.value = res.records || res.data?.records || []
      closeToast()
    })
    .catch(() => {
      closeToast()
      showToast('获取数据失败')
    })
}
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const params = {
      current: -1,
      size: -1,
      deviceName: searchKeyword.value || undefined,
    };
    getUpkeepPage(params)
      .then(res => {
        // å¦‚æžœres.data不是数组,设置为空数组
        upkeepList.value = res.records || res.data?.records || [];
        closeToast();
      })
      .catch(() => {
        closeToast();
        showToast("获取数据失败");
      });
  };
  // æ–°å¢žé™„ä»¶ - è·³è½¬åˆ°é™„件页面
  const addFile = id => {
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("upkeepId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/fileList",
    });
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
  uni.showLoading({
    title: message,
    mask: true
  });
};
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
// å…³é—­æç¤º
const closeToast = () => {
  uni.hideLoading();
};
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
// åˆ‡æ¢é€‰æ‹©çŠ¶æ€
const toggleSelection = (item) => {
  const index = multipleList.value.findIndex(selected => selected.id === item.id)
  if (index > -1) {
    multipleList.value.splice(index, 1)
  } else {
    multipleList.value.push(item)
  }
}
// æ£€æŸ¥æ˜¯å¦å·²é€‰æ‹©
const isSelected = (item) => {
  return multipleList.value.some(selected => selected.id === item.id)
}
// æ–°å¢žä¿å…» - è·³è½¬åˆ°ä¿å…»é¡µé¢
const addMaintain = (id) => {
  if (!id && multipleList.value.length !== 1) {
    showToast('请选择一条记录')
    return
  }
  const targetId = id || multipleList.value[0].id
  // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
  uni.setStorageSync('repairId', targetId)
  uni.navigateTo({
    url: '/pages/equipmentManagement/upkeep/maintain'
  })
}
// æ–°å¢žè®¡åˆ’ - è·³è½¬åˆ°æ–°å¢žé¡µé¢
const addPlan = () => {
  uni.navigateTo({
    url: '/pages/equipmentManagement/upkeep/add'
  })
}
// ç¼–辑 - è·³è½¬åˆ°add页面,通过id区分新增还是编辑
const edit = (id) => {
  if (!id) return
  // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
  uni.setStorageSync('repairId', id)
  uni.navigateTo({
    url: '/pages/equipmentManagement/upkeep/add'
  })
}
// åˆ é™¤ä¿å…»æ•°æ®
const delUpkeepByIds = async (ids) => {
  const deleteIds = Array.isArray(ids) ? ids : [ids]
  if (deleteIds.length === 0) {
    showToast('请选择要删除的记录')
    return
  }
  uni.showModal({
    title: '警告',
    content: '确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?',
    confirmText: '确定',
    cancelText: '取消',
    success: async (res) => {
      if (!res.confirm) return
      try {
        // é€ä¸ªåˆ é™¤
        for (const id of deleteIds) {
          const response = await delUpkeep(id)
          if (response.code !== 200) {
            showToast('删除失败')
            return
          }
        }
        showToast('删除成功')
        multipleList.value = []
        getList()
      } catch (e) {
        showToast('删除失败')
      }
  // åˆ‡æ¢é€‰æ‹©çŠ¶æ€
  const toggleSelection = item => {
    const index = multipleList.value.findIndex(
      selected => selected.id === item.id
    );
    if (index > -1) {
      multipleList.value.splice(index, 1);
    } else {
      multipleList.value.push(item);
    }
  })
}
  };
onMounted(() => {
  getList()
})
  // æ£€æŸ¥æ˜¯å¦å·²é€‰æ‹©
  const isSelected = item => {
    return multipleList.value.some(selected => selected.id === item.id);
  };
onShow(() => {
  getList()
})
  // æ–°å¢žä¿å…» - è·³è½¬åˆ°ä¿å…»é¡µé¢
  const addMaintain = id => {
    if (!id && multipleList.value.length !== 1) {
      showToast("请选择一条记录");
      return;
    }
    const targetId = id || multipleList.value[0].id;
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("repairId", targetId);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/maintain",
    });
  };
  // æ–°å¢žè®¡åˆ’ - è·³è½¬åˆ°æ–°å¢žé¡µé¢
  const addPlan = () => {
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/add",
    });
  };
  // ç¼–辑 - è·³è½¬åˆ°add页面,通过id区分新增还是编辑
  const edit = id => {
    if (!id) return;
    // ä½¿ç”¨æœ¬åœ°å­˜å‚¨ä¼ é€’id
    uni.setStorageSync("repairId", id);
    uni.navigateTo({
      url: "/pages/equipmentManagement/upkeep/add",
    });
  };
  // åˆ é™¤ä¿å…»æ•°æ®
  const delUpkeepByIds = async ids => {
    const deleteIds = Array.isArray(ids) ? ids : [ids];
    if (deleteIds.length === 0) {
      showToast("请选择要删除的记录");
      return;
    }
    uni.showModal({
      title: "警告",
      content: "确认删除保养数据, æ­¤æ“ä½œä¸å¯é€†?",
      confirmText: "确定",
      cancelText: "取消",
      success: async res => {
        if (!res.confirm) return;
        try {
          // é€ä¸ªåˆ é™¤
          for (const id of deleteIds) {
            const response = await delUpkeep(id);
            if (response.code !== 200) {
              showToast("删除失败");
              return;
            }
          }
          showToast("删除成功");
          multipleList.value = [];
          getList();
        } catch (e) {
          showToast("删除失败");
        }
      },
    });
  };
  onMounted(() => {
    getList();
  });
  onShow(() => {
    getList();
  });
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
// è®¾å¤‡ä¿å…»ç‰¹æœ‰æ ·å¼
.sales-account {
  padding-bottom: 80px; // ä¸ºæµ®åŠ¨æŒ‰é’®ç•™å‡ºç©ºé—´
}
  // è®¾å¤‡ä¿å…»ç‰¹æœ‰æ ·å¼
  .sales-account {
    padding-bottom: 80px; // ä¸ºæµ®åŠ¨æŒ‰é’®ç•™å‡ºç©ºé—´
  }
.action-section {
  padding: 10px 20px;
  background: #ffffff;
  border-bottom: 1px solid #f0f0f0;
}
  .action-section {
    padding: 10px 20px;
    background: #ffffff;
    border-bottom: 1px solid #f0f0f0;
  }
.action-section .action-buttons {
  gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
  justify-content: flex-start;
}
  .action-section .action-buttons {
    gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
    justify-content: flex-start;
  }
.checkbox-wrapper {
  display: flex;
  align-items: center;
}
  .checkbox-wrapper {
    display: flex;
    align-items: center;
  }
.status-tag {
  display: flex;
  align-items: center;
}
  .status-tag {
    display: flex;
    align-items: center;
  }
.detail-label {
  min-width: 80px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 60px ä¸åŒ
}
  .detail-label {
    min-width: 80px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 60px ä¸åŒ
  }
.detail-value {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
  .detail-value {
    display: flex;
    justify-content: flex-end;
    align-items: center;
  }
.ledger-item .action-buttons {
  gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
}
  .ledger-item .action-buttons {
    gap: 8px; // ä¸Žå…¬å…±æ ·å¼ä¸­çš„ 12px ä¸åŒ
  }
</style>
src/pages/inspectionUpload/components/formDia.vue
@@ -21,8 +21,8 @@
            name="before"
            multiple
            :maxCount="10"
            :maxSize="1024 * 1024"
            accept="video/*"
            :maxSize="5 * 1024 * 1024"
            accept="image/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
@@ -36,8 +36,8 @@
            name="after"
            multiple
            :maxCount="10"
            :maxSize="1024 * 1024"
            accept="video/*"
            :maxSize="5 * 1024 * 1024"
            accept="image/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
@@ -51,8 +51,8 @@
            name="issue"
            multiple
            :maxCount="10"
            :maxSize="1024 * 1024"
            accept="video/*"
            :maxSize="5 * 1024 * 1024"
            accept="image/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
@@ -67,8 +67,10 @@
</template>
<script setup>
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { submitInspectionRecord } from '@/api/equipmentManagement/inspection.js'
import { getToken } from '@/utils/auth'
import config from '@/config'
const emit = defineEmits(['closeDia'])
@@ -78,50 +80,106 @@
const issueModelValue = ref([])
const infoData = ref(null)
// è®¡ç®—上传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';
})
const uploadSingleFile = async (fileItem, typeValue) => {
  const token = getToken()
  if (!token) throw new Error('用户未登录')
  // H5: u-upload å¯èƒ½ç»™åŽŸç”Ÿ File(fileItem.file)
  const rawFile = fileItem?.file
  if (rawFile) {
    const formData = new FormData()
    formData.append('file', rawFile, rawFile.name || 'image.jpg')
    formData.append('type', String(typeValue))
    const res = await fetch(uploadFileUrl.value, {
      method: 'POST',
      headers: { Authorization: 'Bearer ' + token },
      body: formData
    })
    const data = await res.json()
    if (data?.code !== 200) throw new Error(data?.msg || '上传失败')
    return {
      url: data?.data?.url,
      name: rawFile.name || 'image.jpg',
      status: 'success'
    }
  }
  // éž H5 / å…¼å®¹ï¼šèµ° uni.uploadFile
  return await new Promise((resolve, reject) => {
    uni.uploadFile({
      url: uploadFileUrl.value,
      filePath: fileItem.url,
      name: 'file',
      header: {
        'Authorization': `Bearer ${token}`
      },
      formData: {
        type: typeValue
      },
      success: (res) => {
        try {
          const data = JSON.parse(res.data)
          if (data.code === 200) {
            resolve({
              url: data.data.url,
              name: fileItem.name,
              status: 'success'
            })
          } else {
            reject(new Error(data.msg || '上传失败'))
          }
        } catch (e) {
          reject(e)
        }
      },
      fail: (err) => reject(err)
    })
  })
}
// æ–‡ä»¶ä¸Šä¼ å¤„理
const afterRead = (event) => {
  const { name, file } = event
  
  // ä¸Šä¼ æ–‡ä»¶åˆ°æœåС噍
  uni.uploadFile({
    url: '/api/upload', // æ›¿æ¢ä¸ºå®žé™…的上传接口
    filePath: file.url,
    name: 'file',
    success: (res) => {
      const data = JSON.parse(res.data)
      if (data.code === 200) {
        const fileItem = {
          url: data.data.url,
          name: file.name,
          status: 'success'
        }
        // æ ¹æ®name添加到对应的数组
        if (name === 'before') {
          beforeModelValue.value.push(fileItem)
        } else if (name === 'after') {
          afterModelValue.value.push(fileItem)
        } else if (name === 'issue') {
          issueModelValue.value.push(fileItem)
        }
        uni.showToast({
          title: '上传成功',
          icon: 'success'
        })
      } else {
        uni.showToast({
          title: '上传失败',
          icon: 'error'
        })
  // æ ¹æ®ä¸Šä¼ ç±»åž‹è®¾ç½®ä¸åŒçš„type值
  let typeValue = 10 // é»˜è®¤å€¼
  if (name === 'before') {
    typeValue = 10 // ç”Ÿäº§å‰
  } else if (name === 'after') {
    typeValue = 11 // ç”Ÿäº§ä¸­
  } else if (name === 'issue') {
    typeValue = 12 // ç”Ÿäº§åŽ
  }
  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)
      }
    },
    fail: () => {
      uni.showToast({
        title: '上传失败',
        icon: 'error'
      })
    }
    uni.showToast({ title: '上传成功', icon: 'success' })
  }).catch((err) => {
    console.error('上传失败:', err)
    uni.showToast({ title: '上传失败', icon: 'error' })
  })
}
src/pages/inspectionUpload/index.vue
@@ -68,32 +68,6 @@
    </view>
    <!-- æ‰«ç åŒºåŸŸ - å…¨å±€å¼¹çª— -->
    <view v-if="isScanning" class="qr-scan-overlay">
      <view class="qr-scan-container">
        <view class="scan-header">
          <text class="scan-title">扫描二维码</text>
          <u-button type="error" size="small" @click.stop="stopScan" :customStyle="{
            borderRadius: '15px',
            height: '30px',
            fontSize: '12px'
          }">
            å…³é—­
          </u-button>
        </view>
        <camera class="qr-camera" device-position="back" flash="off" @scancode="handleScanCode"
          @error="handleCameraError"></camera>
        <view class="scan-frame-wrapper">
          <view class="scan-frame"></view>
          <view class="scan-tip">请将二维码放入框内</view>
        </view>
        <u-alert v-if="cameraError" :title="cameraError" type="error" :showIcon="true" :closable="true"
          @close="cameraError = ''" :customStyle="{
            margin: '10px 0'
          }"></u-alert>
      </view>
    </view>
    <!-- å›¾ç‰‡ä¸Šä¼ å¼¹çª— - åŽŸç”Ÿå®žçŽ° -->
    <view v-if="showUploadDialog" class="custom-modal-overlay" @click="closeUploadDialog">
      <view class="custom-modal-container" @click.stop>
@@ -162,10 +136,10 @@
              <view v-if="getCurrentFiles().length > 0" class="file-list">
                <view v-for="(file, index) in getCurrentFiles()" :key="index" class="file-item">
                  <view class="file-preview-container">
                    <image v-if="file?.path?.fileType === 'image'"
                      :src="file?.url || file?.tempFilePath?.tempFilePath || file?.path?.tempFilePath"
                    <image v-if="file.type === 'image' || (file.type !== 'video' && !file.type)"
                      :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
                      class="file-preview" mode="aspectFill" />
                    <view v-else class="video-preview">
                    <view v-else-if="file.type === 'video'" class="video-preview">
                      <uni-icons type="videocam" name="videocam" size="18" color="#fff"
                        style="margin-right: 5px;"></uni-icons>
                      <text class="video-text">视频</text>
@@ -294,6 +268,7 @@
import { getLedgerById } from '@/api/equipmentManagement/ledger.js'
import { inspectionTaskList, uploadInspectionTask } from "@/api/inspectionManagement";
import { getToken } from "@/utils/auth";
import config from '@/config'
// ç»„件引用已移除
@@ -347,7 +322,7 @@
// ä¸Šä¼ é…ç½®
const uploadConfig = {
  action: "/common/minioUploads",
  action: "/file/upload",
  limit: 10,
  fileSize: 50, // MB
  fileType: ['jpg', 'jpeg', 'png', 'mp4', 'mov'],
@@ -356,15 +331,7 @@
// è®¡ç®—上传URL
const uploadFileUrl = computed(() => {
  let baseUrl = '';
  if (process.env.VUE_APP_BASE_API) {
    baseUrl = process.env.VUE_APP_BASE_API;
  } else if (process.env.NODE_ENV === 'development') {
    baseUrl = 'http://114.132.189.42:9068';
  } else {
    baseUrl = 'http://114.132.189.42:9068';
  }
    const baseUrl = 'http://114.132.189.42:9030';
  return baseUrl + uploadConfig.action;
})
@@ -377,10 +344,6 @@
// è¯·æ±‚取消标志,用于取消正在进行的请求
let isRequestCancelled = false
// æ‰«ç ç›¸å…³çŠ¶æ€
const isScanning = ref(false)
const cameraError = ref('')
const pagesPames = reactive({
  size: 10,
@@ -438,11 +401,6 @@
onUnmounted(() => {
  // è®¾ç½®å–消标志,阻止后续的异步操作
  isRequestCancelled = true
  // åœæ­¢æ‰«ç 
  if (isScanning.value) {
    isScanning.value = false
  }
  // å…³é—­ä¸Šä¼ å¼¹çª—
  if (showUploadDialog.value) {
@@ -528,114 +486,77 @@
  }
}
// ä¸ºæŒ‡å®šä»»åŠ¡å¼€å§‹æ‰«ç 
// ä¸ºæŒ‡å®šä»»åŠ¡å¼€å§‹æ‰«ç ï¼ˆçœŸæœºï¼‰
const startScanForTask = async (task) => {
  try {
    // è®°å½•当前扫描的任务
    currentScanningTask.value = task
    // æ˜¾ç¤ºæ‰«æç•Œé¢
    isScanning.value = true
    // ä½¿ç”¨uniapp的扫码API
    uni.scanCode({
      success: (res) => {
        handleScanSuccess(res)
      },
      fail: (err) => {
        console.error('扫码失败:', err)
        uni.showToast({
          title: '扫码失败',
          icon: 'error'
        })
        // å…³é—­æ‰«æç•Œé¢
        isScanning.value = false
      }
    })
  } catch (e) {
    console.error('启动扫码失败:', e)
    uni.showToast({
      title: '启动扫码失败',
      icon: 'error'
    })
    isScanning.value = false
  }
}
// åœæ­¢æ‰«ç 
const stopScan = () => {
  isScanning.value = false
  currentScanningTask.value = null
}
// æ‰«ç æˆåŠŸå¤„ç†
const handleScanSuccess = async (result) => {
// æ‰«ç æˆåŠŸå¤„ç†ï¼šæ ¡éªŒåŽæ‰“å¼€ä¸Šä¼ å¼¹çª—
const handleScanSuccess = (result) => {
  try {
    // è§£æžäºŒç»´ç æ•°æ®ï¼Œæå–deviceId
    let deviceId = ''
    // æ£€æŸ¥æ˜¯å¦æ˜¯URL格式
    if (result.result.includes('deviceId=')) {
      // ä»ŽURL中提取deviceId
      const url = result.result
      const match = url.match(/deviceId=(\d+)/)
      if (match && match[1]) {
        deviceId = match[1]
      }
    } else {
      // å°è¯•解析JSON格式
      try {
        const qrData = JSON.parse(result.result)
        deviceId = qrData.deviceId || qrData.qrCodeId || ''
      } catch (e) {
        // å¦‚果不是JSON格式,直接使用结果
        deviceId = result.result
    if (result?.result && typeof result.result === 'string') {
      if (result.result.includes('deviceId=')) {
        const match = result.result.match(/deviceId=(\d+)/)
        if (match && match[1]) deviceId = match[1]
      } else {
        try {
          const qrData = JSON.parse(result.result)
          deviceId = qrData.deviceId || qrData.qrCodeId || ''
        } catch (e) {
          deviceId = result.result
        }
      }
    }
    if (!deviceId) {
      uni.showToast({
        title: '未识别到设备ID',
        icon: 'error'
      })
      isScanning.value = false
      uni.showToast({ title: '未识别到设备ID', icon: 'error' })
      return
    }
    // èŽ·å–å½“å‰ä»»åŠ¡çš„taskId
    const currentTaskId = currentScanningTask.value?.taskId || currentScanningTask.value?.id
    // å¯¹æ¯”deviceId和taskId
    if (deviceId === currentTaskId.toString()) {
      uni.showToast({
        title: '识别成功',
        icon: 'success'
      })
      // å…ˆå…³é—­æ‰«æç•Œé¢
      isScanning.value = false
      // å»¶è¿Ÿæ‰“开上传弹窗,确保扫描界面完全关闭
      setTimeout(() => {
        openUploadDialog(currentScanningTask.value)
      }, 300)
    } else {
      uni.showToast({
        title: '请扫描正确的设备',
        icon: 'error'
      })
      // å…³é—­æ‰«æç•Œé¢
      isScanning.value = false
    if (!currentTaskId) {
      uni.showToast({ title: '任务信息缺失', icon: 'error' })
      return
    }
    if (deviceId === currentTaskId.toString()) {
      uni.showToast({ title: '识别成功', icon: 'success' })
      openUploadDialog(currentScanningTask.value)
    } else {
      uni.showToast({ title: '请扫描正确的设备', icon: 'error' })
    }
  } catch (error) {
    console.error('扫码结果处理失败:', error)
    uni.showToast({
      title: error.message || '数据解析失败',
      title: error?.message || '数据解析失败',
      icon: 'error'
    })
    // å…³é—­æ‰«æç•Œé¢
    isScanning.value = false
  }
}
// æ‰“开上传弹窗
const openUploadDialog = (task) => {
@@ -787,10 +708,20 @@
      arr.push(...issueModelValue.value);
    }
    // ä¼ ç»™åŽç«¯çš„临时文件ID列表(tempFileIds)
    // å…¼å®¹ï¼šæœ‰äº›æŽ¥å£å¯èƒ½è¿”回 tempId / tempFileId / id
    let tempFileIds = []
    if (arr !== null && arr.length > 0) {
      tempFileIds = arr
        .map((item) => item?.tempId ?? item?.tempFileId ?? item?.id)
        .filter((v) => v !== undefined && v !== null && v !== '')
    }
    // æäº¤æ•°æ®
    infoData.value.storageBlobDTO = arr;
    // æ·»åŠ å¼‚å¸¸çŠ¶æ€ä¿¡æ¯
    infoData.value.hasException = hasException.value;
    infoData.value.tempFileIds = tempFileIds;
    const result = await uploadInspectionTask({ ...infoData.value });
    // æ£€æŸ¥æäº¤ç»“æžœ
@@ -840,16 +771,6 @@
  }
}
// æ‘„像头错误处理
const handleCameraError = (error) => {
  cameraError.value = '摄像头访问失败,请检查权限设置'
}
// æ‰«ç äº‹ä»¶å¤„理
const handleScanCode = (result) => {
  handleScanSuccess(result)
}
// æŸ¥çœ‹é™„ä»¶
const viewAttachments = async (task) => {
  try {
@@ -859,32 +780,40 @@
    // è§£æžæ–°çš„æ•°æ®ç»“æž„
    attachmentList.value = []
    // ç”Ÿäº§å‰é™„ä»¶ (type=0)
    if (task.beforeProduction && Array.isArray(task.beforeProduction)) {
      const beforeFiles = task.beforeProduction.map(file => ({
    // åŽç«¯åæ˜¾å­—段(你提供的数据结构):
    // - commonFileListBefore:生产前(通常 type=10)
    // - commonFileListAfter:生产中(通常 type=11)
    // - commonFileList:可能是全部/兜底(若包含生产后,一般 type=12)
    const allList = Array.isArray(task?.commonFileList) ? task.commonFileList : []
    const beforeList = Array.isArray(task?.commonFileListBefore)
      ? task.commonFileListBefore
      : allList.filter(f => f?.type === 10)
    const afterList = Array.isArray(task?.commonFileListAfter)
      ? task.commonFileListAfter
      : allList.filter(f => f?.type === 11)
    // å¦‚果后端后续补了 commonFileListIssue,则优先用;否则从 commonFileList é‡ŒæŒ‰ type=12 å…œåº•
    const issueList = Array.isArray(task?.commonFileListIssue)
      ? task.commonFileListIssue
      : allList.filter(f => f?.type === 12)
    const mapToViewFile = (file, viewType) => {
      const u = normalizeFileUrl(file?.url || file?.downloadUrl || '')
      return {
        ...file,
        type: 0 // ç¡®ä¿type为0
      }))
      attachmentList.value.push(...beforeFiles)
        // ç”¨äºŽä¸‰æ ‡ç­¾é¡µåˆ†ç»„:0=生产前 1=生产中 2=生产后
        type: viewType,
        name: file?.name || file?.originalFilename || file?.bucketFilename,
        bucketFilename: file?.bucketFilename || file?.name,
        originalFilename: file?.originalFilename || file?.name,
        url: u,
        downloadUrl: u,
        size: file?.size || file?.byteSize,
      }
    }
    // ç”Ÿäº§ä¸­é™„ä»¶ (type=1)
    if (task.afterProduction && Array.isArray(task.afterProduction)) {
      const afterFiles = task.afterProduction.map(file => ({
        ...file,
        type: 1 // ç¡®ä¿type为1
      }))
      attachmentList.value.push(...afterFiles)
    }
    // ç”Ÿäº§åŽé™„ä»¶ (type=2)
    if (task.productionIssues && Array.isArray(task.productionIssues)) {
      const issueFiles = task.productionIssues.map(file => ({
        ...file,
        type: 2 // ç¡®ä¿type为2
      }))
      attachmentList.value.push(...issueFiles)
    }
    attachmentList.value.push(...beforeList.map(f => mapToViewFile(f, 0)))
    attachmentList.value.push(...afterList.map(f => mapToViewFile(f, 1)))
    attachmentList.value.push(...issueList.map(f => mapToViewFile(f, 2)))
    showAttachmentDialog.value = true
@@ -917,13 +846,13 @@
const getTabType = () => {
  switch (currentUploadType.value) {
    case 'before':
      return 0
      return 10
    case 'after':
      return 1
      return 11
    case 'issue':
      return 2
      return 12
    default:
      return 0
      return 10
  }
}
// èŽ·å–å½“å‰æŸ¥çœ‹ç±»åž‹çš„é™„ä»¶
@@ -954,6 +883,41 @@
  const name = file.bucketFilename || file.originalFilename || file.name || ''
  const ext = name.split('.').pop()?.toLowerCase()
  return ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)
}
// æ–‡ä»¶è®¿é—®åŸºç¡€åŸŸï¼ˆåŽç«¯è¦æ±‚前缀)
const filePreviewBase = 'http://114.132.189.42:9098'
// å°†åŽç«¯è¿”回的文件地址规范成可访问URL
// å…¼å®¹åœºæ™¯ï¼š
// - å·²ç»æ˜¯ http/https:直接返回
// - ä»¥ / å¼€å¤´ï¼šæ‹¼æŽ¥ filePreviewBase
// - Windows æœ¬åœ°è·¯å¾„(如 D:\ruoyi\prod\uploads...\xx.jpg):尝试截取 prod ä¹‹åŽçš„相对路径并拼接 filePreviewBase
const normalizeFileUrl = (rawUrl) => {
  try {
    if (!rawUrl || typeof rawUrl !== 'string') return ''
    const url = rawUrl.trim()
    if (!url) return ''
    if (/^https?:\/\//i.test(url)) return url
    if (url.startsWith('/')) return `${filePreviewBase}${url}`
    // Windows path -> web path
    if (/^[a-zA-Z]:\\/.test(url)) {
      const normalized = url.replace(/\\/g, '/')
      const idx = normalized.indexOf('/prod/')
      if (idx >= 0) {
        const relative = normalized.slice(idx + '/prod/'.length)
        return `${filePreviewBase}/${relative}`
      }
      // å…œåº•:无法推断映射规则时,至少把反斜杠变成正斜杠
      return normalized
    }
    // å…¶ä»–相对路径:直接用 baseUrl æ‹¼ä¸€ä¸‹
    return `${filePreviewBase}/${url.replace(/^\//, '')}`
  } catch (e) {
    return rawUrl || ''
  }
}
// é¢„览附件
@@ -994,52 +958,80 @@
  })
}
// ä½¿ç”¨ç›¸æœº
// æ‹ç…§/拍视频(真机优先用 chooseMedia;不支持则降级)
const chooseMedia = (type) => {
  let mediaPamaes = {
    count: 1,
    mediaType: [type || 'image'],
    sizeType: ['compressed', 'original'],
    sourceType: ['camera'],
  if (getCurrentFiles().length >= uploadConfig.limit) {
    uni.showToast({ title: `最多只能选择${uploadConfig.limit}个文件`, icon: 'none' })
    return
  }
  uni.chooseMedia({
    ...mediaPamaes,
    success: (res) => {
      try {
        if (!res.tempFiles || res.tempFiles.length === 0) {
          throw new Error('未获取到图片文件');
  const remaining = uploadConfig.limit - getCurrentFiles().length
  // ä¼˜å…ˆï¼šchooseMedia(支持 image/video)
  if (typeof uni.chooseMedia === 'function') {
    uni.chooseMedia({
      count: Math.min(remaining, 1),
      mediaType: [type || 'image'],
      sizeType: ['compressed', 'original'],
      sourceType: ['camera'],
      success: (res) => {
        try {
          const files = res?.tempFiles || []
          if (!files.length) throw new Error('未获取到文件')
          files.forEach((tf, idx) => {
            const filePath = tf.tempFilePath || tf.path || ''
            const fileType = tf.fileType || type || 'image'
            const ext = fileType === 'video' ? 'mp4' : 'jpg'
            const file = {
              tempFilePath: filePath,
              path: filePath,
              type: fileType,
              name: `${fileType}_${Date.now()}_${idx}.${ext}`,
              size: tf.size || 0,
              duration: tf.duration || 0,
              createTime: Date.now(),
              uid: Date.now() + Math.random() + idx
            }
            handleBeforeUpload(file)
          })
        } catch (e) {
          console.error('处理拍摄结果失败:', e)
          uni.showToast({ title: '处理文件失败', icon: 'error' })
        }
      },
      fail: (err) => {
        console.error('拍摄失败:', err)
        uni.showToast({ title: '拍摄失败', icon: 'error' })
      }
    })
    return
  }
        const tempFilePath = res.tempFiles[0];
        const tempFile = res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {};
        const file = {
          tempFilePath: tempFilePath,
          path: tempFilePath, // ä¿æŒå…¼å®¹æ€§
  // é™çº§ï¼šchooseImage / chooseVideo
  if (type === 'video') {
    chooseVideo()
  } else {
    uni.chooseImage({
      count: 1,
      sizeType: ['compressed', 'original'],
      sourceType: ['camera'],
      success: (res) => {
        const tempFilePath = res?.tempFilePaths?.[0]
        const tempFile = res?.tempFiles?.[0] || {}
        if (!tempFilePath) return
        handleBeforeUpload({
          tempFilePath,
          path: tempFilePath,
          type: 'image',
          name: `photo_${Date.now()}.jpg`,
          size: tempFile.size || 0,
          createTime: new Date().getTime(),
          createTime: Date.now(),
          uid: Date.now() + Math.random()
        };
        handleBeforeUpload(file);
      } catch (error) {
        console.error('处理拍照结果失败:', error);
        uni.showToast({
          title: '处理图片失败',
          icon: 'error'
        });
        })
      }
    },
    fail: (err) => {
      console.error('拍照失败:', err);
      uni.showToast({
        title: '拍照失败: ' + (err.errMsg || '未知错误'),
        icon: 'error'
      });
    }
  })
    })
  }
}
// æ‹ç…§
@@ -1193,36 +1185,6 @@
// ä¸Šä¼ å‰æ ¡éªŒ
const handleBeforeUpload = async (file) => {
  // æ£€æŸ¥ç½‘络连接
  const hasNetwork = await checkNetworkConnection();
  if (!hasNetwork) {
    uni.showToast({
      title: '网络连接不可用,请检查网络设置',
      icon: 'none'
    });
    return false;
  }
  // æ ¡éªŒæ–‡ä»¶å¤§å°
  if (uploadConfig.fileSize && file.size) {
    const isLt = file.size / 1024 / 1024 < uploadConfig.fileSize;
    if (!isLt) {
      uni.showToast({
        title: `文件大小不能超过 ${uploadConfig.fileSize} MB!`,
        icon: 'none'
      });
      return false;
    }
  }
  // æ ¡éªŒè§†é¢‘æ—¶é•¿
  if (file.type === 'video' && file.duration && file.duration > uploadConfig.maxVideoDuration) {
    uni.showToast({
      title: `视频时长不能超过 ${uploadConfig.maxVideoDuration} ç§’!`,
      icon: 'none'
    });
    return false;
  }
  // æ ¡éªŒæ–‡ä»¶ç±»åž‹
  if (uploadConfig.fileType && Array.isArray(uploadConfig.fileType) && uploadConfig.fileType.length > 0) {
@@ -1258,18 +1220,11 @@
  return true;
}
// æ–‡ä»¶ä¸Šä¼ å¤„理
const uploadFile = (file) => {
// æ–‡ä»¶ä¸Šä¼ å¤„理(真机走 uni.uploadFile)
const uploadFile = async (file) => {
  uploading.value = true;
  uploadProgress.value = 0;
  number.value++; // å¢žåŠ ä¸Šä¼ è®¡æ•°
  // ç¡®ä¿æ–‡ä»¶è·¯å¾„正确
  const filePath = file.tempFilePath?.tempFilePath || file.path?.tempFilePath || '';
  if (!filePath) {
    handleUploadError('文件路径不存在');
    return;
  }
  // ç¡®ä¿token存在
  const token = getToken();
@@ -1278,20 +1233,28 @@
    return;
  }
  // å‡†å¤‡ä¸Šä¼ å‚æ•°
  const uploadParams = {
  const typeValue = getTabType(); // ç”Ÿäº§å‰:10, ç”Ÿäº§ä¸­:11, ç”Ÿäº§åŽ:12
  uploadWithUniUploadFile(file, file.tempFilePath || file.path || '', typeValue, token);
}
// ä½¿ç”¨uni.uploadFile上传(非H5环境或H5回退方案)
const uploadWithUniUploadFile = (file, filePath, typeValue, token) => {
  if (!filePath) {
    handleUploadError('文件路径不存在');
    return;
  }
  const uploadTask = uni.uploadFile({
    url: uploadFileUrl.value,
    filePath: filePath,
    name: 'files',
    name: 'file',
    formData: {
      type: getTabType() || 0
      type: typeValue
    },
    header: {
      'Authorization': `Bearer ${token}`
    }
  };
  const uploadTask = uni.uploadFile({
    ...uploadParams,
    },
    success: (res) => {
      try {
        if (res.statusCode === 200) {
@@ -1348,7 +1311,10 @@
}
// ä¸Šä¼ å¤±è´¥å¤„理
const handleUploadError = (message = '上传文件失败', showRetry = true) => {
const handleUploadError = (message = '上传文件失败', showRetry = false) => {
  uploading.value = false;
  uploadProgress.value = 0;
  if (showRetry) {
    uni.showModal({
      title: '上传失败',
@@ -1369,63 +1335,69 @@
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
const handleUploadSuccess = (res, file) => {
  if (res.code === 200 && res.data && Array.isArray(res.data) && res.data.length > 0) {
    const uploadedFile = res.data[0];
  console.log('上传成功响应:', res);
  // å¤„理不同的数据结构:可能是数组,也可能是单个对象
  let uploadedFile = null;
  uploadedFile = res.data;
    // æ ¹æ®å½“前上传类型设置type字段
    let typeValue = 0; // é»˜è®¤ä¸ºç”Ÿäº§å‰
    switch (currentUploadType.value) {
      case 'before':
        typeValue = 0;
        break;
      case 'after':
        typeValue = 1;
        break;
      case 'issue':
        typeValue = 2;
        break;
    }
    // ç¡®ä¿ä¸Šä¼ çš„æ–‡ä»¶æ•°æ®å®Œæ•´ï¼ŒåŒ…含id和type
    const fileData = {
      ...file,
      id: uploadedFile.id, // æ·»åŠ æœåŠ¡å™¨è¿”å›žçš„id
      url: uploadedFile.url || uploadedFile.downloadUrl,
      bucketFilename: uploadedFile.bucketFilename || file.name,
      downloadUrl: uploadedFile.downloadUrl || uploadedFile.url,
      size: uploadedFile.size || file.size,
      createTime: uploadedFile.createTime || new Date().getTime(),
      type: typeValue // æ·»åŠ ç±»åž‹å­—æ®µï¼š0=生产前, 1=生产中, 2=生产后
    };
    uploadList.value.push(fileData);
    uploadedSuccessfully();
  } else {
  if (!uploadedFile) {
    console.error('无法解析上传响应数据:', res);
    number.value--; // ä¸Šä¼ å¤±è´¥æ—¶å‡å°‘计数
    handleUploadError(res.msg || '上传失败');
    handleUploadError('上传响应数据格式错误', false);
    return;
  }
  // æ ¹æ®å½“前上传类型设置type字段
  let typeValue = 0; // é»˜è®¤ä¸ºç”Ÿäº§å‰
  switch (currentUploadType.value) {
    case 'before':
      typeValue = 0;
      break;
    case 'after':
      typeValue = 1;
      break;
    case 'issue':
      typeValue = 2;
      break;
  }
  // ç¡®ä¿ä¸Šä¼ çš„æ–‡ä»¶æ•°æ®å®Œæ•´ï¼ŒåŒ…含id和type
  const fileData = {
    ...file,
    id: uploadedFile.id, // æ·»åŠ æœåŠ¡å™¨è¿”å›žçš„id
    tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id,
    url: uploadedFile.url || uploadedFile.downloadUrl || file.tempFilePath || file.path,
    bucketFilename: uploadedFile.bucketFilename || uploadedFile.originalFilename || file.name,
    downloadUrl: uploadedFile.downloadUrl || uploadedFile.url,
    size: uploadedFile.size || uploadedFile.byteSize || file.size,
    createTime: uploadedFile.createTime || new Date().getTime(),
    type: typeValue // æ·»åŠ ç±»åž‹å­—æ®µï¼š0=生产前, 1=生产中, 2=生产后
  };
  uploadList.value.push(fileData);
  // ç«‹å³æ·»åŠ åˆ°å¯¹åº”çš„åˆ†ç±»ï¼Œä¸ç­‰å¾…æ‰€æœ‰æ–‡ä»¶ä¸Šä¼ å®Œæˆ
  switch (currentUploadType.value) {
    case 'before':
      beforeModelValue.value.push(fileData);
      break;
    case 'after':
      afterModelValue.value.push(fileData);
      break;
    case 'issue':
      issueModelValue.value.push(fileData);
      break;
  }
  // é‡ç½®ä¸Šä¼ åˆ—表(因为已经添加到对应分类了)
  uploadList.value = [];
  number.value = 0;
}
// ä¸Šä¼ ç»“束处理
// ä¸Šä¼ ç»“束处理(已废弃,现在在handleUploadSuccess中直接处理)
const uploadedSuccessfully = () => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    // æ ¹æ®å½“前上传类型,将文件添加到对应的分类
    switch (currentUploadType.value) {
      case 'before':
        beforeModelValue.value = [...beforeModelValue.value, ...uploadList.value];
        break;
      case 'after':
        afterModelValue.value = [...afterModelValue.value, ...uploadList.value];
        break;
      case 'issue':
        issueModelValue.value = [...issueModelValue.value, ...uploadList.value];
        break;
    }
    // é‡ç½®çŠ¶æ€
    uploadList.value = [];
    number.value = 0;
  }
  // æ­¤å‡½æ•°å·²ä¸å†ä½¿ç”¨ï¼Œæ–‡ä»¶ä¸Šä¼ æˆåŠŸåŽç«‹å³æ·»åŠ åˆ°å¯¹åº”åˆ†ç±»
}
// æ ¼å¼åŒ–文件大小
src/pages/procurementManagement/paymentLedger/detail.vue
@@ -1,294 +1,305 @@
<template>
    <view class="receipt-payment-detail">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <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" :key="index" class="detail-item">
                <view class="item-header">
                    <view class="item-left">
                        <view class="record-icon">
                            <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                        </view>
                        <text class="item-index">{{ index + 1 }}</text>
                    </view>
                    <view class="item-date">{{ item.happenTime }}</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>
                    </view>
                    <view class="detail-row">
                        <text class="detail-label">付款金额(元)</text>
                        <text class="detail-value highlight">{{ formatAmount(item.currentPaymentAmount) }}</text>
                    </view>
                    <view class="detail-row">
                        <text class="detail-label">应付金额(元)</text>
                        <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text>
                    </view>
                </view>
            </view>
        </view>
        <view v-else class="no-data">
            <text>暂无回款记录</text>
        </view>
    </view>
  <view class="receipt-payment-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <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"
            :key="index"
            class="detail-item">
        <view class="item-header">
          <view class="item-left">
            <view class="record-icon">
              <up-icon name="file-text"
                       size="16"
                       color="#ffffff"></up-icon>
            </view>
            <text class="item-index">{{ index + 1 }}</text>
          </view>
          <view class="item-date">{{ item.happenTime }}</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>
          </view>
          <view class="detail-row">
            <text class="detail-label">付款金额(元)</text>
            <text class="detail-value highlight">{{ formatAmount(item.currentPaymentAmount) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">应付金额(元)</text>
            <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <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, computed, onMounted } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import {
    paymentLedgerList,
    paymentRecordList,
  } from "@/api/procurementManagement/paymentLedger";
// å®¢æˆ·ä¿¡æ¯
const supplierId = ref('');
  // å®¢æˆ·ä¿¡æ¯
  const supplierId = ref("");
// è¡¨æ ¼æ•°æ®
const tableData = ref([]);
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
const invoiceTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
        return sum + (parseFloat(item.invoiceAmount) || 0);
    }, 0);
});
  const invoiceTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.invoiceAmount) || 0);
    }, 0);
  });
const receiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
        return sum + (parseFloat(item.receiptAmount) || 0);
    }, 0);
});
  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 unReceiptTotal = computed(() => {
    return tableData.value.reduce((sum, item) => {
      return sum + (parseFloat(item.unReceiptAmount) || 0);
    }, 0);
  });
// è¿”回上一页
const goBack = () => {
    uni.removeStorageSync('supplierId')
    uni.navigateBack();
};
  // è¿”回上一页
  const goBack = () => {
    uni.removeStorageSync("supplierId");
    uni.navigateBack();
  };
// èŽ·å–é¡µé¢å‚æ•°
const getPageParams = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–ä¾›åº”å•†ID
    const storedSupplierId = uni.getStorageSync('supplierId');
    if (storedSupplierId) {
        supplierId.value = storedSupplierId;
    }
};
  // èŽ·å–é¡µé¢å‚æ•°
  const getPageParams = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–ä¾›åº”å•†ID
    const storedSupplierId = uni.getStorageSync("supplierId");
    if (storedSupplierId) {
      supplierId.value = storedSupplierId;
    }
  };
// æŸ¥è¯¢åˆ—表
const getList = () => {
    if (!supplierId.value) {
        uni.showToast({
            title: '客户信息缺失',
            icon: 'error'
        });
        return;
    }
    showLoadingToast('加载中...')
    paymentRecordList(supplierId.value).then((res) => {
        tableData.value = res.data;
        closeToast()
    }).catch(() => {
        closeToast()
        uni.showToast({
            title: '查询失败',
            icon: 'error'
        });
    });
};
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    if (!supplierId.value) {
      uni.showToast({
        title: "客户信息缺失",
        icon: "error",
      });
      return;
    }
    showLoadingToast("加载中...");
    paymentRecordList(supplierId.value)
      .then(res => {
        tableData.value = res.data;
        closeToast();
      })
      .catch(() => {
        closeToast();
        uni.showToast({
          title: "查询失败",
          icon: "error",
        });
      });
  };
// æ ¼å¼åŒ–金额
const formatAmount = (amount) => {
    return amount ? parseFloat(amount).toFixed(2) : '0.00';
};
  // æ ¼å¼åŒ–金额
  const formatAmount = amount => {
    return amount ? parseFloat(amount).toFixed(2) : "0.00";
  };
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
    uni.showLoading({
        title: message,
        mask: true
    });
};
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
// å…³é—­æç¤º
const closeToast = () => {
    uni.hideLoading();
};
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶èŽ·å–å‚æ•°å¹¶åˆ·æ–°åˆ—è¡¨
    getPageParams();
    getList();
});
  onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶èŽ·å–å‚æ•°å¹¶åˆ·æ–°åˆ—è¡¨
    getPageParams();
    getList();
  });
</script>
<style scoped lang="scss">
.receipt-payment-detail {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
}
  .receipt-payment-detail {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
  }
.u-divider {
    margin: 0 !important;
}
  .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-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-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
.summary-label {
    font-size: 14px;
    color: #666;
}
    &:last-child {
      margin-bottom: 0;
    }
  }
.summary-value {
    font-size: 14px;
    color: #333;
    font-weight: 500;
}
  .summary-label {
    font-size: 14px;
    color: #666;
  }
.summary-value.highlight {
    color: #2979ff;
    font-weight: 600;
}
  .summary-value {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
.summary-value.danger {
    color: #ff4757;
    font-weight: 600;
}
  .summary-value.highlight {
    color: #2979ff;
    font-weight: 600;
  }
.detail-list {
    padding: 20px;
}
  .summary-value.danger {
    color: #ff4757;
    font-weight: 600;
  }
.detail-item {
    background: #ffffff;
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    padding: 0 16px;
}
  .detail-list {
    padding: 20px;
  }
.item-header {
    padding: 10px 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
  .detail-item {
    background: #ffffff;
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    padding: 0 16px;
  }
.item-left {
    display: flex;
    align-items: center;
    gap: 8px;
}
  .item-header {
    padding: 10px 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
.record-icon {
    width: 24px;
    height: 24px;
    background: #2979ff;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
}
  .item-left {
    display: flex;
    align-items: center;
    gap: 8px;
  }
.item-index {
    font-size: 14px;
    color: #333;
    font-weight: 500;
}
  .record-icon {
    width: 24px;
    height: 24px;
    background: #2979ff;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
.item-date {
    font-size: 12px;
    color: #666;
}
  .item-index {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
.item-details {
    padding: 16px 0;
}
  .item-date {
    font-size: 12px;
    color: #666;
  }
.detail-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 8px;
    &:last-child {
        margin-bottom: 0;
    }
}
  .item-details {
    padding: 16px 0;
  }
.detail-label {
    font-size: 12px;
    color: #777777;
    min-width: 60px;
}
  .detail-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 8px;
.detail-value {
    font-size: 12px;
    color: #000000;
    text-align: right;
    flex: 1;
    margin-left: 16px;
}
    &:last-child {
      margin-bottom: 0;
    }
  }
.detail-value.highlight {
    color: #2979ff;
    font-weight: 500;
}
  .detail-label {
    font-size: 12px;
    color: #777777;
    min-width: 60px;
  }
.detail-value.danger {
    color: #ff4757;
    font-weight: 500;
}
  .detail-value {
    font-size: 12px;
    color: #000000;
    text-align: right;
    flex: 1;
    margin-left: 16px;
  }
.no-data {
    padding: 40px 0;
    text-align: center;
    color: #999;
}
  .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/procurementManagement/procurementLedger/detail.vue
@@ -1,875 +1,1442 @@
<template>
  <view class="account-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="台账详情" @back="goBack" />
         <!-- è¡¨å•区域 -->
        <up-form @submit="onSubmit" label-width="110" ref="formRef" :rules="rules" :model="form">
                <up-form-item label="采购合同号" prop="purchaseContractNumber">
                    <up-input v-model="form.purchaseContractNumber" placeholder="自动生成" disabled />
                </up-form-item>
                <up-form-item
                    label="销售合同号"
                    prop="salesContractNo"
                    required
                    @click="showPicker = true"
                >
                    <up-input
                        v-model="form.salesContractNo"
                        readonly=""
                        @click="showPicker = true"
                        placeholder="点击选择销售合同号"
                    />
                    <template #right>
                        <up-icon
                            name="arrow-right"
                            @click="showPicker = true"
                        ></up-icon>
                    </template>
                </up-form-item>
                <up-form-item
                    label="供应商名称"
                    prop="supplierName"
                    required
                    @click="showCustomerPicker = true"
                >
                    <up-input
                        v-model="form.supplierName"
                        readonly=""
                        @click="showCustomerPicker = true"
                        placeholder="点击选择供应商"
                    />
                    <template #right>
                        <up-icon
                            name="arrow-right"
                            @click="showCustomerPicker = true"
                        ></up-icon>
                    </template>
                </up-form-item>
                <up-form-item label="项目名称" prop="projectName" required >
                    <up-input
                        v-model="form.projectName"
                        placeholder="请输入项目名称"
                    />
                </up-form-item>
                <up-form-item label="付款方式" prop="paymentMethod" >
                    <up-input v-model="form.paymentMethod" placeholder="请输入付款方式" />
                </up-form-item>
                <up-form-item label="录入人" prop="recorderName" >
                    <up-input v-model="form.recorderName" placeholder="请输入" disabled />
                </up-form-item>
                <up-form-item label="录入日期" prop="entryDate" >
                    <up-input v-model="form.entryDate" placeholder="请输入" disabled />
                </up-form-item>
                <!-- é”€å”®åˆåŒå·é€‰æ‹© -->
                <up-action-sheet
                    :show="showPicker"
                    :actions="salesContractActionList"
                    title="选择销售合同号"
                    @select="onSalesmanSelect"
                    @close="showPicker = false"
                />
                <!-- ä¾›åº”商选择 -->
                <up-action-sheet
                    :show="showCustomerPicker"
                    :actions="supplierActionList"
                    title="选择供应商"
                    @select="onCustomerSelect"
                    @close="showCustomerPicker = false"
                />
                <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
                <up-popup :show="showCategoryPicker" mode="bottom">
                    <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
                    <view class="popup-header">
                        <view @click="showCategoryPicker = false" class="cancelButton">取消</view>
                        <view @click="confirmCategorySelection" class="confirmButton">确定</view>
                    </view>
                    <u-tree
                        :data="productOptions"
                        :props="defaultProps"
                        show-checkbox
                        default-expand-all
                        check-strictly
                        @check-change="onCategoryConfirm"
                    />
                </up-popup>
                <!-- è§„格型号选择器 -->
                <up-action-sheet
                    :show="showSpecificationPicker"
                    :actions="specificationActionList"
                    title="选择规格型号"
                    @select="onSpecificationSelect"
                    @close="showSpecificationPicker = false"
                />
                <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
                <up-action-sheet
                    :show="showTaxRatePicker"
                    :actions="taxRateActionList"
                    title="选择税率"
                    @select="onTaxRateSelect"
                    @close="showTaxRatePicker = false"
                />
                <!-- å‘票类型选择器 -->
                <up-action-sheet
                    :show="showInvoiceTypePicker"
                    :actions="invoiceTypeActionList"
                    title="选择发票类型"
                    @select="onInvoiceTypeSelect"
                    @close="showInvoiceTypePicker = false"
                />
                <!-- äº§å“ä¿¡æ¯ -->
                <view class="product-section">
                    <view class="section-header">
                        <view>
                        <text class="section-title">产品信息</text>
                        </view>
                        <view>
                            <up-button type="primary" size="small" @click="addProduct" class="add-btn" v-if="operationType !== 'view'">
                            æ–°å¢ž
                        </up-button>
                        </view>
                    </view>
                    <view class="product-card" v-for="(product, idx) in productData" :key="idx">
                        <!-- äº§å“ç±» -->
                        <view class="product-header">
                            <view class="product-title">
                                <up-icon name="file-text" size="16" color="#2979ff"></up-icon>
                                <text class="product-productCategory">产品 {{ idx + 1 }}</text>
                            </view>
                            <!-- æ“ä½œæŒ‰é’® -->
                            <view class="product-actions" v-if="operationType !== 'view'">
                                <up-button type="error" size="mini" @click="removeProduct(idx)" class="del-btn">
                                    åˆ é™¤
                                </up-button>
                            </view>
                        </view>
                        <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                        <view class="product-form">
                            <!-- äº§å“å¤§ç±» -->
                            <up-form-item
                                label="产品大类"
                                prop="productCategory"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.productCategory"
                                    readonly
                                    placeholder="请选择"
                                    @click="openCategoryPicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showCategoryPicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                            <!-- è§„格型号 -->
                            <up-form-item
                                label="规格型号"
                                prop="specificationModel"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.specificationModel"
                                    readonly
                                    placeholder="请选择"
                                    @click="openSpecificationPicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showSpecificationPicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                            <!-- å•位 -->
                            <up-form-item
                                label="单位"
                                prop="unit"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.unit"
                                    placeholder="请输入"
                                />
                            </up-form-item>
                            <!-- ç¨Žçއ -->
                            <up-form-item
                                label="税率(%)"
                                prop="taxRate"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxRate"
                                    readonly
                                    placeholder="请选择"
                                    @click="openTaxRatePicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showTaxRatePicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                            <!-- å«ç¨Žå•ä»· -->
                            <up-form-item
                                label="含税单价(元)"
                                prop="taxInclusiveUnitPrice"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxInclusiveUnitPrice"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatTaxPrice(idx)"
                                />
                            </up-form-item>
                            <!-- æ•°é‡ -->
                            <up-form-item
                                label="数量"
                                prop="quantity"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.quantity"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatAmount(idx)"
                                />
                            </up-form-item>
                            <!-- å«ç¨Žæ€»ä»· -->
                            <up-form-item
                                label="含税总价(元)"
                                prop="taxInclusiveTotalPrice"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxInclusiveTotalPrice"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatTaxTotal(idx)"
                                />
                            </up-form-item>
                            <!-- ä¸å«ç¨Žæ€»ä»· -->
                            <up-form-item
                                label="不含税总价(元)"
                                prop="taxExclusiveTotalPrice"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.taxExclusiveTotalPrice"
                                    type="number"
                                    placeholder="请输入"
                                    @blur="formatNoTaxTotal(idx)"
                                />
                            </up-form-item>
                            <!-- å‘票类型 -->
                            <up-form-item
                                label="发票类型"
                                prop="invoiceType"
                                required
                                :rules="productRules"
                            >
                                <up-input
                                    v-model="product.invoiceType"
                                    readonly
                                    placeholder="请选择"
                                    @click="openInvoiceTypePicker(idx)"
                                />
                                <template #right>
                                    <up-icon
                                        name="arrow-right"
                                        @click="showInvoiceTypePicker = true"
                                    ></up-icon>
                                </template>
                            </up-form-item>
                        </view>
                    </view>
                </view>
                <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
                <FooterButtons
                    :show="operationType !== 'view'"
                    cancelText="取消"
                    confirmText="保存"
                    @cancel="goBack"
                    @confirm="onSubmit"
                />
        </up-form>
    <PageHeader title="台账详情"
                @back="goBack" />
    <!-- è¡¨å•区域 -->
    <up-form @submit="onSubmit"
             label-width="110"
             ref="formRef"
             :rules="rules"
             :model="form">
      <up-form-item label="采购合同号"
                    prop="purchaseContractNumber">
        <up-input v-model="form.purchaseContractNumber"
                  placeholder="自动生成"
                  disabled />
      </up-form-item>
      <up-form-item label="销售合同号"
                    prop="salesContractNo"
                    required
                    @click="showPicker = true">
        <up-input v-model="form.salesContractNo"
                  readonly=""
                  @click="showPicker = true"
                  placeholder="点击选择销售合同号" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="供应商名称"
                    prop="supplierName"
                    required
                    @click="showCustomerPicker = true">
        <up-input v-model="form.supplierName"
                  readonly=""
                  @click="showCustomerPicker = true"
                  placeholder="点击选择供应商" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showCustomerPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="项目名称"
                    prop="projectName"
                    required>
        <up-input v-model="form.projectName"
                  placeholder="请输入项目名称" />
      </up-form-item>
      <up-form-item label="付款方式"
                    prop="paymentMethod">
        <up-input v-model="form.paymentMethod"
                  placeholder="请输入付款方式" />
      </up-form-item>
      <up-form-item label="签订日期"
                    required
                    prop="executionDate">
        <up-input v-model="form.executionDate"
                  placeholder="请选择"
                  readonly="" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showTimePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="录入人"
                    prop="recorderName">
        <up-input v-model="form.recorderName"
                  placeholder="请输入"
                  disabled />
      </up-form-item>
      <up-form-item label="录入日期"
                    prop="entryDate">
        <up-input v-model="form.entryDate"
                  placeholder="请输入"
                  disabled />
      </up-form-item>
      <view class="approval-process">
        <view class="approval-header">
          <text class="approval-title">审核流程</text>
          <text class="approval-desc">每个步骤只能选择一个审批人</text>
        </view>
        <view class="approval-steps">
          <view v-for="(step, stepIndex) in approverNodes"
                :key="stepIndex"
                class="approval-step">
            <view class="step-dot"></view>
            <view class="step-title">
              <text>审批人</text>
            </view>
            <view class="approver-container">
              <view v-if="step.nickName"
                    class="approver-item">
                <view class="approver-avatar">
                  <text class="avatar-text">{{ step.nickName.charAt(0) }}</text>
                  <view class="status-dot"></view>
                </view>
                <view class="approver-info">
                  <text class="approver-name">{{ step.nickName }}</text>
                </view>
                <view class="delete-approver-btn"
                      @click="removeApprover(stepIndex)">×</view>
              </view>
              <view v-else
                    class="add-approver-btn"
                    @click="addApprover(stepIndex)">
                <view class="add-circle">+</view>
                <text class="add-label">选择审批人</text>
              </view>
            </view>
            <view class="step-line"
                  v-if="stepIndex < approverNodes.length - 1"></view>
            <view class="delete-step-btn"
                  v-if="approverNodes.length > 1"
                  @click="removeApprovalStep(stepIndex)">删除节点</view>
          </view>
        </view>
        <view class="add-step-btn">
          <u-button icon="plus"
                    plain
                    type="primary"
                    style="width: 100%"
                    @click="addApprovalStep">新增节点</u-button>
        </view>
      </view>
      <up-popup :show="showTimePicker"
                mode="bottom"
                @close="showTimePicker = false">
        <up-datetime-picker :show="true"
                            v-model="currentDate"
                            @confirm="onDateConfirm"
                            @cancel="showTimePicker = false"
                            mode="date" />
      </up-popup>
      <!-- é”€å”®åˆåŒå·é€‰æ‹© -->
      <up-action-sheet :show="showPicker"
                       :actions="salesContractActionList"
                       title="选择销售合同号"
                       @select="onSalesmanSelect"
                       @close="showPicker = false" />
      <!-- ä¾›åº”商选择 -->
      <up-action-sheet :show="showCustomerPicker"
                       :actions="supplierActionList"
                       title="选择供应商"
                       @select="onCustomerSelect"
                       @close="showCustomerPicker = false" />
      <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
      <up-popup :show="showCategoryPicker"
                mode="bottom">
        <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
        <view class="popup-header">
          <view @click="showCategoryPicker = false"
                class="cancelButton">取消</view>
          <view @click="confirmCategorySelection"
                class="confirmButton">确定</view>
        </view>
        <u-tree :data="productOptions"
                :props="defaultProps"
                show-checkbox
                default-expand-all
                check-strictly
                @check-change="onCategoryConfirm" />
      </up-popup>
      <!-- è§„格型号选择器 -->
      <up-action-sheet :show="showSpecificationPicker"
                       :actions="specificationActionList"
                       title="选择规格型号"
                       @select="onSpecificationSelect"
                       @close="showSpecificationPicker = false" />
      <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
      <up-action-sheet :show="showTaxRatePicker"
                       :actions="taxRateActionList"
                       title="选择税率"
                       @select="onTaxRateSelect"
                       @close="showTaxRatePicker = false" />
      <!-- å‘票类型选择器 -->
      <up-action-sheet :show="showInvoiceTypePicker"
                       :actions="invoiceTypeActionList"
                       title="选择发票类型"
                       @select="onInvoiceTypeSelect"
                       @close="showInvoiceTypePicker = false" />
      <!-- äº§å“ä¿¡æ¯ -->
      <view class="product-section">
        <view class="section-header">
          <view>
            <text class="section-title">产品信息</text>
          </view>
          <view>
            <up-button type="primary"
                       size="small"
                       @click="addProduct"
                       class="add-btn"
                       v-if="operationType !== 'view'">
              æ–°å¢ž
            </up-button>
          </view>
        </view>
        <view class="product-card"
              v-for="(product, idx) in productData"
              :key="idx">
          <!-- äº§å“ç±» -->
          <view class="product-header">
            <view class="product-title">
              <up-icon name="file-text"
                       size="16"
                       color="#2979ff"></up-icon>
              <text class="product-productCategory">产品 {{ idx + 1 }}</text>
            </view>
            <!-- æ“ä½œæŒ‰é’® -->
            <view class="product-actions"
                  v-if="operationType !== 'view'">
              <up-button type="error"
                         size="mini"
                         @click="removeProduct(idx)"
                         class="del-btn">
                åˆ é™¤
              </up-button>
            </view>
          </view>
          <!-- äº§å“ä¿¡æ¯è¡¨å• -->
          <view class="product-form">
            <!-- äº§å“å¤§ç±» -->
            <up-form-item label="产品大类"
                          prop="productCategory"
                          required
                          :rules="productRules">
              <up-input v-model="product.productCategory"
                        readonly
                        placeholder="请选择"
                        @click="openCategoryPicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showCategoryPicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- è§„格型号 -->
            <up-form-item label="规格型号"
                          prop="specificationModel"
                          required
                          :rules="productRules">
              <up-input v-model="product.specificationModel"
                        readonly
                        placeholder="请选择"
                        @click="openSpecificationPicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showSpecificationPicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- å•位 -->
            <up-form-item label="单位"
                          prop="unit"
                          required
                          :rules="productRules">
              <up-input v-model="product.unit"
                        placeholder="请输入" />
            </up-form-item>
            <!-- ç¨Žçއ -->
            <up-form-item label="税率(%)"
                          prop="taxRate"
                          required
                          :rules="productRules">
              <up-input v-model="product.taxRate"
                        readonly
                        placeholder="请选择"
                        @click="openTaxRatePicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showTaxRatePicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- å«ç¨Žå•ä»· -->
            <up-form-item label="含税单价(元)"
                          prop="taxInclusiveUnitPrice"
                          required
                          :rules="productRules">
              <up-input v-model="product.taxInclusiveUnitPrice"
                        type="number"
                        placeholder="请输入"
                        @blur="formatTaxPrice(idx)" />
            </up-form-item>
            <!-- æ•°é‡ -->
            <up-form-item label="数量"
                          prop="quantity"
                          required
                          :rules="productRules">
              <up-input v-model="product.quantity"
                        type="number"
                        placeholder="请输入"
                        @blur="formatAmount(idx)" />
            </up-form-item>
            <!-- å«ç¨Žæ€»ä»· -->
            <up-form-item label="含税总价(元)"
                          prop="taxInclusiveTotalPrice"
                          required
                          :rules="productRules">
              <up-input v-model="product.taxInclusiveTotalPrice"
                        type="number"
                        placeholder="请输入"
                        @blur="formatTaxTotal(idx)" />
            </up-form-item>
            <!-- ä¸å«ç¨Žæ€»ä»· -->
            <up-form-item label="不含税总价(元)"
                          prop="taxExclusiveTotalPrice"
                          required
                          :rules="productRules">
              <up-input v-model="product.taxExclusiveTotalPrice"
                        type="number"
                        placeholder="请输入"
                        @blur="formatNoTaxTotal(idx)" />
            </up-form-item>
            <!-- å‘票类型 -->
            <up-form-item label="发票类型"
                          prop="invoiceType"
                          required
                          :rules="productRules">
              <up-input v-model="product.invoiceType"
                        readonly
                        placeholder="请选择"
                        @click="openInvoiceTypePicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showInvoiceTypePicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- åº“存预警数量 -->
            <up-form-item label="库存预警数量"
                          prop="warnNum"
                          required
                          :rules="productRules">
              <up-input v-model="product.warnNum"
                        type="number"
                        placeholder="请输入" />
            </up-form-item>
            <up-form-item label="是否质检"
                          prop="invoiceType"
                          required
                          :rules="productRules">
              <u-radio-group v-model="product.isChecked"
                             placement="row"
                             @change="groupChange">
                <u-radio :customStyle="{marginRight: '40rpx'}"
                         label="是"
                         :name="true">
                </u-radio>
                <u-radio label="否"
                         :name="false">
                </u-radio>
              </u-radio-group>
            </up-form-item>
          </view>
        </view>
      </view>
      <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
      <FooterButtons :show="operationType !== 'view'"
                     cancelText="取消"
                     confirmText="保存"
                     @cancel="goBack"
                     @confirm="onSubmit" />
    </up-form>
  </view>
</template>
<script setup>
import {onMounted, ref, computed} from 'vue';
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useUserStore from "@/store/modules/user";
import {calculateTaxExclusiveTotalPrice} from "@/utils/summarizeTable";
import {formatDateToYMD} from '@/utils/ruoyi'
import {
    addOrEditPurchase, createPurchaseNo,
    getOptions,
    getPurchaseById,
    getSalesNo
} from "@/api/procurementManagement/procurementLedger";
import PageHeader from '@/components/PageHeader.vue';
import FooterButtons from '@/components/FooterButtons.vue';
  import { onMounted, ref, computed } from "vue";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import useUserStore from "@/store/modules/user";
  import { calculateTaxExclusiveTotalPrice } from "@/utils/summarizeTable";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import {
    addOrEditPurchase,
    createPurchaseNo,
    getOptions,
    getPurchaseById,
    getSalesNo,
    approveProcessGetInfo,
  } from "@/api/procurementManagement/procurementLedger";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  import { userListNoPageByTenantId } from "@/api/system/user";
  // èŽ·å–é¡µé¢å‚æ•°
  const operationType = ref("");
  const editData = ref(null);
  const formRef = ref(null);
// èŽ·å–é¡µé¢å‚æ•°
const operationType = ref('');
const editData = ref(null);
const formRef = ref(null);
const userStore = useUserStore()
const form = ref({
    id: '',
  salesContractNo: '',
    purchaseContractNumber: '',
    supplierId: '',
    supplierName: '',
    projectName: '',
  paymentMethod: '',
    recorderId: '',
    recorderName: '',
    entryDate: '',
});
const showPicker = ref(false);
const showCustomerPicker = ref(false);
const salesContractList = ref([]);
const supplierList = ref([]);
const productData = ref([]);
// è®¡ç®—销售合同号选择列表
const salesContractActionList = computed(() => {
    return salesContractList.value.map(item => ({
        name: item.text,
        value: item.value
    }))
})
// è®¡ç®—供应商选择列表
const supplierActionList = computed(() => {
    return supplierList.value.map(item => ({
        name: item.text,
        value: item.value
    }))
})
// é€‰æ‹©å™¨ç›¸å…³å˜é‡
const showCategoryPicker = ref(false);
const showSpecificationPicker = ref(false);
const showTaxRatePicker = ref(false);
const showInvoiceTypePicker = ref(false);
const currentProductIndex = ref(0);
// é€‰é¡¹æ•°æ®
const productOptions = ref([]);
const selectedCategoryNode = ref(null);
const defaultProps = ref({
    children: 'children',
    label: 'label',
    nodeKey: 'id'
});
const modelOptions = ref([]);
const taxRateOptions = ref([
  { text: '1', value: '1' },
  { text: '6', value: '6' },
  { text: '13', value: '13' },
]);
const invoiceTypeOptions = ref([
  { text: '增普票', value: '增普票' },
  { text: '增专票', value: '增专票' },
]);
// è®¡ç®—规格型号选择列表
const specificationActionList = computed(() => {
    return modelOptions.value.map(model => ({
        name: model.text,
        value: model.value,
        unit: model.unit
    }))
})
// è®¡ç®—税率选择列表
const taxRateActionList = computed(() => {
    return taxRateOptions.value.map(rate => ({
        name: rate.text,
        value: rate.value
    }))
})
// è®¡ç®—发票类型选择列表
const invoiceTypeActionList = computed(() => {
    return invoiceTypeOptions.value.map(type => ({
        name: type.text,
        value: type.value
    }))
})
// è¡¨å•校验规则
const rules = {
    salesContractNo: [
        { required: true, message: '请选择销售合同号', trigger: 'blur' }
    ],
    supplierName: [
        { required: true, message: '请选择供应商名称', trigger: 'blur' }
    ],
    projectName: [
        { required: true, message: '请输入项目名称', trigger: 'blur' }
    ]
};
// äº§å“ä¿¡æ¯æ ¡éªŒè§„则
const productRules = {
    productCategory: [
        { required: true, message: '请选择产品大类', trigger: 'blur' }
    ],
    specificationModel: [
        { required: true, message: '请选择规格型号', trigger: 'blur' }
    ],
    unit: [
        { required: true, message: '请输入单位', trigger: 'blur' }
    ],
    taxRate: [
        { required: true, message: '请选择税率', trigger: 'blur' }
    ],
    taxInclusiveUnitPrice: [
        { required: true, message: '请输入含税单价', trigger: 'blur' },
        { type: 'number', min: 0, message: '含税单价必须大于0', trigger: 'blur' }
    ],
    quantity: [
        { required: true, message: '请输入数量', trigger: 'blur' },
        { type: 'number', min: 0, message: '数量必须大于0', trigger: 'blur' }
    ],
    taxInclusiveTotalPrice: [
        { required: true, message: '请输入含税总价', trigger: 'blur' },
        { type: 'number', min: 0, message: '含税总价必须大于0', trigger: 'blur' }
    ],
    taxExclusiveTotalPrice: [
        { required: true, message: '请输入不含税总价', trigger: 'blur' },
        { type: 'number', min: 0, message: '不含税总价必须大于0', trigger: 'blur' }
    ],
    invoiceType: [
        { required: true, message: '请选择发票类型', trigger: 'blur' }
    ]
};
const addProduct = () => {
    if (productData.value === null) {
        productData.value = []
    }
    productData.value.push({
    productCategory: '',
    specificationModel: '',
        productModelId: '',
    unit: '',
    taxRate: '',
    taxInclusiveUnitPrice: '',
    quantity: '',
    taxInclusiveTotalPrice: '',
    taxExclusiveTotalPrice: '',
    invoiceType: ''
  const userStore = useUserStore();
  const form = ref({
    id: "",
    salesContractNo: "",
    purchaseContractNumber: "",
    supplierId: "",
    supplierName: "",
    projectName: "",
    paymentMethod: "",
    recorderId: "",
    recorderName: "",
    entryDate: "",
    approveUserIds: "",
    executionDate: "",
  });
};
  const showTimePicker = ref(false);
  const showPicker = ref(false);
  const showCustomerPicker = ref(false);
  const salesContractList = ref([]);
  const supplierList = ref([]);
  const productData = ref([]);
  const currentDate = ref(Date.now());
  // è®¡ç®—销售合同号选择列表
  const salesContractActionList = computed(() => {
    return salesContractList.value.map(item => ({
      name: item.text,
      value: item.value,
    }));
  });
// é”€å”®åˆåŒå·é€‰æ‹©äº‹ä»¶
const onSalesmanSelect = (item) => {
    form.value.salesContractNo = item.name
    // æŸ¥æ‰¾å¯¹åº”çš„id
    const selectedItem = salesContractList.value.find(contract => contract.text === item.name);
    if (selectedItem) {
        form.value.salesLedgerId = selectedItem.value;
    }
    showPicker.value = false;
}
  // è®¡ç®—供应商选择列表
  const supplierActionList = computed(() => {
    return supplierList.value.map(item => ({
      name: item.text,
      value: item.value,
    }));
  });
// ä¾›åº”商选择事件
const onCustomerSelect = (item) => {
    form.value.supplierName = item.name
    // æŸ¥æ‰¾å¯¹åº”çš„id
    const selectedItem = supplierList.value.find(supplier => supplier.text === item.name);
    if (selectedItem) {
        form.value.supplierId = selectedItem.value;
    }
    showCustomerPicker.value = false;
}
  // é€‰æ‹©å™¨ç›¸å…³å˜é‡
  const showCategoryPicker = ref(false);
  const showSpecificationPicker = ref(false);
  const showTaxRatePicker = ref(false);
  const showInvoiceTypePicker = ref(false);
  const currentProductIndex = ref(0);
const removeProduct = (idx) => {
    productData.value.splice(idx, 1);
};
  // é€‰é¡¹æ•°æ®
  const productOptions = ref([]);
  const selectedCategoryNode = ref(null);
  const defaultProps = ref({
    children: "children",
    label: "label",
    nodeKey: "id",
  });
// æ˜¾ç¤ºé€‰æ‹©å™¨
const openCategoryPicker = (idx) => {
  currentProductIndex.value = idx;
  showCategoryPicker.value = true;
};
  const modelOptions = ref([]);
  const taxRateOptions = ref([
    { text: "1", value: "1" },
    { text: "6", value: "6" },
    { text: "13", value: "13" },
  ]);
const openSpecificationPicker = (idx) => {
  currentProductIndex.value = idx;
  showSpecificationPicker.value = true;
};
  const invoiceTypeOptions = ref([
    { text: "增普票", value: "增普票" },
    { text: "增专票", value: "增专票" },
  ]);
const openTaxRatePicker = (idx) => {
  currentProductIndex.value = idx;
  showTaxRatePicker.value = true;
};
  // è®¡ç®—规格型号选择列表
  const specificationActionList = computed(() => {
    return modelOptions.value.map(model => ({
      name: model.text,
      value: model.value,
      unit: model.unit,
    }));
  });
const openInvoiceTypePicker = (idx) => {
  currentProductIndex.value = idx;
  showInvoiceTypePicker.value = true;
};
  // è®¡ç®—税率选择列表
  const taxRateActionList = computed(() => {
    return taxRateOptions.value.map(rate => ({
      name: rate.text,
      value: rate.value,
    }));
  });
// é€‰æ‹©å™¨ç¡®è®¤äº‹ä»¶
const onCategoryConfirm = (node) => {
    // èŽ·å–é€‰ä¸­çš„èŠ‚ç‚¹ä¿¡æ¯
    console.log('selected node---', node);
    // å­˜å‚¨é€‰ä¸­çš„节点,用于确认时获取数据
    selectedCategoryNode.value = node;
};
  // è®¡ç®—发票类型选择列表
  const invoiceTypeActionList = computed(() => {
    return invoiceTypeOptions.value.map(type => ({
      name: type.text,
      value: type.value,
    }));
  });
// ç¡®è®¤äº§å“å¤§ç±»é€‰æ‹©
const confirmCategorySelection = () => {
    if (selectedCategoryNode.value) {
        // è®¾ç½®é€‰ä¸­çš„产品大类
        productData.value[currentProductIndex.value].productCategory = selectedCategoryNode.value.label;
        const id = selectedCategoryNode.value.id
        // é‡ç½®é€‰ä¸­çš„节点
        selectedCategoryNode.value = null;
        productData.value[currentProductIndex.value].specificationModel = ''
        productData.value[currentProductIndex.value].productModelId = ''
        getModels(id)
    }
    showCategoryPicker.value = false;
};
  // è¡¨å•校验规则
  const rules = {
    salesContractNo: [
      { required: true, message: "请选择销售合同号", trigger: "blur" },
    ],
    supplierName: [
      { required: true, message: "请选择供应商名称", trigger: "blur" },
    ],
    projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
    executionDate: [
      { required: true, message: "请选择签订日期", trigger: "blur" },
    ],
  };
// èŽ·å–è§„æ ¼åž‹å·
const getModels = (value) => {
    modelList({ id: value }).then((res) => {
        modelOptions.value = res.map(user => ({
            text: user.model,
            value: user.id,
            unit: user.unit,
        }));
    });
};
  // äº§å“ä¿¡æ¯æ ¡éªŒè§„则
  const productRules = {
    productCategory: [
      { required: true, message: "请选择产品大类", trigger: "blur" },
    ],
    specificationModel: [
      { required: true, message: "请选择规格型号", trigger: "blur" },
    ],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    taxRate: [{ required: true, message: "请选择税率", trigger: "blur" }],
    taxInclusiveUnitPrice: [
      { required: true, message: "请输入含税单价", trigger: "blur" },
      { type: "number", min: 0, message: "含税单价必须大于0", trigger: "blur" },
    ],
    quantity: [
      { required: true, message: "请输入数量", trigger: "blur" },
      { type: "number", min: 0, message: "数量必须大于0", trigger: "blur" },
    ],
    taxInclusiveTotalPrice: [
      { required: true, message: "请输入含税总价", trigger: "blur" },
      { type: "number", min: 0, message: "含税总价必须大于0", trigger: "blur" },
    ],
    taxExclusiveTotalPrice: [
      { required: true, message: "请输入不含税总价", trigger: "blur" },
      { type: "number", min: 0, message: "不含税总价必须大于0", trigger: "blur" },
    ],
    invoiceType: [{ required: true, message: "请选择发票类型", trigger: "blur" }],
// è§„格型号选择事件
const onSpecificationSelect = (item) => {
    productData.value[currentProductIndex.value].specificationModel = item.name
    productData.value[currentProductIndex.value].productModelId = item.value
    productData.value[currentProductIndex.value].unit = item.unit
    showSpecificationPicker.value = false;
};
    warnNum: [
      { required: true, message: "请输入库存预警数量", trigger: "blur" },
      {
        type: "number",
        min: 0,
        message: "库存预警数量必须大于0",
        trigger: "blur",
      },
    ],
  };
// ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
const onTaxRateSelect = (item) => {
    productData.value[currentProductIndex.value].taxRate = item.value
    showTaxRatePicker.value = false;
    // é‡æ–°è®¡ç®—不含税总价
    const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice)
    const taxRate = parseFloat(item.value)
    if (inclusiveTotalPrice && taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate)
    }
};
// å‘票类型选择事件
const onInvoiceTypeSelect = (item) => {
    productData.value[currentProductIndex.value].invoiceType = item.name
    showInvoiceTypePicker.value = false;
};
// æ ¼å¼åŒ–函数 - å›ºå®šä¸¤ä½å°æ•°
const formatTaxPrice = (idx) => {
  if (productData.value[idx].taxInclusiveUnitPrice) {
    const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
    if (!isNaN(value)) {
            productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2);
  const addProduct = () => {
    if (productData.value === null) {
      productData.value = [];
    }
  }
    if (!productData.value[currentProductIndex.value].taxRate) {
        uni.showToast({
            title: '请先选择税率',
            icon: 'none'
        });
        return;
    }
    const quantity = parseFloat(productData.value[currentProductIndex.value].quantity);
    const unitPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveUnitPrice);
    if (!quantity || quantity <= 0 || !unitPrice) {
        return;
    }
    // è®¡ç®—含税总价
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                productData.value[currentProductIndex.value].taxInclusiveTotalPrice,
                productData.value[currentProductIndex.value].taxRate
            );
    }
};
    productData.value.push({
      productCategory: "",
      specificationModel: "",
      productModelId: "",
      unit: "",
      taxRate: "",
      taxInclusiveUnitPrice: "",
      quantity: "",
      taxInclusiveTotalPrice: "",
      taxExclusiveTotalPrice: "",
      invoiceType: "",
      isChecked: false,
      warnNum: "",
    });
  };
// æ•°é‡è¾“入框失焦
const formatAmount = (idx) => {
  if (productData.value[idx].quantity) {
    const value = parseFloat(productData.value[idx].quantity);
    if (!isNaN(value)) {
            productData.value[idx].quantity = value.toFixed(2);
  // é”€å”®åˆåŒå·é€‰æ‹©äº‹ä»¶
  const onSalesmanSelect = item => {
    form.value.salesContractNo = item.name;
    // æŸ¥æ‰¾å¯¹åº”çš„id
    const selectedItem = salesContractList.value.find(
      contract => contract.text === item.name
    );
    if (selectedItem) {
      form.value.salesLedgerId = selectedItem.value;
    }
  }
    if (!productData.value[currentProductIndex.value].taxRate) {
        uni.showToast({
            title: '请先选择税率',
            icon: 'none'
        });
        return;
    }
    const quantity = parseFloat(productData.value[currentProductIndex.value].quantity);
    const unitPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveUnitPrice);
    if (!quantity || quantity <= 0 || !unitPrice) {
        return;
    }
    // è®¡ç®—含税总价
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                productData.value[currentProductIndex.value].taxInclusiveTotalPrice,
                productData.value[currentProductIndex.value].taxRate
            );
    }
};
    showPicker.value = false;
  };
// å«ç¨Žæ€»ä»·å¤±ç„¦ï¼Œæ ¹æ®å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const formatTaxTotal = (idx) => {
  if (productData.value[idx].taxInclusiveTotalPrice) {
    const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice);
    if (!isNaN(value)) {
            productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2);
  // ä¾›åº”商选择事件
  const onCustomerSelect = item => {
    form.value.supplierName = item.name;
    // æŸ¥æ‰¾å¯¹åº”çš„id
    const selectedItem = supplierList.value.find(
      supplier => supplier.text === item.name
    );
    if (selectedItem) {
      form.value.supplierId = selectedItem.value;
    }
  }
    const totalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice);
    const quantity = parseFloat(productData.value[currentProductIndex.value].quantity);
    if (!totalPrice || !quantity || quantity <= 0) {
        return;
    }
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                totalPrice,
                productData.value[currentProductIndex.value].taxRate
            );
    }
};
    showCustomerPicker.value = false;
  };
// ä¸å«ç¨Žæ€»ä»·å¤±ç„¦, æ ¹æ®ä¸å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const formatNoTaxTotal = (idx) => {
  if (productData.value[idx].taxExclusiveTotalPrice) {
    const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice);
    if (!isNaN(value)) {
            productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2);
  const removeProduct = idx => {
    productData.value.splice(idx, 1);
  };
  // æ˜¾ç¤ºé€‰æ‹©å™¨
  const openCategoryPicker = idx => {
    currentProductIndex.value = idx;
    showCategoryPicker.value = true;
  };
  const openSpecificationPicker = idx => {
    currentProductIndex.value = idx;
    showSpecificationPicker.value = true;
  };
  const openTaxRatePicker = idx => {
    currentProductIndex.value = idx;
    showTaxRatePicker.value = true;
  };
  const openInvoiceTypePicker = idx => {
    currentProductIndex.value = idx;
    showInvoiceTypePicker.value = true;
  };
  // é€‰æ‹©å™¨ç¡®è®¤äº‹ä»¶
  const onCategoryConfirm = node => {
    // èŽ·å–é€‰ä¸­çš„èŠ‚ç‚¹ä¿¡æ¯
    console.log("selected node---", node);
    // å­˜å‚¨é€‰ä¸­çš„节点,用于确认时获取数据
    selectedCategoryNode.value = node;
  };
  // ç¡®è®¤äº§å“å¤§ç±»é€‰æ‹©
  const confirmCategorySelection = () => {
    if (selectedCategoryNode.value) {
      // è®¾ç½®é€‰ä¸­çš„产品大类
      productData.value[currentProductIndex.value].productCategory =
        selectedCategoryNode.value.label;
      const id = selectedCategoryNode.value.id;
      // é‡ç½®é€‰ä¸­çš„节点
      selectedCategoryNode.value = null;
      productData.value[currentProductIndex.value].specificationModel = "";
      productData.value[currentProductIndex.value].productModelId = "";
      getModels(id);
    }
  }
    if (!productData.value[currentProductIndex.value].taxRate) {
        uni.showToast({
            title: '请先选择税率',
            icon: 'none'
        });
        return;
    }
    const exclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxExclusiveTotalPrice);
    const quantity = parseFloat(productData.value[currentProductIndex.value].quantity);
    const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate);
    if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
        return;
    }
    // å…ˆè®¡ç®—含税总价 = ä¸å«ç¨Žæ€»ä»· / (1 - ç¨Žçއ/100)
    const taxRateDecimal = taxRate / 100;
    const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
};
    showCategoryPicker.value = false;
  };
const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync('operationType');
    uni.removeStorageSync('editData');
    uni.navigateBack();
};
  // èŽ·å–è§„æ ¼åž‹å·
  const getModels = value => {
    modelList({ id: value }).then(res => {
      modelOptions.value = res.map(user => ({
        text: user.model,
        value: user.id,
        unit: user.unit,
      }));
    });
  };
const onSubmit = () => {
    if (productData.value !== null && productData.value.length > 0) {
        form.value.productData = JSON.parse(JSON.stringify(productData.value));
    } else {
        uni.showToast({
            title: '请添加产品信息',
            icon: 'none'
        });
        return
    }
    form.value.type = 2;
    addOrEditPurchase(form.value).then((res) => {
        uni.showToast({
            title: '提交成功',
            icon: 'success',
        });
        goBack();
    });
};
  // è§„格型号选择事件
  const onSpecificationSelect = item => {
    productData.value[currentProductIndex.value].specificationModel = item.name;
    productData.value[currentProductIndex.value].productModelId = item.value;
    productData.value[currentProductIndex.value].unit = item.unit;
    showSpecificationPicker.value = false;
  };
const setUserInfo = () => {
    form.value.recorderId = userStore.id;
    form.value.recorderName = userStore.nickName;
    // è®¾ç½®å½“天日期
    const today = new Date()
    const year = today.getFullYear()
    const month = String(today.getMonth() + 1).padStart(2, '0')
    const day = String(today.getDate()).padStart(2, '0')
    form.value.entryDate = `${year}-${month}-${day}`
};
  // ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
  const onTaxRateSelect = item => {
    productData.value[currentProductIndex.value].taxRate = item.value;
    showTaxRatePicker.value = false;
    // é‡æ–°è®¡ç®—不含税总价
    const inclusiveTotalPrice = parseFloat(
      productData.value[currentProductIndex.value].taxInclusiveTotalPrice
    );
    const taxRate = parseFloat(item.value);
    if (inclusiveTotalPrice && taxRate) {
      productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate);
    }
  };
// å¡«å……表单数据(编辑模式)
const fillFormData = () => {
  if (!editData.value) return;
    getPurchaseById({ id: editData.value.id, type: 2 }).then((res) => {
        productData.value = res.productData;
    });
    console.log(editData.value)
  // å¡«å……基本信息
  form.value.salesContractNo = editData.value.salesContractNo || '';
  form.value.supplierName = editData.value.supplierName || '';
  form.value.projectName = editData.value.projectName || '';
  form.value.paymentMethod = editData.value.paymentMethod || '';
  form.value.salesLedgerId = editData.value.salesLedgerId || '';
  form.value.recorderId = editData.value.recorderId || '';
  form.value.recorderName = editData.value.recorderName || '';
  form.value.entryDate = editData.value.entryDate || '';
  form.value.id = editData.value.id || '';
  form.value.supplierId = editData.value.supplierId || '';
};
  // å‘票类型选择事件
  const onInvoiceTypeSelect = item => {
    productData.value[currentProductIndex.value].invoiceType = item.name;
    showInvoiceTypePicker.value = false;
  };
const getSalesNoList = () => {
    getSalesNo().then((res) => {
        // å°†ç”¨æˆ·æ•°æ®ç»„装成 picker éœ€è¦çš„æ ¼å¼
        salesContractList.value = res.map(user => ({
            text: user.salesContractNo,
            value: user.id
        }));
    })
};
  // æ ¼å¼åŒ–函数 - å›ºå®šä¸¤ä½å°æ•°
  const formatTaxPrice = idx => {
    if (productData.value[idx].taxInclusiveUnitPrice) {
      const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
      if (!isNaN(value)) {
        productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2);
      }
    }
    if (!productData.value[currentProductIndex.value].taxRate) {
      uni.showToast({
        title: "请先选择税率",
        icon: "none",
      });
      return;
    }
    const quantity = parseFloat(
      productData.value[currentProductIndex.value].quantity
    );
    const unitPrice = parseFloat(
      productData.value[currentProductIndex.value].taxInclusiveUnitPrice
    );
const getOptionsLIst = () => {
    getOptions().then((res) => {
        // å°†ç”¨æˆ·æ•°æ®ç»„装成 picker éœ€è¦çš„æ ¼å¼
        supplierList.value = res.data.map(item => ({
            text: item.supplierName,
            value: item.id
        }));
    })
};
    if (!quantity || quantity <= 0 || !unitPrice) {
      return;
    }
    // è®¡ç®—含税总价
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (
      unitPrice * quantity
    ).toFixed(2);
const convertIdToValue = (data) => {
    // å¦‚果传入的不是数组,则返回空数组
    if (!Array.isArray(data)) {
        return [];
    }
    // é€’归映射函数
    return data.map(item => {
        // åˆ›å»ºæ–°å¯¹è±¡ï¼Œæ˜ å°„字段
        const mappedItem = {
            label: item.label, // å…³é”®ï¼šå°† label æ˜ å°„为 text
            id: item.id,       // ä¿ç•™ id
        };
        // å¦‚果存在 children æ•°ç»„,则递归处理
        if (item.children && Array.isArray(item.children) && item.children.length > 0) {
            mappedItem.children = convertIdToValue(item.children);
        }
        return mappedItem;
    });
};
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
      productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
          productData.value[currentProductIndex.value].taxInclusiveTotalPrice,
          productData.value[currentProductIndex.value].taxRate
        );
    }
  };
// èŽ·å–äº§å“å¤§ç±»tree数据
const getProductOptions = () => {
    productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res);
    });
};
  // æ•°é‡è¾“入框失焦
  const formatAmount = idx => {
    if (productData.value[idx].quantity) {
      const value = parseFloat(productData.value[idx].quantity);
      if (!isNaN(value)) {
        productData.value[idx].quantity = value.toFixed(2);
      }
    }
    if (!productData.value[currentProductIndex.value].taxRate) {
      uni.showToast({
        title: "请先选择税率",
        icon: "none",
      });
      return;
    }
    const quantity = parseFloat(
      productData.value[currentProductIndex.value].quantity
    );
    const unitPrice = parseFloat(
      productData.value[currentProductIndex.value].taxInclusiveUnitPrice
    );
onMounted(() => {
    // èŽ·å–é¡µé¢å‚æ•°
    operationType.value = uni.getStorageSync('operationType') || '';
    // èŽ·å–é”€å”®åˆåŒå·åˆ—è¡¨
    getSalesNoList()
    // èŽ·å–ä¾›åº”å•†åˆ—è¡¨
    getOptionsLIst()
    // èŽ·å–äº§å“å¤§ç±»tree数据
    getProductOptions()
    // èµ‹å€¼é»˜è®¤ä¿¡æ¯
    if (operationType.value === 'add') {
        setUserInfo()
        createPurchaseNo().then((res) => {
            form.value.purchaseContractNumber = res.data;
        });
    }
    // èŽ·å–ç¼–è¾‘æ•°æ®å¹¶å¡«å……è¡¨å•
    const editDataStr = uni.getStorageSync('editData');
    if (editDataStr) {
        try {
            editData.value = JSON.parse(editDataStr);
            // å¦‚果是编辑模式,等待数据加载完成后填充表单数据
            if (operationType.value !== 'add' && editData.value) {
                // ä½¿ç”¨ nextTick ç¡®ä¿æ•°æ®åŠ è½½å®ŒæˆåŽå†å¡«å……
                setTimeout(() => {
                    fillFormData();
                }, 100);
            }
        } catch (error) {
            console.error('解析编辑数据失败:', error);
        }
    }
});
    if (!quantity || quantity <= 0 || !unitPrice) {
      return;
    }
    // è®¡ç®—含税总价
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (
      unitPrice * quantity
    ).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
      productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
          productData.value[currentProductIndex.value].taxInclusiveTotalPrice,
          productData.value[currentProductIndex.value].taxRate
        );
    }
  };
  // å«ç¨Žæ€»ä»·å¤±ç„¦ï¼Œæ ¹æ®å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
  const formatTaxTotal = idx => {
    if (productData.value[idx].taxInclusiveTotalPrice) {
      const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice);
      if (!isNaN(value)) {
        productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2);
      }
    }
    const totalPrice = parseFloat(
      productData.value[currentProductIndex.value].taxInclusiveTotalPrice
    );
    const quantity = parseFloat(
      productData.value[currentProductIndex.value].quantity
    );
    if (!totalPrice || !quantity || quantity <= 0) {
      return;
    }
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (
      totalPrice / quantity
    ).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[currentProductIndex.value].taxRate) {
      productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
          totalPrice,
          productData.value[currentProductIndex.value].taxRate
        );
    }
  };
  // ä¸å«ç¨Žæ€»ä»·å¤±ç„¦, æ ¹æ®ä¸å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
  const formatNoTaxTotal = idx => {
    if (productData.value[idx].taxExclusiveTotalPrice) {
      const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice);
      if (!isNaN(value)) {
        productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2);
      }
    }
    if (!productData.value[currentProductIndex.value].taxRate) {
      uni.showToast({
        title: "请先选择税率",
        icon: "none",
      });
      return;
    }
    const exclusiveTotalPrice = parseFloat(
      productData.value[currentProductIndex.value].taxExclusiveTotalPrice
    );
    const quantity = parseFloat(
      productData.value[currentProductIndex.value].quantity
    );
    const taxRate = parseFloat(
      productData.value[currentProductIndex.value].taxRate
    );
    if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
      return;
    }
    // å…ˆè®¡ç®—含税总价 = ä¸å«ç¨Žæ€»ä»· / (1 - ç¨Žçއ/100)
    const taxRateDecimal = taxRate / 100;
    const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
    productData.value[currentProductIndex.value].taxInclusiveTotalPrice =
      inclusiveTotalPrice.toFixed(2);
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (
      inclusiveTotalPrice / quantity
    ).toFixed(2);
  };
  const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync("operationType");
    uni.removeStorageSync("editData");
    uni.navigateBack();
  };
  const onSubmit = () => {
    const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
    if (hasEmptyApprover) {
      uni.showToast({
        title: "请为所有审批节点选择审批人!",
        icon: "none",
      });
      return;
    }
    const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
    if (productData.value !== null && productData.value.length > 0) {
      form.value.productData = JSON.parse(JSON.stringify(productData.value));
    } else {
      uni.showToast({
        title: "请添加产品信息",
        icon: "none",
      });
      return;
    }
    // å¦‚æžœsalesLedgerId为空,则不传递salesContractNo
    if (!form.value.salesLedgerId) {
      form.value.salesContractNo = "";
    }
    if (operationType.value == "add") {
      delete form.value.id;
    }
    form.value.approveUserIds = approveUserIds;
    form.value.type = 2;
    addOrEditPurchase(form.value).then(res => {
      uni.showToast({
        title: "提交成功",
        icon: "success",
      });
      goBack();
    });
  };
  const setUserInfo = () => {
    form.value.recorderId = userStore.id;
    form.value.recorderName = userStore.nickName;
    // è®¾ç½®å½“天日期
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0");
    const day = String(today.getDate()).padStart(2, "0");
    form.value.entryDate = `${year}-${month}-${day}`;
  };
  // ç¡®è®¤æ—¥æœŸé€‰æ‹©
  const onDateConfirm = e => {
    form.value.executionDate = formatDateToYMD(e.value);
    currentDate.value = e.value;
    showTimePicker.value = false;
  };
  // å¡«å……表单数据(编辑模式)
  const fillFormData = () => {
    if (!editData.value) return;
    getPurchaseById({ id: editData.value.id, type: 2 }).then(res => {
      productData.value = res.productData;
      if (res && res.approveUserIds) {
        const userIds = res.approveUserIds.split(",");
        approverNodes.value = userIds.map((userId, idx) => {
          const userIdNum = parseInt(userId.trim());
          // ä»ŽuserList中找到对应的用户信息
          console.log(userList.value, "userList.value");
          const userInfo = userList.value.find(user => user.userId === userIdNum);
          return {
            id: idx + 1,
            userId: userIdNum,
            nickName: userInfo ? userInfo.nickName : null,
          };
        });
        nextApproverId = userIds.length + 1;
      } else {
        // æ–°å¢žæ¨¡å¼ï¼Œåˆå§‹åŒ–一个空的审批节点
        approverNodes.value = [{ id: 1, userId: null, nickName: null }];
        nextApproverId = 2;
      }
    });
    console.log(editData.value);
    // å¡«å……基本信息
    form.value.purchaseContractNumber =
      editData.value.purchaseContractNumber || "";
    form.value.salesContractNo = editData.value.salesContractNo || "";
    form.value.supplierName = editData.value.supplierName || "";
    form.value.projectName = editData.value.projectName || "";
    form.value.paymentMethod = editData.value.paymentMethod || "";
    form.value.salesLedgerId = editData.value.salesLedgerId || "";
    form.value.recorderId = editData.value.recorderId || "";
    form.value.recorderName = editData.value.recorderName || "";
    form.value.entryDate = editData.value.entryDate || "";
    form.value.id = editData.value.id || "";
    form.value.supplierId = editData.value.supplierId || "";
    form.value.executionDate = editData.value.executionDate || "";
  };
  const getSalesNoList = () => {
    getSalesNo().then(res => {
      // å°†ç”¨æˆ·æ•°æ®ç»„装成 picker éœ€è¦çš„æ ¼å¼
      salesContractList.value = res.map(user => ({
        text: user.salesContractNo,
        value: user.id,
      }));
    });
  };
  const getOptionsLIst = () => {
    getOptions().then(res => {
      // å°†ç”¨æˆ·æ•°æ®ç»„装成 picker éœ€è¦çš„æ ¼å¼
      supplierList.value = res.data.map(item => ({
        text: item.supplierName,
        value: item.id,
      }));
    });
  };
  const convertIdToValue = data => {
    // å¦‚果传入的不是数组,则返回空数组
    if (!Array.isArray(data)) {
      return [];
    }
    // é€’归映射函数
    return data.map(item => {
      // åˆ›å»ºæ–°å¯¹è±¡ï¼Œæ˜ å°„字段
      const mappedItem = {
        label: item.label, // å…³é”®ï¼šå°† label æ˜ å°„为 text
        id: item.id, // ä¿ç•™ id
      };
      // å¦‚果存在 children æ•°ç»„,则递归处理
      if (
        item.children &&
        Array.isArray(item.children) &&
        item.children.length > 0
      ) {
        mappedItem.children = convertIdToValue(item.children);
      }
      return mappedItem;
    });
  };
  // èŽ·å–äº§å“å¤§ç±»tree数据
  const getProductOptions = () => {
    productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
    });
  };
  const approverNodes = ref([]);
  let nextApproverId = 2;
  const userList = ref([]);
  onMounted(() => {
    // èŽ·å–é¡µé¢å‚æ•°
    operationType.value = uni.getStorageSync("operationType") || "";
    userListNoPageByTenantId().then(res => {
      userList.value = res.data;
    });
    // èŽ·å–é”€å”®åˆåŒå·åˆ—è¡¨
    getSalesNoList();
    // èŽ·å–ä¾›åº”å•†åˆ—è¡¨
    getOptionsLIst();
    // èŽ·å–äº§å“å¤§ç±»tree数据
    getProductOptions();
    // èµ‹å€¼é»˜è®¤ä¿¡æ¯
    if (operationType.value === "add") {
      setUserInfo();
      createPurchaseNo().then(res => {
        form.value.purchaseContractNumber = res.data;
      });
    }
    // ç›‘听联系人选择事件
    uni.$on("selectContact", handleSelectContact);
    // èŽ·å–ç¼–è¾‘æ•°æ®å¹¶å¡«å……è¡¨å•
    const editDataStr = uni.getStorageSync("editData");
    if (editDataStr) {
      try {
        editData.value = JSON.parse(editDataStr);
        // å¦‚果是编辑模式,等待数据加载完成后填充表单数据
        if (operationType.value !== "add" && editData.value) {
          // ä½¿ç”¨ nextTick ç¡®ä¿æ•°æ®åŠ è½½å®ŒæˆåŽå†å¡«å……
          setTimeout(() => {
            fillFormData();
          }, 100);
        }
      } catch (error) {
        console.error("解析编辑数据失败:", error);
      }
    } else {
      approverNodes.value = [{ id: 1, userId: null }];
    }
  });
  // å¤„理联系人选择结果
  const handleSelectContact = data => {
    const { stepIndex, contact } = data;
    // å°†é€‰ä¸­çš„联系人设置为对应审批步骤的审批人
    console.log(contact);
    console.log(stepIndex, "stepIndex");
    console.log(approverNodes.value[stepIndex], "审批人");
    approverNodes.value[stepIndex].userId = contact.userId;
    approverNodes.value[stepIndex].nickName = contact.nickName;
  };
  const addApprover = stepIndex => {
    // è·³è½¬åˆ°è”系人选择页面
    uni.setStorageSync("stepIndex", stepIndex);
    uni.navigateTo({
      url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect",
    });
  };
  const addApprovalStep = () => {
    // æ·»åŠ æ–°çš„å®¡æ‰¹æ­¥éª¤
    approverNodes.value.push({ userId: null, nickName: null });
    console.log(approverNodes.value, "approverNodes.value");
  };
  const removeApprover = stepIndex => {
    // ç§»é™¤å®¡æ‰¹äºº
    approverNodes.value[stepIndex].userId = null;
    approverNodes.value[stepIndex].nickName = null;
  };
  const removeApprovalStep = stepIndex => {
    // ç¡®ä¿è‡³å°‘保留一个审批步骤
    if (approverNodes.value.length > 1) {
      approverNodes.value.splice(stepIndex, 1);
    } else {
      uni.showToast({
        title: "至少需要一个审批步骤",
        icon: "none",
      });
    }
  };
</script>
<style scoped lang="scss">
@import '@/static/scss/form-common.scss';
</style>
  @import "@/static/scss/form-common.scss";
  .approval-process {
    background: #fff;
    margin: 16px;
    border-radius: 16px;
    padding: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  }
  .approval-header {
    margin-bottom: 16px;
  }
  .approval-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
  .approval-desc {
    font-size: 12px;
    color: #999;
  }
  /* æ ·å¼å¢žå¼ºä¸ºâ€œç®€æ´å°åœ†åœˆé£Žæ ¼â€ */
  .approval-steps {
    padding-left: 22px;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      left: 11px;
      top: 40px;
      bottom: 40px;
      width: 2px;
      background: linear-gradient(
        to bottom,
        #e6f7ff 0%,
        #bae7ff 50%,
        #91d5ff 100%
      );
      border-radius: 1px;
    }
  }
  .approval-step {
    position: relative;
    margin-bottom: 24px;
    &::before {
      content: "";
      position: absolute;
      left: -18px;
      top: 14px; // ä»Ž 8px è°ƒæ•´ä¸º 14px,与文字中心对齐
      width: 12px;
      height: 12px;
      background: #fff;
      border: 3px solid #006cfb;
      border-radius: 50%;
      z-index: 2;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }
  }
  .step-title {
    top: 12px;
    margin-bottom: 12px;
    position: relative;
    margin-left: 6px;
  }
  .step-title text {
    font-size: 14px;
    color: #666;
    background: #f0f0f0;
    padding: 4px 12px;
    border-radius: 12px;
    position: relative;
    line-height: 1.4; // ç¡®ä¿æ–‡å­—行高一致
  }
  .approver-item {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 16px;
    padding: 16px;
    gap: 12px;
    position: relative;
    border: 1px solid #e6f7ff;
    box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08);
    transition: all 0.3s ease;
  }
  .approver-avatar {
    width: 48px;
    height: 48px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  }
  .avatar-text {
    color: #fff;
    font-size: 18px;
    font-weight: 600;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  }
  .approver-info {
    flex: 1;
    position: relative;
  }
  .approver-name {
    display: block;
    font-size: 16px;
    color: #333;
    font-weight: 500;
    position: relative;
  }
  .approver-dept {
    font-size: 12px;
    color: #999;
    background: rgba(0, 108, 251, 0.05);
    padding: 2px 8px;
    border-radius: 8px;
    display: inline-block;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      left: 4px;
      top: 50%;
      transform: translateY(-50%);
      width: 2px;
      height: 2px;
      background: #006cfb;
      border-radius: 50%;
    }
  }
  .delete-approver-btn {
    font-size: 16px;
    color: #ff4d4f;
    background: linear-gradient(
      135deg,
      rgba(255, 77, 79, 0.1) 0%,
      rgba(255, 77, 79, 0.05) 100%
    );
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
    position: relative;
  }
  .add-approver-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
    border: 2px dashed #006cfb;
    border-radius: 16px;
    padding: 20px;
    color: #006cfb;
    font-size: 14px;
    position: relative;
    transition: all 0.3s ease;
    &::before {
      content: "";
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 32px;
      height: 32px;
      border: 2px solid #006cfb;
      border-radius: 50%;
      opacity: 0;
      transition: all 0.3s ease;
    }
  }
  .delete-step-btn {
    color: #ff4d4f;
    font-size: 12px;
    background: linear-gradient(
      135deg,
      rgba(255, 77, 79, 0.1) 0%,
      rgba(255, 77, 79, 0.05) 100%
    );
    padding: 6px 12px;
    border-radius: 12px;
    display: inline-block;
    position: relative;
    transition: all 0.3s ease;
    &::before {
      content: "";
      position: absolute;
      left: 6px;
      top: 50%;
      transform: translateY(-50%);
      width: 4px;
      height: 4px;
      background: #ff4d4f;
      border-radius: 50%;
    }
  }
  .step-line {
    display: none; // éšè—åŽŸæ¥çš„çº¿æ¡ï¼Œä½¿ç”¨ä¼ªå…ƒç´ ä»£æ›¿
  }
  .add-step-btn {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 6.375rem;
    background: #c7c9cc;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 14rem;
    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  // åŠ¨ç”»å®šä¹‰
  @keyframes pulse {
    0% {
      transform: scale(1);
      opacity: 1;
    }
    50% {
      transform: scale(1.2);
      opacity: 0.7;
    }
    100% {
      transform: scale(1);
      opacity: 1;
    }
  }
  @keyframes rotate {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
  @keyframes ripple {
    0% {
      transform: translate(-50%, -50%) scale(0.8);
      opacity: 1;
    }
    100% {
      transform: translate(-50%, -50%) scale(1.6);
      opacity: 0;
    }
  }
  /* å¦‚果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */
  .step-line {
    position: absolute;
    left: 4px;
    top: 48px;
    width: 2px;
    height: calc(100% - 48px);
    background: #e5e7eb;
  }
  .approver-container {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 16px;
    gap: 12px;
    padding: 10px 0;
    background: transparent;
    border: none;
    box-shadow: none;
  }
  .approver-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 10px;
    background: transparent;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }
  .approver-avatar {
    position: relative;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: #f3f4f6;
    border: 2px solid #e5e7eb;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: none; /* ç¦ç”¨æ—‹è½¬ç­‰åŠ¨ç”»ï¼Œå›žå½’ç®€æ´ */
  }
  .avatar-text {
    font-size: 14px;
    color: #374151;
    font-weight: 600;
  }
  .add-approver-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
  }
  .add-approver-btn .add-circle {
    width: 40px;
    height: 40px;
    border: 2px dashed #a0aec0;
    border-radius: 50%;
    color: #6b7280;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    line-height: 1;
  }
  .add-approver-btn .add-label {
    color: #3b82f6;
    font-size: 14px;
  }
</style>
src/pages/procurementManagement/procurementLedger/index.vue
@@ -1,193 +1,221 @@
<template>
    <view class="sales-account">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="采购台账" @back="goBack" />
        <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
        <view class="search-section">
            <view class="search-bar">
                <view class="search-input">
                    <up-input
                        class="search-text"
                        placeholder="请输入采购合同号搜索"
                        v-model="purchaseContractNumber"
                        @change="getList"
                        clearable
                    />
                </view>
                <view class="filter-button" @click="getList">
                    <up-icon name="search" size="24" color="#999"></up-icon>
                </view>
            </view>
        </view>
        <!-- é‡‡è´­å°è´¦ç€‘布流 -->
        <view class="ledger-list" v-if="ledgerList.length > 0">
            <view v-for="(item, index) in ledgerList" :key="index">
                <view class="ledger-item" @click="handleInfo('edit', item)">
                    <view class="item-header">
                        <view class="item-left">
                            <view class="document-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                            </view>
                            <text class="item-id">{{ item.purchaseContractNumber }}</text>
                        </view>
                        <!--                            <view class="item-tag">-->
                        <!--                                <text class="tag-text">{{ item.recorder }}</text>-->
                        <!--                            </view>-->
                    </view>
                    <up-divider></up-divider>
                    <view class="item-details">
                        <view class="detail-row">
                            <text class="detail-label">销售合同号</text>
                            <text class="detail-value">{{ item.salesContractNo }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">供应商名称</text>
                            <text class="detail-value">{{ item.supplierName }}</text>
                        </view>
                        <view class="detail-row">
                            <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">{{ item.paymentMethod }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">合同金额(元)</text>
                            <text class="detail-value highlight">{{ item.contractAmount }}</text>
                        </view>
                        <up-divider></up-divider>
                        <view class="detail-info">
                            <view class="detail-row">
                                <text class="detail-label">录入人</text>
                                <text class="detail-value">{{ item.recorderName }}</text>
                            </view>
                            <view class="detail-row">
                                <text class="detail-label">录入日期</text>
                                <text class="detail-value">{{ item.entryDate }}</text>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
        </view>
        <view v-else class="no-data">
            <text>暂无采购台账数据</text>
        </view>
        <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
        <view class="fab-button" @click="handleInfo('add')">
            <up-icon name="plus" size="24" color="#ffffff"></up-icon>
        </view>
    </view>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="采购台账"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入采购合同号搜索"
                    v-model="purchaseContractNumber"
                    @change="getList"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- é‡‡è´­å°è´¦ç€‘布流 -->
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item"
              @click="handleInfo('edit', item)">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.purchaseContractNumber }}</text>
            </view>
            <view class="item-tag">
              <u-tag :type="getApprovalStatusType(item.approvalStatus)">
                {{ approvalStatusText[item.approvalStatus] || '未知状态' }}
              </u-tag>
            </view>
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <text class="detail-label">销售合同号</text>
              <text class="detail-value">{{ item.salesContractNo }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">供应商名称</text>
              <text class="detail-value">{{ item.supplierName }}</text>
            </view>
            <view class="detail-row">
              <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">{{ item.paymentMethod }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">合同金额(元)</text>
              <text class="detail-value highlight">{{ item.contractAmount }}</text>
            </view>
            <up-divider></up-divider>
            <view class="detail-info">
              <view class="detail-row">
                <text class="detail-label">录入人</text>
                <text class="detail-value">{{ item.recorderName }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">录入日期</text>
                <text class="detail-value">{{ item.entryDate }}</text>
              </view>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无采购台账数据</text>
    </view>
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
    <view class="fab-button"
          @click="handleInfo('add')">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import useUserStore from "@/store/modules/user";
import PageHeader from "@/components/PageHeader.vue";
import {purchaseListPage} from "@/api/procurementManagement/procurementLedger";
const userStore = useUserStore()
  import { ref } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import useUserStore from "@/store/modules/user";
  import PageHeader from "@/components/PageHeader.vue";
  import { purchaseListPage } from "@/api/procurementManagement/procurementLedger";
  const userStore = useUserStore();
  const approvalStatusText = {
    1: "待审核",
    2: "审批中",
    3: "审批通过",
    4: "审批失败",
  };
  // èŽ·å–å®¡æ‰¹çŠ¶æ€æ ‡ç­¾ç±»åž‹
  const getApprovalStatusType = status => {
    const typeMap = {
      1: "info", // å¾…审核 - ç°è‰²
      2: "warning", // å®¡æ‰¹ä¸­ - æ©™è‰²
      3: "success", // å®¡æ‰¹é€šè¿‡ - ç»¿è‰²
      4: "error", // å®¡æ‰¹å¤±è´¥ - çº¢è‰²
    };
    return typeMap[status] || "";
  };
  // æœç´¢å…³é”®è¯
  const purchaseContractNumber = ref("");
// æœç´¢å…³é”®è¯
const purchaseContractNumber = ref('');
  // é‡‡è´­å°è´¦æ•°æ®
  const ledgerList = ref([]);
// é‡‡è´­å°è´¦æ•°æ®
const ledgerList = ref([]);
// è¿”回上一页
const goBack = () => {
    uni.navigateBack();
};
// æŸ¥è¯¢åˆ—表
const getList = () => {
    showLoadingToast('加载中...')
    const page = {
        current: -1,
        size: -1
    }
    purchaseListPage({...page, purchaseContractNumber: purchaseContractNumber.value}).then((res) => {
        ledgerList.value = res.data.records;
        closeToast()
    }).catch(() => {
        closeToast()
    });
};
// æ˜¾ç¤ºåŠ è½½æç¤º
const showLoadingToast = (message) => {
    uni.showLoading({
        title: message,
        mask: true
    });
};
// å…³é—­æç¤º
const closeToast = () => {
    uni.hideLoading();
};
// å¤„理台账信息操作(查看/编辑/新增)
const handleInfo = (type, row) => {
  try {
    // è®¾ç½®æ“ä½œç±»åž‹
    uni.setStorageSync('operationType', type);
    // å¦‚果是查看或编辑操作
    if (type !== 'add') {
      // éªŒè¯è¡Œæ•°æ®æ˜¯å¦å­˜åœ¨
      if (!row) {
        uni.showToast({
          title: '数据不存在',
          icon: 'error'
        });
        return;
      }
      // æ£€æŸ¥æƒé™ï¼šåªæœ‰å½•入人才能编辑
      if (row.recorderName != userStore.nickName) {
        // éžå½•入人跳转到只读详情页面
        uni.setStorageSync('editData', JSON.stringify(row));
        uni.navigateTo({
          url: '/pages/procurementManagement/procurementLedger/view'
        });
        return;
      }
      // å½•入人编辑:存储数据并跳转到编辑页面
      uni.setStorageSync('editData', JSON.stringify(row));
      uni.navigateTo({
        url: '/pages/procurementManagement/procurementLedger/detail'
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const page = {
      current: -1,
      size: -1,
    };
    purchaseListPage({
      ...page,
      purchaseContractNumber: purchaseContractNumber.value,
    })
      .then(res => {
        ledgerList.value = res.data.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
      return;
    }
    // æ–°å¢žæ“ä½œï¼šç›´æŽ¥è·³è½¬åˆ°ç¼–辑页面
    uni.navigateTo({
      url: '/pages/procurementManagement/procurementLedger/detail'
    });
  } catch (error) {
    console.error('处理台账信息操作失败:', error);
    uni.showToast({
      title: '操作失败,请重试',
      icon: 'error'
    });
  }
};
  };
onShow(() => {
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  // å¤„理台账信息操作(查看/编辑/新增)
  const handleInfo = (type, row) => {
    try {
      // è®¾ç½®æ“ä½œç±»åž‹
      uni.setStorageSync("operationType", type);
      // å¦‚果是查看或编辑操作
      if (type !== "add") {
        // éªŒè¯è¡Œæ•°æ®æ˜¯å¦å­˜åœ¨
        if (!row) {
          uni.showToast({
            title: "数据不存在",
            icon: "error",
          });
          return;
        }
        // æ£€æŸ¥æƒé™ï¼šåªæœ‰å½•入人才能编辑
        if (row.recorderName != userStore.nickName) {
          // éžå½•入人跳转到只读详情页面
          uni.setStorageSync("editData", JSON.stringify(row));
          uni.navigateTo({
            url: "/pages/procurementManagement/procurementLedger/view",
          });
          return;
        }
        // å½•入人编辑:存储数据并跳转到编辑页面
        uni.setStorageSync("editData", JSON.stringify(row));
        uni.navigateTo({
          url: "/pages/procurementManagement/procurementLedger/detail",
        });
        return;
      }
      // æ–°å¢žæ“ä½œï¼šç›´æŽ¥è·³è½¬åˆ°ç¼–辑页面
      uni.navigateTo({
        url: "/pages/procurementManagement/procurementLedger/detail",
      });
    } catch (error) {
      console.error("处理台账信息操作失败:", error);
      uni.showToast({
        title: "操作失败,请重试",
        icon: "error",
      });
    }
  };
  onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°åˆ—表
    getList();
});
  });
</script>
<style scoped lang="scss">
@import '@/styles/procurement-common.scss';
  @import "@/styles/procurement-common.scss";
// é‡‡è´­å°è´¦ç‰¹æœ‰æ ·å¼ï¼ˆå¦‚有需要可在此添加)
  // é‡‡è´­å°è´¦ç‰¹æœ‰æ ·å¼ï¼ˆå¦‚有需要可在此添加)
</style>
src/pages/sales/salesAccount/detail.vue
@@ -1,230 +1,213 @@
<template>
  <view class="account-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="台账详情" @back="goBack" />
         <!-- è¡¨å•区域 -->
        <up-form @submit="onSubmit" label-width="110" ref="formRef" :rules="rules" :model="form">
            <up-form-item label="销售合同号" prop="salesContractNo" >
                <up-input v-model="form.salesContractNo" placeholder="自动生成" disabled />
            </up-form-item>
            <up-form-item
                label="业务员"
                prop="salesman"
                required
                @click="showPicker = true"
            >
                <up-input
                    v-model="form.salesman"
                    readonly
                    @click="showPicker = true"
                    placeholder="点击选择业务员"
                />
                <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showPicker = true"
                    ></up-icon>
                </template>
            </up-form-item>
            <up-form-item label="客户合同号" prop="customerContractNo" required >
                <up-input
                    v-model="form.customerContractNo"
                    placeholder="请输入客户合同号"
                />
            </up-form-item>
            <up-form-item
                label="客户名称"
                prop="customerName"
                required
            >
                <up-input
                    v-model="form.customerName"
                    readonly
                    placeholder="点击选择客户"
                    @click="showCustomerPicker = true"
                />
                <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showCustomerPicker = true"
                    ></up-icon>
                </template>
            </up-form-item>
            <up-form-item label="项目名称" prop="projectName" required >
                <up-input v-model="form.projectName" placeholder="请输入项目名称" />
            </up-form-item>
            <up-form-item
                label="签订日期"
                prop="executionDate"
                required
            >
                <up-input
                    v-model="form.executionDate"
                    readonly
                    placeholder="点击选择时间"
                    @click="showDatePicker = true"
                />
                <template #right>
                    <up-icon
                        name="arrow-right"
                        @click="showDatePicker = true"
                    ></up-icon>
                </template>
            </up-form-item>
            <up-form-item label="付款方式" prop="paymentMethod" >
                <up-input v-model="form.paymentMethod" placeholder="请输入付款方式" />
            </up-form-item>
            <up-form-item label="录入人" prop="entryPersonName" >
                <up-input v-model="form.entryPersonName" placeholder="请输入" disabled />
            </up-form-item>
            <up-form-item label="录入日期" prop="entryDate" >
                <up-input v-model="form.entryDate" placeholder="请输入" disabled />
            </up-form-item>
            <!-- ä¸šåŠ¡å‘˜é€‰æ‹© -->
            <up-action-sheet
                :show="showPicker"
                :actions="userActionList"
                title="选择业务员"
                @select="onSalesmanSelect"
                @close="showPicker = false"
            />
            <!-- æ—¥æœŸé€‰æ‹© -->
            <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
                <up-datetime-picker
                    :show="true"
                    v-model="pickerDateValue"
                    @confirm="onDateConfirm"
                    @cancel="showDatePicker = false"
                    mode="date"
                />
            </up-popup>
            <!-- å®¢æˆ·é€‰æ‹© -->
            <up-action-sheet
                :show="showCustomerPicker"
                :actions="customerActionList"
                title="选择客户"
                @select="onCustomerSelect"
                @close="showCustomerPicker = false"
            />
            <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
            <up-popup :show="showCategoryPicker" mode="bottom">
                <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
                <view class="popup-header">
                    <view @click="showCategoryPicker = false" class="cancelButton">取消</view>
                    <view @click="confirmCategorySelection" class="confirmButton">确定</view>
                </view>
                <u-tree
                    :data="productOptions"
                    :props="defaultProps"
                    show-checkbox
                    default-expand-all
                    check-strictly
                    @check-change="onCategoryConfirm"
                />
            </up-popup>
            <!-- è§„格型号选择器 -->
            <up-action-sheet
                :show="showSpecificationPicker"
                :actions="specificationActionList"
                title="选择规格型号"
                @select="onSpecificationSelect"
                @close="showSpecificationPicker = false"
            />
            <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
            <up-action-sheet
                :show="showTaxRatePicker"
                :actions="taxRateActionList"
                title="选择税率"
                @select="onTaxRateSelect"
                @close="showTaxRatePicker = false"
            />
            <!-- å‘票类型选择器 -->
            <up-action-sheet
                :show="showInvoiceTypePicker"
                :actions="invoiceTypeActionList"
                title="选择发票类型"
                @select="onInvoiceTypeSelect"
                @close="showInvoiceTypePicker = false"
            />
            <!-- äº§å“ä¿¡æ¯ -->
            <view class="product-section">
                <view class="section-header">
                    <view>
                        <text class="section-title">产品信息</text>
                    </view>
                    <view>
                        <up-button type="primary" size="small" @click="addProduct" class="add-btn" v-if="operationType !== 'view'">
                            æ–°å¢ž
                        </up-button>
                    </view>
                </view>
                <view class="product-card" v-for="(product, idx) in productData" :key="idx">
                    <!-- äº§å“ç±» -->
                    <view class="product-header">
                        <view class="product-title">
                            <view class="document-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                            </view>
                            <text class="product-productCategory">产品 {{ idx + 1 }}</text>
                        </view>
                        <!-- æ“ä½œæŒ‰é’® -->
                        <view class="product-actions" v-if="operationType !== 'view'">
                            <up-button type="error" size="mini" @click="removeProduct(idx)" class="del-btn">
                                åˆ é™¤
                            </up-button>
                        </view>
                    </view>
                    <!-- äº§å“ä¿¡æ¯è¡¨å• -->
                    <view class="product-form">
                        <!-- äº§å“å¤§ç±» -->
                        <up-form-item
                            label="产品大类"
                            prop="productCategory"
                            required
                        >
                            <up-input
                                v-model="product.productCategory"
                                readonly
                                placeholder="请选择"
                                @click="openCategoryPicker(idx)"
                            />
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showCategoryPicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                        <!-- è§„格型号 -->
                        <up-form-item
                            label="规格型号"
                            prop="specificationModel"
                            required
                        >
                            <up-input
                                v-model="product.specificationModel"
                                readonly
                                placeholder="请选择"
                                @click="openSpecificationPicker(idx)"
                            />
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showSpecificationPicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                        <!-- ç»‘定机器 -->
                        <up-form-item
    <PageHeader title="台账详情"
                @back="goBack" />
    <!-- è¡¨å•区域 -->
    <up-form @submit="onSubmit"
             label-width="110"
             ref="formRef"
             :rules="rules"
             :model="form">
      <up-form-item label="销售合同号"
                    prop="salesContractNo">
        <up-input v-model="form.salesContractNo"
                  placeholder="自动生成"
                  disabled />
      </up-form-item>
      <up-form-item label="业务员"
                    prop="salesman"
                    required
                    @click="showPicker = true">
        <up-input v-model="form.salesman"
                  readonly
                  @click="showPicker = true"
                  placeholder="点击选择业务员" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="客户合同号"
                    prop="customerContractNo"
                    required>
        <up-input v-model="form.customerContractNo"
                  placeholder="请输入客户合同号" />
      </up-form-item>
      <up-form-item label="客户名称"
                    prop="customerName"
                    required>
        <up-input v-model="form.customerName"
                  readonly
                  placeholder="点击选择客户"
                  @click="showCustomerPicker = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showCustomerPicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="项目名称"
                    prop="projectName"
                    required>
        <up-input v-model="form.projectName"
                  placeholder="请输入项目名称" />
      </up-form-item>
      <up-form-item label="签订日期"
                    prop="executionDate"
                    required>
        <up-input v-model="form.executionDate"
                  readonly
                  placeholder="点击选择时间"
                  @click="showDatePicker = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showDatePicker = true"></up-icon>
        </template>
      </up-form-item>
      <up-form-item label="付款方式"
                    prop="paymentMethod">
        <up-input v-model="form.paymentMethod"
                  placeholder="请输入付款方式" />
      </up-form-item>
      <up-form-item label="录入人"
                    prop="entryPersonName">
        <up-input v-model="form.entryPersonName"
                  placeholder="请输入"
                  disabled />
      </up-form-item>
      <up-form-item label="录入日期"
                    prop="entryDate">
        <up-input v-model="form.entryDate"
                  placeholder="请输入"
                  disabled />
      </up-form-item>
      <!-- ä¸šåŠ¡å‘˜é€‰æ‹© -->
      <up-action-sheet :show="showPicker"
                       :actions="userActionList"
                       title="选择业务员"
                       @select="onSalesmanSelect"
                       @close="showPicker = false" />
      <!-- æ—¥æœŸé€‰æ‹© -->
      <up-popup :show="showDatePicker"
                mode="bottom"
                @close="showDatePicker = false">
        <up-datetime-picker :show="true"
                            v-model="pickerDateValue"
                            @confirm="onDateConfirm"
                            @cancel="showDatePicker = false"
                            mode="date" />
      </up-popup>
      <!-- å®¢æˆ·é€‰æ‹© -->
      <up-action-sheet :show="showCustomerPicker"
                       :actions="customerActionList"
                       title="选择客户"
                       @select="onCustomerSelect"
                       @close="showCustomerPicker = false" />
      <!-- äº§å“å¤§ç±»é€‰æ‹©å™¨ -->
      <up-popup :show="showCategoryPicker"
                mode="bottom">
        <!-- å¤´éƒ¨æŒ‰é’®åŒºåŸŸ -->
        <view class="popup-header">
          <view @click="showCategoryPicker = false"
                class="cancelButton">取消</view>
          <view @click="confirmCategorySelection"
                class="confirmButton">确定</view>
        </view>
        <u-tree :data="productOptions"
                :props="defaultProps"
                show-checkbox
                default-expand-all
                check-strictly
                @check-change="onCategoryConfirm" />
      </up-popup>
      <!-- è§„格型号选择器 -->
      <up-action-sheet :show="showSpecificationPicker"
                       :actions="specificationActionList"
                       title="选择规格型号"
                       @select="onSpecificationSelect"
                       @close="showSpecificationPicker = false" />
      <!-- ç¨ŽçŽ‡é€‰æ‹©å™¨ -->
      <up-action-sheet :show="showTaxRatePicker"
                       :actions="taxRateActionList"
                       title="选择税率"
                       @select="onTaxRateSelect"
                       @close="showTaxRatePicker = false" />
      <!-- å‘票类型选择器 -->
      <up-action-sheet :show="showInvoiceTypePicker"
                       :actions="invoiceTypeActionList"
                       title="选择发票类型"
                       @select="onInvoiceTypeSelect"
                       @close="showInvoiceTypePicker = false" />
      <!-- äº§å“ä¿¡æ¯ -->
      <view class="product-section">
        <view class="section-header">
          <view>
            <text class="section-title">产品信息</text>
          </view>
          <view>
            <up-button type="primary"
                       size="small"
                       @click="addProduct"
                       class="add-btn"
                       v-if="operationType !== 'view'">
              æ–°å¢ž
            </up-button>
          </view>
        </view>
        <view class="product-card"
              v-for="(product, idx) in productData"
              :key="idx">
          <!-- äº§å“ç±» -->
          <view class="product-header">
            <view class="product-title">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="product-productCategory">产品 {{ idx + 1 }}</text>
            </view>
            <!-- æ“ä½œæŒ‰é’® -->
            <view class="product-actions"
                  v-if="operationType !== 'view'">
              <up-button type="error"
                         size="mini"
                         @click="removeProduct(idx)"
                         class="del-btn">
                åˆ é™¤
              </up-button>
            </view>
          </view>
          <!-- äº§å“ä¿¡æ¯è¡¨å• -->
          <view class="product-form">
            <!-- äº§å“å¤§ç±» -->
            <up-form-item label="产品大类"
                          prop="productCategory"
                          required>
              <up-input v-model="product.productCategory"
                        readonly
                        placeholder="请选择"
                        @click="openCategoryPicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showCategoryPicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- è§„格型号 -->
            <up-form-item label="规格型号"
                          prop="specificationModel"
                          required>
              <up-input v-model="product.specificationModel"
                        readonly
                        placeholder="请选择"
                        @click="openSpecificationPicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showSpecificationPicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- ç»‘定机器 -->
            <!-- <up-form-item
                            label="绑定机器"
                            prop="speculativeTradingName"
                            required
@@ -234,760 +217,730 @@
                                v-model="product.speculativeTradingName"
                                placeholder="请输入"
                            />
                        </up-form-item>
                        <!-- å•位 -->
                        <up-form-item
                            label="单位"
                            prop="unit"
                            required
                        >
                            <up-input
                                v-model="product.unit"
                                placeholder="请输入"
                            />
                        </up-form-item>
                        <!-- ç¨Žçއ -->
                        <up-form-item
                            label="税率(%)"
                            prop="taxRate"
                            required
                        >
                            <up-input
                                v-model="product.taxRate"
                                readonly
                                placeholder="请选择"
                                @click="openTaxRatePicker(idx)"
                            />
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showTaxRatePicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                        <!-- å«ç¨Žå•ä»· -->
                        <up-form-item
                            label="含税单价(元)"
                            prop="taxInclusiveUnitPrice"
                            required
                        >
                            <up-input
                                v-model="product.taxInclusiveUnitPrice"
                                type="number"
                                placeholder="请输入"
                                @blur="formatTaxPrice(idx)"
                            />
                        </up-form-item>
                        <!-- æ•°é‡ -->
                        <up-form-item
                            label="数量"
                            prop="quantity"
                            required
                        >
                            <up-input
                                v-model="product.quantity"
                                type="number"
                                placeholder="请输入"
                                @blur="formatAmount(idx)"
                            />
                        </up-form-item>
                        <!-- å«ç¨Žæ€»ä»· -->
                        <up-form-item
                            label="含税总价(元)"
                            prop="taxInclusiveTotalPrice"
                            required
                        >
                            <up-input
                                v-model="product.taxInclusiveTotalPrice"
                                type="number"
                                placeholder="请输入"
                                @blur="formatTaxTotal(idx)"
                            />
                        </up-form-item>
                        <!-- ä¸å«ç¨Žæ€»ä»· -->
                        <up-form-item
                            label="不含税总价(元)"
                            prop="taxExclusiveTotalPrice"
                            required
                        >
                            <up-input
                                v-model="product.taxExclusiveTotalPrice"
                                type="number"
                                placeholder="请输入"
                                @blur="formatNoTaxTotal(idx)"
                            />
                        </up-form-item>
                        <!-- å‘票类型 -->
                        <up-form-item
                            label="发票类型"
                            prop="invoiceType"
                            required
                        >
                            <up-input
                                v-model="product.invoiceType"
                                readonly
                                placeholder="请选择"
                                @click="openInvoiceTypePicker(idx)"
                            />
                            <template #right>
                                <up-icon
                                    name="arrow-right"
                                    @click="showInvoiceTypePicker = true"
                                ></up-icon>
                            </template>
                        </up-form-item>
                    </view>
                </view>
            </view>
        </up-form>
        <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
        <FooterButtons
            :show="operationType !== 'view'"
            cancelText="取消"
            confirmText="保存"
            @cancel="goBack"
            @confirm="onSubmit"
        />
                        </up-form-item> -->
            <!-- å•位 -->
            <up-form-item label="单位"
                          prop="unit"
                          required>
              <up-input v-model="product.unit"
                        placeholder="请输入" />
            </up-form-item>
            <!-- ç¨Žçއ -->
            <up-form-item label="税率(%)"
                          prop="taxRate"
                          required>
              <up-input v-model="product.taxRate"
                        readonly
                        placeholder="请选择"
                        @click="openTaxRatePicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showTaxRatePicker = true"></up-icon>
              </template>
            </up-form-item>
            <!-- å«ç¨Žå•ä»· -->
            <up-form-item label="含税单价(元)"
                          prop="taxInclusiveUnitPrice"
                          required>
              <up-input v-model="product.taxInclusiveUnitPrice"
                        type="number"
                        placeholder="请输入"
                        @blur="formatTaxPrice(idx)" />
            </up-form-item>
            <!-- æ•°é‡ -->
            <up-form-item label="数量"
                          prop="quantity"
                          required>
              <up-input v-model="product.quantity"
                        type="number"
                        placeholder="请输入"
                        @blur="formatAmount(idx)" />
            </up-form-item>
            <!-- å«ç¨Žæ€»ä»· -->
            <up-form-item label="含税总价(元)"
                          prop="taxInclusiveTotalPrice"
                          required>
              <up-input v-model="product.taxInclusiveTotalPrice"
                        type="number"
                        placeholder="请输入"
                        @blur="formatTaxTotal(idx)" />
            </up-form-item>
            <!-- ä¸å«ç¨Žæ€»ä»· -->
            <up-form-item label="不含税总价(元)"
                          prop="taxExclusiveTotalPrice"
                          required>
              <up-input v-model="product.taxExclusiveTotalPrice"
                        type="number"
                        placeholder="请输入"
                        @blur="formatNoTaxTotal(idx)" />
            </up-form-item>
            <!-- å‘票类型 -->
            <up-form-item label="发票类型"
                          prop="invoiceType"
                          required>
              <up-input v-model="product.invoiceType"
                        readonly
                        placeholder="请选择"
                        @click="openInvoiceTypePicker(idx)" />
              <template #right>
                <up-icon name="arrow-right"
                         @click="showInvoiceTypePicker = true"></up-icon>
              </template>
            </up-form-item>
          </view>
        </view>
      </view>
    </up-form>
    <!-- ä½¿ç”¨å…¬å…±åº•部按钮组件 -->
    <FooterButtons :show="operationType !== 'view'"
                   cancelText="取消"
                   confirmText="保存"
                   @cancel="goBack"
                   @confirm="onSubmit" />
  </view>
</template>
<script setup>
import {onMounted, ref, computed} from 'vue';
import {userListNoPage} from "@/api/system/user";
import { formatDateToYMD } from '@/utils/ruoyi'
import {
    addOrUpdateSalesLedger,
    customerList,
    getSalesLedgerWithProducts,
    modelList,
    productTreeList
} from "@/api/salesManagement/salesLedger";
import useUserStore from "@/store/modules/user";
import {calculateTaxExclusiveTotalPrice} from "@/utils/summarizeTable";
import PageHeader from '@/components/PageHeader.vue';
import FooterButtons from '@/components/FooterButtons.vue';
  import { onMounted, ref, computed } from "vue";
  import { userListNoPage } from "@/api/system/user";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import {
    addOrUpdateSalesLedger,
    customerList,
    getSalesLedgerWithProducts,
    modelList,
    productTreeList,
  } from "@/api/salesManagement/salesLedger";
  import useUserStore from "@/store/modules/user";
  import { calculateTaxExclusiveTotalPrice } from "@/utils/summarizeTable";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
// èŽ·å–é¡µé¢å‚æ•°
const operationType = ref('');
const editData = ref(null);
const formRef = ref(null);
  // èŽ·å–é¡µé¢å‚æ•°
  const operationType = ref("");
  const editData = ref(null);
  const formRef = ref(null);
const userStore = useUserStore()
const form = ref({
    id: '',
  salesContractNo: '',
    customerContractNo: '',
    customerId: '',
    customerName: '',
    projectName: '',
    executionDate: '',
  paymentMethod: '',
    entryPerson: '',
    entryPersonName: '',
    entryDate: '',
});
const showPicker = ref(false);
const showDatePicker = ref(false);
const pickerDateValue = ref(Date.now());
const showCustomerPicker = ref(false);
const userList = ref([]);
const customerOption = ref([]);
const userActionList = computed(() => {
    return userList.value.map(user => ({
        name: user.text,
        value: user.value
    }))
})
const formatter = (type, value) => {
    if (type === 'year') {
        return `${value}`;
    }
    if (type === 'month') {
        return `${value}`;
    }
    if (type === 'day') {
        return `${value}`;
    }
    return value;
};
const customerActionList = computed(() => {
    return customerOption.value.map(customer => ({
        name: customer.text,
        value: customer.value
    }))
})
// æ—¥æœŸé€‰æ‹©åˆ—表已移除,改用 up-datetime-picker
// äº§å“å¤§ç±»é€‰æ‹©åˆ—表
const categoryActionList = computed(() => {
    const flattenCategories = (categories, result = []) => {
        categories.forEach(category => {
            result.push({
                name: category.label,
                value: category.id
            })
            if (category.children && category.children.length > 0) {
                flattenCategories(category.children, result)
            }
        })
        return result
    }
    return flattenCategories(productOptions.value)
})
// è§„格型号选择列表
const specificationActionList = computed(() => {
    return modelOptions.value.map(model => ({
        name: model.text,
        value: model.value,
        unit: model.unit,
        speculativeTradingName: model.speculativeTradingName
    }))
})
// ç¨ŽçŽ‡é€‰æ‹©åˆ—è¡¨
const taxRateActionList = computed(() => {
    return taxRateOptions.value.map(rate => ({
        name: rate.text,
        value: rate.value
    }))
})
// å‘票类型选择列表
const invoiceTypeActionList = computed(() => {
    return invoiceTypeOptions.value.map(type => ({
        name: type.text,
        value: type.value
    }))
})
const productData = ref([]);
// é€‰æ‹©å™¨ç›¸å…³å˜é‡
const showCategoryPicker = ref(false);
const showSpecificationPicker = ref(false);
const showTaxRatePicker = ref(false);
const showInvoiceTypePicker = ref(false);
// é€‰æ‹©å™¨æ˜¾ç¤ºçŠ¶æ€å˜é‡å·²åœ¨ä¸Šé¢å®šä¹‰
// ä¸´æ—¶å˜é‡å·²ä¸å†éœ€è¦
const currentProductIndex = ref(0);
// é€‰é¡¹æ•°æ®
const productOptions = ref([]);
const selectedCategoryNode = ref(null);
const defaultProps = ref({
    children: 'children',
    label: 'label',
    nodeKey: 'id'
});
const modelOptions = ref([]);
// é˜²æ­¢å¾ªçŽ¯è®¡ç®—çš„æ ‡å¿—
// const isCalculating = ref(false);
const taxRateOptions = ref([
  { text: '1', value: '1' },
  { text: '6', value: '6' },
  { text: '13', value: '13' },
]);
const invoiceTypeOptions = ref([
  { text: '增普票', value: '增普票' },
  { text: '增专票', value: '增专票' },
]);
// è¡¨å•校验规则
const rules = {
    salesman: [
        { required: true, message: '请选择业务员', trigger: 'change' }
    ],
    customerContractNo: [
        { required: true, message: '请输入客户合同号', trigger: 'blur' }
    ],
    customerName: [
        { required: true, message: '请选择客户名称', trigger: 'change' }
    ],
    projectName: [
        { required: true, message: '请输入项目名称', trigger: 'blur' }
    ],
    executionDate: [
        { required: true, message: '请选择签订日期', trigger: 'change' }
    ]
};
const addProduct = () => {
    if (productData.value === null) {
        productData.value = []
    }
    productData.value.push({
    productCategory: '',
    specificationModel: '',
        productModelId: '',
    unit: '',
    speculativeTradingName: '',
    taxRate: '',
    taxInclusiveUnitPrice: '',
    quantity: '',
    taxInclusiveTotalPrice: '',
    taxExclusiveTotalPrice: '',
    invoiceType: ''
  const userStore = useUserStore();
  const form = ref({
    id: "",
    salesContractNo: "",
    customerContractNo: "",
    customerId: "",
    customerName: "",
    projectName: "",
    executionDate: "",
    paymentMethod: "",
    entryPerson: "",
    entryPersonName: "",
    entryDate: "",
  });
};
// ä¸šåŠ¡å‘˜é€‰æ‹©äº‹ä»¶
const onSalesmanSelect = (item) => {
    form.value.salesman = item.name
}
// æ—¥æœŸç¡®è®¤äº‹ä»¶
const onDateConfirm = (e) => {
    form.value.executionDate = formatDateToYMD(e.value)
    // ä¿æŒpickerDateValue为时间戳格式,而不是格式化的字符串
    pickerDateValue.value = e.value
    showDatePicker.value = false;
}
// å®¢æˆ·é€‰æ‹©äº‹ä»¶
const onCustomerSelect = (item) => {
    form.value.customerName = item.name
    form.value.customerId = item.value
}
// åŽŸæœ‰çš„ç¡®è®¤æ–¹æ³•å·²è¢«æ–°çš„action-sheet选择方法替代
const removeProduct = (idx) => {
    productData.value.splice(idx, 1);
};
// æ˜¾ç¤ºé€‰æ‹©å™¨
const openCategoryPicker = (idx) => {
  currentProductIndex.value = idx;
  showCategoryPicker.value = true;
};
const openSpecificationPicker = (idx) => {
  currentProductIndex.value = idx;
  showSpecificationPicker.value = true;
};
const openTaxRatePicker = (idx) => {
  currentProductIndex.value = idx;
  showTaxRatePicker.value = true;
};
const openInvoiceTypePicker = (idx) => {
  currentProductIndex.value = idx;
  showInvoiceTypePicker.value = true;
};
// é€‰æ‹©å™¨ç¡®è®¤äº‹ä»¶
const onCategoryConfirm = (node) => {
    // èŽ·å–é€‰ä¸­çš„èŠ‚ç‚¹ä¿¡æ¯
    console.log('selected node---', node);
    // å­˜å‚¨é€‰ä¸­çš„节点,用于确认时获取数据
    selectedCategoryNode.value = node;
};
// ç¡®è®¤äº§å“å¤§ç±»é€‰æ‹©
const confirmCategorySelection = () => {
    if (selectedCategoryNode.value) {
        // è®¾ç½®é€‰ä¸­çš„产品大类
        productData.value[currentProductIndex.value].productCategory = selectedCategoryNode.value.label;
        const id = selectedCategoryNode.value.id
        // é‡ç½®é€‰ä¸­çš„节点
        selectedCategoryNode.value = null;
        productData.value[currentProductIndex.value].specificationModel = ''
        productData.value[currentProductIndex.value].productModelId = ''
        getModels(id)
    }
    showCategoryPicker.value = false;
};
// èŽ·å–è§„æ ¼åž‹å·
const getModels = (value) => {
    modelList({ id: value }).then((res) => {
        modelOptions.value = res.map(user => ({
            text: user.model,
            value: user.id,
            unit: user.unit,
            speculativeTradingName: user.speculativeTradingName,
        }));
    });
};
// è§„格型号选择事件
const onSpecificationSelect = (item) => {
console.log('selected item---', item);
    productData.value[currentProductIndex.value].specificationModel = item.name
    productData.value[currentProductIndex.value].productModelId = item.value
    productData.value[currentProductIndex.value].unit = item.unit
    productData.value[currentProductIndex.value].speculativeTradingName = item.speculativeTradingName
}
// ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
const onTaxRateSelect = (item) => {
    productData.value[currentProductIndex.value].taxRate = item.value
    // é‡æ–°è®¡ç®—不含税总价
    const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice)
    const taxRate = parseFloat(item.value)
    if (inclusiveTotalPrice && taxRate) {
        productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate)
    }
};
// å‘票类型选择事件
const onInvoiceTypeSelect = (item) => {
    productData.value[currentProductIndex.value].invoiceType = item.name
};
// æ ¼å¼åŒ–函数 - å›ºå®šä¸¤ä½å°æ•°
const formatTaxPrice = (idx) => {
  if (productData.value[idx].taxInclusiveUnitPrice) {
    const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
    if (!isNaN(value)) {
        productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2);
  const showPicker = ref(false);
  const showDatePicker = ref(false);
  const pickerDateValue = ref(Date.now());
  const showCustomerPicker = ref(false);
  const userList = ref([]);
  const customerOption = ref([]);
  const userActionList = computed(() => {
    return userList.value.map(user => ({
      name: user.text,
      value: user.value,
    }));
  });
  const formatter = (type, value) => {
    if (type === "year") {
      return `${value}`;
    }
  }
    if (!productData.value[idx].taxRate) {
        uni.showToast({
            title: '请先选择税率',
            icon: 'none'
        });
        return;
    }
    const quantity = parseFloat(productData.value[idx].quantity);
    const unitPrice = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
    if (!quantity || quantity <= 0 || !unitPrice) {
        return;
    }
    // è®¡ç®—含税总价
    productData.value[idx].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[idx].taxRate) {
        productData.value[idx].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                productData.value[idx].taxInclusiveTotalPrice,
                productData.value[idx].taxRate
            );
    }
};
// æ•°é‡è¾“入框失焦
const formatAmount = (idx) => {
  if (productData.value[idx].quantity) {
    const value = parseFloat(productData.value[idx].quantity);
    if (!isNaN(value)) {
        productData.value[idx].quantity = value.toFixed(2);
    if (type === "month") {
      return `${value}`;
    }
  }
    if (!productData.value[idx].taxRate) {
        uni.showToast({
            title: '请先选择税率',
            icon: 'none'
        });
        return;
    }
    const quantity = parseFloat(productData.value[idx].quantity);
    const unitPrice = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
    if (!quantity || quantity <= 0 || !unitPrice) {
        return;
    }
    // è®¡ç®—含税总价
    productData.value[idx].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[idx].taxRate) {
        productData.value[idx].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                productData.value[idx].taxInclusiveTotalPrice,
                productData.value[idx].taxRate
            );
    }
};
// å«ç¨Žæ€»ä»·å¤±ç„¦ï¼Œæ ¹æ®å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const formatTaxTotal = (idx) => {
  if (productData.value[idx].taxInclusiveTotalPrice) {
    const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice);
    if (!isNaN(value)) {
        productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2);
    if (type === "day") {
      return `${value}`;
    }
  }
    const totalPrice = parseFloat(productData.value[idx].taxInclusiveTotalPrice);
    const quantity = parseFloat(productData.value[idx].quantity);
    if (!totalPrice || !quantity || quantity <= 0) {
        return;
    }
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[idx].taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[idx].taxRate) {
        productData.value[idx].taxExclusiveTotalPrice =
            calculateTaxExclusiveTotalPrice(
                totalPrice,
                productData.value[idx].taxRate
            );
    }
};
// ä¸å«ç¨Žæ€»ä»·å¤±ç„¦, æ ¹æ®ä¸å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
const formatNoTaxTotal = (idx) => {
  if (productData.value[idx].taxExclusiveTotalPrice) {
    const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice);
    if (!isNaN(value)) {
        productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2);
    }
  }
    if (!productData.value[idx].taxRate) {
        uni.showToast({
            title: '请先选择税率',
            icon: 'none'
        });
        return;
    }
    const exclusiveTotalPrice = parseFloat(productData.value[idx].taxExclusiveTotalPrice);
    const quantity = parseFloat(productData.value[idx].quantity);
    const taxRate = parseFloat(productData.value[idx].taxRate);
    if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
        return;
    }
    // å…ˆè®¡ç®—含税总价 = ä¸å«ç¨Žæ€»ä»· / (1 - ç¨Žçއ/100)
    const taxRateDecimal = taxRate / 100;
    const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
    productData.value[idx].taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2);
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[idx].taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2);
};
const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync('operationType');
    uni.removeStorageSync('editData');
    uni.navigateBack();
};
const onSubmit = async () => {
    // é¦–先校验基本表单
    const formValid = await formRef.value.validate().catch(() => false);
    if (!formValid) {
        return;
    }
    // æ ¡éªŒäº§å“ä¿¡æ¯
    if (!productData.value || productData.value.length === 0) {
        uni.showToast({
            title: '请添加产品信息',
            icon: 'none'
        });
        return;
    }
    // æ£€æŸ¥æ¯ä¸ªäº§å“æ˜¯å¦å¡«å†™å®Œæ•´
    for (let i = 0; i < productData.value.length; i++) {
        const product = productData.value[i];
        // ä¼˜åŒ–数字字段验证,处理可能的字符串格式数值
        const taxInclusiveUnitPrice = parseFloat(product.taxInclusiveUnitPrice);
        const quantity = parseFloat(product.quantity);
        const taxInclusiveTotalPrice = parseFloat(product.taxInclusiveTotalPrice);
        const taxExclusiveTotalPrice = parseFloat(product.taxExclusiveTotalPrice);
        if (!product.productCategory) {
            uni.showToast({
                title: `产品${i + 1}:请选择产品大类`,
                icon: 'none'
            });
            return;
        }
        if (!product.specificationModel) {
            uni.showToast({
                title: `产品${i + 1}:请选择规格型号`,
                icon: 'none'
            });
            return;
        }
        if (!product.unit) {
            uni.showToast({
                title: `产品${i + 1}:请输入单位`,
                icon: 'none'
            });
            return;
        }
        if (!product.taxRate) {
            uni.showToast({
                title: `产品${i + 1}:请选择税率`,
                icon: 'none'
            });
            return;
        }
        if (isNaN(taxInclusiveUnitPrice) || taxInclusiveUnitPrice <= 0) {
            uni.showToast({
                title: `产品${i + 1}:请输入有效的含税单价`,
                icon: 'none'
            });
            return;
        }
        if (isNaN(quantity) || quantity <= 0) {
            uni.showToast({
                title: `产品${i + 1}:请输入有效的数量`,
                icon: 'none'
            });
            return;
        }
        if (isNaN(taxInclusiveTotalPrice) || taxInclusiveTotalPrice <= 0) {
            uni.showToast({
                title: `产品${i + 1}:请输入有效的含税总价`,
                icon: 'none'
            });
            return;
        }
        if (isNaN(taxExclusiveTotalPrice) || taxExclusiveTotalPrice <= 0) {
            uni.showToast({
                title: `产品${i + 1}:请输入有效的不含税总价`,
                icon: 'none'
            });
            return;
        }
        if (!product.invoiceType) {
            uni.showToast({
                title: `产品${i + 1}:请选择发票类型`,
                icon: 'none'
            });
            return;
        }
    }
    // è¡¨å•校验通过,提交数据
    form.value.productData = JSON.parse(JSON.stringify(productData.value));
    form.value.type = 1;
    addOrUpdateSalesLedger(form.value).then((res) => {
        uni.showToast({
            title: '提交成功',
            icon: 'success',
        });
        goBack();
    });
};
const setUserInfo = () => {
    form.value.entryPerson = userStore.id;
    form.value.entryPersonName = userStore.nickName;
    // è®¾ç½®å½“天日期
    const today = new Date()
    const year = today.getFullYear()
    const month = String(today.getMonth() + 1).padStart(2, '0')
    const day = String(today.getDate()).padStart(2, '0')
    form.value.entryDate = `${year}-${month}-${day}`
    // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨é»˜è®¤å€¼ä¸ºä»Šå¤©
    pickerDateValue.value = `${year}-${month}-${day}`
}
// å¡«å……表单数据(编辑模式)
const fillFormData = () => {
  if (!editData.value) return;
    getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then((res) => {
        productData.value = res.productData;
    });
    console.log(editData.value)
  // å¡«å……基本信息
  form.value.salesContractNo = editData.value.salesContractNo || '';
  form.value.customerContractNo = editData.value.customerContractNo || '';
  form.value.customerName = editData.value.customerName || '';
  form.value.projectName = editData.value.projectName || '';
  form.value.executionDate = editData.value.executionDate || '';
  form.value.paymentMethod = editData.value.paymentMethod || '';
  form.value.salesman = editData.value.salesman || '';
  form.value.entryPerson = editData.value.entryPerson || '';
  form.value.entryPersonName = editData.value.entryPersonName || '';
  form.value.entryDate = editData.value.entryDate || '';
  form.value.id = editData.value.id || '';
  form.value.customerId = editData.value.customerId || '';
  // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨çš„值
  if (editData.value.executionDate) {
        pickerDateValue.value = editData.value.executionDate
    }
};
const getUserList = () => {
    userListNoPage().then((res) => {
        // ç§»é™¤å¤šä½™çš„æ•°ç»„包装
        userList.value = res.data.map(user => ({
            text: user.nickName,
            value: user.nickName
        }));
    })
};
const getCustomerList = () => {
    customerList().then((res) => {
        // ç§»é™¤å¤šä½™çš„æ•°ç»„包装
        customerOption.value = res.map(item => ({
            text: item.customerName,
            value: item.id
        }));
    })
};
const convertIdToValue = (data) => {
    // å¦‚果传入的不是数组,则返回空数组
    if (!Array.isArray(data)) {
        return [];
    }
    // é€’归映射函数
    return data.map(item => {
        // åˆ›å»ºæ–°å¯¹è±¡ï¼Œæ˜ å°„字段
        const mappedItem = {
            label: item.label, // å…³é”®ï¼šå°† label æ˜ å°„为 text
            id: item.id,       // ä¿ç•™ id
        };
        // å¦‚果存在 children æ•°ç»„,则递归处理
        if (item.children && Array.isArray(item.children) && item.children.length > 0) {
            mappedItem.children = convertIdToValue(item.children);
        }
        return mappedItem;
    });
};
// èŽ·å–äº§å“å¤§ç±»tree数据
const getProductOptions = () => {
    productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res);
    });
};
    return value;
  };
  const customerActionList = computed(() => {
    return customerOption.value.map(customer => ({
      name: customer.text,
      value: customer.value,
    }));
  });
  // æ—¥æœŸé€‰æ‹©åˆ—表已移除,改用 up-datetime-picker
onMounted(() => {
    // èŽ·å–é¡µé¢å‚æ•°
    operationType.value = uni.getStorageSync('operationType') || '';
    // èŽ·å–äººå‘˜åˆ—è¡¨
    getUserList()
    // èŽ·å–å®¢æˆ·åˆ—è¡¨
    getCustomerList()
    // èŽ·å–äº§å“å¤§ç±»tree数据
    getProductOptions()
    // èµ‹å€¼é»˜è®¤ä¿¡æ¯
    if (operationType.value === 'add') {
        setUserInfo()
    }
    // èŽ·å–ç¼–è¾‘æ•°æ®å¹¶å¡«å……è¡¨å•
    const editDataStr = uni.getStorageSync('editData');
    if (editDataStr) {
        try {
            editData.value = JSON.parse(editDataStr);
            // å¦‚果是编辑模式,等待数据加载完成后填充表单数据
            if (operationType.value !== 'add' && editData.value) {
                // ä½¿ç”¨ nextTick ç¡®ä¿æ•°æ®åŠ è½½å®ŒæˆåŽå†å¡«å……
                setTimeout(() => {
                    fillFormData();
                }, 100);
            }
        } catch (error) {
            console.error('解析编辑数据失败:', error);
        }
    }
});
  // äº§å“å¤§ç±»é€‰æ‹©åˆ—表
  const categoryActionList = computed(() => {
    const flattenCategories = (categories, result = []) => {
      categories.forEach(category => {
        result.push({
          name: category.label,
          value: category.id,
        });
        if (category.children && category.children.length > 0) {
          flattenCategories(category.children, result);
        }
      });
      return result;
    };
    return flattenCategories(productOptions.value);
  });
  // è§„格型号选择列表
  const specificationActionList = computed(() => {
    return modelOptions.value.map(model => ({
      name: model.text,
      value: model.value,
      unit: model.unit,
      speculativeTradingName: model.speculativeTradingName,
    }));
  });
  // ç¨ŽçŽ‡é€‰æ‹©åˆ—è¡¨
  const taxRateActionList = computed(() => {
    return taxRateOptions.value.map(rate => ({
      name: rate.text,
      value: rate.value,
    }));
  });
  // å‘票类型选择列表
  const invoiceTypeActionList = computed(() => {
    return invoiceTypeOptions.value.map(type => ({
      name: type.text,
      value: type.value,
    }));
  });
  const productData = ref([]);
  // é€‰æ‹©å™¨ç›¸å…³å˜é‡
  const showCategoryPicker = ref(false);
  const showSpecificationPicker = ref(false);
  const showTaxRatePicker = ref(false);
  const showInvoiceTypePicker = ref(false);
  // é€‰æ‹©å™¨æ˜¾ç¤ºçŠ¶æ€å˜é‡å·²åœ¨ä¸Šé¢å®šä¹‰
  // ä¸´æ—¶å˜é‡å·²ä¸å†éœ€è¦
  const currentProductIndex = ref(0);
  // é€‰é¡¹æ•°æ®
  const productOptions = ref([]);
  const selectedCategoryNode = ref(null);
  const defaultProps = ref({
    children: "children",
    label: "label",
    nodeKey: "id",
  });
  const modelOptions = ref([]);
  // é˜²æ­¢å¾ªçŽ¯è®¡ç®—çš„æ ‡å¿—
  // const isCalculating = ref(false);
  const taxRateOptions = ref([
    { text: "1", value: "1" },
    { text: "6", value: "6" },
    { text: "13", value: "13" },
  ]);
  const invoiceTypeOptions = ref([
    { text: "增普票", value: "增普票" },
    { text: "增专票", value: "增专票" },
  ]);
  // è¡¨å•校验规则
  const rules = {
    salesman: [{ required: true, message: "请选择业务员", trigger: "change" }],
    customerContractNo: [
      { required: true, message: "请输入客户合同号", trigger: "blur" },
    ],
    customerName: [
      { required: true, message: "请选择客户名称", trigger: "change" },
    ],
    projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
    executionDate: [
      { required: true, message: "请选择签订日期", trigger: "change" },
    ],
  };
  const addProduct = () => {
    if (productData.value === null) {
      productData.value = [];
    }
    productData.value.push({
      productCategory: "",
      specificationModel: "",
      productModelId: "",
      unit: "",
      speculativeTradingName: "",
      taxRate: "",
      taxInclusiveUnitPrice: "",
      quantity: "",
      taxInclusiveTotalPrice: "",
      taxExclusiveTotalPrice: "",
      invoiceType: "",
    });
  };
  // ä¸šåŠ¡å‘˜é€‰æ‹©äº‹ä»¶
  const onSalesmanSelect = item => {
    form.value.salesman = item.name;
  };
  // æ—¥æœŸç¡®è®¤äº‹ä»¶
  const onDateConfirm = e => {
    form.value.executionDate = formatDateToYMD(e.value);
    // ä¿æŒpickerDateValue为时间戳格式,而不是格式化的字符串
    pickerDateValue.value = e.value;
    showDatePicker.value = false;
  };
  // å®¢æˆ·é€‰æ‹©äº‹ä»¶
  const onCustomerSelect = item => {
    form.value.customerName = item.name;
    form.value.customerId = item.value;
  };
  // åŽŸæœ‰çš„ç¡®è®¤æ–¹æ³•å·²è¢«æ–°çš„action-sheet选择方法替代
  const removeProduct = idx => {
    productData.value.splice(idx, 1);
  };
  // æ˜¾ç¤ºé€‰æ‹©å™¨
  const openCategoryPicker = idx => {
    currentProductIndex.value = idx;
    showCategoryPicker.value = true;
  };
  const openSpecificationPicker = idx => {
    currentProductIndex.value = idx;
    showSpecificationPicker.value = true;
  };
  const openTaxRatePicker = idx => {
    currentProductIndex.value = idx;
    showTaxRatePicker.value = true;
  };
  const openInvoiceTypePicker = idx => {
    currentProductIndex.value = idx;
    showInvoiceTypePicker.value = true;
  };
  // é€‰æ‹©å™¨ç¡®è®¤äº‹ä»¶
  const onCategoryConfirm = node => {
    // èŽ·å–é€‰ä¸­çš„èŠ‚ç‚¹ä¿¡æ¯
    console.log("selected node---", node);
    // å­˜å‚¨é€‰ä¸­çš„节点,用于确认时获取数据
    selectedCategoryNode.value = node;
  };
  // ç¡®è®¤äº§å“å¤§ç±»é€‰æ‹©
  const confirmCategorySelection = () => {
    if (selectedCategoryNode.value) {
      // è®¾ç½®é€‰ä¸­çš„产品大类
      productData.value[currentProductIndex.value].productCategory =
        selectedCategoryNode.value.label;
      const id = selectedCategoryNode.value.id;
      // é‡ç½®é€‰ä¸­çš„节点
      selectedCategoryNode.value = null;
      productData.value[currentProductIndex.value].specificationModel = "";
      productData.value[currentProductIndex.value].productModelId = "";
      getModels(id);
    }
    showCategoryPicker.value = false;
  };
  // èŽ·å–è§„æ ¼åž‹å·
  const getModels = value => {
    modelList({ id: value }).then(res => {
      modelOptions.value = res.map(user => ({
        text: user.model,
        value: user.id,
        unit: user.unit,
        speculativeTradingName: user.speculativeTradingName,
      }));
    });
  };
  // è§„格型号选择事件
  const onSpecificationSelect = item => {
    console.log("selected item---", item);
    productData.value[currentProductIndex.value].specificationModel = item.name;
    productData.value[currentProductIndex.value].productModelId = item.value;
    productData.value[currentProductIndex.value].unit = item.unit;
    productData.value[currentProductIndex.value].speculativeTradingName =
      item.speculativeTradingName;
  };
  // ç¨ŽçŽ‡é€‰æ‹©äº‹ä»¶
  const onTaxRateSelect = item => {
    productData.value[currentProductIndex.value].taxRate = item.value;
    // é‡æ–°è®¡ç®—不含税总价
    const inclusiveTotalPrice = parseFloat(
      productData.value[currentProductIndex.value].taxInclusiveTotalPrice
    );
    const taxRate = parseFloat(item.value);
    if (inclusiveTotalPrice && taxRate) {
      productData.value[currentProductIndex.value].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate);
    }
  };
  // å‘票类型选择事件
  const onInvoiceTypeSelect = item => {
    productData.value[currentProductIndex.value].invoiceType = item.name;
  };
  // æ ¼å¼åŒ–函数 - å›ºå®šä¸¤ä½å°æ•°
  const formatTaxPrice = idx => {
    if (productData.value[idx].taxInclusiveUnitPrice) {
      const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
      if (!isNaN(value)) {
        productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2);
      }
    }
    if (!productData.value[idx].taxRate) {
      uni.showToast({
        title: "请先选择税率",
        icon: "none",
      });
      return;
    }
    const quantity = parseFloat(productData.value[idx].quantity);
    const unitPrice = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
    if (!quantity || quantity <= 0 || !unitPrice) {
      return;
    }
    // è®¡ç®—含税总价
    productData.value[idx].taxInclusiveTotalPrice = (
      unitPrice * quantity
    ).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[idx].taxRate) {
      productData.value[idx].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
          productData.value[idx].taxInclusiveTotalPrice,
          productData.value[idx].taxRate
        );
    }
  };
  // æ•°é‡è¾“入框失焦
  const formatAmount = idx => {
    if (productData.value[idx].quantity) {
      const value = parseFloat(productData.value[idx].quantity);
      if (!isNaN(value)) {
        productData.value[idx].quantity = value.toFixed(2);
      }
    }
    if (!productData.value[idx].taxRate) {
      uni.showToast({
        title: "请先选择税率",
        icon: "none",
      });
      return;
    }
    const quantity = parseFloat(productData.value[idx].quantity);
    const unitPrice = parseFloat(productData.value[idx].taxInclusiveUnitPrice);
    if (!quantity || quantity <= 0 || !unitPrice) {
      return;
    }
    // è®¡ç®—含税总价
    productData.value[idx].taxInclusiveTotalPrice = (
      unitPrice * quantity
    ).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[idx].taxRate) {
      productData.value[idx].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
          productData.value[idx].taxInclusiveTotalPrice,
          productData.value[idx].taxRate
        );
    }
  };
  // å«ç¨Žæ€»ä»·å¤±ç„¦ï¼Œæ ¹æ®å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
  const formatTaxTotal = idx => {
    if (productData.value[idx].taxInclusiveTotalPrice) {
      const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice);
      if (!isNaN(value)) {
        productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2);
      }
    }
    const totalPrice = parseFloat(productData.value[idx].taxInclusiveTotalPrice);
    const quantity = parseFloat(productData.value[idx].quantity);
    if (!totalPrice || !quantity || quantity <= 0) {
      return;
    }
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[idx].taxInclusiveUnitPrice = (
      totalPrice / quantity
    ).toFixed(2);
    // å¦‚果有税率,计算不含税总价
    if (productData.value[idx].taxRate) {
      productData.value[idx].taxExclusiveTotalPrice =
        calculateTaxExclusiveTotalPrice(
          totalPrice,
          productData.value[idx].taxRate
        );
    }
  };
  // ä¸å«ç¨Žæ€»ä»·å¤±ç„¦, æ ¹æ®ä¸å«ç¨Žæ€»ä»·è®¡ç®—含税单价和数量
  const formatNoTaxTotal = idx => {
    if (productData.value[idx].taxExclusiveTotalPrice) {
      const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice);
      if (!isNaN(value)) {
        productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2);
      }
    }
    if (!productData.value[idx].taxRate) {
      uni.showToast({
        title: "请先选择税率",
        icon: "none",
      });
      return;
    }
    const exclusiveTotalPrice = parseFloat(
      productData.value[idx].taxExclusiveTotalPrice
    );
    const quantity = parseFloat(productData.value[idx].quantity);
    const taxRate = parseFloat(productData.value[idx].taxRate);
    if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) {
      return;
    }
    // å…ˆè®¡ç®—含税总价 = ä¸å«ç¨Žæ€»ä»· / (1 - ç¨Žçއ/100)
    const taxRateDecimal = taxRate / 100;
    const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal);
    productData.value[idx].taxInclusiveTotalPrice =
      inclusiveTotalPrice.toFixed(2);
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productData.value[idx].taxInclusiveUnitPrice = (
      inclusiveTotalPrice / quantity
    ).toFixed(2);
  };
  const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync("operationType");
    uni.removeStorageSync("editData");
    uni.navigateBack();
  };
  const onSubmit = async () => {
    // é¦–先校验基本表单
    const formValid = await formRef.value.validate().catch(() => false);
    if (!formValid) {
      return;
    }
    // æ ¡éªŒäº§å“ä¿¡æ¯
    if (!productData.value || productData.value.length === 0) {
      uni.showToast({
        title: "请添加产品信息",
        icon: "none",
      });
      return;
    }
    // æ£€æŸ¥æ¯ä¸ªäº§å“æ˜¯å¦å¡«å†™å®Œæ•´
    for (let i = 0; i < productData.value.length; i++) {
      const product = productData.value[i];
      // ä¼˜åŒ–数字字段验证,处理可能的字符串格式数值
      const taxInclusiveUnitPrice = parseFloat(product.taxInclusiveUnitPrice);
      const quantity = parseFloat(product.quantity);
      const taxInclusiveTotalPrice = parseFloat(product.taxInclusiveTotalPrice);
      const taxExclusiveTotalPrice = parseFloat(product.taxExclusiveTotalPrice);
      if (!product.productCategory) {
        uni.showToast({
          title: `产品${i + 1}:请选择产品大类`,
          icon: "none",
        });
        return;
      }
      if (!product.specificationModel) {
        uni.showToast({
          title: `产品${i + 1}:请选择规格型号`,
          icon: "none",
        });
        return;
      }
      if (!product.unit) {
        uni.showToast({
          title: `产品${i + 1}:请输入单位`,
          icon: "none",
        });
        return;
      }
      if (!product.taxRate) {
        uni.showToast({
          title: `产品${i + 1}:请选择税率`,
          icon: "none",
        });
        return;
      }
      if (isNaN(taxInclusiveUnitPrice) || taxInclusiveUnitPrice <= 0) {
        uni.showToast({
          title: `产品${i + 1}:请输入有效的含税单价`,
          icon: "none",
        });
        return;
      }
      if (isNaN(quantity) || quantity <= 0) {
        uni.showToast({
          title: `产品${i + 1}:请输入有效的数量`,
          icon: "none",
        });
        return;
      }
      if (isNaN(taxInclusiveTotalPrice) || taxInclusiveTotalPrice <= 0) {
        uni.showToast({
          title: `产品${i + 1}:请输入有效的含税总价`,
          icon: "none",
        });
        return;
      }
      if (isNaN(taxExclusiveTotalPrice) || taxExclusiveTotalPrice <= 0) {
        uni.showToast({
          title: `产品${i + 1}:请输入有效的不含税总价`,
          icon: "none",
        });
        return;
      }
      if (!product.invoiceType) {
        uni.showToast({
          title: `产品${i + 1}:请选择发票类型`,
          icon: "none",
        });
        return;
      }
    }
    // è¡¨å•校验通过,提交数据
    form.value.productData = JSON.parse(JSON.stringify(productData.value));
    form.value.type = 1;
    addOrUpdateSalesLedger(form.value).then(res => {
      uni.showToast({
        title: "提交成功",
        icon: "success",
      });
      goBack();
    });
  };
  const setUserInfo = () => {
    form.value.entryPerson = userStore.id;
    form.value.entryPersonName = userStore.nickName;
    // è®¾ç½®å½“天日期
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0");
    const day = String(today.getDate()).padStart(2, "0");
    form.value.entryDate = `${year}-${month}-${day}`;
    // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨é»˜è®¤å€¼ä¸ºä»Šå¤©
    pickerDateValue.value = `${year}-${month}-${day}`;
  };
  // å¡«å……表单数据(编辑模式)
  const fillFormData = () => {
    if (!editData.value) return;
    getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then(res => {
      productData.value = res.productData;
    });
    console.log(editData.value);
    // å¡«å……基本信息
    form.value.salesContractNo = editData.value.salesContractNo || "";
    form.value.customerContractNo = editData.value.customerContractNo || "";
    form.value.customerName = editData.value.customerName || "";
    form.value.projectName = editData.value.projectName || "";
    form.value.executionDate = editData.value.executionDate || "";
    form.value.paymentMethod = editData.value.paymentMethod || "";
    form.value.salesman = editData.value.salesman || "";
    form.value.entryPerson = editData.value.entryPerson || "";
    form.value.entryPersonName = editData.value.entryPersonName || "";
    form.value.entryDate = editData.value.entryDate || "";
    form.value.id = editData.value.id || "";
    form.value.customerId = editData.value.customerId || "";
    // è®¾ç½®æ—¥æœŸé€‰æ‹©å™¨çš„值
    if (editData.value.executionDate) {
      pickerDateValue.value = editData.value.executionDate;
    }
  };
  const getUserList = () => {
    userListNoPage().then(res => {
      // ç§»é™¤å¤šä½™çš„æ•°ç»„包装
      userList.value = res.data.map(user => ({
        text: user.nickName,
        value: user.nickName,
      }));
    });
  };
  const getCustomerList = () => {
    customerList().then(res => {
      // ç§»é™¤å¤šä½™çš„æ•°ç»„包装
      customerOption.value = res.map(item => ({
        text: item.customerName,
        value: item.id,
      }));
    });
  };
  const convertIdToValue = data => {
    // å¦‚果传入的不是数组,则返回空数组
    if (!Array.isArray(data)) {
      return [];
    }
    // é€’归映射函数
    return data.map(item => {
      // åˆ›å»ºæ–°å¯¹è±¡ï¼Œæ˜ å°„字段
      const mappedItem = {
        label: item.label, // å…³é”®ï¼šå°† label æ˜ å°„为 text
        id: item.id, // ä¿ç•™ id
      };
      // å¦‚果存在 children æ•°ç»„,则递归处理
      if (
        item.children &&
        Array.isArray(item.children) &&
        item.children.length > 0
      ) {
        mappedItem.children = convertIdToValue(item.children);
      }
      return mappedItem;
    });
  };
  // èŽ·å–äº§å“å¤§ç±»tree数据
  const getProductOptions = () => {
    productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
    });
  };
  onMounted(() => {
    // èŽ·å–é¡µé¢å‚æ•°
    operationType.value = uni.getStorageSync("operationType") || "";
    // èŽ·å–äººå‘˜åˆ—è¡¨
    getUserList();
    // èŽ·å–å®¢æˆ·åˆ—è¡¨
    getCustomerList();
    // èŽ·å–äº§å“å¤§ç±»tree数据
    getProductOptions();
    // èµ‹å€¼é»˜è®¤ä¿¡æ¯
    if (operationType.value === "add") {
      setUserInfo();
    }
    // èŽ·å–ç¼–è¾‘æ•°æ®å¹¶å¡«å……è¡¨å•
    const editDataStr = uni.getStorageSync("editData");
    if (editDataStr) {
      try {
        editData.value = JSON.parse(editDataStr);
        // å¦‚果是编辑模式,等待数据加载完成后填充表单数据
        if (operationType.value !== "add" && editData.value) {
          // ä½¿ç”¨ nextTick ç¡®ä¿æ•°æ®åŠ è½½å®ŒæˆåŽå†å¡«å……
          setTimeout(() => {
            fillFormData();
          }, 100);
        }
      } catch (error) {
        console.error("解析编辑数据失败:", error);
      }
    }
  });
</script>
<style lang="scss">
@import '@/static/scss/form-common.scss';
  @import "@/static/scss/form-common.scss";
</style>
src/pages/sales/salesAccount/goOut.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,657 @@
<template>
  <view class="account-detail">
    <PageHeader title="发货"
                @back="goBack" />
    <!-- è¡¨å•区域 -->
    <u-form ref="formRef"
            @submit="submitForm"
            :rules="rules"
            :model="form"
            label-width="140rpx">
      <u-form-item prop="typeValue"
                   label="发货类型"
                   required>
        <u-input v-model="typeValue"
                 readonly
                 placeholder="请选择发货方式"
                 @click="showPicker = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showPicker = true"></up-icon>
        </template>
      </u-form-item>
    </u-form>
    <!-- é€‰æ‹©å™¨å¼¹çª— -->
    <up-action-sheet :show="showPicker"
                     :actions="productOptions"
                     title="发货方式"
                     @select="onConfirm"
                     @close="showPicker = false" />
    <!-- å®¡æ ¸æµç¨‹åŒºåŸŸ -->
    <view class="approval-process">
      <view class="approval-header">
        <text class="approval-title">审核流程</text>
        <text class="approval-desc">每个步骤只能选择一个审批人</text>
      </view>
      <view class="approval-steps">
        <view v-for="(step, stepIndex) in approverNodes"
              :key="stepIndex"
              class="approval-step">
          <view class="step-dot"></view>
          <view class="step-title">
            <text>审批人</text>
          </view>
          <view class="approver-container">
            <view v-if="step.nickName"
                  class="approver-item">
              <view class="approver-avatar">
                <text class="avatar-text">{{ step.nickName.charAt(0) }}</text>
                <view class="status-dot"></view>
              </view>
              <view class="approver-info">
                <text class="approver-name">{{ step.nickName }}</text>
              </view>
              <view class="delete-approver-btn"
                    @click="removeApprover(stepIndex)">×</view>
            </view>
            <view v-else
                  class="add-approver-btn"
                  @click="addApprover(stepIndex)">
              <view class="add-circle">+</view>
              <text class="add-label">选择审批人</text>
            </view>
          </view>
          <view class="step-line"
                v-if="stepIndex < approverNodes.length - 1"></view>
          <view class="delete-step-btn"
                v-if="approverNodes.length > 1"
                @click="removeApprovalStep(stepIndex)">删除节点</view>
        </view>
      </view>
      <view class="add-step-btn">
        <u-button icon="plus"
                  plain
                  type="primary"
                  style="width: 100%"
                  @click="addApprovalStep">新增节点</u-button>
      </view>
    </view>
    <!-- åº•部按钮 -->
    <view class="footer-btns">
      <u-button class="cancel-btn"
                @click="goBack">取消</u-button>
      <u-button class="save-btn"
                @click="submitForm">发货</u-button>
    </view>
  </view>
</template>
<script setup>
  import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { addShippingInfo } from "@/api/salesManagement/salesLedger";
  const showToast = message => {
    uni.showToast({
      title: message,
      icon: "none",
    });
  };
  import { userListNoPageByTenantId } from "@/api/system/user";
  const data = reactive({
    form: {
      approveTime: "",
      approveId: "",
      approveUser: "",
      approveUserName: "",
      approveDeptName: "",
      approveDeptId: "",
      approveReason: "",
      checkResult: "",
      tempFileIds: [],
      approverList: [], // æ–°å¢žå­—段,存储所有节点的审批人id
      startDate: "",
      endDate: "",
      location: "",
      price: "",
    },
    rules: {
      typeValue: [{ required: false, message: "请选择", trigger: "change" }],
    },
  });
  const { form, rules } = toRefs(data);
  const showPicker = ref(false);
  const productOptions = ref([
    {
      value: "货车",
      name: "货车",
    },
    {
      value: "快递",
      name: "快递",
    },
  ]);
  const operationType = ref("");
  const currentApproveStatus = ref("");
  const approverNodes = ref([]);
  const userList = ref([]);
  const formRef = ref(null);
  const approveType = ref(0);
  const goOutData = ref({});
  onMounted(async () => {
    try {
      userListNoPageByTenantId().then(res => {
        userList.value = res.data;
      });
      // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–å‘è´§è¯¦æƒ…
      goOutData.value = JSON.parse(uni.getStorageSync("goOutData"));
      console.log(goOutData.value, "goOutData.value");
      // åˆå§‹åŒ–审批流程节点,默认一个节点
      approverNodes.value = [{ id: 1, userId: null }];
      // ç›‘听联系人选择事件
      uni.$on("selectContact", handleSelectContact);
    } catch (error) {
      console.error("获取失败:", error);
    }
  });
  onUnmounted(() => {
    // ç§»é™¤äº‹ä»¶ç›‘听
    uni.$off("selectContact", handleSelectContact);
  });
  const typeValue = ref("货车");
  const onConfirm = item => {
    // è®¾ç½®é€‰ä¸­çš„部门
    typeValue.value = item.name;
    showPicker.value = false;
  };
  const goBack = () => {
    // æ¸…除本地存储的数据
    uni.removeStorageSync("operationType");
    uni.removeStorageSync("invoiceLedgerEditRow");
    uni.removeStorageSync("approveType");
    uni.navigateBack();
  };
  const submitForm = () => {
    // æ£€æŸ¥æ¯ä¸ªå®¡æ‰¹æ­¥éª¤æ˜¯å¦éƒ½æœ‰å®¡æ‰¹äºº
    const hasEmptyStep = approverNodes.value.some(step => !step.nickName);
    if (hasEmptyStep) {
      showToast("请为每个审批步骤选择审批人");
      return;
    }
    formRef.value
      .validate()
      .then(valid => {
        if (valid) {
          // è¡¨å•校验通过,可以提交数据
          // æ”¶é›†æ‰€æœ‰èŠ‚ç‚¹çš„å®¡æ‰¹äººid
          console.log("approverNodes---", approverNodes.value);
          const approveUserIds = approverNodes.value
            .map(node => node.userId)
            .join(",");
          const params = {
            salesLedgerId: goOutData.value.salesLedgerId,
            salesLedgerProductId: goOutData.value.id,
            type: typeValue.value,
            approveUserIds,
          };
          console.log(params, "params");
          addShippingInfo(params).then(res => {
            showToast("发货成功");
            setTimeout(() => {
              goBack();
            }, 500);
          });
        }
      })
      .catch(error => {
        console.error("表单校验失败:", error);
        // å°è¯•获取具体的错误字段
        if (error && error.errors) {
          const firstError = error.errors[0];
          if (firstError) {
            uni.showToast({
              title: firstError.message || "表单校验失败,请检查必填项",
              icon: "none",
            });
            return;
          }
        }
        // æ˜¾ç¤ºé€šç”¨é”™è¯¯ä¿¡æ¯
        uni.showToast({
          title: "表单校验失败,请检查必填项",
          icon: "none",
        });
      });
  };
  // å¤„理联系人选择结果
  const handleSelectContact = data => {
    const { stepIndex, contact } = data;
    // å°†é€‰ä¸­çš„联系人设置为对应审批步骤的审批人
    approverNodes.value[stepIndex].userId = contact.userId;
    approverNodes.value[stepIndex].nickName = contact.nickName;
  };
  const addApprover = stepIndex => {
    // è·³è½¬åˆ°è”系人选择页面
    uni.setStorageSync("stepIndex", stepIndex);
    uni.navigateTo({
      url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect",
    });
  };
  const addApprovalStep = () => {
    // æ·»åŠ æ–°çš„å®¡æ‰¹æ­¥éª¤
    approverNodes.value.push({ userId: null, nickName: null });
  };
  const removeApprover = stepIndex => {
    // ç§»é™¤å®¡æ‰¹äºº
    approverNodes.value[stepIndex].userId = null;
    approverNodes.value[stepIndex].nickName = null;
  };
  const removeApprovalStep = stepIndex => {
    // ç¡®ä¿è‡³å°‘保留一个审批步骤
    if (approverNodes.value.length > 1) {
      approverNodes.value.splice(stepIndex, 1);
    } else {
      uni.showToast({
        title: "至少需要一个审批步骤",
        icon: "none",
      });
    }
  };
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .approval-process {
    background: #fff;
    margin: 16px;
    border-radius: 16px;
    padding: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  }
  .approval-header {
    margin-bottom: 16px;
  }
  .approval-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
  .approval-desc {
    font-size: 12px;
    color: #999;
  }
  /* æ ·å¼å¢žå¼ºä¸ºâ€œç®€æ´å°åœ†åœˆé£Žæ ¼â€ */
  .approval-steps {
    padding-left: 22px;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      left: 11px;
      top: 40px;
      bottom: 40px;
      width: 2px;
      background: linear-gradient(
        to bottom,
        #e6f7ff 0%,
        #bae7ff 50%,
        #91d5ff 100%
      );
      border-radius: 1px;
    }
  }
  .approval-step {
    position: relative;
    margin-bottom: 24px;
    &::before {
      content: "";
      position: absolute;
      left: -18px;
      top: 14px; // ä»Ž 8px è°ƒæ•´ä¸º 14px,与文字中心对齐
      width: 12px;
      height: 12px;
      background: #fff;
      border: 3px solid #006cfb;
      border-radius: 50%;
      z-index: 2;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }
  }
  .step-title {
    top: 12px;
    margin-bottom: 12px;
    position: relative;
    margin-left: 6px;
  }
  .step-title text {
    font-size: 14px;
    color: #666;
    background: #f0f0f0;
    padding: 4px 12px;
    border-radius: 12px;
    position: relative;
    line-height: 1.4; // ç¡®ä¿æ–‡å­—行高一致
  }
  .approver-item {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 16px;
    padding: 16px;
    gap: 12px;
    position: relative;
    border: 1px solid #e6f7ff;
    box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08);
    transition: all 0.3s ease;
  }
  .approver-avatar {
    width: 48px;
    height: 48px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  }
  .avatar-text {
    color: #fff;
    font-size: 18px;
    font-weight: 600;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  }
  .approver-info {
    flex: 1;
    position: relative;
  }
  .approver-name {
    display: block;
    font-size: 16px;
    color: #333;
    font-weight: 500;
    position: relative;
  }
  .approver-dept {
    font-size: 12px;
    color: #999;
    background: rgba(0, 108, 251, 0.05);
    padding: 2px 8px;
    border-radius: 8px;
    display: inline-block;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      left: 4px;
      top: 50%;
      transform: translateY(-50%);
      width: 2px;
      height: 2px;
      background: #006cfb;
      border-radius: 50%;
    }
  }
  .delete-approver-btn {
    font-size: 16px;
    color: #ff4d4f;
    background: linear-gradient(
      135deg,
      rgba(255, 77, 79, 0.1) 0%,
      rgba(255, 77, 79, 0.05) 100%
    );
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
    position: relative;
  }
  .add-approver-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
    border: 2px dashed #006cfb;
    border-radius: 16px;
    padding: 20px;
    color: #006cfb;
    font-size: 14px;
    position: relative;
    transition: all 0.3s ease;
    &::before {
      content: "";
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 32px;
      height: 32px;
      border: 2px solid #006cfb;
      border-radius: 50%;
      opacity: 0;
      transition: all 0.3s ease;
    }
  }
  .delete-step-btn {
    color: #ff4d4f;
    font-size: 12px;
    background: linear-gradient(
      135deg,
      rgba(255, 77, 79, 0.1) 0%,
      rgba(255, 77, 79, 0.05) 100%
    );
    padding: 6px 12px;
    border-radius: 12px;
    display: inline-block;
    position: relative;
    transition: all 0.3s ease;
    &::before {
      content: "";
      position: absolute;
      left: 6px;
      top: 50%;
      transform: translateY(-50%);
      width: 4px;
      height: 4px;
      background: #ff4d4f;
      border-radius: 50%;
    }
  }
  .step-line {
    display: none; // éšè—åŽŸæ¥çš„çº¿æ¡ï¼Œä½¿ç”¨ä¼ªå…ƒç´ ä»£æ›¿
  }
  .add-step-btn {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .footer-btns {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: #fff;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 0.75rem 0;
    box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
    z-index: 1000;
  }
  .cancel-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 6.375rem;
    background: #c7c9cc;
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  .save-btn {
    font-weight: 400;
    font-size: 1rem;
    color: #ffffff;
    width: 14rem;
    background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
    box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
    border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
  }
  // åŠ¨ç”»å®šä¹‰
  @keyframes pulse {
    0% {
      transform: scale(1);
      opacity: 1;
    }
    50% {
      transform: scale(1.2);
      opacity: 0.7;
    }
    100% {
      transform: scale(1);
      opacity: 1;
    }
  }
  @keyframes rotate {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
  @keyframes ripple {
    0% {
      transform: translate(-50%, -50%) scale(0.8);
      opacity: 1;
    }
    100% {
      transform: translate(-50%, -50%) scale(1.6);
      opacity: 0;
    }
  }
  /* å¦‚果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */
  .step-line {
    position: absolute;
    left: 4px;
    top: 48px;
    width: 2px;
    height: calc(100% - 48px);
    background: #e5e7eb;
  }
  .approver-container {
    display: flex;
    align-items: center;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 16px;
    gap: 12px;
    padding: 10px 0;
    background: transparent;
    border: none;
    box-shadow: none;
  }
  .approver-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 10px;
    background: transparent;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }
  .approver-avatar {
    position: relative;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: #f3f4f6;
    border: 2px solid #e5e7eb;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: none; /* ç¦ç”¨æ—‹è½¬ç­‰åŠ¨ç”»ï¼Œå›žå½’ç®€æ´ */
  }
  .avatar-text {
    font-size: 14px;
    color: #374151;
    font-weight: 600;
  }
  .add-approver-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
  }
  .add-approver-btn .add-circle {
    width: 40px;
    height: 40px;
    border: 2px dashed #a0aec0;
    border-radius: 50%;
    color: #6b7280;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    line-height: 1;
  }
  .add-approver-btn .add-label {
    color: #3b82f6;
    font-size: 14px;
  }
</style>
src/pages/sales/salesAccount/index.vue
@@ -1,196 +1,216 @@
<template>
    <view class="sales-account">
        <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="销售台账" @back="goBack" />
        <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
        <view class="search-section">
            <view class="search-bar">
                <view class="search-input">
                    <up-input
                        class="search-text"
                        placeholder="请输入销售合同号搜索"
                        v-model="salesContractNo"
                        @change="getList"
                        clearable
                    />
                </view>
                <view class="filter-button" @click="getList">
                    <up-icon name="search" size="24" color="#999"></up-icon>
                </view>
            </view>
        </view>
        <!-- é”€å”®å°è´¦ç€‘布流 -->
        <view class="ledger-list" v-if="ledgerList.length > 0">
            <view v-for="(item, index) in ledgerList" :key="index">
                <view class="ledger-item" @click="handleInfo('edit', item)">
                    <view class="item-header">
                        <view class="item-left">
                            <view class="document-icon">
                                <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
                            </view>
                            <text class="item-id">{{ item.salesContractNo }}</text>
                        </view>
                        <!--                            <view class="item-tag">-->
                        <!--                                <text class="tag-text">{{ item.recorder }}</text>-->
                        <!--                            </view>-->
                    </view>
                    <up-divider></up-divider>
                    <view class="item-details">
                        <view class="detail-row">
                            <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">{{ item.customerContractNo }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">业务员</text>
                            <text class="detail-value">{{ item.salesman }}</text>
                        </view>
                        <view class="detail-row">
                            <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">{{ item.paymentMethod }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">合同金额(元)</text>
                            <text class="detail-value highlight">{{ item.contractAmount }}</text>
                        </view>
                        <view class="detail-row">
                            <text class="detail-label">签订日期</text>
                            <text class="detail-value">{{ item.executionDate }}</text>
                        </view>
                        <up-divider></up-divider>
                        <view class="detail-info">
                            <view class="detail-row">
                                <text class="detail-label">录入人</text>
                                <text class="detail-value">{{ item.entryPersonName }}</text>
                            </view>
                            <view class="detail-row">
                                <text class="detail-label">录入日期</text>
                                <text class="detail-value">{{ item.entryDate }}</text>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
        </view>
        <view v-else class="no-data">
            <text>暂无销售台账数据</text>
        </view>
        <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
        <view class="fab-button" @click="handleInfo('add')">
            <up-icon name="plus" size="24" color="#ffffff"></up-icon>
        </view>
    </view>
  <view class="sales-account">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="销售台账"
                @back="goBack" />
    <!-- æœç´¢å’Œç­›é€‰åŒºåŸŸ -->
    <view class="search-section">
      <view class="search-bar">
        <view class="search-input">
          <up-input class="search-text"
                    placeholder="请输入销售合同号搜索"
                    v-model="salesContractNo"
                    @change="getList"
                    clearable />
        </view>
        <view class="filter-button"
              @click="getList">
          <up-icon name="search"
                   size="24"
                   color="#999"></up-icon>
        </view>
      </view>
    </view>
    <!-- é”€å”®å°è´¦ç€‘布流 -->
    <view class="ledger-list"
          v-if="ledgerList.length > 0">
      <view v-for="(item, index) in ledgerList"
            :key="index">
        <view class="ledger-item"
              @click="handleInfo('edit', item)">
          <view class="item-header">
            <view class="item-left">
              <view class="document-icon">
                <up-icon name="file-text"
                         size="16"
                         color="#ffffff"></up-icon>
              </view>
              <text class="item-id">{{ item.salesContractNo }}</text>
            </view>
            <!--                            <view class="item-tag">-->
            <!--                                <text class="tag-text">{{ item.recorder }}</text>-->
            <!--                            </view>-->
          </view>
          <up-divider></up-divider>
          <view class="item-details">
            <view class="detail-row">
              <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">{{ item.customerContractNo }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">业务员</text>
              <text class="detail-value">{{ item.salesman }}</text>
            </view>
            <view class="detail-row">
              <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">{{ item.paymentMethod }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">合同金额(元)</text>
              <text class="detail-value highlight">{{ item.contractAmount }}</text>
            </view>
            <view class="detail-row">
              <text class="detail-label">签订日期</text>
              <text class="detail-value">{{ item.executionDate }}</text>
            </view>
            <up-divider></up-divider>
            <view class="detail-info">
              <view class="detail-row">
                <text class="detail-label">录入人</text>
                <text class="detail-value">{{ item.entryPersonName }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">录入日期</text>
                <text class="detail-value">{{ item.entryDate }}</text>
              </view>
            </view>
            <up-divider></up-divider>
            <u-button class="detail-button"
                      size="small"
                      type="primary"
                      @click="openOut(item)">
              å‘货状态
            </u-button>
          </view>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无销售台账数据</text>
    </view>
    <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
    <view class="fab-button"
          @click="handleInfo('add')">
      <up-icon name="plus"
               size="24"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
<script setup>
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import {ledgerListPage} from "@/api/salesManagement/salesLedger";
import useUserStore from "@/store/modules/user";
import PageHeader from "@/components/PageHeader.vue";
const userStore = useUserStore()
const showLoadingToast = (message) => {
    uni.showLoading({
        title: message,
        mask: true
    })
}
const closeToast = () => {
    uni.hideLoading()
}
  import { ref } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import { ledgerListPage } from "@/api/salesManagement/salesLedger";
  import useUserStore from "@/store/modules/user";
  import PageHeader from "@/components/PageHeader.vue";
  const userStore = useUserStore();
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  const closeToast = () => {
    uni.hideLoading();
  };
// æœç´¢å…³é”®è¯
const salesContractNo = ref('');
  // æœç´¢å…³é”®è¯
  const salesContractNo = ref("");
// é”€å”®å°è´¦æ•°æ®
const ledgerList = ref([]);
  // é”€å”®å°è´¦æ•°æ®
  const ledgerList = ref([]);
// è¿”回上一页
const goBack = () => {
    uni.navigateBack();
};
// æŸ¥è¯¢åˆ—表
const getList = () => {
    showLoadingToast('加载中...')
    const page = {
        current: -1,
        size: -1
    }
    ledgerListPage({...page, salesContractNo: salesContractNo.value}).then((res) => {
        console.log('销售台账----', res);
        ledgerList.value = res.records;
        closeToast()
    }).catch(() => {
        closeToast()
    });
};
  // è¿”回上一页
  const goBack = () => {
    uni.navigateBack();
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    const page = {
      current: -1,
      size: -1,
    };
    ledgerListPage({ ...page, salesContractNo: salesContractNo.value })
      .then(res => {
        console.log("销售台账----", res);
        ledgerList.value = res.records;
        closeToast();
      })
      .catch(() => {
        closeToast();
      });
  };
  const openOut = item => {
    uni.setStorageSync("outData", JSON.stringify(item));
    uni.navigateTo({
      url: "/pages/sales/salesAccount/out",
    });
  };
  // å¤„理台账信息操作(查看/编辑/新增)
  const handleInfo = (type, row) => {
    try {
      // è®¾ç½®æ“ä½œç±»åž‹
      uni.setStorageSync("operationType", type);
// å¤„理台账信息操作(查看/编辑/新增)
const handleInfo = (type, row) => {
    try {
        // è®¾ç½®æ“ä½œç±»åž‹
        uni.setStorageSync('operationType', type);
        // å¦‚果是查看或编辑操作
        if (type !== 'add') {
            // éªŒè¯è¡Œæ•°æ®æ˜¯å¦å­˜åœ¨
            if (!row) {
                uni.showToast({
                    title: '数据不存在',
                    icon: 'error'
                });
                return;
            }
            // æ£€æŸ¥æƒé™ï¼šåªæœ‰å½•入人才能编辑
            if (row.entryPerson != userStore.id) {
                // éžå½•入人跳转到只读详情页面
                uni.setStorageSync('editData', JSON.stringify(row));
                uni.navigateTo({
                    url: '/pages/sales/salesAccount/view'
                });
                return;
            }
            // å½•入人编辑:存储数据并跳转到编辑页面
            uni.setStorageSync('editData', JSON.stringify(row));
            uni.navigateTo({
                url: '/pages/sales/salesAccount/detail'
            });
            return;
        }
        // æ–°å¢žæ“ä½œï¼šç›´æŽ¥è·³è½¬åˆ°ç¼–辑页面
        uni.navigateTo({
            url: '/pages/sales/salesAccount/detail'
        });
    } catch (error) {
        console.error('处理台账信息操作失败:', error);
        uni.showToast({
            title: '操作失败,请重试',
            icon: 'error'
        });
    }
};
      // å¦‚果是查看或编辑操作
      if (type !== "add") {
        // éªŒè¯è¡Œæ•°æ®æ˜¯å¦å­˜åœ¨
        if (!row) {
          uni.showToast({
            title: "数据不存在",
            icon: "error",
          });
          return;
        }
onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°åˆ—表
    getList();
});
        // æ£€æŸ¥æƒé™ï¼šåªæœ‰å½•入人才能编辑
        if (row.entryPerson != userStore.id) {
          // éžå½•入人跳转到只读详情页面
          uni.setStorageSync("editData", JSON.stringify(row));
          uni.navigateTo({
            url: "/pages/sales/salesAccount/view",
          });
          return;
        }
        // å½•入人编辑:存储数据并跳转到编辑页面
        uni.setStorageSync("editData", JSON.stringify(row));
        uni.navigateTo({
          url: "/pages/sales/salesAccount/detail",
        });
        return;
      }
      // æ–°å¢žæ“ä½œï¼šç›´æŽ¥è·³è½¬åˆ°ç¼–辑页面
      uni.navigateTo({
        url: "/pages/sales/salesAccount/detail",
      });
    } catch (error) {
      console.error("处理台账信息操作失败:", error);
      uni.showToast({
        title: "操作失败,请重试",
        icon: "error",
      });
    }
  };
  onShow(() => {
    // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°åˆ—表
    getList();
  });
</script>
<style scoped lang="scss">
@import '@/styles/sales-common.scss';
  @import "@/styles/sales-common.scss";
</style>
src/pages/sales/salesAccount/out.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,407 @@
<template>
  <view class="receipt-payment-detail">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
    <PageHeader title="发货状态"
                @back="goBack" />
    <!-- ç»Ÿè®¡ä¿¡æ¯ -->
    <view class="summary-info">
      <view class="summary-item">
        <text class="summary-label">客户名称</text>
        <text class="summary-value">{{ outData.customerName }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">合同金额</text>
        <text class="summary-value">{{ outData.contractAmount }}</text>
      </view>
      <view class="summary-item">
        <text class="summary-label">签订日期</text>
        <text class="summary-value">{{ outData.executionDate }}</text>
      </view>
    </view>
    <!-- å›žæ¬¾è®°å½•明细列表 -->
    <view class="detail-list"
          v-if="tableData.length > 0">
      <view v-for="(item, index) in tableData"
            :key="index"
            class="detail-item">
        <view class="item-header">
          <view class="item-left">
            <view class="record-icon">
              <up-icon name="file-text"
                       size="16"
                       color="#ffffff"></up-icon>
            </view>
            <text class="item-index">{{ item.productCategory }}</text>
          </view>
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">产品大类</text>
            <text class="detail-value">{{ item.productCategory }}</text>
          </view>
          <view class="detail-row">
            <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.unit }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">产品状态</text>
            <text v-if="item.approveStatus === 1"
                  class="detail-value highlight">充足</text>
            <text v-else
                  class="detail-value danger">不足</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">发货状态</text>
            <u-tag size="mini"
                   :type="getShippingStatusType(item)">{{ getShippingStatusText(item) }}</u-tag>
          </view>
          <view class="detail-row">
            <text class="detail-label">快递公司</text>
            <text class="detail-value">{{ item.expressCompany }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">快递单号</text>
            <text class="detail-value">{{ item.expressNumber }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">发货车牌</text>
            <u-tag size="mini"
                   v-if="item.shippingCarNumber"
                   type="success">{{ item.shippingCarNumber }}</u-tag>
            <u-tag v-else
                   size="mini"
                   type="info">-</u-tag>
          </view>
          <view class="detail-row">
            <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.quantity }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">税率(%)</text>
            <text class="detail-value">{{ item.taxRate }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">含税单价(元)</text>
            <text class="detail-value">{{ item.taxInclusiveUnitPrice }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">含税总价(元)</text>
            <text class="detail-value">{{ item.taxInclusiveTotalPrice }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">不含税总价(元)</text>
            <text class="detail-value">{{ item.taxExclusiveTotalPrice }}</text>
          </view>
          <up-divider></up-divider>
          <u-button class="detail-button"
                    size="small"
                    type="primary"
                    :disabled="!canShip(item)"
                    @click="goout(item)">
            å‘è´§
          </u-button>
        </view>
      </view>
    </view>
    <view v-else
          class="no-data">
      <text>暂无回款记录</text>
    </view>
  </view>
</template>
<script setup>
  import { ref, computed, onMounted } from "vue";
  import { productList } from "@/api/salesManagement/salesLedger";
  // å®¢æˆ·ä¿¡æ¯
  const supplierId = ref("");
  // è¡¨æ ¼æ•°æ®
  const tableData = ref([]);
  // è¿”回上一页
  const goBack = () => {
    uni.removeStorageSync("supplierId");
    uni.navigateBack();
  };
  const getShippingStatusType = row => {
    // å¦‚果已发货(有发货日期或车牌号),显示绿色
    if (row.shippingDate || row.shippingCarNumber) {
      return "success";
    }
    // èŽ·å–å‘è´§çŠ¶æ€å­—æ®µ
    const status = row.shippingStatus;
    // å¦‚果状态为空或未定义,默认为灰色(待发货)
    if (status === null || status === undefined || status === "") {
      return "info";
    }
    // çŠ¶æ€æ˜¯å­—ç¬¦ä¸²
    const statusStr = String(status).trim();
    const typeTextMap = {
      å¾…发货: "info",
      å¾…审核: "info",
      å®¡æ ¸ä¸­: "warning",
      å®¡æ ¸æ‹’绝: "danger",
      å®¡æ ¸é€šè¿‡: "success",
      å·²å‘è´§: "success",
    };
    return typeTextMap[statusStr] || "info";
  };
  const getShippingStatusText = row => {
    // å¦‚果已发货(有发货日期或车牌号),显示"已发货"
    if (row.shippingDate || row.shippingCarNumber) {
      return "已发货";
    }
    // èŽ·å–å‘è´§çŠ¶æ€å­—æ®µ
    const status = row.shippingStatus;
    // å¦‚果状态为空或未定义,默认为"待发货"
    if (status === null || status === undefined || status === "") {
      return "待发货";
    }
    // çŠ¶æ€æ˜¯å­—ç¬¦ä¸²
    const statusStr = String(status).trim();
    const statusTextMap = {
      å¾…发货: "待发货",
      å¾…审核: "待审核",
      å®¡æ ¸ä¸­: "审核中",
      å®¡æ ¸æ‹’绝: "审核拒绝",
      å®¡æ ¸é€šè¿‡: "审核通过",
      å·²å‘è´§: "已发货",
    };
    return statusTextMap[statusStr] || "待发货";
  };
  // èŽ·å–é¡µé¢å‚æ•°
  const getPageParams = () => {
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–ä¾›åº”å•†ID
    const storedSupplierId = uni.getStorageSync("supplierId");
    if (storedSupplierId) {
      supplierId.value = storedSupplierId;
    }
  };
  const goout = item => {
    uni.setStorageSync("goOutData", JSON.stringify(item));
    uni.navigateTo({
      url: "/pages/sales/salesAccount/goOut",
    });
  };
  // æŸ¥è¯¢åˆ—表
  const getList = () => {
    showLoadingToast("加载中...");
    productList({
      salesLedgerId: outData.value.id,
      type: 1,
    })
      .then(res => {
        tableData.value = res.data;
        closeToast();
      })
      .catch(() => {
        closeToast();
        uni.showToast({
          title: "查询失败",
          icon: "error",
        });
      });
  };
  const canShip = row => {
    // äº§å“çŠ¶æ€å¿…é¡»æ˜¯å……è¶³ï¼ˆapproveStatus === 1)
    if (row.approveStatus !== 1) {
      return false;
    }
    // èŽ·å–å‘è´§çŠ¶æ€
    const shippingStatus = row.shippingStatus;
    // å¦‚果已发货(有发货日期或车牌号),不能再次发货
    if (row.shippingDate || row.shippingCarNumber) {
      return false;
    }
    // å‘货状态必须是"待发货"或"审核拒绝"
    const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
    return statusStr === "待发货" || statusStr === "审核拒绝";
  };
  // æ˜¾ç¤ºåŠ è½½æç¤º
  const showLoadingToast = message => {
    uni.showLoading({
      title: message,
      mask: true,
    });
  };
  // å…³é—­æç¤º
  const closeToast = () => {
    uni.hideLoading();
  };
  const outData = ref({});
  onMounted(() => {
    // é¡µé¢åŠ è½½æ—¶èŽ·å–å‚æ•°å¹¶åˆ·æ–°åˆ—è¡¨
    getPageParams();
    // ä»Žæœ¬åœ°å­˜å‚¨èŽ·å–å‘è´§çŠ¶æ€æ•°æ®
    outData.value = JSON.parse(uni.getStorageSync("outData"));
    getList();
  });
</script>
<style scoped lang="scss">
  .receipt-payment-detail {
    min-height: 100vh;
    background: #f8f9fa;
    position: relative;
  }
  .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 {
    padding: 20px;
  }
  .detail-item {
    background: #ffffff;
    border-radius: 12px;
    margin-bottom: 16px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    padding: 0 16px;
  }
  .item-header {
    padding: 10px 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .item-left {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .record-icon {
    width: 24px;
    height: 24px;
    background: #2979ff;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .item-index {
    font-size: 14px;
    color: #333;
    font-weight: 500;
  }
  .item-date {
    font-size: 12px;
    color: #666;
  }
  .item-details {
    padding: 16px 0;
  }
  .detail-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: 8px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .detail-label {
    font-size: 12px;
    color: #777777;
    min-width: 60px;
  }
  .detail-value {
    font-size: 12px;
    color: #000000;
    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 {
    padding: 40px 0;
    text-align: center;
    color: #999;
  }
</style>
src/pages/sales/salesAccount/view.vue
@@ -1,8 +1,8 @@
<template>
  <view class="account-view">
    <!-- ä½¿ç”¨é€šç”¨é¡µé¢å¤´éƒ¨ç»„ä»¶ -->
        <PageHeader title="台账详情" @back="goBack" />
    <PageHeader title="台账详情"
                @back="goBack" />
    <!-- åŸºæœ¬ä¿¡æ¯å±•示 -->
    <view class="info-section">
      <view class="section-title">基本信息</view>
@@ -45,19 +45,22 @@
        </view>
      </view>
    </view>
    <!-- äº§å“ä¿¡æ¯å±•示 -->
    <view class="product-section" v-if="productData && productData.length > 0">
    <view class="product-section"
          v-if="productData && productData.length > 0">
      <view class="section-title">产品信息</view>
      <view class="product-card" v-for="(product, idx) in productData" :key="idx">
      <view class="product-card"
            v-for="(product, idx) in productData"
            :key="idx">
        <view class="product-header">
          <view class="product-title">
            <!-- æ›¿æ¢ van-icon ä¸º u-icon -->
            <u-icon name="file-text" color="#2979ff" size="15" />
            <u-icon name="file-text"
                    color="#2979ff"
                    size="15" />
            <text class="product-productCategory">产品 {{ idx + 1 }}</text>
          </view>
        </view>
        <view class="product-info">
          <view class="info-grid">
            <view class="info-item">
@@ -68,10 +71,10 @@
              <text class="info-label">规格型号</text>
              <text class="info-value">{{ product.specificationModel }}</text>
            </view>
            <view class="info-item">
            <!-- <view class="info-item">
              <text class="info-label">绑定机器</text>
              <text class="info-value">{{ product.speculativeTradingName }}</text>
            </view>
            </view> -->
            <view class="info-item">
              <text class="info-label">单位</text>
              <text class="info-value">{{ product.unit }}</text>
@@ -104,192 +107,192 @@
        </view>
      </view>
    </view>
    <view v-else class="no-product">
    <view v-else
          class="no-product">
      <text>暂无产品信息</text>
    </view>
  </view>
</template>
<script setup>
import {onMounted, ref} from 'vue';
import {getSalesLedgerWithProducts} from "@/api/salesManagement/salesLedger";
import PageHeader from '@/components/PageHeader.vue';
  import { onMounted, ref } from "vue";
  import { getSalesLedgerWithProducts } from "@/api/salesManagement/salesLedger";
  import PageHeader from "@/components/PageHeader.vue";
// èŽ·å–é¡µé¢å‚æ•°
const editData = ref(null);
  // èŽ·å–é¡µé¢å‚æ•°
  const editData = ref(null);
const form = ref({
  id: '',
  salesContractNo: '',
  customerContractNo: '',
  customerId: '',
  customerName: '',
  projectName: '',
  executionDate: '',
  paymentMethod: '',
  entryPerson: '',
  entryPersonName: '',
  entryDate: '',
  salesman: ''
});
// äº§å“æ•°æ®
const productData = ref([]);
// è¿”回上一页
const goBack = () => {
  // æ¸…理本地存储的数据
  uni.removeStorageSync('editData');
  uni.navigateBack();
};
// å¡«å……表单数据
const fillFormData = () => {
  if (!editData.value) return;
  // èŽ·å–å®Œæ•´çš„äº§å“ä¿¡æ¯
    getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then((res) => {
    productData.value = res.productData || [];
        form.value = {...res}
  const form = ref({
    id: "",
    salesContractNo: "",
    customerContractNo: "",
    customerId: "",
    customerName: "",
    projectName: "",
    executionDate: "",
    paymentMethod: "",
    entryPerson: "",
    entryPersonName: "",
    entryDate: "",
    salesman: "",
  });
};
onMounted(() => {
  // èŽ·å–ç¼–è¾‘æ•°æ®å¹¶å¡«å……è¡¨å•
  const editDataStr = uni.getStorageSync('editData');
  if (editDataStr) {
    try {
      editData.value = JSON.parse(editDataStr);
      // ä½¿ç”¨ nextTick ç¡®ä¿æ•°æ®åŠ è½½å®ŒæˆåŽå†å¡«å……
      setTimeout(() => {
        fillFormData();
      }, 100);
    } catch (error) {
      console.error('解析编辑数据失败:', error);
  // äº§å“æ•°æ®
  const productData = ref([]);
  // è¿”回上一页
  const goBack = () => {
    // æ¸…理本地存储的数据
    uni.removeStorageSync("editData");
    uni.navigateBack();
  };
  // å¡«å……表单数据
  const fillFormData = () => {
    if (!editData.value) return;
    // èŽ·å–å®Œæ•´çš„äº§å“ä¿¡æ¯
    getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then(res => {
      productData.value = res.productData || [];
      form.value = { ...res };
    });
  };
  onMounted(() => {
    // èŽ·å–ç¼–è¾‘æ•°æ®å¹¶å¡«å……è¡¨å•
    const editDataStr = uni.getStorageSync("editData");
    if (editDataStr) {
      try {
        editData.value = JSON.parse(editDataStr);
        // ä½¿ç”¨ nextTick ç¡®ä¿æ•°æ®åŠ è½½å®ŒæˆåŽå†å¡«å……
        setTimeout(() => {
          fillFormData();
        }, 100);
      } catch (error) {
        console.error("解析编辑数据失败:", error);
      }
    }
  }
});
  });
</script>
<style scoped lang="scss">
.account-view {
  min-height: 100vh;
  background: #f8f9fa;
  padding-bottom: 2rem;
}
  .account-view {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 2rem;
  }
.header {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 1rem 1.25rem;
  border-bottom: 0.0625rem solid #f0f0f0;
  position: sticky;
  top: 0;
  z-index: 100;
}
  .header {
    display: flex;
    align-items: center;
    background: #fff;
    padding: 1rem 1.25rem;
    border-bottom: 0.0625rem solid #f0f0f0;
    position: sticky;
    top: 0;
    z-index: 100;
  }
.title {
  flex: 1;
  text-align: center;
  font-size: 1.125rem;
  font-weight: 600;
  color: #333;
}
  .title {
    flex: 1;
    text-align: center;
    font-size: 1.125rem;
    font-weight: 600;
    color: #333;
  }
.info-section {
  background: #fff;
  margin: 1rem;
  padding: 1rem;
  border-radius: 0.5rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
  .info-section {
    background: #fff;
    margin: 1rem;
    padding: 1rem;
    border-radius: 0.5rem;
    box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.04);
  }
.product-section {
  background: #fff;
  margin: 1rem;
  padding: 1rem;
  border-radius: 0.5rem;
  box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04);
}
  .product-section {
    background: #fff;
    margin: 1rem;
    padding: 1rem;
    border-radius: 0.5rem;
    box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.04);
  }
.section-title {
  font-size: 1rem;
  font-weight: 600;
  color: #333;
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
  .section-title {
    font-size: 1rem;
    font-weight: 600;
    color: #333;
    margin-bottom: 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 0.0625rem solid #e8e8e8;
  }
.info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
}
  .info-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
  }
.info-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
  .info-item {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }
.info-label {
  font-size: 0.75rem;
  color: #666;
  font-weight: 400;
}
  .info-label {
    font-size: 0.75rem;
    color: #666;
    font-weight: 400;
  }
.info-value {
  font-size: 0.875rem;
  color: #333;
  font-weight: 500;
}
  .info-value {
    font-size: 0.875rem;
    color: #333;
    font-weight: 500;
  }
.info-value.highlight {
  color: #2979ff;
  font-weight: 600;
}
  .info-value.highlight {
    color: #2979ff;
    font-weight: 600;
  }
.product-card {
  background: #f8f9fa;
  border-radius: 0.5rem;
  padding: 1rem;
  margin-bottom: 1rem;
  border: 0.0625rem solid #e8e8e8;
}
  .product-card {
    background: #f8f9fa;
    border-radius: 0.5rem;
    padding: 1rem;
    margin-bottom: 1rem;
    border: 0.0625rem solid #e8e8e8;
  }
.product-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 0.0625rem solid #e8e8e8;
}
  .product-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 0.0625rem solid #e8e8e8;
  }
.product-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
  .product-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
.product-productCategory {
  font-size: 0.875rem;
  font-weight: 500;
  color: #333;
}
  .product-productCategory {
    font-size: 0.875rem;
    font-weight: 500;
    color: #333;
  }
.product-info .info-grid {
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
}
  .product-info .info-grid {
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
  }
.no-product {
  text-align: center;
  padding: 2rem;
  color: #999;
  font-size: 0.875rem;
}
  .no-product {
    text-align: center;
    padding: 2rem;
    color: #999;
    font-size: 0.875rem;
  }
</style>