gaoluyang
2026-05-20 de4ac959d99138074276563d6d4ca44d76b17705
src/pages/sales/salesQuotation/edit.vue
@@ -1,124 +1,301 @@
<template>
  <view class="account-detail">
    <PageHeader :title="pageTitle" @back="goBack" />
    <PageHeader :title="pageTitle"
                @back="goBack" />
    <view class="form-container">
      <up-form ref="formRef" :model="form" label-width="110" input-align="right" error-message-align="right">
        <u-cell-group title="产品信息" class="form-section">
      <up-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="110"
               input-align="right"
               error-message-align="right">
        <u-cell-group title="基础信息"
                      class="form-section">
          <up-form-item label="客户名称"
                        prop="customer"
                        required>
            <up-input v-model="form.customer"
                      placeholder="请选择客户"
                      readonly
                      @click="showCustomerSheet = true" />
            <template #right>
              <up-icon name="arrow-right"
                       @click="showCustomerSheet = true"></up-icon>
            </template>
          </up-form-item>
          <up-form-item label="业务员"
                        prop="salesperson"
                        required>
            <up-input v-model="form.salesperson"
                      placeholder="请选择业务员"
                      readonly
                      @click="showSalespersonSheet = true" />
            <template #right>
              <up-icon name="arrow-right"
                       @click="showSalespersonSheet = true"></up-icon>
            </template>
          </up-form-item>
          <up-form-item label="报价日期"
                        prop="quotationDate"
                        required>
            <up-input v-model="form.quotationDate"
                      placeholder="请选择报价日期"
                      readonly
                      @click="showQuotationDatePicker = true" />
            <template #right>
              <up-icon name="arrow-right"
                       @click="showQuotationDatePicker = true"></up-icon>
            </template>
          </up-form-item>
          <up-form-item label="有效期至"
                        prop="validDate"
                        required>
            <up-input v-model="form.validDate"
                      placeholder="请选择有效期"
                      readonly
                      @click="showValidDatePicker = true" />
            <template #right>
              <up-icon name="arrow-right"
                       @click="showValidDatePicker = true"></up-icon>
            </template>
          </up-form-item>
          <up-form-item label="付款方式"
                        prop="paymentMethod"
                        required>
            <up-input v-model="form.paymentMethod"
                      placeholder="请输入付款方式"
                      clearable />
          </up-form-item>
          <up-form-item label="备注"
                        prop="remark">
            <up-textarea v-model="form.remark"
                         placeholder="请输入备注"
                         auto-height />
          </up-form-item>
        </u-cell-group>
        <u-cell-group title="产品信息"
                      class="form-section">
          <view class="section-tools">
            <up-button type="primary" size="small" text="新增产品" :disabled="isEditMode" @click="addProduct" />
            <up-button type="primary"
                       size="small"
                       text="新增产品"
                       @click="addProduct" />
          </view>
          <view v-if="form.products.length === 0" class="empty-text"><text>暂无产品</text></view>
          <view v-else class="product-list">
            <view v-for="(product, index) in form.products" :key="product.uid || index" class="product-card">
          <view v-if="form.products.length === 0"
                class="empty-text">
            <text>暂无产品,请先添加产品</text>
          </view>
          <view v-else
                class="product-list">
            <view v-for="(product, index) in form.products"
                  :key="product.uid"
                  class="product-card">
              <view class="product-header">
                <text class="product-title">产品 {{ index + 1 }}</text>
                <up-icon name="trash" color="#ee0a24" size="18" @click="removeProduct(index)"></up-icon>
                <up-icon name="trash"
                         color="#ee0a24"
                         size="18"
                         @click="removeProduct(index)"></up-icon>
              </view>
              <up-divider></up-divider>
              <view class="product-body">
                <up-form-item label="产品">
                  <up-input v-model="product.product" placeholder="请选择产品" readonly @click="openProductPicker(index)" />
                  <template #right><up-icon name="arrow-right" @click="openProductPicker(index)"></up-icon></template>
                <up-form-item label="产品名称">
                  <up-input v-model="product.product"
                            placeholder="请选择产品"
                            readonly
                            @click="openProductPicker(index)" />
                  <template #right>
                    <up-icon name="arrow-right"
                             @click="openProductPicker(index)"></up-icon>
                  </template>
                </up-form-item>
                <up-form-item label="规格">
                  <up-input v-model="product.specification" placeholder="请选择规格" readonly @click="openModelPicker(index)" />
                  <template #right><up-icon name="arrow-right" @click="openModelPicker(index)"></up-icon></template>
                <up-form-item label="规格型号">
                  <up-input v-model="product.ProductModel"
                            placeholder="请选择规格型号"
                            readonly
                            @click="openModelPicker(index)" />
                  <template #right>
                    <up-icon name="arrow-right"
                             @click="openModelPicker(index)"></up-icon>
                  </template>
                </up-form-item>
                <up-form-item label="单位"><up-input v-model="product.unit" placeholder="请输入单位" clearable /></up-form-item>
                <up-form-item label="纸张"><up-input v-model="product.paper" placeholder="请输入纸张" clearable /></up-form-item>
                <up-form-item label="定量"><up-input v-model="product.paperWeight" placeholder="请输入定量" clearable /></up-form-item>
                <up-form-item label="单位">
                  <up-input v-model="product.unit"
                            placeholder="请输入单位"
                            clearable />
                </up-form-item>
                <up-form-item label="数量">
                  <up-input v-model="product.quantity" type="number" placeholder="请输入数量" clearable @blur="calculateAmount(product)" />
                  <up-input v-model="product.quantity"
                            type="number"
                            placeholder="请输入数量"
                            clearable
                            @blur="calculateAmount(product)" />
                </up-form-item>
                <up-form-item label="单价">
                  <up-input v-model="product.unitPrice" type="number" placeholder="请输入单价" clearable @blur="calculateAmount(product)" />
                </up-form-item>
                <up-form-item label="印版费">
                  <up-input v-model="product.printingFee" type="number" placeholder="请输入印版费" clearable @blur="syncTotalAmount" />
                </up-form-item>
                <up-form-item label="刀版费">
                  <up-input v-model="product.dieCuttingFee" type="number" placeholder="请输入刀版费" clearable @blur="syncTotalAmount" />
                </up-form-item>
                <up-form-item label="磨具费">
                  <up-input v-model="product.grindingFee" type="number" placeholder="请输入磨具费" clearable @blur="syncTotalAmount" />
                  <up-input v-model="product.unitPrice"
                            type="number"
                            placeholder="请输入单价"
                            clearable
                            @blur="calculateAmount(product)" />
                </up-form-item>
                <up-form-item label="金额">
                  <up-input :model-value="formatAmount(product.amount)" disabled placeholder="自动计算(数量*单价)" />
                  <up-input :model-value="formatAmount(product.amount)"
                            disabled
                            placeholder="自动计算" />
                </up-form-item>
              </view>
            </view>
          </view>
        </u-cell-group>
        <u-cell-group title="备注信息" class="form-section">
          <up-form-item label="备注">
            <up-textarea v-model="form.remark" placeholder="请输入备注(选填)" auto-height />
          </up-form-item>
        </u-cell-group>
        <u-cell-group title="汇总" class="form-section">
        <u-cell-group title="汇总信息"
                      class="form-section">
          <up-form-item label="报价总额">
            <up-input :model-value="formatAmount(totalAmount)" disabled placeholder="自动汇总" />
            <up-input :model-value="formatAmount(totalAmount)"
                      disabled
                      placeholder="自动汇总" />
          </up-form-item>
          <view class="summary-tip">总额规则:单价 + 印版费 + 刀版费 + 磨具费(按产品逐行求和)</view>
        </u-cell-group>
      </up-form>
    </view>
    <FooterButtons :loading="loading" confirmText="保存" @cancel="goBack" @confirm="handleSubmit" />
    <up-action-sheet :show="showProductSheet" title="选择产品" :actions="productActions" @select="onSelectProduct" @close="showProductSheet = false" />
    <up-action-sheet :show="showModelSheet" title="选择规格" :actions="modelActions" @select="onSelectModel" @close="showModelSheet = false" />
    <FooterButtons :loading="loading"
                   confirmText="保存"
                   @cancel="goBack"
                   @confirm="handleSubmit" />
    <up-action-sheet :show="showCustomerSheet"
                     title="选择客户"
                     :actions="customerActions"
                     @select="onSelectCustomer"
                     @close="showCustomerSheet = false" />
    <up-action-sheet :show="showSalespersonSheet"
                     title="选择业务员"
                     :actions="salespersonActions"
                     @select="onSelectSalesperson"
                     @close="showSalespersonSheet = false" />
    <up-action-sheet :show="showProductSheet"
                     title="选择产品"
                     :actions="productActions"
                     @select="onSelectProduct"
                     @close="showProductSheet = false" />
    <up-action-sheet :show="showModelSheet"
                     title="选择规格型号"
                     :actions="modelActions"
                     @select="onSelectModel"
                     @close="showModelSheet = false" />
    <up-datetime-picker :show="showQuotationDatePicker"
                        v-model="quotationDateValue"
                        mode="date"
                        @confirm="onQuotationDateConfirm"
                        @cancel="showQuotationDatePicker = false" />
    <up-datetime-picker :show="showValidDatePicker"
                        v-model="validDateValue"
                        mode="date"
                        @confirm="onValidDateConfirm"
                        @cancel="showValidDatePicker = false" />
  </view>
