zhangwencui
17 小时以前 5b76649a97e92e3f02cdbb71e871443524fd0348
销售台账增加发货列表和发货功能
已添加2个文件
已修改4个文件
1991 ■■■■ 文件已修改
src/api/salesManagement/salesLedger.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/procurementManagement/paymentLedger/detail.vue 517 ●●●● 补丁 | 查看 | 原始文档 | 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/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": "修改台账",
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/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>