gaoluyang
10 小时以前 9289a4fbacafcc7a80385fc3a3167f383d5cf991
src/views/salesManagement/salesQuotation/index.vue
@@ -56,23 +56,23 @@
        <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="status" label="报价状态" width="100">-->
<!--          <template #default="scope">-->
<!--            <el-tag :type="getStatusType(scope.row.status)">-->
<!--              {{ scope.row.status }}-->
<!--            </el-tag>-->
<!--          </template>-->
<!--        </el-table-column>-->
        <el-table-column label="操作" width="250" fixed="right" align="center">
        <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)" v-if="scope.row.status === '草稿'">编辑</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)" v-if="scope.row.status === '草稿'">删除</el-button>
            <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['待审批','拒绝'].includes(scope.row.status)">编辑</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -88,100 +88,176 @@
    </el-card>
    <!-- 新增/编辑对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="1300px" :close-on-click-modal="false">
      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
    <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="never">
        <el-card class="form-card" shadow="hover">
          <template #header>
            <span class="card-title">基本信息</span>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><Document /></el-icon>
              <span class="card-title">基本信息</span>
            </div>
          </template>
          <el-row :gutter="20">
            <el-col :span="12">
              <el-form-item label="客户名称" prop="customer">
                <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%" @change="handleCustomerChange">
                           <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-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="业务员" prop="salesperson">
                <el-select v-model="form.salesperson" placeholder="请选择业务员" style="width: 100%">
                           <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="20">
            <el-col :span="12">
              <el-form-item label="报价日期" prop="quotationDate">
                <el-date-picker
                  v-model="form.quotationDate"
                  type="date"
                  placeholder="选择报价日期"
                  style="width: 100%"
                  format="YYYY-MM-DD"
                  value-format="YYYY-MM-DD"
                />
              </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"
                />
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="12">
              <el-form-item label="付款方式" prop="paymentMethod">
                <el-select v-model="form.paymentMethod" placeholder="请选择付款方式" style="width: 100%">
                  <el-option label="全款到付" value="全款到付"></el-option>
                  <el-option label="分期付款" value="分期付款"></el-option>
                  <el-option label="月结" value="月结"></el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="交货期" prop="deliveryPeriod">
                <el-input v-model="form.deliveryPeriod" placeholder="请输入交货期" />
              </el-form-item>
            </el-col>
          </el-row>
          <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%" @change="handleCustomerChange" 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-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>
                    <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
                    v-model="form.quotationDate"
                    type="date"
                    placeholder="选择报价日期"
                    style="width: 100%"
                    format="YYYY-MM-DD"
                    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"
                        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>
          </div>
        </el-card>
        <!-- 产品信息 -->
        <el-card class="form-card" shadow="never">
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header">
            <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">添加产品</el-button>
              <el-button type="primary" size="small" @click="addProduct" class="header-btn">
                <el-icon><Plus /></el-icon>
                添加产品
              </el-button>
            </div>
          </template>
          <el-table :data="form.products" border style="width: 100%">
          <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">
              <template #default="scope">
                <el-input v-model="scope.row.product" placeholder="请输入产品名称" />
                        <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="150">
              <template #default="scope">
                <el-input v-model="scope.row.specification" placeholder="规格型号" />
              </template>
            </el-table-column>
            <el-table-column prop="quantity" label="数量">
              <template #default="scope">
                <el-input-number v-model="scope.row.quantity" :min="1" :precision="0" style="width: 100%" />
                        <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="单位">
