src/views/salesManagement/salesQuotation/index.vue
@@ -15,15 +15,6 @@
            </template>
          </el-input>
        </el-col>
        <el-col :span="8">
          <el-select v-model="searchForm.customer" placeholder="请选择客户" clearable>
                  <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName">
                     {{
                        item.customerName + "——" + item.taxpayerIdentificationNumber
                     }}
                  </el-option>
          </el-select>
        </el-col>
<!--        <el-col :span="6">-->
<!--          <el-select v-model="searchForm.status" placeholder="请选择报价状态" clearable>-->
<!--            <el-option label="草稿" value="草稿"></el-option>-->
@@ -52,26 +43,12 @@
      >
            <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column prop="quotationNo" label="报价单号" />
        <el-table-column prop="customer" label="客户名称" />
        <el-table-column prop="salesperson" label="业务员" width="100" />
        <el-table-column prop="quotationDate" label="报价日期" width="120" />
        <el-table-column prop="validDate" label="有效期至" width="120" />
        <el-table-column prop="status" label="审批状态" width="120" align="center">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)" disable-transitions>
              {{ row.status || '--' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="totalAmount" label="报价金额" width="120">
          <template #default="scope">
            ¥{{ scope.row.totalAmount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column prop="salesperson" label="业务员" />
        <el-table-column prop="quotationDate" label="报价日期" />
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['待审批','拒绝'].includes(scope.row.status)">编辑</el-button>
            <el-button link type="primary" @click="handleView(scope.row)" style="color: #67C23A">查看</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
@@ -91,7 +68,7 @@
    <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
      <div class="quotation-form-container">
        <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form">
        <!-- 基本信息 -->
        <!-- 基本信息(仅保留业务员和报价日期) -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
@@ -102,22 +79,17 @@
          <div class="form-content">
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="客户名称" prop="customer">
                  <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%" clearable filterable>
                    <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"></el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="业务员" prop="salesperson">
                  <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%" clearable filterable>
                    <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                      :value="item.nickName" />
                  <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%" clearable>
                    <el-option
                      v-for="item in userList"
                      :key="item.nickName"
                      :label="item.nickName"
                      :value="item.nickName"
                    />
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="报价日期" prop="quotationDate">
                  <el-date-picker
@@ -129,83 +101,6 @@
                    value-format="YYYY-MM-DD"
                    clearable
                  />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="有效期至" prop="validDate">
                  <el-date-picker
                    v-model="form.validDate"
                    type="date"
                    placeholder="选择有效期"
                    style="width: 100%"
                    format="YYYY-MM-DD"
                    value-format="YYYY-MM-DD"
                    clearable
                  />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="付款方式" prop="paymentMethod">
                  <el-input v-model="form.paymentMethod" placeholder="请输入付款方式" clearable />
                </el-form-item>
              </el-col>
            </el-row>
          </div>
        </el-card>
        <!-- 审批人信息 -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><UserFilled /></el-icon>
              <span class="card-title">审批人选择</span>
              <el-button type="primary" size="small" @click="addApproverNode" class="header-btn">
                <el-icon><Plus /></el-icon>
                新增节点
              </el-button>
            </div>
          </template>
          <div class="form-content">
            <el-row>
              <el-col :span="24">
                <el-form-item>
                  <div class="approver-nodes-container">
                    <div
                      v-for="(node, index) in approverNodes"
                      :key="node.id"
                      class="approver-node-item"
                    >
                      <div class="approver-node-label">
                        <span class="node-step">{{ index + 1 }}</span>
                        <span class="node-text">审批人</span>
                        <el-icon class="arrow-icon"><ArrowRight /></el-icon>
                      </div>
                      <el-select
                        v-model="node.userId"
                        placeholder="选择人员"
                        class="approver-select"
                                    filterable
                        clearable
                      >
                        <el-option
                          v-for="user in userList"
                          :key="user.userId"
                          :label="user.nickName"
                          :value="user.userId"
                        />
                      </el-select>
                      <el-button
                        type="danger"
                        size="small"
                        :icon="Delete"
                        @click="removeApproverNode(index)"
                        v-if="approverNodes.length > 1"
                        class="remove-btn"
                      >删除</el-button>
                    </div>
                  </div>
                </el-form-item>
              </el-col>
            </el-row>
@@ -228,52 +123,53 @@
            <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">
              <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%"
                  />
                </el-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%"
                        />
              </template>
            </el-table-column>
            <el-table-column prop="specification" label="规格型号" width="200">
            <el-table-column prop="specification" label="规格型号" width="150">
              <template #default="scope">
                <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item">
                  <el-select
                    v-model="scope.row.specificationId"
                    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>
                </el-form-item>
                        <el-select
                           v-model="scope.row.specificationId"
                           placeholder="请选择"
                           clearable
                           @change="getProductModel($event, scope.row)"
                        >
                           <el-option
                              v-for="item in modelOptions"
                              :key="item.id"
                              :label="item.model"
                              :value="item.id"
                           />
                        </el-select>
              </template>
            </el-table-column>
            <el-table-column prop="unit" label="单位">
              <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>
                <el-input v-model="scope.row.unit" placeholder="单位" />
              </template>
            </el-table-column>
            <el-table-column prop="unitPrice" label="单价">
            <el-table-column prop="unitPrice" label="一批商单价">
              <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="2" style="width: 100%" />
                </el-form-item>
                <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column prop="unitPriceTwo" label="终端商单价">
              <template #default="scope">
                <el-input-number v-model="scope.row.unitPriceTwo" :min="0" :precision="2" style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column prop="unitPriceThree" label="单价">
              <template #default="scope">
                <el-input-number v-model="scope.row.unitPriceThree" :min="0" :precision="2" style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80" align="center">
@@ -315,28 +211,29 @@
    <el-dialog v-model="viewDialogVisible" title="报价详情" width="800px">
      <el-descriptions :column="2" border>
        <el-descriptions-item label="报价单号">{{ currentQuotation.quotationNo }}</el-descriptions-item>
        <el-descriptions-item label="客户名称">{{ currentQuotation.customer }}</el-descriptions-item>
        <el-descriptions-item label="业务员">{{ currentQuotation.salesperson }}</el-descriptions-item>
        <el-descriptions-item label="报价日期">{{ currentQuotation.quotationDate }}</el-descriptions-item>
        <el-descriptions-item label="有效期至">{{ currentQuotation.validDate }}</el-descriptions-item>
        <el-descriptions-item label="付款方式">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
<!--        <el-descriptions-item label="报价状态">-->
<!--          <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
<!--        </el-descriptions-item>-->
        <el-descriptions-item label="报价总额" :span="2">
          <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span>
        </el-descriptions-item>
      </el-descriptions>
      
      <div style="margin: 20px 0;">
      <div style="margin-top: 20px;">
        <h4>产品明细</h4>
        <el-table :data="currentQuotation.products" border style="width: 100%">
          <el-table-column prop="product" label="产品名称" />
          <el-table-column prop="specification" label="规格型号" />
          <el-table-column prop="unit" label="单位" />
          <el-table-column prop="unitPrice" label="单价">
          <el-table-column prop="unitPrice" label="一批商单价">
            <template #default="scope">
              ¥{{ scope.row.unitPrice.toFixed(2) }}
            </template>
          </el-table-column>
          <el-table-column prop="unitPriceTwo" label="终端商单价">
            <template #default="scope">
              ¥{{ scope.row.unitPriceTwo?.toFixed(2) }}
            </template>
          </el-table-column>
          <el-table-column prop="unitPriceThree" label="单价">
            <template #default="scope">
              ¥{{ scope.row.unitPriceThree?.toFixed(2) }}
            </template>
          </el-table-column>
        </el-table>
@@ -353,20 +250,18 @@
<script setup>
import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete } from '@element-plus/icons-vue'
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 {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js'
import {userListNoPage} from "@/api/system/user.js";
import {customerList} from "@/api/salesManagement/salesLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
import {listCustomerPrivatePool} from "@/api/basicData/customerFile.js";
// 响应式数据
const loading = ref(false)
const searchForm = reactive({
  quotationNo: '',
  customer: '',
  status: ''
})
@@ -400,57 +295,27 @@
  totalAmount: 0
})
const baseRules = {
  customer: [{ required: true, message: '请选择客户', trigger: 'change' }],
const rules = {
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  quotationDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }],
  validDate: [{ required: true, message: '请选择有效期', trigger: 'change' }],
  paymentMethod: [{ required: true, message: '请输入付款方式', trigger: 'blur' }]
  quotationDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }]
}
const productRowRules = {
  productId: [{ required: true, message: '请选择产品名称', trigger: 'change' }],
  specificationId: [{ required: true, message: '请选择规格型号', trigger: 'change' }],
  unit: [{ required: true, message: '请填写单位', trigger: 'blur' }],
  unitPrice: [{ required: true, message: '请填写单价', trigger: 'change' }]
}
const rules = computed(() => {
  const r = { ...baseRules }
  ;(form.products || []).forEach((_, i) => {
    r[`products.${i}.productId`] = productRowRules.productId
    r[`products.${i}.specificationId`] = productRowRules.specificationId
    r[`products.${i}.unit`] = productRowRules.unit
    r[`products.${i}.unitPrice`] = productRowRules.unitPrice
  })
  return r
})
const userList = ref([]);
const customerOption = ref([]);
// 审批人节点相关
const approverNodes = ref([
  { id: 1, userId: null }
])
let nextApproverId = 2
const isEdit = ref(false)
const editId = ref(null)
const currentQuotation = ref({})
const formRef = ref()
// 添加审批人节点
function addApproverNode() {
  approverNodes.value.push({ id: nextApproverId++, userId: null })
}
// 删除审批人节点
function removeApproverNode(index) {
  approverNodes.value.splice(index, 1)
}
// 计算属性
const filteredList = computed(() => {
  let list = quotationList.value
  if (searchForm.quotationNo) {
    list = list.filter(item => item.quotationNo.includes(searchForm.quotationNo))
  }
  if (searchForm.status) {
    list = list.filter(item => item.status === searchForm.status)
  }
  return list
})
@@ -467,20 +332,13 @@
const resetSearch = () => {
  searchForm.quotationNo = ''
  searchForm.customer = ''
  searchForm.status = ''
  // 重置到第一页并重新查询
  pagination.currentPage = 1
  handleSearch()
}
const handleAdd = async () => {
  dialogTitle.value = '新增报价'
  isEdit.value = false
  resetForm()
  // 重置审批人节点
  approverNodes.value = [{ id: 1, userId: null }]
  nextApproverId = 2
  dialogVisible.value = true
   let userLists = await userListNoPage();
   // 只复制需要的字段,避免将组件引用放入响应式对象
@@ -490,8 +348,13 @@
    userName: item.userName || ''
  }));
   getProductOptions();
   listCustomerPrivatePool({current: -1,size:-1}).then((res) => {
      customerOption.value = res.data.records;
   customerList().then((res) => {
      // 只复制需要的字段,避免将组件引用放入响应式对象
      customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
   });
}
const getProductOptions = () => {
@@ -534,7 +397,7 @@
   if (!value) {
      row.productId = '';
      row.product = '';
      row.modelOptions = [];
      modelOptions.value = [];
      row.specificationId = '';
      row.specification = '';
      row.unit = '';
@@ -547,9 +410,9 @@
   if (label) {
      row.product = label;
   }
   // 获取规格型号列表,设置到当前行的 modelOptions
   // 获取规格型号列表
   modelList({ id: value }).then((res) => {
      row.modelOptions = res || [];
      modelOptions.value = res || [];
   });
};
const getProductModel = (value, row) => {
@@ -563,11 +426,10 @@
   }
   // 更新 specificationId(v-model 已经自动更新,这里确保一致性)
   row.specificationId = value;
   const modelOptions = row.modelOptions || [];
   const index = modelOptions.findIndex((item) => item.id === value);
   const index = modelOptions.value.findIndex((item) => item.id === value);
   if (index !== -1) {
      row.specification = modelOptions[index].model;
      row.unit = modelOptions[index].unit;
      row.specification = modelOptions.value[index].model;
      row.unit = modelOptions.value[index].unit;
   } else {
      row.specification = '';
      row.unit = '';
@@ -605,7 +467,9 @@
      specification: product.specification || '',
      quantity: product.quantity || 0,
      unit: product.unit || '',
      unitPrice: product.unitPrice || 0,
      unitPrice: product.unitPrice || 0, // 一批商单价
      unitPriceTwo: product.unitPriceTwo || product.dealerUnitPrice || 0, // 终端商单价
      unitPriceThree: product.unitPriceThree || 0, // 单价
      amount: product.amount || 0
    })) : [],
    totalAmount: row.totalAmount || 0
