| src/api/salesManagement/salesLedger.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/assets/styles/element-ui.scss | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/components/Dialog/FileListDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/components/Dialog/FormDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/components/Dialog/ImportDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/inventoryManagement/dispatchLog/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesQuotation/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/salesManagement/salesLedger.js
@@ -109,3 +109,11 @@ params: query, }); } // éå®å°è´¦é¡µé¢åè´§ï¼æ¥è¯¢åºåæ¯å¦å è¶³ export function getProductInventory(query) { return request({ url: "/sales/ledger/getProductInventory", method: "get", params: query, }); } src/assets/styles/element-ui.scss
@@ -66,7 +66,7 @@ color: #2e3033; } .el-dialog__body { padding: 16px 40px 0 40px; padding: 16px 40px 20px 40px; max-height: 74vh; overflow-y: auto; } src/components/Dialog/FileListDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,309 @@ <template> <el-dialog v-model="dialogVisible" :title="title" :width="width" :before-close="handleClose" > <div class="file-list-toolbar" v-if="showToolbar"> <template v-if="useBuiltInUpload"> <el-upload v-model:file-list="uploadFileList" class="upload-demo" :action="uploadAction" :headers="uploadHeaders" :show-file-list="false" :on-success="handleDefaultUploadSuccess" :on-error="handleDefaultUploadError" > <el-button v-if="showUploadButton" type="primary" size="small" > ä¸ä¼ éä»¶ </el-button> </el-upload> </template> <template v-else> <el-button v-if="showUploadButton" type="primary" size="small" @click="handleUpload" > æ°å¢éä»¶ </el-button> </template> </div> <el-table :data="tableData" border :height="tableHeight"> <el-table-column :label="nameColumnLabel" :prop="nameColumnProp" :min-width="nameColumnMinWidth" show-overflow-tooltip /> <el-table-column v-if="showActions" fixed="right" label="æä½" :width="actionColumnWidth" align="center" > <template #default="scope"> <el-button v-if="showDownload" link type="primary" size="small" @click="handleDownload(scope.row)" > ä¸è½½ </el-button> <el-button v-if="showPreview" link type="primary" size="small" @click="handlePreview(scope.row)" > é¢è§ </el-button> <el-button v-if="showDeleteButton" link type="danger" size="small" @click="handleDelete(scope.row, scope.$index)" > å é¤ </el-button> <slot name="actions" :row="scope.row"></slot> </template> </el-table-column> <slot name="columns"></slot> </el-table> </el-dialog> <filePreview v-if="showPreview" ref="filePreviewRef" /> </template> <script setup> import { ref, computed, getCurrentInstance } from 'vue' import { ElMessage } from 'element-plus' import filePreview from '@/components/filePreview/index.vue' import { getToken } from '@/utils/auth' const props = defineProps({ modelValue: { type: Boolean, default: false }, title: { type: String, default: 'éä»¶' }, width: { type: String, default: '40%' }, tableHeight: { type: String, default: '40vh' }, nameColumnLabel: { type: String, default: 'éä»¶åç§°' }, nameColumnProp: { type: String, default: 'name' }, nameColumnMinWidth: { type: [String, Number], default: 400 }, actionColumnWidth: { type: [String, Number], default: 160 }, showActions: { type: Boolean, default: true }, showDownload: { type: Boolean, default: true }, showPreview: { type: Boolean, default: true }, showUploadButton: { type: Boolean, default: false }, showDeleteButton: { type: Boolean, default: false }, urlField: { type: String, default: 'url' }, downloadMethod: { type: Function, default: null }, previewMethod: { type: Function, default: null }, uploadMethod: { type: Function, default: null }, deleteMethod: { type: Function, default: null }, rulesRegulationsManagementId: { type: [String, Number], default: '' }, uploadUrl: { type: String, default: `${import.meta.env.VITE_APP_BASE_API}/file/upload` } }) const emit = defineEmits(['update:modelValue', 'close', 'download', 'preview', 'upload', 'delete']) const { proxy } = getCurrentInstance() const filePreviewRef = ref(null) const uploadFileList = ref([]) const dialogVisible = computed({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) const tableData = ref([]) const showToolbar = computed(() => props.showUploadButton) const useBuiltInUpload = computed(() => !props.uploadMethod) const uploadAction = computed(() => props.uploadUrl) const uploadHeaders = computed(() => ({ Authorization: `Bearer ${getToken()}` })) const handleClose = () => { emit('close') dialogVisible.value = false } const handleDownload = (row) => { if (props.downloadMethod) { props.downloadMethod(row) } else { // é»è®¤ä¸è½½æ¹æ³ proxy.$download.name(row[props.urlField]) } emit('download', row) } const handlePreview = (row) => { if (props.previewMethod) { props.previewMethod(row) } else { // é»è®¤é¢è§æ¹æ³ if (filePreviewRef.value) { filePreviewRef.value.open(row[props.urlField]) } } emit('preview', row) } const open = (list) => { dialogVisible.value = true tableData.value = list || [] } const handleUpload = async () => { if (props.uploadMethod) { // 妿æä¾äºèªå®ä¹ä¸ä¼ æ¹æ³ï¼ç±ç¶ç»ä»¶è´è´£æ´æ°å表ï¼éè¿ setListï¼ // è¿éä¸åèªå¨æ·»å ï¼é¿å ä¸ç¶ç»ä»¶ç setList éå¤ await props.uploadMethod() } emit('upload') } const handleDelete = async (row, index) => { if (props.deleteMethod) { const result = await props.deleteMethod(row, index) if (result === false) { return } // 妿æä¾äº deleteMethodï¼ç±ç¶ç»ä»¶è´è´£å·æ°å表ï¼ä¸å¨è¿éå é¤ } else { // å¦ææ²¡ææä¾ deleteMethodï¼æå¨ç»ä»¶å é¨å é¤ removeAttachment(index) } emit('delete', row) } const addAttachment = (item) => { tableData.value = [...tableData.value, item] } const handleDefaultUploadSuccess = async (res, file) => { if (res?.code !== 200) { ElMessage.error(res?.msg || 'æä»¶ä¸ä¼ 失败') return } if (!props.rulesRegulationsManagementId) { ElMessage.error('缺å°è§ç« å¶åº¦IDï¼æ æ³ä¿åéä»¶') return } const fileName = res?.data?.originalName || file?.name const fileUrl = res?.data?.tempPath || res?.data?.url const payload = { fileName, fileUrl, rulesRegulationsManagementId: props.rulesRegulationsManagementId, raw: res?.data || {} } emit('upload', payload) } const handleDefaultUploadError = () => { ElMessage.error('æä»¶ä¸ä¼ 失败') } const removeAttachment = (index) => { if (index > -1 && index < tableData.value.length) { const newList = [...tableData.value] newList.splice(index, 1) tableData.value = newList } } const setList = (list) => { tableData.value = list || [] } defineExpose({ open, addAttachment, removeAttachment, setList, handleUpload, handleDelete }) </script> <style scoped> .file-list-toolbar { margin-bottom: 8px; text-align: right; } </style> src/components/Dialog/FormDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,73 @@ <template> <el-dialog v-model="dialogVisible" :title="computedTitle" :width="width" @close="handleClose" > <slot></slot> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleConfirm">确认</el-button> <el-button @click="handleCancel">åæ¶</el-button> </div> </template> </el-dialog> </template> <script setup> import { computed } from 'vue' const props = defineProps({ modelValue: { type: Boolean, default: false }, title: { type: [String, Function], default: '' }, operationType: { type: String, default: '' }, width: { type: String, default: '70%' } }) const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel']) const dialogVisible = computed({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) const computedTitle = computed(() => { if (typeof props.title === 'function') { return props.title(props.operationType) } return props.title }) const handleClose = () => { emit('close') } const handleConfirm = () => { emit('confirm') } const handleCancel = () => { emit('cancel') dialogVisible.value = false } </script> <style scoped> .dialog-footer { text-align: center; } </style> src/components/Dialog/ImportDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,172 @@ <template> <el-dialog :title="title" v-model="dialogVisible" :width="width" :append-to-body="appendToBody" @close="handleClose" > <el-upload ref="uploadRef" :limit="limit" :accept="accept" :headers="headers" :action="action" :disabled="disabled" :before-upload="beforeUpload" :on-progress="onProgress" :on-success="onSuccess" :on-error="onError" :on-change="onChange" :auto-upload="autoUpload" drag > <el-icon class="el-icon--upload"><UploadFilled /></el-icon> <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> <template #tip> <div class="el-upload__tip text-center"> <span>{{ tipText }}</span> <el-link v-if="showDownloadTemplate" type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline; margin-left: 5px;" @click="handleDownloadTemplate" >ä¸è½½æ¨¡æ¿</el-link > </div> </template> </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="handleConfirm">ç¡® å®</el-button> <el-button @click="handleCancel">å æ¶</el-button> </div> </template> </el-dialog> </template> <script setup> import { computed, ref } from 'vue' import { UploadFilled } from '@element-plus/icons-vue' const props = defineProps({ modelValue: { type: Boolean, default: false }, title: { type: String, default: 'å¯¼å ¥' }, width: { type: String, default: '400px' }, appendToBody: { type: Boolean, default: true }, limit: { type: Number, default: 1 }, accept: { type: String, default: '.xlsx, .xls' }, headers: { type: Object, default: () => ({}) }, action: { type: String, required: true }, disabled: { type: Boolean, default: false }, autoUpload: { type: Boolean, default: false }, tipText: { type: String, default: 'ä» å è®¸å¯¼å ¥xlsãxlsxæ ¼å¼æä»¶ã' }, showDownloadTemplate: { type: Boolean, default: true }, beforeUpload: { type: Function, default: null }, onProgress: { type: Function, default: null }, onSuccess: { type: Function, default: null }, onError: { type: Function, default: null }, onChange: { type: Function, default: null } }) const emit = defineEmits(['update:modelValue', 'close', 'confirm', 'cancel', 'download-template']) const dialogVisible = computed({ get: () => props.modelValue, set: (val) => emit('update:modelValue', val) }) const uploadRef = ref(null) const handleClose = () => { emit('close') } const handleConfirm = () => { emit('confirm') } const submit = () => { if (uploadRef.value) { uploadRef.value.submit() } } const handleCancel = () => { emit('cancel') dialogVisible.value = false } const handleDownloadTemplate = () => { emit('download-template') } defineExpose({ uploadRef, submit, clearFiles: () => { if (uploadRef.value) { uploadRef.value.clearFiles() } } }) </script> <style scoped> .dialog-footer { text-align: center; } </style> src/views/inventoryManagement/dispatchLog/index.vue
@@ -160,7 +160,7 @@ <div v-for="(item, index) in printData" :key="index" class="print-page"> <div class="delivery-note"> <div class="header"> <div class="company-name">é¼è¯çå®ä¸æéè´£ä»»å ¬å¸</div> <div class="company-name">éæµ·æ¹æ°´å³¡åä¸å屿éå ¬å¸</div> <div class="document-title">é¶å®åè´§å</div> </div> @@ -558,7 +558,7 @@ <div class="print-page"> <div class="delivery-note"> <div class="header"> <div class="company-name">é¼è¯çå®ä¸æéè´£ä»»å ¬å¸</div> <div class="company-name">éæµ·æ¹æ°´å³¡åä¸å屿éå ¬å¸</div> <div class="document-title">é¶å®åè´§å</div> </div> src/views/procurementManagement/procurementLedger/index.vue
@@ -39,7 +39,6 @@ <div class="table_list"> <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> <el-button type="primary" @click="openForm('add')">æ°å¢å°è´¦</el-button> <el-button type="success" @click="openScanAddDialog">æ«ç æ°å¢</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="danger" plain @click="handleDelete">å é¤</el-button> </div> @@ -146,7 +145,7 @@ <el-table-column fixed="right" label="æä½" min-width="150" width="100" align="center" > <template #default="scope"> @@ -157,13 +156,6 @@ @click="openForm('edit', scope.row)" :disabled="scope.row.receiptPaymentAmount>0 || scope.row.recorderName !== userStore.nickName" >ç¼è¾</el-button > <el-button link type="success" size="small" @click="showQRCode(scope.row)" >çæäºç»´ç </el-button > </template> @@ -225,6 +217,7 @@ <el-select v-model="form.supplierId" placeholder="è¯·éæ©" filterable clearable > <el-option @@ -583,129 +576,6 @@ </template> </el-dialog> <!-- äºç»´ç æ¾ç¤ºå¯¹è¯æ¡ --> <el-dialog v-model="qrCodeDialogVisible" title="éè´ååå·äºç»´ç " width="400px" center > <div style="text-align: center;"> <img :src="qrCodeUrl" alt="äºç»´ç " style="width:200px;height:200px;" /> <div style="margin: 20px;"> <el-button type="primary" @click="downloadQRCode">ä¸è½½äºç»´ç å¾ç</el-button> </div> </div> </el-dialog> <!-- æ«ç æ°å¢å¯¹è¯æ¡ --> <el-dialog v-model="scanAddDialogVisible" title="æ«ç æ°å¢éè´å°è´¦" width="70%" @close="closeScanAddDialog" > <el-form :model="scanAddForm" label-width="140px" label-position="top" :rules="scanAddRules" ref="scanAddFormRef" > <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç å 容ï¼"> <el-input v-model="scanAddForm.scanContent" type="textarea" :rows="3" placeholder="è¯·æ«æäºç»´ç ææå¨è¾å ¥éè´ååä¿¡æ¯" @input="parseScanContent" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="éè´ååå·ï¼" prop="purchaseContractNumber"> <el-input v-model="scanAddForm.purchaseContractNumber" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¾åºååç§°ï¼" prop="supplierName"> <el-input v-model="scanAddForm.supplierName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="项ç®åç§°ï¼" prop="projectName"> <el-input v-model="scanAddForm.projectName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ååéé¢(å )ï¼" prop="contractAmount"> <el-input-number v-model="scanAddForm.contractAmount" :precision="2" :step="0.1" clearable style="width: 100%" placeholder="请è¾å ¥" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="仿¬¾æ¹å¼ï¼"> <el-input v-model="scanAddForm.paymentMethod" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½å ¥äººï¼"> <el-input v-model="scanAddForm.recorderName" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="夿³¨ï¼"> <el-input v-model="scanAddForm.remark" type="textarea" :rows="2" placeholder="请è¾å ¥å¤æ³¨ä¿¡æ¯" clearable /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitScanAdd">确认æ°å¢</el-button> <el-button @click="closeScanAddDialog">åæ¶</el-button> </div> </template> </el-dialog> <!-- æ«ç ç»è®°å¯¹è¯æ¡ --> <el-dialog v-model="scanDialogVisible" @@ -848,10 +718,6 @@ import dayjs from "dayjs"; const userStore = useUserStore(); // äºç»´ç ç¸å ³åé const qrCodeDialogVisible = ref(false); const qrCodeUrl = ref(""); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); @@ -1430,65 +1296,6 @@ } }; // æ¾ç¤ºäºç»´ç const showQRCode = async (row) => { try { // æå»ºäºç»´ç å 容ï¼åªå å«éè´ååå·ï¼çº¯ææ¬ï¼ const qrContent = row.purchaseContractNumber || ''; // æ£æ¥å 容æ¯å¦ä¸ºç©º if (!qrContent || qrContent.trim() === '') { proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); return; } qrCodeUrl.value = await QRCode.toDataURL(qrContent, { width: 200, margin: 2, color: { dark: '#000000', light: '#FFFFFF' } }); qrCodeDialogVisible.value = true; } catch (error) { console.error('çæäºç»´ç 失败:', error); proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); } }; // ä¸è½½äºç»´ç const downloadQRCode = () => { if (!qrCodeUrl.value) { proxy.$modal.msgWarning("äºç»´ç æªçæ"); return; } const a = document.createElement('a'); a.href = qrCodeUrl.value; a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); proxy.$modal.msgSuccess("ä¸è½½æå"); }; // æ«ç æ°å¢å¯¹è¯æ¡ç¸å ³åé const scanAddDialogVisible = ref(false); const scanAddForm = reactive({ scanContent: "", purchaseContractNumber: "", supplierName: "", projectName: "", contractAmount: "", paymentMethod: "", recorderName: "", scanRemark: "", }); const scanAddRules = { purchaseContractNumber: [{ required: true, message: "请è¾å ¥éè´ååå·", trigger: "blur" }], supplierName: [{ required: true, message: "请è¾å ¥ä¾åºååç§°", trigger: "blur" }], projectName: [{ required: true, message: "请è¾å ¥é¡¹ç®åç§°", trigger: "blur" }], }; // æ«ç ç»è®°å¯¹è¯æ¡ç¸å ³åé const scanDialogVisible = ref(false); const scanForm = reactive({ @@ -1504,68 +1311,6 @@ scanRemark: [{ required: true, message: "请è¾å ¥æ«ç 夿³¨", trigger: "blur" }], }; const scanRecords = ref([]); // æå¼æ«ç æ°å¢å¯¹è¯æ¡ const openScanAddDialog = () => { scanAddForm.scanContent = ""; scanAddForm.purchaseContractNumber = ""; scanAddForm.supplierName = ""; scanAddForm.projectName = ""; scanAddForm.contractAmount = ""; scanAddForm.paymentMethod = ""; scanAddForm.recorderName = userStore.nickName; scanAddForm.scanRemark = ""; scanAddDialogVisible.value = true; }; // è§£ææ«ç å å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ const parseScanContent = (content) => { if (!content) return; // 模æè§£æäºç»´ç å 容ï¼è¿éå¯ä»¥æ ¹æ®å®é éæ±è°æ´è§£æé»è¾ // å设æ«ç å å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|项ç®|éé¢|仿¬¾æ¹å¼ const parts = content.split('|'); if (parts.length >= 3) { scanAddForm.purchaseContractNumber = parts[0] || ""; scanAddForm.supplierName = parts[1] || ""; scanAddForm.projectName = parts[2] || ""; scanAddForm.contractAmount = parts[3] || ""; scanAddForm.paymentMethod = parts[4] || ""; } }; // å ³éæ«ç æ°å¢å¯¹è¯æ¡ const closeScanAddDialog = () => { scanAddDialogVisible.value = false; proxy.resetForm("scanAddFormRef"); }; // æäº¤æ«ç æ°å¢ const submitScanAdd = () => { proxy.$refs["scanAddFormRef"].validate((valid) => { if (valid) { // æå»ºæ°å¢æ°æ® const newData = { purchaseContractNumber: scanAddForm.purchaseContractNumber, supplierName: scanAddForm.supplierName, projectName: scanAddForm.projectName, contractAmount: scanAddForm.contractAmount, paymentMethod: scanAddForm.paymentMethod, recorderName: scanAddForm.recorderName, entryDate: getCurrentDate(), remark: scanAddForm.scanRemark, type: 2 }; // æ¨¡ææ°å¢æå proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); closeScanAddDialog(); // å¯ä»¥éæ©æ¯å¦å·æ°å表 // getList(); } }); }; // æå¼æ«ç ç»è®°å¯¹è¯æ¡ const openScanDialog = (row) => { src/views/salesManagement/salesLedger/index.vue
@@ -100,7 +100,7 @@ <el-select v-model="form.customerId" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'" filterable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> {{ item.customerName + "ââ" + item.taxpayerIdentificationNumber item.customerName+'-'+item.type }} </el-option> </el-select> @@ -309,7 +309,7 @@ <div v-for="(item, index) in printData" :key="index" class="print-page"> <div class="delivery-note"> <div class="header"> <div class="company-name">é¼è¯çå®ä¸æéè´£ä»»å ¬å¸</div> <div class="company-name">éæµ·æ¹æ°´å³¡åä¸å屿éå ¬å¸</div> <div class="document-title">é¶å®åè´§å</div> </div> @@ -466,6 +466,7 @@ delProduct, delLedgerFile, } from "@/api/salesManagement/salesLedger.js"; import { getQuotationDetail } from "@/api/salesManagement/salesQuotation.js"; import { modelList, productTreeList } from "@/api/basicData/product.js"; import useFormData from "@/hooks/useFormData.js"; import dayjs from "dayjs"; @@ -653,6 +654,7 @@ if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; productForm.value.unit = modelOptions.value[index].unit; fetchQuotationPrice(); } else { productForm.value.specificationModel = null; productForm.value.unit = null; @@ -671,6 +673,29 @@ } } return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull }; // æ ¹æ®æ¥ä»·æ¥å£åå¡«åä»· const fetchQuotationPrice = async () => { // éè¦å®¢æ·ç±»åã产ååç§°ãè§æ ¼ const customer = customerOption.value.find((c) => c.id === form.value.customerId); const customerType = customer?.customerType || customer?.type; const productName = productForm.value.productCategory; const specification = productForm.value.specificationModel; try { const { data } = await getQuotationDetail({ type: customerType, productName, specification, }); const price = data; if (price !== null && price !== undefined) { productForm.value.taxInclusiveUnitPrice = Number(price); mathNum(); // éæ°è®¡ç®æ»ä»· } } catch (error) { console.error("è·åæ¥ä»·å价失败", error); } }; function convertIdToValue(data) { return data.map((item) => { @@ -1153,7 +1178,7 @@ <div class="print-page"> <div class="delivery-note"> <div class="header"> <div class="company-name">é¼è¯çå®ä¸æéè´£ä»»å ¬å¸</div> <div class="company-name">éæµ·æ¹æ°´å³¡åä¸å屿éå ¬å¸</div> <div class="document-title">é¶å®åè´§å</div> </div> src/views/salesManagement/salesQuotation/index.vue
@@ -15,15 +15,6 @@ </template> </el-input> </el-col> <el-col :span="8"> <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> {{ item.customerName + "ââ" + item.taxpayerIdentificationNumber }} </el-option> </el-select> </el-col> <!-- <el-col :span="6">--> <!-- <el-select v-model="searchForm.status" placeholder="è¯·éæ©æ¥ä»·ç¶æ" clearable>--> <!-- <el-option label="è稿" value="è稿"></el-option>--> @@ -51,28 +42,14 @@ height="calc(100vh - 22em)" > <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column prop="quotationNo" label="æ¥ä»·åå·" width="150" /> <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="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 prop="quotationNo" label="æ¥ä»·åå·" /> <el-table-column prop="salesperson" label="ä¸å¡å" /> <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" /> <el-table-column label="æä½" width="200" fixed="right" align="center"> <template #default="scope"> <el-button link type="primary" @click="handleView(scope.row)">æ¥ç</el-button> <el-button link type="primary" @click="handleEdit(scope.row)" 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,94 +65,62 @@ </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"> <!-- åºæ¬ä¿¡æ¯ --> <el-card class="form-card" shadow="never"> <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> <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-date-picker v-model="form.deliveryPeriod" type="date" placeholder="éæ©äº¤è´§æ" style="width: 100%" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /> </el-form-item> </el-col> </el-row> <div class="form-content"> <el-row :gutter="24"> <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-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-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-tree-select @@ -207,24 +152,24 @@ </el-select> </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%" /> </template> </el-table-column> <el-table-column prop="unit" label="åä½"> <template #default="scope"> <el-input v-model="scope.row.unit" placeholder="åä½" /> </template> </el-table-column> <el-table-column prop="unitPrice" label="åä»·"> <el-table-column prop="unitPrice" label="䏿¹ååä»·"> <template #default="scope"> <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" @change="calculateAmount(scope.row)" /> <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> </template> </el-table-column> <el-table-column prop="amount" label="éé¢" width="120"> <el-table-column prop="unitPriceTwo" label="ç»ç«¯ååä»·"> <template #default="scope"> <span>Â¥{{ scope.row.amount.toFixed(2) }}</span> <el-input-number v-model="scope.row.unitPriceTwo" :min="0" :precision="2" style="width: 100%" /> </template> </el-table-column> <el-table-column prop="unitPriceThree" label="åä»·"> <template #default="scope"> <el-input-number v-model="scope.row.unitPriceThree" :min="0" :precision="2" style="width: 100%" /> </template> </el-table-column> <el-table-column label="æä½" width="80" align="center"> @@ -233,83 +178,41 @@ </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"> <el-descriptions :column="2" border> <el-descriptions-item label="æ¥ä»·åå·">{{ currentQuotation.quotationNo }}</el-descriptions-item> <el-descriptions-item label="客æ·åç§°">{{ currentQuotation.customer }}</el-descriptions-item> <el-descriptions-item label="ä¸å¡å">{{ currentQuotation.salesperson }}</el-descriptions-item> <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> <el-descriptions-item label="交货æ">{{ currentQuotation.deliveryPeriod }}</el-descriptions-item> <!-- <el-descriptions-item label="æ¥ä»·ç¶æ">--> <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>--> <!-- </el-descriptions-item>--> <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span> </el-descriptions-item> </el-descriptions> <div style="margin-top: 20px;"> @@ -317,16 +220,20 @@ <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="åä»·"> <el-table-column prop="unitPrice" label="䏿¹ååä»·"> <template #default="scope"> Â¥{{ scope.row.unitPrice.toFixed(2) }} </template> </el-table-column> <el-table-column prop="amount" label="éé¢"> <el-table-column prop="unitPriceTwo" label="ç»ç«¯ååä»·"> <template #default="scope"> Â¥{{ scope.row.amount.toFixed(2) }} Â¥{{ scope.row.unitPriceTwo?.toFixed(2) }} </template> </el-table-column> <el-table-column prop="unitPriceThree" label="åä»·"> <template #default="scope"> Â¥{{ scope.row.unitPriceThree?.toFixed(2) }} </template> </el-table-column> </el-table> @@ -343,8 +250,9 @@ <script setup> import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { Search } from '@element-plus/icons-vue' import { Search, Document, Box, EditPen, Plus } from '@element-plus/icons-vue' import Pagination from '@/components/PIMTable/Pagination.vue' import FormDialog from '@/components/Dialog/FormDialog.vue' import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js' import {userListNoPage} from "@/api/system/user.js"; import {customerList} from "@/api/salesManagement/salesLedger.js"; @@ -354,7 +262,6 @@ const loading = ref(false) const searchForm = reactive({ quotationNo: '', customer: '', status: '' }) @@ -377,7 +284,6 @@ quotationDate: '', validDate: '', paymentMethod: '', deliveryPeriod: '', status: 'è稿', remark: '', products: [], @@ -390,12 +296,8 @@ }) const rules = { 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: 'change' }], deliveryPeriod: [{ required: true, message: 'è¯·éæ©äº¤è´§æ', trigger: 'change' }] quotationDate: [{ required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change' }] } const userList = ref([]); const customerOption = ref([]); @@ -411,9 +313,6 @@ if (searchForm.quotationNo) { list = list.filter(item => item.quotationNo.includes(searchForm.quotationNo)) } if (searchForm.customer) { list = list.filter(item => item.customer === searchForm.customer) } if (searchForm.status) { list = list.filter(item => item.status === searchForm.status) } @@ -423,17 +322,16 @@ // æ¹æ³ const getStatusType = (status) => { const statusMap = { 'è稿': 'info', 'å·²åé': 'primary', '客æ·ç¡®è®¤': 'success', 'å·²è¿æ': 'danger' 'å¾ å®¡æ¹': 'info', 'å®¡æ ¸ä¸': 'primary', 'éè¿': 'success', 'æç»': 'danger' } return statusMap[status] || 'info' } const resetSearch = () => { searchForm.quotationNo = '' searchForm.customer = '' searchForm.status = '' } @@ -460,8 +358,10 @@ }); } const getProductOptions = () => { productTreeList().then((res) => { // è¿å Promiseï¼ä¾¿äºç¼è¾æ¶ await ç¡®ä¿è½åæ¾ return productTreeList().then((res) => { productOptions.value = convertIdToValue(res); return productOptions.value }); }; function convertIdToValue(data) { @@ -477,6 +377,19 @@ 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; @@ -545,7 +458,6 @@ quotationDate: row.quotationDate || '', validDate: row.validDate || '', paymentMethod: row.paymentMethod || '', deliveryPeriod: row.deliveryPeriod || '', status: row.status || '', remark: row.remark || '', products: row.products ? row.products.map(product => ({ @@ -555,7 +467,9 @@ specification: product.specification || '', quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, unitPrice: product.unitPrice || 0, // 䏿¹ååä»· unitPriceTwo: product.unitPriceTwo || product.dealerUnitPrice || 0, // ç»ç«¯ååä»· unitPriceThree: product.unitPriceThree || 0, // åä»· amount: product.amount || 0 })) : [], totalAmount: row.totalAmount || 0 @@ -563,10 +477,14 @@ viewDialogVisible.value = true } const handleEdit = (row) => { const handleEdit = async (row) => { dialogTitle.value = 'ç¼è¾æ¥ä»·' isEdit.value = true editId.value = row.id form.id = row.id || form.id || null // å å è½½äº§åæ æ°æ®ï¼å¦å el-tree-select æ æ³åæ¾äº§ååç§° await getProductOptions() // åªå¤å¶éè¦çåæ®µï¼é¿å å°ç»ä»¶å¼ç¨æ¾å ¥ååºå¼å¯¹è±¡ form.quotationNo = row.quotationNo || '' form.customer = row.customer || '' @@ -574,25 +492,42 @@ form.quotationDate = row.quotationDate || '' form.validDate = row.validDate || '' form.paymentMethod = row.paymentMethod || '' form.deliveryPeriod = row.deliveryPeriod || '' form.status = row.status || 'è稿' form.remark = row.remark || '' form.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 })) : [] 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, unitPriceTwo: product.unitPriceTwo || product.dealerUnitPrice || 0, unitPriceThree: product.unitPriceThree || 0, amount: product.amount || 0 } }) : [] form.subtotal = row.subtotal || 0 form.freight = row.freight || 0 form.otherFee = row.otherFee || 0 form.discountRate = row.discountRate || 0 form.discountAmount = row.discountAmount || 0 form.totalAmount = row.totalAmount || 0 // å è½½ç¨æ·å表 let userLists = await userListNoPage(); userList.value = (userLists.data || []).map(item => ({ userId: item.userId, nickName: item.nickName || '', userName: item.userName || '' })); dialogVisible.value = true } @@ -625,7 +560,6 @@ form.quotationDate = '' form.validDate = '' form.paymentMethod = '' form.deliveryPeriod = '' form.status = 'è稿' form.remark = '' form.products = [] @@ -646,6 +580,8 @@ specification: '', quantity: 1, unit: '', unitPriceTwo: 0, unitPriceThree: 0, unitPrice: 0, amount: 0 }) @@ -683,6 +619,12 @@ return } // è®¡ç®ææäº§åçåä»·æ»å 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) @@ -695,30 +637,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('æ°å¢æå') } } @@ -731,7 +659,7 @@ } const handleSearch = ()=>{ const params = { page:pagination, ...pagination, ...searchForm } getQuotationList(params).then(res=>{ @@ -746,7 +674,6 @@ quotationDate: item.quotationDate || '', validDate: item.validDate || '', paymentMethod: item.paymentMethod || '', deliveryPeriod: item.deliveryPeriod || '', status: item.status || 'è稿', remark: item.remark || '', products: item.products ? item.products.map(product => ({ @@ -757,6 +684,8 @@ quantity: product.quantity || 0, unit: product.unit || '', unitPrice: product.unitPrice || 0, unitPriceTwo: product.unitPriceTwo || product.dealerUnitPrice || 0, unitPriceThree: product.unitPriceThree || 0, amount: product.amount || 0 })) : [], subtotal: item.subtotal || 0, @@ -784,27 +713,117 @@ }) </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; } .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>