| | |
| | | </el-form-item> |
| | | </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="附件"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | :action="upload.url" |
| | | multiple |
| | | ref="fileUpload" |
| | | auto-upload |
| | | :headers="upload.headers" |
| | | :before-upload="handleBeforeUpload" |
| | | :on-error="handleUploadError" |
| | | :on-success="handleUploadSuccess" |
| | | :on-remove="handleRemove" |
| | | :on-preview="handlePreview" |
| | | > |
| | | <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"> |
| | | 文件格式支持 doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </div> |
| | | </el-card> |
| | | </el-form> |
| | | </div> |
| | | </FormDialog> |
| | |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <filePreview ref="filePreviewRef" /> |
| | | </div> |
| | | </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, Paperclip, View, Download } 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 { customerList, delLedgerFile } from "@/api/salesManagement/salesLedger.js"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import filePreview from "@/components/filePreview/index.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // 响应式数据 |
| | | const loading = ref(false) |
| | |
| | | const dialogVisible = ref(false) |
| | | const viewDialogVisible = ref(false) |
| | | const dialogTitle = ref('新增报价') |
| | | const fileList = ref([]) |
| | | const fileUpload = ref() |
| | | const filePreviewRef = ref() |
| | | const upload = reactive({ |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | }) |
| | | const form = reactive({ |
| | | quotationNo: '', |
| | | customer: '', |
| | |
| | | otherFee: 0, |
| | | discountRate: 0, |
| | | discountAmount: 0, |
| | | totalAmount: 0 |
| | | totalAmount: 0, |
| | | tempFileIds: [] |
| | | }) |
| | | |
| | | const baseRules = { |
| | |
| | | handleSearch() |
| | | } |
| | | |
| | | const normalizeQuotationFiles = (raw) => { |
| | | const list = |
| | | (raw && Array.isArray(raw.salesLedgerFiles) && raw.salesLedgerFiles) || |
| | | (raw && Array.isArray(raw.quotationFiles) && raw.quotationFiles) || |
| | | (raw && Array.isArray(raw.fileList) && raw.fileList) || |
| | | (raw && Array.isArray(raw.files) && raw.files) || |
| | | [] |
| | | return list |
| | | .map((item) => ({ |
| | | id: item?.id, |
| | | name: item?.fileName || item?.name || item?.originalName || item?.filename || "附件", |
| | | url: item?.fileUrl || item?.url || item?.path || item?.tempPath, |
| | | tempId: item?.tempId, |
| | | })) |
| | | .filter((i) => i.url) |
| | | } |
| | | |
| | | const handleAdd = async () => { |
| | | dialogTitle.value = '新增报价' |
| | | isEdit.value = false |
| | | resetForm() |
| | | fileList.value = [] |
| | | // 重置审批人节点 |
| | | approverNodes.value = [{ id: 1, userId: null }] |
| | | nextApproverId = 2 |
| | |
| | | userName: item.userName || '' |
| | | })); |
| | | |
| | | fileList.value = normalizeQuotationFiles(row) |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | |
| | | form.discountRate = 0 |
| | | form.discountAmount = 0 |
| | | form.totalAmount = 0 |
| | | form.tempFileIds = [] |
| | | form.files = [] |
| | | fileList.value = [] |
| | | } |
| | | |
| | | const addProduct = () => { |
| | |
| | | // 可以根据客户信息自动填充一些默认值 |
| | | } |
| | | |
| | | function handleBeforeUpload() { |
| | | proxy?.$modal?.loading?.("正在上传文件,请稍候...") |
| | | return true |
| | | } |
| | | function handleUploadError() { |
| | | proxy?.$modal?.closeLoading?.() |
| | | ElMessage.error("上传文件失败") |
| | | } |
| | | function handleUploadSuccess(res, file) { |
| | | proxy?.$modal?.closeLoading?.() |
| | | if (res?.code === 200) { |
| | | file.tempId = res?.data?.tempId |
| | | const url = res?.data?.tempPath || res?.data?.url |
| | | if (url) file.url = url |
| | | file.name = res?.data?.originalName || file?.name |
| | | ElMessage.success("上传成功") |
| | | } else { |
| | | ElMessage.error(res?.msg || "上传失败") |
| | | fileUpload.value?.handleRemove?.(file) |
| | | } |
| | | } |
| | | function handleRemove(file) { |
| | | if (!isEdit.value) return |
| | | if (!file?.id) return |
| | | delLedgerFile([file.id]).then((res) => { |
| | | if (res?.code === 200) { |
| | | ElMessage.success("删除成功") |
| | | } else { |
| | | ElMessage.error(res?.msg || "删除失败") |
| | | } |
| | | }).catch(() => { |
| | | ElMessage.error("删除失败") |
| | | }) |
| | | } |
| | | |
| | | const handleDownload = (file) => { |
| | | if (!file?.url) return |
| | | proxy?.$modal?.loading?.("正在下载文件,请稍候...") |
| | | proxy.$download.name(file.url); |
| | | proxy?.$modal?.closeLoading?.() |
| | | } |
| | | |
| | | function handlePreview(file) { |
| | | const url = file?.url || file?.response?.data?.tempPath || file?.response?.data?.url |
| | | if (!url) return |
| | | filePreviewRef.value?.open?.(url) |
| | | } |
| | | function triggerRemoveFile(file) { |
| | | fileUpload.value?.handleRemove?.(file) |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | |
| | | |
| | | // 收集所有节点的审批人id |
| | | form.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | |
| | | form.files = fileList.value.map(f => ({ |
| | | tempId: f.tempId, |
| | | name: f.name, |
| | | url: f.url, |
| | | uid: f.uid, |
| | | })) || [] |
| | | |
| | | // 计算所有产品的单价总和 |
| | | form.totalAmount = form.products.reduce((sum, product) => { |
| | |
| | | // 审批人(用于编辑时反显) |
| | | approveUserIds: item.approveUserIds || '', |
| | | remark: item.remark || '', |
| | | salesLedgerFiles: normalizeQuotationFiles(item), |
| | | products: item.products ? item.products.map(product => ({ |
| | | productId: product.productId || '', |
| | | product: product.product || product.productName || '', |