chenhj
20 小时以前 cb76922f5e8ccade2bdd3e1c73c64c2fe711c45f
feat(salesQuotation): 添加导入详情功能及优化界面交互

- 新增 ImportQuotationDetail 组件用于显示导入报价单详情
- 在报价单列表中增加“导入详情”按钮,点击可查看详细信息
- 优化报价单界面模板空白和缩进格式,提升代码可读性
- 新增导入报价单表单的审批人选择与附件上传功能
- 修正审批节点和导入审批节点数据结构一致性
- 调整文件上传提示,支持Excel格式模板下载及上传
- 增加查看详情对话框中产品明细及备注显示完善
- 优化按钮和图标排版,统一元素样式与交互体验
已添加2个文件
已修改1个文件
1168 ■■■■■ 文件已修改
src/api/salesManagement/salesQuotationRecord.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/ImportQuotationDetail.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 1052 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salesQuotationRecord.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
// é”€å”®æŠ¥ä»·é¡µé¢æŽ¥å£
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢æŠ¥ä»·å•列表
export function getQuotationRecordList(query) {
  return request({
    url: "/sales/quotationRecord/listPage",
    method: "get",
    params: query,
  });
}
src/views/salesManagement/salesQuotation/ImportQuotationDetail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
<script setup>
import {ref} from 'vue'
import {getQuotationRecordList} from "@/api/salesManagement/salesQuotationRecord.js";
const props = defineProps({
  showModal: {
    type: Boolean,
    required: true
  },
  quotationId: {
    type: Number,
    required: true
  }
})
const emit = defineEmits(['update:showModal'])
const dialogVisible = computed({
  get: () => props.showModal,
  set: (val) => emit('update:showModal', val)
})
const leftTableData = ref([])
const rightTableData = ref([])
const selectedLeftRow = ref(null)
const handleRowClick = (row) => {
  selectedLeftRow.value = row
  rightTableData.value = row.products || []
}
const tableRowClassName = ({row}) => {
  return selectedLeftRow.value === row ? 'selected-row' : ''
}
const fetchData = () => {
  getQuotationRecordList({quotationRecordId: props.quotationId}).then(res => {
    const data = res.data.records || []
    data.forEach(item => {
      leftTableData.value.push(JSON.parse(item?.info || '{}'))
    })
  })
}
const leftColumns = [
  {prop: 'customer', label: '客户名称', minWidth: '120'},
  {prop: 'salesperson', label: '业务员', minWidth: '120'},
  {prop: 'quotationDate', label: '报价日期', minWidth: '120'},
  {prop: 'validDate', label: '有效期至', minWidth: '120'},
  {prop: 'paymentMethod', label: '支付方式', minWidth: '120'},
  {prop: 'remark', label: '备注', minWidth: '120'}
]
const rightColumns = [
  {prop: 'product', label: '产品名称', minWidth: '120'},
  {prop: 'specification', label: '型号', minWidth: '120'},
  {prop: 'unit', label: '单位', minWidth: '100'},
  {prop: 'unitPrice', label: '单价', minWidth: '100'}
]
onMounted(() => {
  fetchData()
})
</script>
<template>
  <el-dialog v-model="dialogVisible" title="详情" width="1000px" :close-on-click-modal="false">
    <el-row :gutter="20">
      <el-col :span="12">
        <div class="table-title">报价单记录</div>
        <el-table :data="leftTableData" border height="400" stripe highlight-current-row
                  @row-click="handleRowClick" :row-class-name="tableRowClassName">
          <el-table-column v-for="col in leftColumns" :key="col.prop" :prop="col.prop" :label="col.label"
                           :min-width="col.minWidth" show-overflow-tooltip/>
        </el-table>
      </el-col>
      <el-col :span="12">
        <div class="table-title">报价单产品</div>
        <el-table :data="rightTableData" border height="400" stripe>
          <el-table-column v-for="col in rightColumns" :key="col.prop" :prop="col.prop" :label="col.label"
                           :min-width="col.minWidth" show-overflow-tooltip/>
        </el-table>
      </el-col>
    </el-row>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="dialogVisible = false">关闭</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<style scoped>
