张诺
17 小时以前 d9b5c1f1310a4449ba3ffbefc9b15ec246789d53
src/views/salesManagement/salesQuotation/index.vue
@@ -38,6 +38,9 @@
          <el-button style="float: right;" type="primary" @click="handleAdd">
            新增报价
          </el-button>
          <el-button style="float: right;" type="primary" @click="handleImport">
            导入文件
          </el-button>
        </el-col>
      </el-row>
@@ -314,6 +317,106 @@
      </div>
    </FormDialog>
    <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
                      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
                        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="removeImportApproverNode(index)"
                        v-if="importApproverNodes.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"><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%;">
                <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>
    </FormDialog>
    <!-- 查看详情对话框 -->
    <el-dialog v-model="viewDialogVisible" title="报价详情" width="800px">
      <el-descriptions :column="2" border>
@@ -354,15 +457,17 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, markRaw, shallowRef } from '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 { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete, Download, View } 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 {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 loading = ref(false)
@@ -382,7 +487,16 @@
})
const dialogVisible = ref(false)
const importDialogVisible = ref(false)
const viewDialogVisible = ref(false)
const importFileList = ref([])
const importApproverNodes = ref([
  { id: 1, userId: null }
])
let nextImportApproverId = 2
const fileUpload = ref(null)
const dialogTitle = ref('新增报价')
const form = reactive({
  quotationNo: '',
@@ -450,6 +564,122 @@
  approverNodes.value.splice(index, 1)
}
// 导入弹窗审批人节点相关
function addImportApproverNode() {
  importApproverNodes.value.push({ id: nextImportApproverId++, userId: null })
}
function removeImportApproverNode(index) {
  importApproverNodes.value.splice(index, 1)
}
function triggerRemoveImportFile(file) {
  const index = importFileList.value.indexOf(file)
  if (index !== -1) {
    importFileList.value.splice(index, 1)
  }
}
async function handleImportSubmit() {
  if (importFileList.value.length === 0) {
    ElMessage.warning('请选择要导入的文件')
    return
  }
  const hasEmptyApprover = importApproverNodes.value.some(node => !node.userId)
  if (hasEmptyApprover) {
    ElMessage.error('请为所有审批节点选择审批人!')
    return
  }
  const selectedFile = importFileList.value[0]
  const rawFile = selectedFile?.raw || selectedFile
  if (!validateImportFile(rawFile)) {
    return
  }
  const formData = new FormData()
  formData.append('file', rawFile)
  // 审核人 IDs,以逗号分割
  const approveUserIds = importApproverNodes.value.map(node => node.userId).join(',')
  formData.append('approveUserIdsJson', approveUserIds)
  loading.value = true
  try {
    const res = await importQuotation(formData)
    if (res.code === 200) {
      ElMessage.success('导入成功')
      importDialogVisible.value = false
      handleSearch()
    }
  } catch (error) {
    console.error('导入失败:', error)
  } finally {
    loading.value = false
  }
}
const validateImportFile = (file) => {
  const fileName = file?.name || ''
  const isExcel = /\.(xls|xlsx)$/i.test(fileName)
  if (!isExcel) {
    ElMessage.error('仅支持 xls/xlsx 格式文件')
    return false
  }
  const isLt100M = (file?.size || 0) / 1024 / 1024 < 100
  if (!isLt100M) {
    ElMessage.error('上传文件大小不能超过 100MB!')
    return false
  }
  return true
}
const handleExceed = (files) => {
  // 达到上限时,新文件替换旧文件
  const file = files?.[0]
  if (!file || !validateImportFile(file)) return
  fileUpload.value?.clearFiles()
  fileUpload.value?.handleStart(file)
  importFileList.value = fileUpload.value?.uploadFiles?.slice(-1) || []
}
const handleFileChange = (file, list) => {
  const currentFile = file?.raw || file
  if (!validateImportFile(currentFile)) {
    fileUpload.value?.handleRemove?.(file)
    importFileList.value = []
    return
  }
  importFileList.value = (list || []).slice(-1)
}
const handleRemove = (file, list) => {
  importFileList.value = list
};
  // 处理文件移除
function triggerRemoveFile(file) {
  fileUpload.value?.handleRemove?.(file) || proxy.$refs.fileUpload?.handleRemove?.(file);
}
// 文件预览
function handlePreview(file) {
  const url = getUploadFileUrl(file)
  if (!url) return
  filePreviewRef.value?.open?.(url)
}
// 文件预览/下载
const handleDownload = (file) => {
  const url = getUploadFileUrl(file)
  if (!url) return
  proxy?.$modal?.loading?.("正在下载文件,请稍候...")
  proxy.$download.name(url);
  proxy?.$modal?.closeLoading?.()
}
// 计算属性
const filteredList = computed(() => {
  let list = quotationList.value
@@ -476,6 +706,24 @@
  handleSearch()
}
// 导入文件
const handleImport = async () => {
  importFileList.value = []
  // ✅ 清空“导入用”的审批人
  importApproverNodes.value = [{ id: 1, userId: null }]
  nextImportApproverId = 2
  let userLists = await userListNoPage();
  importDialogVisible.value = true
  userList.value = (userLists.data || []).map(item => ({
    userId: item.userId,
    nickName: item.nickName || '',
    userName: item.userName || ''
  }));
}
const handleAdd = async () => {
  dialogTitle.value = '新增报价'
  isEdit.value = false