gongchunyi
2026-10-30 221b524e39d5f60761445ccc9608cb975b4d6416
fix: 销售报价选择产品修改
已修改1个文件
239 ■■■■■ 文件已修改
src/views/salesManagement/salesQuotation/index.vue 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue
@@ -161,58 +161,37 @@
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><Box /></el-icon>
              <span class="card-title">产品信息</span>
              <el-button type="primary" size="small" @click="addProduct" class="header-btn">
              <el-button type="primary" size="small" @click="openProductSelect" class="header-btn">
                <el-icon><Plus /></el-icon>
                添加产品
                选择产品
              </el-button>
            </div>
          </template>
          <div class="form-content">
            <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0">
            <el-table-column prop="product" label="产品名称" width="200">
            <el-table-column type="index" label="序号" width="60" align="center" />
            <el-table-column prop="product" label="产品名称" min-width="180">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item">
                  <el-tree-select
                    v-model="scope.row.productId"
                    placeholder="请选择"
                    clearable
                    check-strictly
                    @change="getModels($event, scope.row)"
                    :data="productOptions"
                    :render-after-expand="false"
                    style="width: 100%"
                  />
                  <span>{{ scope.row.product || '--' }}</span>
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column prop="specification" label="规格型号" width="200">
            <el-table-column prop="specification" label="规格型号" min-width="160">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.productModelId`" class="product-table-form-item">
                  <el-select
                    v-model="scope.row.productModelId"
                    placeholder="请选择"
                    clearable
                    @change="getProductModel($event, scope.row)"
                    style="width: 100%"
                  >
                    <el-option
                      v-for="item in scope.row.modelOptions || []"
                      :key="item.id"
                      :label="item.model"
                      :value="item.id"
                    />
                  </el-select>
                  <span>{{ scope.row.specification || '--' }}</span>
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column prop="unit" label="单位">
            <el-table-column prop="unit" label="单位" width="120">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item">
                  <el-input v-model="scope.row.unit" placeholder="单位" clearable/>
                </el-form-item>
              </template>
            </el-table-column>
            <el-table-column prop="unitPrice" label="单价">
            <el-table-column prop="unitPrice" label="单价" min-width="180">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item">
                  <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="6" :step="0.000001" style="width: 100%" />
@@ -225,7 +204,7 @@
              </template>
            </el-table-column>
          </el-table>
          <el-empty v-else description="暂无产品,请点击添加产品" :image-size="80" />
          <el-empty v-else description="暂无产品,请点击选择产品" :image-size="80" />
          </div>
        </el-card>
@@ -253,6 +232,9 @@
      </el-form>
      </div>
    </FormDialog>
    <!-- 选择产品弹窗 -->
    <ProductSelectDialog v-model="productSelectVisible" @confirm="handleProductSelect" />
    <!-- 查看详情对话框 -->
    <el-dialog v-model="viewDialogVisible" title="报价详情" width="800px">
@@ -294,13 +276,13 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue'
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Document, Box, EditPen, Plus } from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
import FormDialog from '@/components/Dialog/FormDialog.vue'
import ProductSelectDialog from '@/views/basicData/product/ProductSelectDialog.vue'
import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js'
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {listCustomer} from "@/api/basicData/customer.js";
import { userListNoPage } from "@/api/system/user.js";
import { formatDecimal, roundAmount } from '@/utils/numberFormat';
@@ -315,8 +297,6 @@
const quotationList = ref([])
const userList = ref([])
const productOptions = ref([]);
const modelOptions  = ref([]);
const pagination = reactive({
  total: 3,
  currentPage: 1,
@@ -325,6 +305,7 @@
const dialogVisible = ref(false)
const viewDialogVisible = ref(false)
const productSelectVisible = ref(false)
const dialogTitle = ref('新增报价')
const form = reactive({
  quotationNo: '',
@@ -408,7 +389,6 @@
  isEdit.value = false
  resetForm()
  dialogVisible.value = true
  getProductOptions();
  fetchCustomerOptions()
}
@@ -418,99 +398,39 @@
    customerOption.value = res.data.records;
  });
}
const getProductOptions = () => {
    // 返回 Promise,便于编辑时 await 确保能反显
    return productTreeList().then((res) => {
        productOptions.value = convertIdToValue(res);
        return productOptions.value
    });
};
function convertIdToValue(data) {
    return data.map((item) => {
        const { id, children, ...rest } = item;
        const newItem = {
            ...rest,
            value: id, // 将 id 改为 value
        };
        if (children && children.length > 0) {
            newItem.children = convertIdToValue(children);
        }
        return newItem;
    });
// 打开选择产品弹窗(支持多选)
const openProductSelect = () => {
  productSelectVisible.value = true
}
// 根据名称反查节点 id,便于仅存名称时的反显
function 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 handleProductSelect = (rows) => {
  if (!Array.isArray(rows) || rows.length === 0) return
  const getRowKey = (row) => String(row.productModelId || `${row.productId || ''}-${row.specification || ''}`)
  const existingKeys = new Set(form.products.map(getRowKey))
  let addedCount = 0
  rows.forEach((row) => {
    const productModelId = row.id ?? row.productModelId ?? ''
    const item = {
      productId: row.productId ?? '',
      product: row.productName || row.product || '',
      productName: row.productName || row.product || '',
      productModelId,
      specification: row.model || row.specification || '',
      quantity: 1,
      unit: row.unit || '',
      unitPrice: 0,
      amount: 0
    }
    const key = getRowKey(item)
    if (existingKeys.has(key)) return
    existingKeys.add(key)
    form.products.push(item)
    addedCount++
  })
  if (addedCount === 0) {
    ElMessage.warning('所选产品已在明细中,请勿重复选择')
  }
}
const getModels = (value, row) => {
    if (!row) return;
    // 如果清空选择,则清空相关字段
    if (!value) {
        row.productId = '';
        row.product = '';
        row.modelOptions = [];
        row.productModelId = '';
        row.specification = '';
        row.unit = '';
        return;
    }
    // 更新 productId(v-model 已经自动更新,这里确保一致性)
    row.productId = value;
    // 找到对应的 label 并赋值给 row.product
    const label = findNodeById(productOptions.value, value);
    if (label) {
        row.product = label;
    }
    // 获取规格型号列表,设置到当前行的 modelOptions
    modelList({ id: value }).then((res) => {
        row.modelOptions = res || [];
    });
};
const getProductModel = (value, row) => {
    if (!row) return;
    // 如果清空选择,则清空相关字段
    if (!value) {
        row.productModelId = '';
        row.specification = '';
        row.unit = '';
        return;
    }
    // 更新 productModelId(v-model 已经自动更新,这里确保一致性)
    row.productModelId = value;
    const modelOptions = row.modelOptions || [];
    const index = modelOptions.findIndex((item) => item.id === value);
    if (index !== -1) {
        row.specification = modelOptions[index].model;
        row.unit = modelOptions[index].unit;
    } else {
        row.specification = '';
        row.unit = '';
    }
};
const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].value === productId) {
            return nodes[i].label; // 找到节点,返回 label
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
            const foundLabel = findNodeById(nodes[i].children, productId);
            if (foundLabel) {
                return foundLabel; // 在子节点中找到,返回 label
            }
        }
    }
    return null; // 没有找到节点,返回null
};
const handleView = (row) => {
  // 只复制需要的字段,避免将组件引用放入响应式对象
  currentQuotation.value = {
@@ -542,8 +462,6 @@
  isEdit.value = true
  editId.value = row.id
  form.id = row.id || form.id || null
  // 先加载产品树数据,否则 el-tree-select 无法反显产品名称
  await getProductOptions()
  await fetchCustomerOptions()
  // 只复制需要的字段,避免将组件引用放入响应式对象
@@ -556,45 +474,16 @@
  form.paymentMethod = row.paymentMethod || ''
  form.status = row.status || '草稿'
  form.remark = row.remark || ''
  form.products = row.products ? await Promise.all(row.products.map(async (product) => {
    const productName = product.product || product.productName || ''
    // 优先用 productId;如果只有名称,尝试反查 id 以便树选择器反显
    const resolvedProductId = product.productId
      ? Number(product.productId)
      : findNodeIdByLabel(productOptions.value, productName) || ''
    // 如果有产品ID,加载对应的规格型号列表
    let modelOptions = [];
    let resolvedProductModelId = product.productModelId || '';
    if (resolvedProductId) {
      try {
        const res = await modelList({ id: resolvedProductId });
        modelOptions = res || [];
        // 如果返回的数据没有 productModelId,但有 specification 名称,根据名称查找 ID
        if (!resolvedProductModelId && product.specification) {
          const foundModel = modelOptions.find(item => item.model === product.specification);
          if (foundModel) {
            resolvedProductModelId = foundModel.id;
          }
        }
      } catch (error) {
        console.error('加载规格型号失败:', error);
      }
    }
    return {
      productId: resolvedProductId,
      product: productName,
      productModelId: resolvedProductModelId,
      specification: product.specification || '',
      quantity: product.quantity || 0,
      unit: product.unit || '',
      unitPrice: product.unitPrice || 0,
      amount: product.amount || 0,
      modelOptions: modelOptions // 为每行添加独立的规格型号列表
    }
  form.products = row.products ? row.products.map((product) => ({
    productId: product.productId || '',
    product: product.product || product.productName || '',
    productName: product.productName || product.product || '',
    productModelId: product.productModelId || '',
    specification: product.specification || '',
    quantity: product.quantity || 0,
    unit: product.unit || '',
    unitPrice: product.unitPrice || 0,
    amount: product.amount || 0
  })) : []
  form.subtotal = row.subtotal || 0
  form.freight = row.freight || 0
@@ -644,20 +533,6 @@
  form.discountRate = 0
  form.discountAmount = 0
  form.totalAmount = 0
}
const addProduct = () => {
  form.products.push({
    productId: '',
    product: '',
    productName: '',
    productModelId: '',
    quantity: 1,
    unit: '',
    unitPrice: 0,
    amount: 0,
    modelOptions: [] // 为每行添加独立的规格型号列表
  })
}
const removeProduct = (index) => {