.table-title {
  font-size: 15px;
  font-weight: bold;
  margin-bottom: 10px;
  color: #303133;
}
:deep(.selected-row) {
  background-color: #ecf5ff !important;
}
</style>
src/views/salesManagement/salesQuotation/index.vue
@@ -5,33 +5,36 @@
      <el-row :gutter="20" class="search-row">
        <el-col :span="8">
          <el-input
            v-model="searchForm.quotationNo"
            placeholder="请输入报价单号"
            clearable
            @keyup.enter="handleSearch"
              v-model="searchForm.quotationNo"
              placeholder="请输入报价单号"
              clearable
              @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
              <el-icon>
                <Search/>
              </el-icon>
            </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-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>-->
<!--            <el-option label="已发送" value="已发送"></el-option>-->
<!--            <el-option label="客户确认" value="客户确认"></el-option>-->
<!--            <el-option label="已过期" value="已过期"></el-option>-->
<!--          </el-select>-->
<!--        </el-col>-->
        <!--        <el-col :span="6">-->
        <!--          <el-select v-model="searchForm.status" placeholder="请选择报价状态" clearable>-->
        <!--            <el-option label="草稿" value="草稿"></el-option>-->
        <!--            <el-option label="已发送" value="已发送"></el-option>-->
        <!--            <el-option label="客户确认" value="客户确认"></el-option>-->
        <!--            <el-option label="已过期" value="已过期"></el-option>-->
        <!--          </el-select>-->
        <!--        </el-col>-->
        <el-col :span="8">
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
@@ -46,19 +49,19 @@
      <!-- æŠ¥ä»·åˆ—表 -->
      <el-table
        :data="filteredList"
        style="width: 100%"
        v-loading="loading"
        border
        stripe
        height="calc(100vh - 22em)"
          :data="filteredList"
          style="width: 100%"
          v-loading="loading"
          border
          stripe
          height="calc(100vh - 22em)"
      >
                <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 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>
