gaoluyang
2026-05-20 de4ac959d99138074276563d6d4ca44d76b17705
src/pages/sales/salesQuotation/index.vue
@@ -1,90 +1,107 @@
<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"
            v-model="searchForm.product"
            placeholder="请输入产品名称搜索"
            clearable
            @change="getList(true)"
          />
          <up-input class="search-text"
                    v-model="quotationNo"
                    placeholder="请输入报价单号搜索"
                    clearable
                    @change="getList" />
        </view>
        <view class="filter-button" @click="getList(true)">
          <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 v-if="quotationList.length > 0" class="ledger-list">
      <view v-for="item in quotationList" :key="item.id" class="ledger-item">
    <view class="tabs-section">
      <up-tabs v-model="tabValue"
               :list="tabList"
               itemStyle="width: 20%;height: 80rpx;"
               @change="onTabChange" />
    </view>
    <view v-if="quotationList.length > 0"
          class="ledger-list">
      <view v-for="item in quotationList"
            :key="item.id"
            class="ledger-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.product || "-" }}</text>
            <text class="item-id">{{ item.quotationNo || "-" }}</text>
          </view>
          <up-tag :text="item.status || '待审批'"
                  :type="getStatusType(item.status)"
                  size="mini"
                  shape="circle" />
        </view>
        <up-divider></up-divider>
        <view class="item-details">
          <view class="detail-row">
            <text class="detail-label">规格</text>
            <text class="detail-value">{{ item.specification || "-" }}</text>
            <text class="detail-label">客户名称</text>
            <text class="detail-value">{{ item.customer || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">纸张</text>
            <text class="detail-value">{{ item.paper || "-" }}</text>
            <text class="detail-label">业务员</text>
            <text class="detail-value">{{ item.salesperson || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">定量</text>
            <text class="detail-value">{{ item.paperWeight || "-" }}</text>
            <text class="detail-label">报价日期</text>
            <text class="detail-value">{{ item.quotationDate || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">单价</text>
            <text class="detail-value">{{ formatAmount(item.unitPrice) }}</text>
            <text class="detail-label">有效期至</text>
            <text class="detail-value">{{ item.validDate || "-" }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">印版费</text>
            <text class="detail-value">{{ formatAmount(item.printingFee) }}</text>
            <text class="detail-label">报价金额</text>
            <text class="detail-value highlight">{{ formatAmount(item.totalAmount) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">刀版费</text>
            <text class="detail-value">{{ formatAmount(item.dieCuttingFee) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">磨具费</text>
            <text class="detail-value">{{ formatAmount(item.grindingFee) }}</text>
          </view>
          <view class="detail-row">
            <text class="detail-label">数量</text>
            <text class="detail-value">{{ Number(item.quantity || 0) }}</text>
          </view>
          <view class="detail-row">
          <view class="detail-row"
                v-if="item.remark">
            <text class="detail-label">备注</text>
            <text class="detail-value">{{ item.remark || "-" }}</text>
            <text class="detail-value">{{ item.remark }}</text>
          </view>
        </view>
        <view class="action-buttons">
          <up-button class="action-btn" size="small" type="primary" @click="goEdit(item)">编辑</up-button>
          <up-button class="action-btn" size="small" @click="goDetail(item)">详情</up-button>
          <up-button class="action-btn" size="small" type="error" plain @click="handleDelete(item)">删除</up-button>
          <up-button class="action-btn"
                     size="small"
                     type="primary"
                     :disabled="!canEdit(item)"
                     @click="goEdit(item)">
            编辑
          </up-button>
          <up-button class="action-btn"
                     size="small"
                     @click="goDetail(item)">详情</up-button>
          <up-button class="action-btn"
                     size="small"
                     type="error"
                     plain
                     @click="handleDelete(item)">
            删除
          </up-button>
        </view>
      </view>
    </view>
    <view v-else class="no-data">
      <text>暂无数据</text>
    <view v-else
          class="no-data">
      <text>暂无销售报价数据</text>
    </view>
    <view class="fab-button" @click="goAdd">
      <up-icon name="plus" size="28" color="#ffffff"></up-icon>
    <view class="fab-button"
          @click="goAdd">
      <up-icon name="plus"
               size="28"
               color="#ffffff"></up-icon>
    </view>
  </view>
</template>
@@ -93,73 +110,85 @@
  import { reactive, ref } from "vue";
  import { onShow } from "@dcloudio/uni-app";
  import PageHeader from "@/components/PageHeader.vue";
  import { deleteQuotation } from "@/api/salesManagement/salesQuotation";
  import { quotationProductListPage } from "@/api/salesManagement/salesQuotationProduct";
  import {
    deleteQuotation,
    getQuotationList,
  } from "@/api/salesManagement/salesQuotation";
  const searchForm = reactive({ product: "" });
  const quotationNo = ref("");
  const quotationList = ref([]);
  const goBack = () => uni.navigateBack();
  const goAdd = () => uni.navigateTo({ url: "/pages/sales/salesQuotation/edit" });
  const tabList = reactive([
    { name: "全部", value: "" },
    { name: "待审批", value: "待审批" },
    { name: "审核中", value: "审核中" },
    { name: "通过", value: "通过" },
    { name: "拒绝", value: "拒绝" },
  ]);
  const tabValue = ref(0);
  const page = {
    current: -1,
    size: -1,
  };
  const goBack = () => {
    uni.navigateBack();
  };
  const goAdd = () => {
    uni.removeStorageSync("salesQuotationDetail");
    uni.navigateTo({ url: "/pages/sales/salesQuotation/edit" });
  };
  const goEdit = item => {
    const source = item?.__raw || item || {};
    uni.setStorageSync("salesQuotationEdit", source);
    if (!canEdit(item)) return;
    uni.setStorageSync("salesQuotationDetail", item || {});
    uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${item.id}` });
  };
  const goDetail = item => {
    uni.setStorageSync("salesQuotationDetail", item || {});
    uni.navigateTo({ url: `/pages/sales/salesQuotation/detail?id=${item.id}` });
  };
  const formatAmount = amount => `¥${Number(amount || 0).toFixed(2)}`;
  const canEdit = item => ["待审批", "拒绝"].includes(item?.status);
  const calcTotalAmountFromProducts = products =>
    Number(
      (products || [])
        .reduce((sum, product) => {
          const unitPrice = Number(product?.unitPrice || 0);
          const printingFee = Number(product?.printingFee || 0);
          const dieCuttingFee = Number(product?.dieCuttingFee || 0);
          const grindingFee = Number(product?.grindingFee || 0);
          return sum + unitPrice + printingFee + dieCuttingFee + grindingFee;
        }, 0)
        .toFixed(2)
    );
  const onTabChange = val => {
    tabValue.value = val.index;
    getList();
  };
  const normalizeQuotation = row => {
    const sourceProducts = Array.isArray(row?.products) && row.products.length ? row.products : [row];
    const first = sourceProducts[0] || {};
    return {
      ...row,
      __raw: row,
      customer: row?.customer || row?.customerName || first?.customer || first?.customerName || "",
      salesperson: row?.salesperson || row?.salesman || row?.salesPerson || first?.salesperson || "",
      quotationDate: row?.quotationDate || row?.quoteDate || first?.quotationDate || "",
      validDate: row?.validDate || row?.expireDate || first?.validDate || "",
      paymentMethod: row?.paymentMethod || row?.paymentType || first?.paymentMethod || "",
      product: first.product || first.productName || row?.product || "",
      specification: first.specification || row?.specification || "",
      paper: first.paper || row?.paper || "",
      paperWeight: first.paperWeight || row?.paperWeight || "",
      unitPrice: Number(first.unitPrice || row?.unitPrice || 0),
      printingFee: Number(first.printingFee || row?.printingFee || 0),
      dieCuttingFee: Number(first.dieCuttingFee || row?.dieCuttingFee || 0),
      grindingFee: Number(first.grindingFee || row?.grindingFee || 0),
      quantity: Number(first.quantity || row?.quantity || 0),
      quotationNo: row?.quotationNo || first?.quotationNo || "",
      totalAmount: Number(row?.totalAmount || calcTotalAmountFromProducts(sourceProducts)),
  const getCurrentStatus = () => {
    const currentTab = tabList[tabValue.value];
    return currentTab?.value || "";
  };
  const formatAmount = amount => {
    const num = Number(amount || 0);
    return `¥${num.toFixed(2)}`;
  };
  const getStatusType = status => {
    const statusMap = {
      待审批: "info",
      审核中: "primary",
      通过: "success",
      拒绝: "danger",
    };
    return statusMap[status] || "info";
  };
  const getList = () => {
    uni.showLoading({ title: "加载中...", mask: true });
    quotationProductListPage({
      current: -1,
      size: -1,
      product: String(searchForm.product || "").trim(),
    getQuotationList({
      ...page,
      quotationNo: quotationNo.value,
      status: getCurrentStatus(),
    })
      .then(res => {
        const records = res?.data?.records || res?.records || [];
        quotationList.value = Array.isArray(records) ? records.map(normalizeQuotation) : [];
        quotationList.value = Array.isArray(records) ? records : [];
      })
      .catch(() => {
        uni.showToast({ title: "查询失败", icon: "error" });
@@ -173,11 +202,11 @@
    if (!item?.id) return;
    uni.showModal({
      title: "删除确认",
      content: "确认删除该报价吗?",
      content: "确认删除该报价单吗?",
      success: res => {
        if (!res.confirm) return;
        uni.showLoading({ title: "处理中...", mask: true });
        deleteQuotation([item.id])
        deleteQuotation(item.id)
          .then(() => {
            uni.showToast({ title: "删除成功", icon: "success" });
            getList();
@@ -200,6 +229,11 @@
<style scoped lang="scss">
  @import "@/styles/sales-common.scss";
  .tabs-section {
    background: #ffffff;
    padding: 0 12px 8px 12px;
  }
  .item-index {
    max-width: 180rpx;
    text-align: center;