</template>
<script setup>
  import { computed, onMounted, ref } from "vue";
  import { computed, onMounted, onUnmounted, ref } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import FooterButtons from "@/components/FooterButtons.vue";
  import PageHeader from "@/components/PageHeader.vue";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import { modelList, productTreeList } from "@/api/basicData/product";
  import { addOrUpdateQuotationProduct, editQuotationProduct } from "@/api/salesManagement/salesQuotationProduct";
  import { userListNoPageByTenantId } from "@/api/system/user";
  import {
    addQuotation,
    getCustomerList,
    updateQuotation,
  } from "@/api/salesManagement/salesQuotation";
  const formRef = ref();
  const loading = ref(false);
  const quotationId = ref("");
  const showCustomerSheet = ref(false);
  const showSalespersonSheet = ref(false);
  const showProductSheet = ref(false);
  const showModelSheet = ref(false);
  const showQuotationDatePicker = ref(false);
  const showValidDatePicker = ref(false);
  const quotationDateValue = ref(Date.now());
  const validDateValue = ref(Date.now());
  const currentProductIndex = ref(-1);
  const customerList = ref([]);
  const salespersonList = ref([]);
  const productList = ref([]);
  const modelActions = ref([]);
  let uidSeed = 1;
  const form = ref({
    id: undefined,
    quotationNo: "",
    customerId: undefined,
    customer: "",
    salesperson: "",
    quotationDate: "",
    validDate: "",
    paymentMethod: "",
    status: "草稿",
    remark: "",
    products: [],
    subtotal: 0,
    freight: 0,
    otherFee: 0,
    discountRate: 0,
    discountAmount: 0,
    totalAmount: 0,
  });
  const rules = {
    customer: [{ required: true, message: "请选择客户", trigger: "change" }],
    salesperson: [{ required: true, message: "请选择业务员", trigger: "change" }],
    quotationDate: [
      { required: true, message: "请选择报价日期", trigger: "change" },
    ],
    validDate: [{ required: true, message: "请选择有效期", trigger: "change" }],
    paymentMethod: [
      { required: true, message: "请输入付款方式", trigger: "blur" },
    ],
  };
  const pageTitle = computed(() => (quotationId.value ? "编辑报价" : "新增报价"));
  const isEditMode = computed(() => Boolean(quotationId.value));
  const productActions = computed(() => productList.value.map(item => ({ name: item.label, value: item.value, label: item.label })));
  const totalAmount = computed(() => calcTotalAmountFromProducts(form.value.products));
  const totalAmount = computed(() =>
    Number(
      (form.value.products || [])
        .reduce((sum, item) => sum + Number(item.amount || 0), 0)
        .toFixed(2)
    )
  );
  const customerActions = computed(() =>
    customerList.value.map(item => ({
      name: item.customerName,
      value: item.id,
    }))
  );
  const salespersonActions = computed(() =>
    salespersonList.value.map(item => ({
      name: item.nickName,
      value: item.nickName,
    }))
  );
  const productActions = computed(() =>
    productList.value.map(item => ({
      name: item.label,
      value: item.value,
      label: item.label,
    }))
  );
  const createEmptyProduct = () => ({
    uid: `p_${uidSeed++}`,
    id: "",
    salesQuotationId: "",
    productId: "",
    product: "",
    specificationId: "",
    specification: "",
    productModelId: "",
    ProductModel: "",
    unit: "",
    paper: "",
    paperWeight: "",
    quantity: 1,
    unitPrice: 0,
    printingFee: 0,
    dieCuttingFee: 0,
    grindingFee: 0,
    amount: 0,
    modelOptions: [],
  });