@@ -191,12 +267,7 @@
            </el-table-column>
            <el-table-column prop="unitPrice" label="单价">
              <template #default="scope">
                <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" @change="calculateAmount(scope.row)" />
              </template>
            </el-table-column>
            <el-table-column prop="amount" label="金额" width="120">
              <template #default="scope">
                <span>¥{{ scope.row.amount.toFixed(2) }}</span>
                <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" />
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80" align="center">
@@ -205,66 +276,34 @@
              </template>
            </el-table-column>
          </el-table>
        </el-card>
        <!-- 费用信息 -->
        <el-card class="form-card" shadow="never">
          <template #header>
            <span class="card-title">费用信息</span>
          </template>
          <el-row :gutter="20">
            <el-col :span="8">
              <el-form-item label="产品小计">
                <el-input-number v-model="form.subtotal" :precision="2" :min="0" style="width: 100%" readonly />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="运费">
                <el-input-number v-model="form.freight" :precision="2" :min="0" style="width: 100%" @change="calculateTotal" />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="其他费用">
                <el-input-number v-model="form.otherFee" :precision="2" :min="0" style="width: 100%" @change="calculateTotal" />
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8">
              <el-form-item label="折扣率(%)">
                <el-input-number v-model="form.discountRate" :precision="2" :min="0" :max="100" style="width: 100%" @change="calculateTotal" />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="折扣金额">
                <el-input-number v-model="form.discountAmount" :precision="2" :min="0" style="width: 100%" readonly />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="报价总额">
                <el-input-number v-model="form.totalAmount" :precision="2" :min="0" style="width: 100%" readonly />
              </el-form-item>
            </el-col>
          </el-row>
          <el-empty v-else description="暂无产品,请点击添加产品" :image-size="80" />
          </div>
        </el-card>
        <!-- 备注信息 -->
        <el-card class="form-card" shadow="never">
        <el-card class="form-card" shadow="hover">
          <template #header>
            <span class="card-title">备注信息</span>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><EditPen /></el-icon>
              <span class="card-title">备注信息</span>
            </div>
          </template>
          <el-form-item label="备注" prop="remark">
            <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input>
          </el-form-item>
          <div class="form-content">
            <el-form-item label="备注" prop="remark">
              <el-input
                type="textarea"
                v-model="form.remark"
                placeholder="请输入备注信息(选填)"
                :rows="4"
                maxlength="500"
                show-word-limit
              ></el-input>
            </el-form-item>
          </div>
        </el-card>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="handleSubmit">确 定</el-button>
        </div>
      </template>
    </el-dialog>
      </div>
    </FormDialog>
    <!-- 查看详情对话框 -->
    <el-dialog v-model="viewDialogVisible" title="报价详情" width="800px">
@@ -275,7 +314,6 @@
        <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="交货期">{{ currentQuotation.deliveryPeriod }}</el-descriptions-item>
<!--        <el-descriptions-item label="报价状态">-->
<!--          <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
<!--        </el-descriptions-item>-->
@@ -289,16 +327,10 @@
        <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="quantity" label="数量" />
          <el-table-column prop="unit" label="单位" />
          <el-table-column prop="unitPrice" label="单价">
            <template #default="scope">
              ¥{{ scope.row.unitPrice.toFixed(2) }}
            </template>
          </el-table-column>
          <el-table-column prop="amount" label="金额">
            <template #default="scope">
              ¥{{ scope.row.amount.toFixed(2) }}
            </template>
          </el-table-column>
        </el-table>
@@ -313,13 +345,15 @@
</template>
<script setup>
import { ref, reactive, computed,onMounted  } from 'vue'
import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
// import Pagination from '@/components/PIMTable/Pagination.vue'
import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete } 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";
// 响应式数据
const loading = ref(false)
@@ -330,11 +364,12 @@
})
const quotationList = ref([])
const productOptions = ref([]);
const modelOptions = ref([]);
const pagination = reactive({
  total: 3,
  currentPage: 1,
  pageSize: 10
  pageSize: 100
})
const dialogVisible = ref(false)
@@ -347,7 +382,6 @@
  quotationDate: '',
  validDate: '',
  paymentMethod: '',
  deliveryPeriod: '',
  status: '草稿',
  remark: '',
  products: [],