@@ -630,65 +494,31 @@
  form.paymentMethod = row.paymentMethod || ''
  form.status = row.status || '草稿'
  form.remark = row.remark || ''
  form.products = row.products ? await Promise.all(row.products.map(async (product) => {
  form.products = row.products ? row.products.map(product => {
    const productName = product.product || product.productName || ''
    // 优先用 productId;如果只有名称,尝试反查 id 以便树选择器反显
    const resolvedProductId = product.productId
    const resolvedId = product.productId
      ? Number(product.productId)
      : findNodeIdByLabel(productOptions.value, productName) || ''
    // 如果有产品ID,加载对应的规格型号列表
    let modelOptions = [];
    let resolvedSpecificationId = product.specificationId || '';
    if (resolvedProductId) {
      try {
        const res = await modelList({ id: resolvedProductId });
        modelOptions = res || [];
        // 如果返回的数据没有 specificationId,但有 specification 名称,根据名称查找 ID
        if (!resolvedSpecificationId && product.specification) {
          const foundModel = modelOptions.find(item => item.model === product.specification);
          if (foundModel) {
            resolvedSpecificationId = foundModel.id;
          }
        }
      } catch (error) {
        console.error('加载规格型号失败:', error);
      }
    }
    return {
      productId: resolvedProductId,
      productId: resolvedId,
      product: productName,
      specificationId: resolvedSpecificationId,
      specificationId: product.specificationId || '',
      specification: product.specification || '',
      quantity: product.quantity || 0,
      unit: product.unit || '',
      unitPrice: product.unitPrice || 0,
      amount: product.amount || 0,
      modelOptions: modelOptions // 为每行添加独立的规格型号列表
      unitPriceTwo: product.unitPriceTwo || product.dealerUnitPrice || 0,
      unitPriceThree: product.unitPriceThree || 0,
      amount: product.amount || 0
    }
  })) : []
  }) : []
  form.subtotal = row.subtotal || 0
  form.freight = row.freight || 0
  form.otherFee = row.otherFee || 0
  form.discountRate = row.discountRate || 0
  form.discountAmount = row.discountAmount || 0
  form.totalAmount = row.totalAmount || 0
  // 反显审批人
  if (row.approveUserIds) {
    const userIds = row.approveUserIds.split(',')
    approverNodes.value = userIds.map((userId, idx) => ({
      id: idx + 1,
      userId: parseInt(userId.trim())
    }))
    nextApproverId = userIds.length + 1
  } else {
    approverNodes.value = [{ id: 1, userId: null }]
    nextApproverId = 2
  }
  
  // 加载用户列表
  let userLists = await userListNoPage();