@@ -127,68 +304,51 @@
    const result = [];
    const walk = list => {
      (list || []).forEach(item => {
        if (item.children && item.children.length) walk(item.children);
        else result.push({ label: item.label || item.productName || "", value: item.id || item.value });
        if (item.children && item.children.length) {
          walk(item.children);
        } else {
          result.push({
            label: item.label || item.productName || "",
            value: item.id || item.value,
          });
        }
      });
    };
    walk(nodes);
    return result;
  };
  const findProductIdByLabel = label => {
    if (!label) return "";
    const hit = (productList.value || []).find(item => item.label === label);
    return hit?.value || "";
  };
  const formatAmount = amount => `¥${Number(amount || 0).toFixed(2)}`;
  const goBack = () => uni.navigateBack();
  const calcTotalAmountFromProducts = products =>
    Number(
      (products || [])
        .reduce((sum, item) => {
          const unitPrice = Number(item?.unitPrice || 0);
          const printingFee = Number(item?.printingFee || 0);
          const dieCuttingFee = Number(item?.dieCuttingFee || 0);
          const grindingFee = Number(item?.grindingFee || 0);
          return sum + unitPrice + printingFee + dieCuttingFee + grindingFee;
        }, 0)
        .toFixed(2)
  const calculateAmount = product => {
    product.amount = Number(
      (Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2)
    );
  const syncTotalAmount = () => {
    form.value.totalAmount = totalAmount.value;
  };
  const calculateAmount = product => {
    product.amount = Number((Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2));
    syncTotalAmount();
  };
  const addProduct = () => {
    if (isEditMode.value) {
      uni.showToast({ title: "编辑模式下不允许新增产品", icon: "none" });
      return;
    }
    form.value.products.push(createEmptyProduct());
  };
  const addProduct = () => form.value.products.push(createEmptyProduct());
  const removeProduct = index => {
    form.value.products.splice(index, 1);
    syncTotalAmount();
    form.value.totalAmount = totalAmount.value;
  };
  const fetchModelOptions = async (productId, product) => {
    const rows = await modelList({ id: productId }).catch(() => []);
    product.modelOptions = Array.isArray(rows) ? rows : [];
    try {
      const res = await modelList({ id: productId });
      const rows = res?.data?.records || res?.data || res?.records || res || [];
      product.modelOptions = Array.isArray(rows) ? rows : [];
    } catch (error) {
      console.error("获取规格型号失败:", error);
      product.modelOptions = [];
    }
  };
  const openProductPicker = index => {
    currentProductIndex.value = index;
    showProductSheet.value = true;
  };
  const openModelPicker = index => {
    currentProductIndex.value = index;
    const current = form.value.products[index];
@@ -196,70 +356,122 @@
      uni.showToast({ title: "请先选择产品", icon: "none" });
      return;
    }
    modelActions.value = (current.modelOptions || []).map(item => ({ name: item.model, value: item.id, unit: item.unit }));
    modelActions.value = (current.modelOptions || []).map(item => ({
      name: item.model || item.specification,
      value: item.id,
      unit: item.unit,
    }));
    if (!modelActions.value.length) {
      uni.showToast({ title: "暂无规格数据", icon: "none" });
      uni.showToast({ title: "暂无规格型号", icon: "none" });
      return;
    }
    showModelSheet.value = true;
  };
  const onSelectCustomer = action => {
    form.value.customerId = action.value;
    form.value.customer = action.name;
    showCustomerSheet.value = false;
  };
  const onSelectSalesperson = action => {
    form.value.salesperson = action.value;
    showSalespersonSheet.value = false;
  };
  const onSelectProduct = action => {
    const current = form.value.products[currentProductIndex.value];
    if (!current) return;
    current.productId = action.value;
    current.product = action.label;
    current.specificationId = "";
    current.specification = "";
    current.productModelId = "";
    current.ProductModel = "";
    current.unit = "";
    current.modelOptions = [];
    showProductSheet.value = false;
    fetchModelOptions(action.value, current);
  };
  const onSelectModel = action => {
    const current = form.value.products[currentProductIndex.value];
    if (!current) return;
    current.specificationId = action.value;
    current.specification = action.name;
    current.productModelId = action.value;
    current.ProductModel = action.name;
    current.unit = action.unit || current.unit;
    showModelSheet.value = false;
  };
  const onQuotationDateConfirm = e => {
    form.value.quotationDate = formatDateToYMD(e.value);
    showQuotationDatePicker.value = false;
  };
  const onValidDateConfirm = e => {
    form.value.validDate = formatDateToYMD(e.value);
    showValidDatePicker.value = false;
  };
  const fetchProductOptions = async () => {
    const productTree = await productTreeList().catch(() => []);
    productList.value = flattenProductTree(Array.isArray(productTree) ? productTree : productTree?.data || []);
  const fetchBaseOptions = async () => {
    const [customers, users, productTree] = await Promise.all([
      getCustomerList({ current: -1, size: -1 }).catch(() => ({})),
      userListNoPageByTenantId().catch(() => ({})),
      productTreeList().catch(() => []),
    ]);
    customerList.value = customers?.data?.records || customers?.records || [];
    const userRows = users?.data || [];
    salespersonList.value = Array.isArray(userRows) ? userRows : [];
    productList.value = flattenProductTree(
      Array.isArray(productTree) ? productTree : productTree?.data || []
    );
  };
  // 根据名称反查节点 id,便于仅存名称时的反显
  const findNodeIdByLabel = (nodes, label) => {
    if (!label) return null;
    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (node.label === label) return node.value;
      if (node.children && node.children.length > 0) {
        const found = findNodeIdByLabel(node.children, label);
        if (found !== null && found !== undefined) return found;
      }
    }
    return null;
  };
  const normalizeProductRows = async rows => {
    const normalized = await Promise.all(
      (Array.isArray(rows) ? rows : []).map(async item => {
        const productName = item.product || item.productName || "";
        // 优先用 productId;如果只有名称,尝试反查 id 以便选择器反显
        let resolvedProductId =
          item.productId ||
          findNodeIdByLabel(productList.value, productName) ||
          "";
        const row = {
          uid: `p_${uidSeed++}`,
          id: item.id || "",
          salesQuotationId: item.salesQuotationId || "",
          productId: item.productId || "",
          product: item.product || item.productName || "",
          specificationId: item.specificationId || "",
          specification: item.specification || "",
          productId: resolvedProductId,
          product: productName,
          productModelId: item.productModelId || "",
          ProductModel: item.ProductModel || item.specification || "",
          unit: item.unit || "",
          paper: item.paper || "",
          paperWeight: item.paperWeight || "",
          quantity: Number(item.quantity || 1),
          unitPrice: Number(item.unitPrice || 0),
          printingFee: Number(item.printingFee || 0),
          dieCuttingFee: Number(item.dieCuttingFee || 0),
          grindingFee: Number(item.grindingFee || 0),
          amount: Number(item.amount || Number(item.quantity || 0) * Number(item.unitPrice || 0)),
          amount: Number(item.amount || 0),
          modelOptions: [],
        };
        if (row.productId) {
          await fetchModelOptions(row.productId, row);
          if (!row.specificationId && row.specification) {
            const matchedModel = (row.modelOptions || []).find(model => model.model === row.specification);
            if (matchedModel) {
              row.specificationId = matchedModel.id;
              if (!row.unit) row.unit = matchedModel.unit || "";
          // 如果没有 productModelId 但有 ProductModel 名称,尝试从 modelOptions 中匹配 ID
          if (!row.productModelId && row.ProductModel) {
            const foundModel = row.modelOptions.find(
              m =>
                m.model === row.ProductModel ||
                m.specification === row.ProductModel
            );
            if (foundModel) {
              row.productModelId = foundModel.id;
              // 统一使用 modelOptions 中的字段
              row.ProductModel =
                foundModel.model || foundModel.specification || row.ProductModel;
              row.unit = foundModel.unit || row.unit;
            }
          }
        }
@@ -269,26 +481,41 @@
    form.value.products = normalized;
  };
  const loadEditFromStorage = async () => {
  const loadDetail = async () => {
    if (!quotationId.value) return;
    const cached = uni.getStorageSync("salesQuotationEdit");
    if (!cached || typeof cached !== "object") return;
    if (cached.id && String(cached.id) !== String(quotationId.value)) return;
    const data = cached;
    form.value = {
      ...form.value,
      id: data.id || form.value.id,
      remark: data.remark || "",
    };
    const rows = Array.isArray(data.products) && data.products.length ? data.products : [data];
    const normalizedRows = rows.map(item => ({
      ...item,
      productId: item.productId || findProductIdByLabel(item.product || item.productName || ""),
    }));
    await normalizeProductRows(normalizedRows);
    syncTotalAmount();
    // 直接从本地存储获取数据,不再调用详情接口
    const cachedData = uni.getStorageSync("salesQuotationDetail");
    if (
      cachedData &&
      (cachedData.id === quotationId.value ||
        cachedData.id === Number(quotationId.value))
    ) {
      const data = cachedData;
      form.value = {
        ...form.value,
        id: data.id,
        quotationNo: data.quotationNo || "",
        customerId: data.customerId,
        customer: data.customer || "",
        salesperson: data.salesperson || "",
        quotationDate: data.quotationDate || "",
        validDate: data.validDate || "",
        paymentMethod: data.paymentMethod || "",
        status: data.status || "草稿",
        remark: data.remark || "",
        subtotal: data.subtotal || 0,
        freight: data.freight || 0,
        otherFee: data.otherFee || 0,
        discountRate: data.discountRate || 0,
        discountAmount: data.discountAmount || 0,
        totalAmount: data.totalAmount || 0,
      };
      await normalizeProductRows(data.products || []);
      form.value.totalAmount = totalAmount.value;
    } else {
      console.warn("未找到缓存的报价单详情数据");
    }
  };
  const validateProducts = () => {
@@ -296,7 +523,14 @@
      uni.showToast({ title: "请至少添加一个产品", icon: "none" });
      return false;
    }
    const invalid = form.value.products.some(item => !item.productId || !item.specificationId || !item.unit || !Number(item.unitPrice || 0));
    const invalid = form.value.products.some(
      item =>
        !item.productId ||
        !item.productModelId ||
        !item.unit ||
        !Number(item.quantity) ||
        !Number(item.unitPrice)
    );
    if (invalid) {
      uni.showToast({ title: "请完善产品信息", icon: "none" });
      return false;
@@ -304,56 +538,30 @@
    return true;
  };
  const buildProductPayload = item => {
    const quantity = Number(item?.quantity || 0);
    const unitPrice = Number(item?.unitPrice || 0);
    const printingFee = Number(item?.printingFee || 0);
    const dieCuttingFee = Number(item?.dieCuttingFee || 0);
    const grindingFee = Number(item?.grindingFee || 0);
    return {
      id: item?.id || undefined,
      salesQuotationId: item?.salesQuotationId || null,
      product: item?.product || "",
      specification: item?.specification || "",
      unit: item?.unit || "",
      paper: item?.paper || "",
      paperWeight: item?.paperWeight || "",
      unitPrice,
      printingFee,
      dieCuttingFee,
      grindingFee,
      quantity,
      amount: Number(item?.amount ?? quantity * unitPrice),
      remark: form.value.remark || "",
    };
  };
  const handleSubmit = async () => {
    if (!validateProducts()) return;
    const valid = await formRef.value.validate().catch(() => false);
    if (!valid || !validateProducts()) return;
    loading.value = true;
    if (quotationId.value) {
      const editingItem = form.value.products[0] || {};
      const payload = buildProductPayload({
        ...editingItem,
        id: editingItem.id || quotationId.value,
      });
      editQuotationProduct(payload)
        .then(() => {
          uni.showToast({ title: "保存成功", icon: "success" });
          setTimeout(() => uni.navigateBack(), 300);
        })
        .catch(() => {
          uni.showToast({ title: "保存失败", icon: "error" });
        })
        .finally(() => {
          loading.value = false;
        });
      return;
    }
    const payloadList = form.value.products.map(item => buildProductPayload(item));
    addOrUpdateQuotationProduct(payloadList)
    // 同步最新的总额
    form.value.totalAmount = totalAmount.value;
    form.value.subtotal = totalAmount.value;
    const payload = {
      ...form.value,
      products: form.value.products.map(item => ({
        productId: item.productId,
        product: item.product,
        productModelId: item.productModelId,
        ProductModel: item.ProductModel,
        quantity: Number(item.quantity || 0),
        unit: item.unit,
        unitPrice: Number(item.unitPrice || 0),
        amount: Number(item.amount || 0),
      })),
    };
    const action = quotationId.value ? updateQuotation : addQuotation;
    action(payload)
      .then(() => {
        uni.showToast({ title: "保存成功", icon: "success" });
        setTimeout(() => uni.navigateBack(), 300);
@@ -371,21 +579,56 @@
      quotationId.value = options.id;
      form.value.id = options.id;
    } else {
      form.value.products = [];
      const today = formatDateToYMD(Date.now());
      form.value.quotationDate = today;
      form.value.validDate = today;
    }
  });
  onMounted(async () => {
    await fetchProductOptions();
    if (quotationId.value) await loadEditFromStorage();
    await fetchBaseOptions();
    if (quotationId.value) {
      await loadDetail();
    }
  });
  onUnmounted(() => {});
</script>
<style scoped lang="scss">
  @import "@/static/scss/form-common.scss";
  .account-detail {
    min-height: 100vh;
    background: #f8f9fa;
    padding-bottom: 100px;
  }
  .form-container {
    padding: 12px 12px 0;
  }
  .hero-card {
    margin-bottom: 12px;
    padding: 18px 18px 16px;
    border-radius: 16px;
    background: linear-gradient(135deg, #eef6ff 0%, #ffffff 100%);
    box-shadow: 0 6px 18px rgba(41, 121, 255, 0.08);
  }
  .hero-title {
    display: block;
    font-size: 18px;
    font-weight: 600;
    color: #1f2d3d;
    margin-bottom: 6px;
  }
  .hero-desc {
    display: block;
    font-size: 13px;
    line-height: 1.6;
    color: #7a8599;
  }
  .form-section {
@@ -435,13 +678,6 @@
    padding: 16px 12px;
    color: #999;
    font-size: 14px;
  }
  .summary-tip {
    padding: 0 24rpx 24rpx;
    color: #909399;
    font-size: 12px;
    line-height: 1.6;
  }
  :deep(.u-cell-group__title) {