@@ -364,16 +398,31 @@
  salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }],
  quotationDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }],
  validDate: [{ required: true, message: '请选择有效期', trigger: 'change' }],
  paymentMethod: [{ required: true, message: '请选择付款方式', trigger: 'change' }],
  deliveryPeriod: [{ required: true, message: '请输入交货期', trigger: 'blur' }]
  paymentMethod: [{ required: true, message: '请输入付款方式', trigger: 'blur' }]
}
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(() => {
@@ -393,10 +442,10 @@
// 方法
const getStatusType = (status) => {
  const statusMap = {
    '草稿': 'info',
    '已发送': 'primary',
    '客户确认': 'success',
    '已过期': 'danger'
    '待审批': 'info',
    '审核中': 'primary',
    '通过': 'success',
    '拒绝': 'danger'
  }
  return statusMap[status] || 'info'
}
@@ -411,24 +460,207 @@
  dialogTitle.value = '新增报价'
  isEdit.value = false
  resetForm()
  // 重置审批人节点
  approverNodes.value = [{ id: 1, userId: null }]
  nextApproverId = 2
  dialogVisible.value = true
   let userLists = await userListNoPage();
   userList.value = userLists.data;
   // 只复制需要的字段,避免将组件引用放入响应式对象
   userList.value = (userLists.data || []).map(item => ({
    userId: item.userId,
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
   getProductOptions();
   customerList().then((res) => {
      customerOption.value = res;
      // 只复制需要的字段,避免将组件引用放入响应式对象
      customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
   });
}
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;
   });
}
// 根据名称反查节点 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 getModels = (value, row) => {
   if (!row) return;
   // 如果清空选择,则清空相关字段
   if (!value) {
      row.productId = '';
      row.product = '';
      modelOptions.value = [];
      row.specificationId = '';
      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;
   }
   // 获取规格型号列表
   modelList({ id: value }).then((res) => {
      modelOptions.value = res || [];
   });
};
const getProductModel = (value, row) => {
   if (!row) return;
   // 如果清空选择,则清空相关字段
   if (!value) {
      row.specificationId = '';
      row.specification = '';
      row.unit = '';
      return;
   }
   // 更新 specificationId(v-model 已经自动更新,这里确保一致性)
   row.specificationId = value;
   const index = modelOptions.value.findIndex((item) => item.id === value);
   if (index !== -1) {
      row.specification = modelOptions.value[index].model;
      row.unit = modelOptions.value[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 = row
  // 只复制需要的字段,避免将组件引用放入响应式对象
  currentQuotation.value = {
    quotationNo: row.quotationNo || '',
    customer: row.customer || '',
    salesperson: row.salesperson || '',
    quotationDate: row.quotationDate || '',
    validDate: row.validDate || '',
    paymentMethod: row.paymentMethod || '',
    status: row.status || '',
    remark: row.remark || '',
    products: row.products ? row.products.map(product => ({
      productId: product.productId || '',
      product: product.product || product.productName || '',
      specificationId: product.specificationId || '',
      specification: product.specification || '',
      quantity: product.quantity || 0,
      unit: product.unit || '',
      unitPrice: product.unitPrice || 0,
      amount: product.amount || 0
    })) : [],
    totalAmount: row.totalAmount || 0
  }
  viewDialogVisible.value = true
}
const handleEdit = (row) => {
const handleEdit = async (row) => {
  dialogTitle.value = '编辑报价'
  isEdit.value = true
  editId.value = row.id
  Object.assign(form, row)
  form.id = row.id || form.id || null
  // 先加载产品树数据,否则 el-tree-select 无法反显产品名称
  await getProductOptions()
  // 只复制需要的字段,避免将组件引用放入响应式对象
  form.quotationNo = row.quotationNo || ''
  form.customer = row.customer || ''
  form.salesperson = row.salesperson || ''
  form.quotationDate = row.quotationDate || ''
  form.validDate = row.validDate || ''
  form.paymentMethod = row.paymentMethod || ''
  form.status = row.status || '草稿'
  form.remark = row.remark || ''
  form.products = row.products ? row.products.map(product => {
    const productName = product.product || product.productName || ''
    // 优先用 productId;如果只有名称,尝试反查 id 以便树选择器反显
    const resolvedId = product.productId
      ? Number(product.productId)
      : findNodeIdByLabel(productOptions.value, productName) || ''
    return {
      productId: resolvedId,
      product: productName,
      specificationId: product.specificationId || '',
      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
  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();
  userList.value = (userLists.data || []).map(item => ({
    userId: item.userId,
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
  dialogVisible.value = true
}
@@ -461,7 +693,6 @@
  form.quotationDate = ''
  form.validDate = ''
  form.paymentMethod = ''
  form.deliveryPeriod = ''
  form.status = '草稿'
  form.remark = ''
  form.products = []
@@ -475,7 +706,10 @@
const addProduct = () => {
  form.products.push({
    productId: '',
    product: '',
    productName: '',
    specificationId: '',
    specification: '',
    quantity: 1,
    unit: '',
@@ -516,6 +750,22 @@
        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) => {
        const price = Number(product.unitPrice) || 0
        return sum + price
      }, 0)
      if (isEdit.value) {
        // 编辑
        const index = quotationList.value.findIndex(item => item.id === editId.value)
@@ -528,30 +778,16 @@
              handleSearch()
            }
          })
          // quotationList.value[index] = { ...form, id: editId.value }
          // ElMessage.success('编辑成功')
        }
      } else {
        // 新增
        // const newId = Math.max(...quotationList.value.map(item => item.id)) + 1
        form.quotationNo = `QT${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}`
        addQuotation(form).then(res=>{
          // console.log(res)
          if(res.code===200){
            ElMessage.success('新增成功')
            dialogVisible.value = false
            handleSearch()
          }
        })
        // quotationList.value.push({
        //   ...form,
        //   // id: newId,
        //   quotationNo: quotationNo
        // })
        // pagination.total++
        // ElMessage.success('新增成功')
      }
      
    }
@@ -564,18 +800,52 @@
}
const handleSearch = ()=>{
  const params = {
    page:pagination,
    ...pagination,
    ...searchForm
  }
  getQuotationList(params).then(res=>{
    // console.log(res)
    if(res.code===200){
      quotationList.value = res.data.records
      // 只复制需要的字段,避免将组件引用或其他对象放入响应式对象
      quotationList.value = (res.data.records || []).map(item => ({
        id: item.id,
        quotationNo: item.quotationNo || '',
        customer: item.customer || '',
        salesperson: item.salesperson || '',
        quotationDate: item.quotationDate || '',
        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 || '',
          product: product.product || product.productName || '',
          specificationId: product.specificationId || '',
          specification: product.specification || '',
          quantity: product.quantity || 0,
          unit: product.unit || '',
          unitPrice: product.unitPrice || 0,
          amount: product.amount || 0
        })) : [],
        subtotal: item.subtotal || 0,
        freight: item.freight || 0,
        otherFee: item.otherFee || 0,
        discountRate: item.discountRate || 0,
        discountAmount: item.discountAmount || 0,
        totalAmount: item.totalAmount || 0
      }))
      pagination.total = res.data.total
    }
  })
   customerList().then((res) => {
      customerOption.value = res;
      // 只复制需要的字段,避免将组件引用放入响应式对象
      customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
   });
}
@@ -584,27 +854,182 @@
})
</script>
<style scoped>
<style scoped lang="scss">
.search-row {
  margin-bottom: 20px;
}
.quotation-form-container {
  padding: 10px 0;
  max-height: calc(100vh - 200px);
  overflow-y: auto;
  &::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  }
  &::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 3px;
    &:hover {
      background: #a8a8a8;
    }
  }
}
.quotation-form {
  .el-form-item {
    margin-bottom: 22px;
  }
}
.form-card {
  margin-bottom: 20px;
  margin-bottom: 24px;
  border-radius: 8px;
  transition: all 0.3s ease;
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important;
  }
  :deep(.el-card__header) {
    padding: 16px 20px;
    background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
    border-bottom: 1px solid #ebeef5;
  }
  :deep(.el-card__body) {
    padding: 20px;
  }
}
.card-title {
  font-weight: bold;
  color: #303133;
}
.card-header {
.card-header-wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  .card-icon {
    font-size: 18px;
    color: #409eff;
  }
  .card-title {
    font-weight: 600;
    font-size: 16px;
    color: #303133;
    flex: 1;
  }
  .header-btn {
    margin-left: auto;
  }
}
.form-content {
  padding: 8px 0;
}
.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 {
  :deep(.el-table__header) {
    background-color: #f5f7fa;
    th {
      background-color: #f5f7fa !important;
      color: #606266;
      font-weight: 600;
    }
  }
  :deep(.el-table__row) {
    &:hover {
      background-color: #f5f7fa;
    }
  }
  :deep(.el-table__cell) {
    padding: 12px 0;
  }
}
.dialog-footer {
  text-align: right;
}
// 响应式优化
@media (max-width: 1200px) {
  .approver-nodes-container {
    gap: 16px;
  }
  .approver-node-item {
    min-width: 160px;
  }
}
</style>