@@ -750,9 +580,10 @@
    specification: '',
    quantity: 1,
    unit: '',
    unitPriceTwo: 0,
    unitPriceThree: 0,
    unitPrice: 0,
    amount: 0,
    modelOptions: [] // 为每行添加独立的规格型号列表
    amount: 0
  })
}
@@ -776,6 +607,10 @@
  form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount
}
const handleCustomerChange = () => {
  // 可以根据客户信息自动填充一些默认值
}
const handleSubmit = () => {
  formRef.value.validate((valid) => {
    if (valid) {
@@ -783,16 +618,6 @@
        ElMessage.warning('请至少添加一个产品')
        return
      }
      // 审批人必填校验
      const hasEmptyApprover = approverNodes.value.some(node => !node.userId)
      if (hasEmptyApprover) {
        ElMessage.error('请为所有审批节点选择审批人!')
        return
      }
      // 收集所有节点的审批人id
      form.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
      
      // 计算所有产品的单价总和
      form.totalAmount = form.products.reduce((sum, product) => {
@@ -831,14 +656,10 @@
const handleCurrentChange = (val) => {
  pagination.currentPage = val.page
  pagination.pageSize = val.limit
  // 分页变化时重新查询列表
  handleSearch()
}
const handleSearch = ()=>{
  const params = {
    // 后端分页参数:current / size
    current: pagination.currentPage,
    size: pagination.pageSize,
    ...pagination,
    ...searchForm
  }
  getQuotationList(params).then(res=>{
@@ -854,8 +675,6 @@
        validDate: item.validDate || '',
        paymentMethod: item.paymentMethod || '',
        status: item.status || '草稿',
        // 审批人(用于编辑时反显)
        approveUserIds: item.approveUserIds || '',
        remark: item.remark || '',
        products: item.products ? item.products.map(product => ({
          productId: product.productId || '',
@@ -865,6 +684,8 @@
          quantity: product.quantity || 0,
          unit: product.unit || '',
          unitPrice: product.unitPrice || 0,
          unitPriceTwo: product.unitPriceTwo || product.dealerUnitPrice || 0,
          unitPriceThree: product.unitPriceThree || 0,
          amount: product.amount || 0
        })) : [],
        subtotal: item.subtotal || 0,
@@ -878,7 +699,12 @@
    }
  })
   customerList().then((res) => {
      customerOption.value = res;
      // 只复制需要的字段,避免将组件引用放入响应式对象
      customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
   });
}
@@ -962,82 +788,6 @@
.form-content {
  padding: 8px 0;
}
.product-table-form-item {
  margin-bottom: 0;
  :deep(.el-form-item__content) {
    margin-left: 0 !important;
  }
  :deep(.el-form-item__label) {
    width: auto;
    min-width: auto;
  }
}
.approver-nodes-container {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  padding: 12px 0;
}
.approver-node-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
  transition: all 0.3s ease;
  min-width: 180px;
  &:hover {
    border-color: #409eff;
    background: #f0f7ff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
  }
}
.approver-node-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: #606266;
  .node-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    background: #409eff;
    color: #fff;
    border-radius: 50%;
    font-size: 12px;
    font-weight: 600;
  }
  .node-text {
    font-weight: 500;
  }
  .arrow-icon {
    color: #909399;
    font-size: 14px;
  }
}
.approver-select {
  width: 100%;
  min-width: 150px;
}
.remove-btn {
  margin-top: 4px;
}
.product-table {