@@ -71,350 +74,384 @@
            Â¥{{ scope.row.totalAmount.toFixed(2) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right" align="center">
        <el-table-column label="操作" width="250" fixed="right" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['待审批','拒绝'].includes(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="primary" @click="handleView(scope.row)" style="color: #67C23A">查看</el-button>
            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
            <el-button link type="primary" @click="handleImportDetail(scope.row)">导入详情</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        :total="pagination.total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="pagination.currentPage"
        :limit="pagination.pageSize"
        @pagination="handleCurrentChange"
          :total="pagination.total"
          layout="total, sizes, prev, pager, next, jumper"
          :page="pagination.currentPage"
          :limit="pagination.pageSize"
          @pagination="handleCurrentChange"
      />
    </el-card>
    <!-- æ–°å¢ž/编辑对话框 -->
    <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
    <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">
              <el-icon class="card-icon"><Document /></el-icon>
              <span class="card-title">基本信息</span>
            </div>
          </template>
          <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"
          <!-- åŸºæœ¬ä¿¡æ¯ -->
          <el-card class="form-card" shadow="hover">
            <template #header>
              <div class="card-header-wrapper">
                <el-icon class="card-icon">
                  <Document/>
                </el-icon>
                <span class="card-title">基本信息</span>
              </div>
            </template>
            <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="hover">
            <template #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" 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">
                  <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>
                  </template>
                </el-table-column>
                <el-table-column prop="specification" label="规格型号" width="200">
                  <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="user in userList"
                          :key="user.userId"
                          :label="user.nickName"
                          :value="user.userId"
                            v-for="item in scope.row.modelOptions || []"
                            :key="item.id"
                            :label="item.model"
                            :value="item.id"
                        />
                      </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="hover">
          <template #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" class="header-btn">
                <el-icon><Plus /></el-icon>
                æ·»åŠ äº§å“
              </el-button>
                    </el-form-item>
                  </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>
                  </template>
                </el-table-column>
                <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>
                  </template>
                </el-table-column>
                <el-table-column label="操作" width="80" align="center">
                  <template #default="scope">
                    <el-button link type="danger" @click="removeProduct(scope.$index)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
              <el-empty v-else description="暂无产品,请点击添加产品" :image-size="80"/>
            </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">
              <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>
              </template>
            </el-table-column>
            <el-table-column prop="specification" label="规格型号" width="200">
              <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>
              </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>
              </template>
            </el-table-column>
            <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>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80" align="center">
              <template #default="scope">
                <el-button link type="danger" @click="removeProduct(scope.$index)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
          <el-empty v-else description="暂无产品,请点击添加产品" :image-size="80" />
          </div>
        </el-card>
          </el-card>
        <!-- å¤‡æ³¨ä¿¡æ¯ -->
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><EditPen /></el-icon>
              <span class="card-title">备注信息</span>
          <!-- å¤‡æ³¨ä¿¡æ¯ -->
          <el-card class="form-card" shadow="hover">
            <template #header>
              <div class="card-header-wrapper">
                <el-icon class="card-icon">
                  <EditPen/>
                </el-icon>
                <span class="card-title">备注信息</span>
              </div>
            </template>
            <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>
          </template>
          <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>
          </el-card>
        </el-form>
      </div>
    </FormDialog>
    <FormDialog v-model="importDialogVisible" title="导入报价单" width="85%" :close-on-click-modal="false" @close="importDialogVisible = false" @confirm="handleImportSubmit" @cancel="importDialogVisible = false">
    <FormDialog v-model="importDialogVisible" title="导入报价单" width="85%" :close-on-click-modal="false"
                @close="importDialogVisible = false" @confirm="handleImportSubmit"
                @cancel="importDialogVisible = false">
      <!-- å®¡æ‰¹äººä¿¡æ¯ -->
        <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="addImportApproverNode" 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
      <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="addImportApproverNode" 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 importApproverNodes"
                      :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
                  >
                    <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
                    >
                      <el-option
                          v-for="user in userList"
                          :key="user.userId"
                          :label="user.nickName"
                          :value="user.userId"
                        />
                      </el-select>
                      <el-button
                      />
                    </el-select>
                    <el-button
                        type="danger"
                        size="small"
                        :icon="Delete"
                        @click="removeImportApproverNode(index)"
                        v-if="importApproverNodes.length > 1"
                        class="remove-btn"
                      >删除</el-button>
                    </div>
                    >删除
                    </el-button>
                  </div>
                </el-form-item>
              </el-col>
            </el-row>
                </div>
              </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">
              <Paperclip/>
            </el-icon>
            <span class="card-title">附件材料</span>
          </div>
        </el-card>
        <el-card class="form-card" shadow="hover">
          <template #header>
            <div class="card-header-wrapper">
              <el-icon class="card-icon"><Paperclip /></el-icon>
              <span class="card-title">附件材料</span>
            </div>
          </template>
          <div class="form-content">
            <el-form-item label="附件材料" prop="files">
          <el-upload
            v-model:file-list="importFileList"
            :limit="1"
            ref="fileUpload"
            :auto-upload="false"
            :on-change="handleFileChange"
            :on-exceed="handleExceed"
            :on-remove="handleRemove"
            :on-preview="handlePreview"
            :show-file-list="true"
          >
            <el-button type="primary">上传</el-button>
            <template #file="{ file }">
              <div style="display:flex; align-items:center; gap: 10px; width: 100%;">
        </template>
        <div class="form-content">
          <el-button type="primary" @click="downloadImportTemplate" style="margin-bottom: 20px">下载模板文件</el-button>
          <el-form-item label="附件材料" prop="files">
            <el-upload
                v-model:file-list="importFileList"
                :limit="1"
                ref="fileUpload"
                :auto-upload="false"
                :on-change="handleFileChange"
                :on-exceed="handleExceed"
                :on-remove="handleRemove"
                :on-preview="handlePreview"
                :show-file-list="true"
            >
              <el-button type="primary">上传</el-button>
              <template #file="{ file }">
                <div style="display:flex; align-items:center; gap: 10px; width: 100%;">
                <span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
                  {{ file.name }}
                </span>
<!--                <div style="display:flex; align-items:center; gap: 6px;">-->
<!--                  <el-button link type="success" :icon="Download" @click="handleDownload(file)" />-->
<!--                  <el-button link type="primary" :icon="View" @click="handlePreview(file)" />-->
<!--                  <el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" />-->
<!--                </div>-->
              </div>
            </template>
            <template #tip>
              <div class="el-upload__tip">
                æ”¯æŒæ–‡æ¡£ï¼ˆxls, xlsx)格式
              </div>
            </template>
          </el-upload>
        </el-form-item>
          </div>
        </el-card>
                  <div style="display:flex; align-items:center; gap: 6px;">
                    <!--                  <el-button link type="success" :icon="Download" @click="handleDownload(file)" />-->
                    <!--                  <el-button link type="primary" :icon="View" @click="handlePreview(file)" />-->
                    <!--                  <el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" />-->
                  </div>
                </div>
              </template>
              <template #tip>
                <div class="el-upload__tip">
                  æ”¯æŒæ–‡æ¡£ï¼ˆxls, xlsx)格式
                </div>
              </template>
            </el-upload>
          </el-form-item>
        </div>
      </el-card>
    </FormDialog>
    <!-- æŸ¥çœ‹è¯¦æƒ…对话框 -->
@@ -426,20 +463,22 @@
        <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="报价状态">-->
        <!--          <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>
          <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;">
        <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="product" label="产品名称"/>
          <el-table-column prop="specification" label="规格型号"/>
          <el-table-column prop="unit" label="单位"/>
          <el-table-column prop="unitPrice" label="单价">
            <template #default="scope">
              Â¥{{ scope.row.unitPrice.toFixed(2) }}
@@ -453,21 +492,38 @@
        <p>{{ currentQuotation.remark }}</p>
      </div>
    </el-dialog>
    <ImportQuotationDetail v-if="showDetail" v-model:showModal="showDetail" :quotationId="currentQuotation.id" />
  </div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, markRaw, shallowRef, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete, Download, View } from '@element-plus/icons-vue'
import {ref, reactive, computed, onMounted, markRaw, shallowRef, getCurrentInstance} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
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, importQuotation} from '@/api/salesManagement/salesQuotation.js'
import ImportQuotationDetail from '@/views/salesManagement/salesQuotation/ImportQuotationDetail.vue'
import {
  getQuotationList,
  addQuotation,
  updateQuotation,
  deleteQuotation,
  importQuotation
} 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 { proxy } = getCurrentInstance();
const {proxy} = getCurrentInstance();
// å“åº”式数据
const loading = ref(false)
@@ -491,7 +547,7 @@
const viewDialogVisible = ref(false)
const importFileList = ref([])
const importApproverNodes = ref([
  { id: 1, userId: null }
  {id: 1, userId: null}
])
let nextImportApproverId = 2
@@ -517,21 +573,21 @@
})
const baseRules = {
  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' }]
  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 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' }]
  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 }
  const r = {...baseRules}
  ;(form.products || []).forEach((_, i) => {
    r[`products.${i}.productId`] = productRowRules.productId
    r[`products.${i}.specificationId`] = productRowRules.specificationId
@@ -545,18 +601,19 @@
// å®¡æ‰¹äººèŠ‚ç‚¹ç›¸å…³
const approverNodes = ref([
  { id: 1, userId: null }
  {id: 1, userId: null}
])
let nextApproverId = 2
const isEdit = ref(false)
const showDetail = ref(false)
const editId = ref(null)
const currentQuotation = ref({})
const formRef = ref()
// æ·»åŠ å®¡æ‰¹äººèŠ‚ç‚¹
function addApproverNode() {
  approverNodes.value.push({ id: nextApproverId++, userId: null })
  approverNodes.value.push({id: nextApproverId++, userId: null})
}
// åˆ é™¤å®¡æ‰¹äººèŠ‚ç‚¹
@@ -566,7 +623,7 @@
// å¯¼å…¥å¼¹çª—审批人节点相关
function addImportApproverNode() {
  importApproverNodes.value.push({ id: nextImportApproverId++, userId: null })
  importApproverNodes.value.push({id: nextImportApproverId++, userId: null})
}
function removeImportApproverNode(index) {
@@ -600,7 +657,7 @@
  const formData = new FormData()
  formData.append('file', rawFile)
  // å®¡æ ¸äºº IDs,以逗号分割
  const approveUserIds = importApproverNodes.value.map(node => node.userId).join(',')
  formData.append('approveUserIdsJson', approveUserIds)
@@ -659,7 +716,7 @@
  importFileList.value = list
};
  // å¤„理文件移除
// å¤„理文件移除
function triggerRemoveFile(file) {
  fileUpload.value?.handleRemove?.(file) || proxy.$refs.fileUpload?.handleRemove?.(file);
}
@@ -711,7 +768,7 @@
  importFileList.value = []
  // âœ… æ¸…空“导入用”的审批人
  importApproverNodes.value = [{ id: 1, userId: null }]
  importApproverNodes.value = [{id: 1, userId: null}]
  nextImportApproverId = 2
  let userLists = await userListNoPage();
@@ -729,118 +786,121 @@
  isEdit.value = false
  resetForm()
  // é‡ç½®å®¡æ‰¹äººèŠ‚ç‚¹
  approverNodes.value = [{ id: 1, userId: null }]
  approverNodes.value = [{id: 1, userId: null}]
  nextApproverId = 2
  dialogVisible.value = true
    let userLists = await userListNoPage();
    // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
    userList.value = (userLists.data || []).map(item => ({
  let userLists = await userListNoPage();
  // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
  userList.value = (userLists.data || []).map(item => ({
    userId: item.userId,
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
    getProductOptions();
    customerList().then((res) => {
        // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
        customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
  getProductOptions();
  customerList().then((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
    });
  // è¿”回 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;
    });
  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;
  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 = '';
        row.modelOptions = [];
        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;
    }
    // èŽ·å–è§„æ ¼åž‹å·åˆ—è¡¨ï¼Œè®¾ç½®åˆ°å½“å‰è¡Œçš„ modelOptions
    modelList({ id: value }).then((res) => {
        row.modelOptions = res || [];
    });
  if (!row) return;
  // å¦‚果清空选择,则清空相关字段
  if (!value) {
    row.productId = '';
    row.product = '';
    row.modelOptions = [];
    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;
  }
  // èŽ·å–è§„æ ¼åž‹å·åˆ—è¡¨ï¼Œè®¾ç½®åˆ°å½“å‰è¡Œçš„ modelOptions
  modelList({id: value}).then((res) => {
    row.modelOptions = res || [];
  });
};
const getProductModel = (value, row) => {
    if (!row) return;
    // å¦‚果清空选择,则清空相关字段
    if (!value) {
        row.specificationId = '';
        row.specification = '';
        row.unit = '';
        return;
    }
    // æ›´æ–° specificationId(v-model å·²ç»è‡ªåŠ¨æ›´æ–°ï¼Œè¿™é‡Œç¡®ä¿ä¸€è‡´æ€§ï¼‰
    row.specificationId = 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 = '';
    }
  if (!row) return;
  // å¦‚果清空选择,则清空相关字段
  if (!value) {
    row.specificationId = '';
    row.specification = '';
    row.unit = '';
    return;
  }
  // æ›´æ–° specificationId(v-model å·²ç»è‡ªåŠ¨æ›´æ–°ï¼Œè¿™é‡Œç¡®ä¿ä¸€è‡´æ€§ï¼‰
  row.specificationId = 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
  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) => {
  // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
@@ -889,18 +949,18 @@
    const productName = product.product || product.productName || ''
    // ä¼˜å…ˆç”¨ productId;如果只有名称,尝试反查 id ä»¥ä¾¿æ ‘选择器反显
    const resolvedProductId = product.productId
      ? Number(product.productId)
      : findNodeIdByLabel(productOptions.value, productName) || ''
        ? Number(product.productId)
        : findNodeIdByLabel(productOptions.value, productName) || ''
    // å¦‚果有产品ID,加载对应的规格型号列表
    let modelOptions = [];
    let resolvedSpecificationId = product.specificationId || '';
    if (resolvedProductId) {
      try {
        const res = await modelList({ id: resolvedProductId });
        const res = await modelList({id: resolvedProductId});
        modelOptions = res || [];
        // å¦‚果返回的数据没有 specificationId,但有 specification åç§°ï¼Œæ ¹æ®åç§°æŸ¥æ‰¾ ID
        if (!resolvedSpecificationId && product.specification) {
          const foundModel = modelOptions.find(item => item.model === product.specification);
@@ -912,7 +972,7 @@
        console.error('加载规格型号失败:', error);
      }
    }
    return {
      productId: resolvedProductId,
      product: productName,
@@ -931,7 +991,7 @@
  form.discountRate = row.discountRate || 0
  form.discountAmount = row.discountAmount || 0
  form.totalAmount = row.totalAmount || 0
  // åæ˜¾å®¡æ‰¹äºº
  if (row.approveUserIds) {
    const userIds = row.approveUserIds.split(',')
@@ -941,10 +1001,10 @@
    }))
    nextApproverId = userIds.length + 1
  } else {
    approverNodes.value = [{ id: 1, userId: null }]
    approverNodes.value = [{id: 1, userId: null}]
    nextApproverId = 2
  }
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  let userLists = await userListNoPage();
  userList.value = (userLists.data || []).map(item => ({
@@ -952,7 +1012,7 @@
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
  dialogVisible.value = true
}
@@ -965,9 +1025,9 @@
  }).then(() => {
    const index = quotationList.value.findIndex(item => item.id === row.id)
    if (index > -1) {
      deleteQuotation(row.id).then(res=>{
      deleteQuotation(row.id).then(res => {
        // console.log(res)
        if(res.code===200){
        if (res.code === 200) {
          ElMessage.success('删除成功')
          handleSearch()
        }
@@ -1049,23 +1109,23 @@
        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)
        if (index > -1) {
          updateQuotation(form).then(res=>{
          updateQuotation(form).then(res => {
            // console.log(res)
            if(res.code===200){
            if (res.code === 200) {
              ElMessage.success('编辑成功')
              dialogVisible.value = false
              handleSearch()
@@ -1074,17 +1134,21 @@
        }
      } else {
        // æ–°å¢ž
        addQuotation(form).then(res=>{
          if(res.code===200){
        addQuotation(form).then(res => {
          if (res.code === 200) {
            ElMessage.success('新增成功')
            dialogVisible.value = false
            handleSearch()
          }
        })
      }
    }
  })
}
const downloadImportTemplate = () => {
  proxy.download("/sales/quotation/downloadTemplate", {}, "报价单导入模板.xlsx");
}
const handleCurrentChange = (val) => {
@@ -1093,16 +1157,16 @@
  // åˆ†é¡µå˜åŒ–时重新查询列表
  handleSearch()
}
const handleSearch = ()=>{
const handleSearch = () => {
  const params = {
    // åŽç«¯åˆ†é¡µå‚数:current / size
    current: pagination.currentPage,
    size: pagination.pageSize,
    ...searchForm
  }
  getQuotationList(params).then(res=>{
  getQuotationList(params).then(res => {
    // console.log(res)
    if(res.code===200){
    if (res.code === 200) {
      // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用或其他对象放入响应式对象
      quotationList.value = (res.data.records || []).map(item => ({
        id: item.id,
@@ -1136,17 +1200,23 @@
      pagination.total = res.data.total
    }
  })
    customerList().then((res) => {
        // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
        customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
  customerList().then((res) => {
    // åªå¤åˆ¶éœ€è¦çš„字段,避免将组件引用放入响应式对象
    customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
      id: item.id,
      customerName: item.customerName || '',
      taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
    }))
    });
  });
}
onMounted(()=>{
const handleImportDetail = (row) => {
  showDetail.value = true
  currentQuotation.value = row
}
onMounted(() => {
  handleSearch()
})
</script>
@@ -1160,16 +1230,16 @@
  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;
    }
@@ -1186,17 +1256,17 @@
  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;
  }
@@ -1206,19 +1276,19 @@
  display: flex;
  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;
  }
@@ -1230,9 +1300,11 @@
.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;
@@ -1257,7 +1329,7 @@
  border: 1px solid #e4e7ed;
  transition: all 0.3s ease;
  min-width: 180px;
  &:hover {
    border-color: #409eff;
    background: #f0f7ff;
@@ -1271,7 +1343,7 @@
  gap: 8px;
  font-size: 14px;
  color: #606266;
  .node-step {
    display: inline-flex;
    align-items: center;
@@ -1284,11 +1356,11 @@
    font-size: 12px;
    font-weight: 600;
  }
  .node-text {
    font-weight: 500;
  }
  .arrow-icon {
    color: #909399;
    font-size: 14px;
@@ -1307,20 +1379,20 @@
.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;
  }
@@ -1335,7 +1407,7 @@
  .approver-nodes-container {
    gap: 16px;
  }
  .approver-node-item {
    min-width: 160px;
  }