| | |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="产品宽(mm):"> |
| | | <el-input v-model="searchForm.width" |
| | | placeholder="请输入" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="产品高(mm):"> |
| | | <el-input v-model="searchForm.height" |
| | | placeholder="请输入" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | </el-form-item> |
| | | <el-form-item label="录入日期:"> |
| | | <el-date-picker v-model="searchForm.entryDate" |
| | | value-format="YYYY-MM-DD" |
| | |
| | | style="width: 140px"> |
| | | <el-option label="未入库" |
| | | :value="0" /> |
| | | <el-option label="已入库" |
| | | <el-option label="部分入库" |
| | | :value="1" /> |
| | | <el-option label="已入库" |
| | | :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | |
| | | <el-button type="primary" |
| | | plain |
| | | @click="handleImport">导入</el-button> |
| | | <el-dropdown @command="handleHistoryImportCommand"> |
| | | <el-button type="primary" |
| | | plain> |
| | | 历史迁移<el-icon class="el-icon--right"> |
| | | <ArrowDown /> |
| | | </el-icon> |
| | | </el-button> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item command="notShipped">未出库</el-dropdown-item> |
| | | <el-dropdown-item command="shipped">已出库</el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | <el-button @click="handleOut">导出</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | |
| | | style="width: 100%" |
| | | :summary-method="summarizeMainTable" |
| | | @expand-change="expandChange" |
| | | height="calc(100vh - 18.5em)"> |
| | | height="calc(100vh - 22em)"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" |
| | |
| | | type="success">已出库</el-tag> |
| | | <el-tag v-else |
| | | type="danger">不足</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="入库状态" |
| | | width="100px" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.productStockStatus == 1" |
| | | type="warning">部分入库</el-tag> |
| | | <el-tag v-else-if="scope.row.productStockStatus == 2" |
| | | type="success">已入库</el-tag> |
| | | <el-tag v-else-if="scope.row.productStockStatus == 0" |
| | | type="info">未入库</el-tag> |
| | | <el-tag v-else |
| | | type="danger">未入库</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <!-- <el-table-column label="发货状态" width="140" align="center"> |
| | |
| | | width="220" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="面积" |
| | | prop="productTotalArea" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="数量" |
| | | prop="productTotalQuantity" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="发货状态" |
| | | width="140" |
| | | align="center"> |
| | |
| | | <el-tag v-if="Number(scope.row.stockStatus) === 0" |
| | | type="info">未入库</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.stockStatus) === 1" |
| | | type="success">部分入库</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.stockStatus) === 2" |
| | | type="success">已入库</el-tag> |
| | | <el-tag v-else |
| | | type="info">-</el-tag> |
| | |
| | | prop="remarks" |
| | | width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="客户备注" |
| | | prop="customerRemarks" |
| | | width="200" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | width="200" |
| | | width="280" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | |
| | | <el-button link |
| | | type="primary" |
| | | @click="downLoadFile(scope.row)">附件</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openLedgerQrDialog(scope.row)">二维码</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | @keydown.capture="handleTabScrollFollow" |
| | | ref="formRef"> |
| | | <!-- 报价单导入入口:放在表单顶部,选择后反显客户/业务员等 --> |
| | | <el-row v-if="operationType === 'add'" |
| | |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id"> |
| | | {{ |
| | | item.customerName + "——" + item.taxpayerIdentificationNumber |
| | | }} |
| | | {{ item.customerName + (item.taxpayerIdentificationNumber ? "——" + item.taxpayerIdentificationNumber : "") }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | </el-table-column> |
| | | <el-table-column label="规格型号" |
| | | prop="specificationModel" |
| | | min-width="160"> |
| | | min-width="200"> |
| | | <template #default="scope"> |
| | | <el-select v-if="scope.row.__editing" |
| | | v-model="scope.row.productModelId" |
| | |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.thickness" |
| | | :min="0" |
| | | :step="0.000000000000001" |
| | | :precision="15" |
| | | :step="1" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable /> |
| | |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.width" |
| | | :min="0" |
| | | :step="1" |
| | |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.height" |
| | | :min="0" |
| | | :step="1" |
| | |
| | | </el-table-column> |
| | | <el-table-column label="结算单片面积(㎡)" |
| | | prop="settlePieceArea" |
| | | min-width="160"> |
| | | min-width="200"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.settlePieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="1" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineSettleAreaChange(scope.row)" /> |
| | | <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span> |
| | | <span v-else>{{ scope.row.settlePieceArea ? Number(scope.row.settlePieceArea).toFixed(4) : "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="数量" |
| | |
| | | min-width="150"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.quantity" |
| | | :step="0.1" |
| | | :step="1" |
| | | :min="0" |
| | | :precision="2" |
| | | :precision="0" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | |
| | | </el-table-column> |
| | | <el-table-column label="面积(m²)" |
| | | prop="actualTotalArea" |
| | | min-width="160"> |
| | | min-width="200"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | controls-position="right" |
| | | v-model="scope.row.actualTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="1" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="自动计算" /> |
| | | <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span> |
| | | <span v-else>{{ scope.row.actualTotalArea ? Number(scope.row.actualTotalArea).toFixed(4) : "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | min-width="140"> |
| | | min-width="160"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.__editing" |
| | | :step="0.01" |
| | |
| | | clearable |
| | | @change="() => handleInlineUnitPriceChange(scope.row)" |
| | | @input="() => handleInlineUnitPriceChange(scope.row)" /> |
| | | <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice ?? 0) }}</span> |
| | | <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="税率(%)" |
| | |
| | | </el-table-column> |
| | | <el-table-column label="楼层编号" |
| | | prop="floorCode" |
| | | min-width="140" |
| | | min-width="250" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-input v-if="scope.row.__editing" |
| | |
| | | @click="editProductInline(scope.row, scope.$index)"> |
| | | 编辑 |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row) || hasEditingProductRow()" |
| | | @click="copyProductInline(scope.row, scope.$index)"> |
| | | 复制新建 |
| | | </el-button> |
| | | <el-popover :width="560" |
| | | trigger="click" |
| | | :hide-after="0" |
| | |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openOtherAmountInline(scope.row)"> |
| | | 其他金额({{ (scope.row.salesProductProcessList || []).length || 0 }}) |
| | | 额外加工({{ (scope.row.salesProductProcessList || []).length || 0 }}) |
| | | </el-button> |
| | | </template> |
| | | <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;"> |
| | | <div style="font-weight: 600; color:#303133;"> |
| | | 其他金额 |
| | | 额外加工 |
| | | </div> |
| | | <el-button type="primary" |
| | | plain |
| | |
| | | <el-select v-model="scope.row.__inlineOtherAmountAddId" |
| | | filterable |
| | | clearable |
| | | placeholder="请选择其他金额项目" |
| | | placeholder="请选择额外加工项目" |
| | | style="width: 100%;" |
| | | :disabled="isProductShipped(scope.row)"> |
| | | <el-option v-for="item in otherAmountSelectOptions" |
| | |
| | | </div> |
| | | <div v-else |
| | | style="color:#909399; font-size: 13px;"> |
| | | 暂无其他金额 |
| | | 暂无额外加工 |
| | | </div> |
| | | </el-popover> |
| | | </template> |
| | |
| | | prop="actualPieceArea"> |
| | | <el-input-number v-model="productForm.actualPieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="0.0001" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | |
| | | prop="actualTotalArea"> |
| | | <el-input-number v-model="productForm.actualTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="0.0001" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable /> |
| | |
| | | prop="settlePieceArea"> |
| | | <el-input-number v-model="productForm.settlePieceArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="0.0001" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | |
| | | prop="settleTotalArea"> |
| | | <el-input-number v-model="productForm.settleTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="0.0001" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable /> |
| | |
| | | prop="settleTotalArea"> |
| | | <el-input-number v-model="productForm.settleTotalArea" |
| | | :min="0" |
| | | :step="0.00001" |
| | | :precision="5" |
| | | :step="0.0001" |
| | | :precision="4" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable /> |
| | |
| | | <FormDialog v-model="importUpload.open" |
| | | :title="importUpload.title" |
| | | :width="'600px'" |
| | | @close="importUpload.open = false" |
| | | :loading="importUpload.isUploading" |
| | | @close="onClose" |
| | | @confirm="submitImportFile" |
| | | @cancel="importUpload.open = false"> |
| | | @cancel="onClose"> |
| | | <el-upload ref="importUploadRef" |
| | | :limit="1" |
| | | accept=".xlsx,.xls" |
| | |
| | | :on-error="importUpload.onError" |
| | | :on-progress="importUpload.onProgress" |
| | | :on-change="importUpload.onChange" |
| | | :on-exceed="importUpload.onExceed" |
| | | :auto-upload="false" |
| | | drag> |
| | | <i class="el-icon-upload"></i> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 入库产品选择弹窗 --> |
| | | <el-dialog v-model="stockDialogVisible" |
| | | title="选择入库产品" |
| | | width="60%" |
| | | :close-on-click-modal="false"> |
| | | <div style="margin-bottom: 12px;"> |
| | | <el-form> |
| | | <el-form-item required> |
| | | <template #label> |
| | | <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;"> |
| | | <span>审批人选择:</span> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="addStockApproverNode" |
| | | icon="Plus">新增节点</el-button> |
| | | </div> |
| | | </template> |
| | | <div class="approver-nodes-container"> |
| | | <div v-for="(node, index) in stockApproverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item"> |
| | | <div class="approver-node-header"> |
| | | <span class="approver-node-label">审批节点 {{ index + 1 }}</span> |
| | | <el-button v-if="stockApproverNodes.length > 1" |
| | | type="danger" |
| | | size="small" |
| | | text |
| | | @click="removeStockApproverNode(index)" |
| | | icon="Delete">删除</el-button> |
| | | </div> |
| | | <el-select v-model="node.userId" |
| | | placeholder="请选择审批人" |
| | | filterable |
| | | clearable |
| | | style="width: 100%;"> |
| | | <el-option v-for="item in stockApproverOptions" |
| | | :key="item.userId" |
| | | :label="item.userName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | <el-table :data="stockProductList" |
| | | border |
| | | stripe |
| | | v-loading="stockLoading" |
| | | height="400px" |
| | | @selection-change="val => selectedStockProductIds = val.map(item => item.id)"> |
| | | <el-table-column type="selection" |
| | | width="55" |
| | | align="center" /> |
| | | <el-table-column align="center" |
| | | label="序号" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column prop="productCategory" |
| | | label="产品大类" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="specificationModel" |
| | | label="规格型号" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="quantity" |
| | | label="数量" |
| | | width="100" /> |
| | | <el-table-column prop="stockedQuantity" |
| | | label="已入库数量" |
| | | width="120" |
| | | align="center" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="floorCode" |
| | | label="楼层编号" |
| | | show-overflow-tooltip /> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button @click="stockDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" |
| | | @click="submitStock" |
| | | :disabled="selectedStockProductIds.length === 0"> |
| | | 确认入库 |
| | | </el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="ledgerQrDialogVisible" |
| | | title="销售订单二维码" |
| | | width="360px" |
| | | draggable |
| | | :close-on-click-modal="false"> |
| | | <div class="ledger-qr-dialog"> |
| | | <img v-if="ledgerQrCompositeUrl" |
| | | :src="ledgerQrCompositeUrl" |
| | | alt="销售订单二维码" |
| | | class="ledger-qr-composite-img" /> |
| | | <el-button type="primary" |
| | | class="ledger-qr-save-btn" |
| | | :disabled="!ledgerQrCompositeUrl" |
| | | @click="downloadLedgerQrCode"> |
| | | 保存图片 |
| | | </el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { printSalesOrder } from "./components/salesOrderPrint.js"; |
| | | import { printSalesDeliveryNote } from "./components/salesDeliveryPrint.js"; |
| | | import { printSalesLabel } from "./components/salesLabelPrint.js"; |
| | | import QRCode from "qrcode"; |
| | | // import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | |
| | | const processFlowSelectBoundRouteId = ref(null); |
| | | const processFlowSelectBoundRouteName = ref(""); |
| | | |
| | | // 入库弹窗相关 |
| | | const stockDialogVisible = ref(false); |
| | | const stockProductList = ref([]); |
| | | const selectedStockProductIds = ref([]); |
| | | const stockLoading = ref(false); |
| | | const currentStockLedgerId = ref(null); |
| | | const stockApproverOptions = ref([]); |
| | | const stockApproverNodes = ref([{ id: 1, userId: null }]); |
| | | let nextStockApproverId = 2; |
| | | const addStockApproverNode = () => { |
| | | stockApproverNodes.value.push({ id: nextStockApproverId++, userId: null }); |
| | | }; |
| | | const removeStockApproverNode = index => { |
| | | stockApproverNodes.value.splice(index, 1); |
| | | }; |
| | | |
| | | const ledgerQrDialogVisible = ref(false); |
| | | const ledgerQrCompositeUrl = ref(""); |
| | | const ledgerQrDownloadBaseName = ref(""); |
| | | |
| | | const sanitizeLedgerQrFilename = s => |
| | | String(s) |
| | | .replace(/[\\/:*?"<>|]/g, "_") |
| | | .trim() |
| | | .slice(0, 80) || "ledger"; |
| | | |
| | | const wrapLedgerQrTextLines = (ctx, text, maxWidth) => { |
| | | const chars = [...text]; |
| | | const lines = []; |
| | | let line = ""; |
| | | for (const ch of chars) { |
| | | const test = line + ch; |
| | | if (ctx.measureText(test).width > maxWidth && line.length) { |
| | | lines.push(line); |
| | | line = ch; |
| | | } else { |
| | | line = test; |
| | | } |
| | | } |
| | | if (line) lines.push(line); |
| | | return lines; |
| | | }; |
| | | |
| | | const buildLedgerQrCompositeDataUrl = row => |
| | | new Promise((resolve, reject) => { |
| | | const payload = JSON.stringify({ |
| | | id: row.id, |
| | | salesContractNo: (row.salesContractNo ?? "").trim(), |
| | | type: "XS", |
| | | }); |
| | | QRCode.toDataURL(payload, { width: 220, margin: 2 }) |
| | | .then(qrDataUrl => { |
| | | const contract = (row.salesContractNo ?? "").trim() || "—"; |
| | | const img = new Image(); |
| | | img.onload = () => { |
| | | const QR_SIZE = 220; |
| | | const padTop = 16; |
| | | const gapAfterQr = 14; |
| | | const bottomPad = 48; |
| | | const horizontalPad = 20; |
| | | const lineHeight = 20; |
| | | const fontSize = 14; |
| | | const label = `销售合同号:${contract}`; |
| | | |
| | | const canvas = document.createElement("canvas"); |
| | | const ctx = canvas.getContext("2d"); |
| | | canvas.width = Math.max(QR_SIZE + horizontalPad * 2, 280); |
| | | ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`; |
| | | const lines = wrapLedgerQrTextLines(ctx, label, canvas.width - horizontalPad * 2); |
| | | const textBlockHeight = lines.length * lineHeight; |
| | | canvas.height = padTop + QR_SIZE + gapAfterQr + textBlockHeight + bottomPad; |
| | | |
| | | ctx.fillStyle = "#ffffff"; |
| | | ctx.fillRect(0, 0, canvas.width, canvas.height); |
| | | |
| | | const qrX = (canvas.width - QR_SIZE) / 2; |
| | | ctx.drawImage(img, qrX, padTop, QR_SIZE, QR_SIZE); |
| | | |
| | | ctx.fillStyle = "#606266"; |
| | | ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`; |
| | | ctx.textAlign = "center"; |
| | | ctx.textBaseline = "top"; |
| | | const textY0 = padTop + QR_SIZE + gapAfterQr; |
| | | lines.forEach((ln, i) => { |
| | | ctx.fillText(ln, canvas.width / 2, textY0 + i * lineHeight); |
| | | }); |
| | | |
| | | const baseName = sanitizeLedgerQrFilename( |
| | | contract !== "—" ? contract : String(row.id) |
| | | ); |
| | | resolve({ dataUrl: canvas.toDataURL("image/png"), baseName }); |
| | | }; |
| | | img.onerror = () => reject(new Error("二维码图片加载失败")); |
| | | img.src = qrDataUrl; |
| | | }) |
| | | .catch(reject); |
| | | }); |
| | | |
| | | const openLedgerQrDialog = async row => { |
| | | if (row?.id === undefined || row?.id === null || row?.id === "") { |
| | | ElMessage.warning("无法生成二维码:缺少台账 ID"); |
| | | return; |
| | | } |
| | | ledgerQrCompositeUrl.value = ""; |
| | | ledgerQrDownloadBaseName.value = ""; |
| | | try { |
| | | const { dataUrl, baseName } = await buildLedgerQrCompositeDataUrl(row); |
| | | ledgerQrCompositeUrl.value = dataUrl; |
| | | ledgerQrDownloadBaseName.value = baseName; |
| | | ledgerQrDialogVisible.value = true; |
| | | } catch { |
| | | ElMessage.error("二维码生成失败"); |
| | | } |
| | | }; |
| | | |
| | | const downloadLedgerQrCode = () => { |
| | | if (!ledgerQrCompositeUrl.value) return; |
| | | const a = document.createElement("a"); |
| | | a.href = ledgerQrCompositeUrl.value; |
| | | a.download = `销售台账二维码-${ledgerQrDownloadBaseName.value}.png`; |
| | | a.click(); |
| | | }; |
| | | |
| | | // 用户信息表单弹框数据 |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | |
| | | customerName: "", // 客户名称 |
| | | customerId: "", // 客户ID(查询下拉) |
| | | salesContractNo: "", // 销售合同编号 |
| | | width: undefined, // 产品宽(mm) |
| | | height: undefined, // 产品高(mm) |
| | | entryDate: null, // 录入日期 |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | deliveryStatus: undefined, // 发货状态:1未发货 2审批中 3审批失败 4已发货 |
| | | stockStatus: undefined, // 入库状态:0未入库 1已入库 |
| | | stockStatus: undefined, // 入库状态:0未入库 1部分入库 2已入库 |
| | | }, |
| | | form: { |
| | | salesContractNo: "", |
| | |
| | | return (productData.value || []).some(r => r && r.__editing); |
| | | }; |
| | | |
| | | const buildEmptyInlineProductRow = () => ({ |
| | | id: null, |
| | | __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`, |
| | | __editing: true, |
| | | __isNew: true, |
| | | __productCategoryId: null, |
| | | productCategory: "", |
| | | productModelId: null, |
| | | specificationModel: "", |
| | | thickness: null, |
| | | quantity: null, |
| | | taxInclusiveUnitPrice: null, |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: null, |
| | | taxExclusiveTotalPrice: null, |
| | | invoiceType: "", |
| | | width: null, |
| | | height: null, |
| | | perimeter: null, |
| | | actualPieceArea: null, |
| | | actualTotalArea: null, |
| | | settlePieceArea: null, |
| | | settleTotalArea: null, |
| | | processRequirement: "", |
| | | remark: "", |
| | | salesProductProcessList: [], |
| | | processFlowConfigId: null, |
| | | floorCode: "", |
| | | heavyBox: "", |
| | | }); |
| | | |
| | | const addProductInline = async () => { |
| | | if (operationType.value === "view") return; |
| | | if (hasEditingProductRow()) { |
| | |
| | | } |
| | | await getProductOptions(); |
| | | await fetchOtherAmountSelectOptions(true); |
| | | const row = { |
| | | id: null, |
| | | __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`, |
| | | __editing: true, |
| | | __isNew: true, |
| | | __productCategoryId: null, |
| | | productCategory: "", |
| | | productModelId: null, |
| | | specificationModel: "", |
| | | thickness: null, |
| | | quantity: 0, |
| | | taxInclusiveUnitPrice: 0, |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: 0, |
| | | taxExclusiveTotalPrice: 0, |
| | | invoiceType: "", |
| | | width: 0, |
| | | height: 0, |
| | | perimeter: 0, |
| | | actualPieceArea: 0, |
| | | actualTotalArea: 0, |
| | | settlePieceArea: 0, |
| | | settleTotalArea: 0, |
| | | processRequirement: "", |
| | | remark: "", |
| | | salesProductProcessList: [], |
| | | processFlowConfigId: null, |
| | | floorCode: "", |
| | | heavyBox: "", |
| | | }; |
| | | const row = buildEmptyInlineProductRow(); |
| | | productData.value.push(row); |
| | | editingProductRow.value = row; |
| | | // 让现有的计算/其他金额逻辑复用当前行 |
| | | productForm.value = row; |
| | | }; |
| | | |
| | | const copyProductInline = async row => { |
| | | if (operationType.value === "view") return; |
| | | if (!row) return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能复制"); |
| | | return; |
| | | } |
| | | if (hasEditingProductRow()) { |
| | | proxy.$modal.msgWarning("请先保存或取消当前编辑行"); |
| | | return; |
| | | } |
| | | await getProductOptions(); |
| | | await fetchOtherAmountSelectOptions(true); |
| | | |
| | | const copied = buildEmptyInlineProductRow(); |
| | | copied.__productCategoryId = |
| | | row.__productCategoryId ?? |
| | | findNodeIdByLabel(productOptions.value, row.productCategory) ?? |
| | | null; |
| | | copied.productCategory = row.productCategory ?? ""; |
| | | copied.productModelId = row.productModelId ?? null; |
| | | copied.specificationModel = row.specificationModel ?? ""; |
| | | copied.thickness = |
| | | row.thickness !== null && row.thickness !== undefined && row.thickness !== "" |
| | | ? Number(row.thickness) |
| | | : null; |
| | | |
| | | // 复制新建仅带出产品大类与规格型号,其他数字字段全部留空,避免出现 0.00 |
| | | copied.quantity = null; |
| | | copied.taxInclusiveUnitPrice = null; |
| | | copied.taxInclusiveTotalPrice = null; |
| | | copied.taxExclusiveTotalPrice = null; |
| | | copied.width = null; |
| | | copied.height = null; |
| | | copied.perimeter = null; |
| | | copied.actualPieceArea = null; |
| | | copied.actualTotalArea = null; |
| | | copied.settlePieceArea = null; |
| | | copied.settleTotalArea = null; |
| | | |
| | | // 复制时按“产品大类 + 规格型号名称”反查型号 id,确保下拉能正确回显 |
| | | try { |
| | | if (copied.__productCategoryId) { |
| | | const models = await modelList({ id: copied.__productCategoryId }); |
| | | modelOptions.value = models || []; |
| | | const matchedModel = (modelOptions.value || []).find( |
| | | m => m.model === copied.specificationModel |
| | | ); |
| | | copied.productModelId = matchedModel?.id ?? copied.productModelId ?? null; |
| | | } |
| | | } catch (e) { |
| | | console.error("复制时加载产品规格型号失败", e); |
| | | } |
| | | |
| | | productData.value.push(copied); |
| | | editingProductRow.value = copied; |
| | | productForm.value = copied; |
| | | }; |
| | | |
| | | const editProductInline = async (row, index) => { |
| | |
| | | if (!row) return false; |
| | | if (!row.productCategory) { |
| | | proxy.$modal.msgWarning("请选择产品大类"); |
| | | return false; |
| | | } |
| | | if (row.width <= 0) { |
| | | proxy.$modal.msgWarning("宽必须大于0"); |
| | | return false; |
| | | } |
| | | if (row.height <= 0) { |
| | | proxy.$modal.msgWarning("高必须大于0"); |
| | | return false; |
| | | } |
| | | if (row.settlePieceArea <= 0) { |
| | | proxy.$modal.msgWarning("结算单片面积必须大于0"); |
| | | return false; |
| | | } |
| | | if (row.quantity <= 0) { |
| | | proxy.$modal.msgWarning("数量必须大于0"); |
| | | return false; |
| | | } |
| | | if (row.actualTotalArea <= 0) { |
| | | proxy.$modal.msgWarning("面积必须大于0"); |
| | | return false; |
| | | } |
| | | if (row.taxInclusiveUnitPrice <= 0) { |
| | | proxy.$modal.msgWarning("含税单价必须大于0"); |
| | | return false; |
| | | } |
| | | if (!row.productModelId) { |
| | |
| | | onChange: (file, fileList) => { |
| | | console.log("文件状态改变", file, fileList); |
| | | }, |
| | | onExceed: (files, fileList) => { |
| | | if (importUploadRef.value) { |
| | | importUploadRef.value.clearFiles(); |
| | | const file = files[0]; |
| | | importUploadRef.value.handleStart(file); |
| | | } |
| | | }, |
| | | onProgress: (event, file, fileList) => { |
| | | console.log("上传中...", event.percent); |
| | | }, |
| | |
| | | proxy.$modal.msgError("导入失败,请重试"); |
| | | }, |
| | | }); |
| | | const HISTORY_IMPORT_URL_MAP = { |
| | | notShipped: "/sales/ledger/salesHistory/notShippingImport", |
| | | shipped: "/sales/ledger/salesHistory/shippingImport", |
| | | }; |
| | | const HISTORY_IMPORT_TEMPLATE_URL_MAP = { |
| | | notShipped: "/sales/ledger/salesHistory/notShippingImportTemplate", |
| | | shipped: "/sales/ledger/salesHistory/shippingImportTemplate", |
| | | }; |
| | | const HISTORY_IMPORT_TEMPLATE_FILE_NAME_MAP = { |
| | | notShipped: "销售发货历史数据导入模板-未发货.xlsx", |
| | | shipped: "销售发货历史数据导入模板-已发货.xlsx", |
| | | }; |
| | | const currentImportCommand = ref("default"); |
| | | |
| | | const changeDaterange = value => { |
| | | if (value) { |
| | |
| | | delete params.customerName; |
| | | } |
| | | } |
| | | const widthValue = |
| | | params.width != null ? String(params.width).trim() : ""; |
| | | if (widthValue) { |
| | | params.width = widthValue; |
| | | } else { |
| | | delete params.width; |
| | | } |
| | | const heightValue = |
| | | params.height != null ? String(params.height).trim() : ""; |
| | | if (heightValue) { |
| | | params.height = heightValue; |
| | | } else { |
| | | delete params.height; |
| | | } |
| | | const shouldAutoExpandBySize = Boolean(params.width || params.height); |
| | | delete params.customerId; |
| | | return ledgerListPage(params) |
| | | .then(res => { |
| | | .then(async res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.records; |
| | | tableData.value.map(item => { |
| | | item.children = []; |
| | | }); |
| | | if (shouldAutoExpandBySize && tableData.value.length > 0) { |
| | | const loadChildrenTasks = tableData.value.map(item => |
| | | productList({ salesLedgerId: item.id, type: 1 }) |
| | | .then(productRes => { |
| | | item.children = Array.isArray(productRes?.data) |
| | | ? productRes.data |
| | | : []; |
| | | }) |
| | | .catch(() => { |
| | | item.children = []; |
| | | }) |
| | | ); |
| | | await Promise.all(loadChildrenTasks); |
| | | expandedRowKeys.value = tableData.value.map(item => item.id); |
| | | } |
| | | total.value = res.total; |
| | | return res; |
| | | }) |
| | |
| | | ElMessage.warning("所选数据缺少id,无法入库"); |
| | | return; |
| | | } |
| | | if (Number(row.stockStatus) === 1) { |
| | | ElMessage.info("该台账已入库,无需重复操作"); |
| | | if (Number(row.stockStatus) === 2) { |
| | | ElMessage.info("该台账已全部入库,无需重复操作"); |
| | | return; |
| | | } |
| | | |
| | | currentStockLedgerId.value = id; |
| | | selectedStockProductIds.value = []; |
| | | stockProductList.value = []; |
| | | stockApproverNodes.value = [{ id: 1, userId: null }]; |
| | | nextStockApproverId = 2; |
| | | stockDialogVisible.value = true; |
| | | stockLoading.value = true; |
| | | |
| | | try { |
| | | await ElMessageBox.confirm("确认对所选台账执行入库?", "提示", { |
| | | const approverRes = await approveUserList({ approveType: 9 }); |
| | | stockApproverOptions.value = Array.isArray(approverRes?.data) |
| | | ? approverRes.data.map(item => ({ |
| | | userId: item.userId, |
| | | userName: item.userName, |
| | | })) |
| | | : []; |
| | | const res = await productList({ salesLedgerId: id, type: 1 }); |
| | | stockProductList.value = []; |
| | | stockProductList.value = |
| | | res.data.filter(item => item.productStockStatus == 0 || item.productStockStatus == 1) || []; |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("获取产品或审批人失败"); |
| | | } finally { |
| | | stockLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const submitStock = async () => { |
| | | const hasEmptyApprover = stockApproverNodes.value.some(node => !node.userId); |
| | | if (hasEmptyApprover) { |
| | | ElMessage.warning("请为所有审批节点选择审批人"); |
| | | return; |
| | | } |
| | | if (selectedStockProductIds.value.length === 0) { |
| | | ElMessage.warning("请选择至少一个产品进行入库"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm("确认对所选产品执行入库?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | |
| | | } catch { |
| | | return; |
| | | } |
| | | |
| | | proxy?.$modal?.loading?.("正在入库,请稍候..."); |
| | | try { |
| | | await salesStock({ id }); |
| | | const approveUserIds = stockApproverNodes.value.map(node => node.userId).join(","); |
| | | const approveUserName = stockApproverNodes.value |
| | | .map(node => stockApproverOptions.value.find(item => String(item.userId) === String(node.userId))?.userName) |
| | | .filter(Boolean) |
| | | .join(","); |
| | | await salesStock({ |
| | | salesLedgerId: currentStockLedgerId.value, |
| | | salesLedgerProducts: selectedStockProductIds.value, |
| | | approveUserIds, |
| | | approveUserName, |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("入库成功"); |
| | | stockDialogVisible.value = false; |
| | | await getList(); |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("入库失败,请稍后重试"); |
| | |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | if (cellValue === null || cellValue === undefined || cellValue === "") { |
| | | return ""; |
| | | } |
| | | const num = Number(cellValue); |
| | | return Number.isFinite(num) ? num.toFixed(2) : ""; |
| | | }; |
| | | |
| | | const scrollElementIntoVisibleArea = target => { |
| | | if (!target || !(target instanceof HTMLElement)) return; |
| | | let parent = target.parentElement; |
| | | while (parent && parent !== document.body) { |
| | | const style = window.getComputedStyle(parent); |
| | | const canScrollX = |
| | | (style.overflowX === "auto" || |
| | | style.overflowX === "scroll" || |
| | | style.overflowX === "overlay") && |
| | | parent.scrollWidth > parent.clientWidth; |
| | | const canScrollY = |
| | | (style.overflowY === "auto" || |
| | | style.overflowY === "scroll" || |
| | | style.overflowY === "overlay") && |
| | | parent.scrollHeight > parent.clientHeight; |
| | | |
| | | if (canScrollX || canScrollY) { |
| | | const parentRect = parent.getBoundingClientRect(); |
| | | const targetRect = target.getBoundingClientRect(); |
| | | if (canScrollX) { |
| | | const targetCenterX = targetRect.left + targetRect.width / 2; |
| | | const parentCenterX = parentRect.left + parentRect.width / 2; |
| | | const deltaX = targetCenterX - parentCenterX; |
| | | if (Math.abs(deltaX) > 2) { |
| | | parent.scrollLeft += deltaX; |
| | | } |
| | | } |
| | | |
| | | if (canScrollY) { |
| | | const targetCenterY = targetRect.top + targetRect.height / 2; |
| | | const parentCenterY = parentRect.top + parentRect.height / 2; |
| | | const deltaY = targetCenterY - parentCenterY; |
| | | if (Math.abs(deltaY) > 2) { |
| | | parent.scrollTop += deltaY; |
| | | } |
| | | } |
| | | } |
| | | |
| | | parent = parent.parentElement; |
| | | } |
| | | }; |
| | | |
| | | const handleTabScrollFollow = e => { |
| | | if (!e || e.key !== "Tab") return; |
| | | requestAnimationFrame(() => { |
| | | const active = document.activeElement; |
| | | if (active instanceof HTMLElement) { |
| | | scrollElementIntoVisibleArea(active); |
| | | } |
| | | }); |
| | | }; |
| | | // 获取tree子数据 |
| | | const getModels = value => { |
| | |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | "contractAmount", |
| | | "productTotalQuantity", |
| | | "productTotalArea", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | }; |
| | | // 导入 |
| | | const handleImport = () => { |
| | | importUpload.title = "导入销售台账"; |
| | | const openImportDialog = (title, url) => { |
| | | importUpload.title = title; |
| | | importUpload.url = import.meta.env.VITE_APP_BASE_API + url; |
| | | importUpload.open = true; |
| | | importUpload.isUploading = false; |
| | | if (importUploadRef.value) { |
| | | importUploadRef.value.clearFiles(); |
| | | } |
| | | }; |
| | | // 导入 |
| | | const handleImport = () => { |
| | | currentImportCommand.value = "default"; |
| | | openImportDialog("导入销售台账", "/sales/ledger/import"); |
| | | }; |
| | | // 历史迁移 |
| | | const handleHistoryImportCommand = command => { |
| | | const url = HISTORY_IMPORT_URL_MAP[command]; |
| | | if (!url) return; |
| | | currentImportCommand.value = command; |
| | | const title = command === "shipped" ? "历史迁移-已出库" : "历史迁移-未出库"; |
| | | openImportDialog(title, url); |
| | | }; |
| | | |
| | | // 下载导入模板 |
| | | const downloadTemplate = () => { |
| | | const command = currentImportCommand.value; |
| | | if (command && command !== "default") { |
| | | const templateUrl = HISTORY_IMPORT_TEMPLATE_URL_MAP[command]; |
| | | const fileName = HISTORY_IMPORT_TEMPLATE_FILE_NAME_MAP[command]; |
| | | if (templateUrl) { |
| | | proxy.download(templateUrl, {}, fileName || "销售发货历史数据导入模板.xlsx"); |
| | | return; |
| | | } |
| | | } |
| | | proxy.download("/sales/ledger/exportTemplate", {}, "销售台账导入模板.xlsx"); |
| | | }; |
| | | const onClose = () => { |
| | | importUpload.open = false; |
| | | if (importUploadRef.value) { |
| | | importUploadRef.value.clearFiles(); |
| | | } |
| | | }; |
| | | |
| | | // 提交导入文件 |
| | |
| | | try { |
| | | const res = await getSalesInvoices(selectedIds); |
| | | const salesInvoiceData = res?.data ?? {}; |
| | | printSalesDeliveryNote(salesInvoiceData, selectedRow); |
| | | await printSalesDeliveryNote(salesInvoiceData, selectedRow, selectedIds); |
| | | } catch (error) { |
| | | console.error("打印销售发货单失败:", error); |
| | | proxy.$modal.msgError("打印失败,请稍后重试"); |
| | |
| | | } else { |
| | | const res = await getProcessCard(selectedId); |
| | | const processCardData = res?.data ?? {}; |
| | | printFinishedProcessCard(processCardData); |
| | | // 补齐二维码所需的台账标识(后端数据有时不带 id) |
| | | if (processCardData && typeof processCardData === "object") { |
| | | processCardData.salesLedgerId = processCardData.salesLedgerId ?? selectedId; |
| | | processCardData.salesContractNo = |
| | | (processCardData.salesContractNo ?? "").trim() || |
| | | String(selectedRow?.salesContractNo ?? "").trim(); |
| | | } |
| | | const routeNodes = processCardData?.routeNodes; |
| | | const isProcessRouteEmpty = |
| | | !Array.isArray(routeNodes) || routeNodes.length === 0; |
| | | if (isProcessRouteEmpty) { |
| | | proxy.$modal.closeLoading(); |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | "当前订单未绑定工艺路线也没有设置默认的工艺路线,是否仍要打印?", |
| | | "提示", |
| | | { |
| | | confirmButtonText: "打印", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | } |
| | | ); |
| | | } catch { |
| | | return; |
| | | } |
| | | await printFinishedProcessCard(processCardData); |
| | | } else { |
| | | await printFinishedProcessCard(processCardData); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | console.error( |
| | |
| | | return statusStr === "待发货" || statusStr === "审核拒绝"; |
| | | }; |
| | | |
| | | const getLedgerDisplayName = ledger => |
| | | String(ledger?.salesContractNo || "").trim() || |
| | | String(ledger?.projectName || "").trim() || |
| | | `ID:${ledger?.id ?? "-"}`; |
| | | |
| | | const validateLedgersStockedBeforeDelivery = async ledgers => { |
| | | const invalidLedgers = []; |
| | | for (const ledger of ledgers || []) { |
| | | const ledgerId = ledger?.id; |
| | | const ledgerName = getLedgerDisplayName(ledger); |
| | | if (!ledgerId) { |
| | | invalidLedgers.push(`${ledgerName}(缺少台账ID)`); |
| | | continue; |
| | | } |
| | | let products = []; |
| | | try { |
| | | const res = await productList({ salesLedgerId: ledgerId, type: 1 }); |
| | | products = Array.isArray(res?.data) ? res.data : []; |
| | | } catch (e) { |
| | | invalidLedgers.push(`${ledgerName}(明细加载失败)`); |
| | | continue; |
| | | } |
| | | const unstockedProducts = products.filter( |
| | | item => Number(item?.productStockStatus) !== 2 |
| | | ); |
| | | if (unstockedProducts.length > 0) { |
| | | invalidLedgers.push( |
| | | `${ledgerName}(未全部入库${unstockedProducts.length}条)` |
| | | ); |
| | | } |
| | | } |
| | | return invalidLedgers; |
| | | }; |
| | | |
| | | const handleBulkDelivery = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | |
| | | } |
| | | |
| | | // 只允许【未发货/审批失败】进入发货流程 |
| | | const canDeliveryLedgers = selectedRows.value.filter(r => { |
| | | const status = Number(r.deliveryStatus); |
| | | return status === 1 || status === 3; |
| | | const statusItem = selectedRows.value[0].deliveryStatus; |
| | | let isTrue = true; |
| | | selectedRows.value.forEach(row => { |
| | | if (row.deliveryStatus != 1 && row.deliveryStatus != 3) { |
| | | proxy.$modal.msgWarning("仅未发货或审批失败的台账可以发货"); |
| | | isTrue = false; |
| | | return; |
| | | } |
| | | if (row.deliveryStatus !== statusItem) { |
| | | proxy.$modal.msgWarning("请选择相同状态的销售台账"); |
| | | isTrue = false; |
| | | return; |
| | | } |
| | | }); |
| | | if (canDeliveryLedgers.length === 0) { |
| | | proxy.$modal.msgWarning("仅未发货或审批失败的台账可以发货"); |
| | | if (!isTrue) { |
| | | return; |
| | | } |
| | | |
| | | proxy.$modal.loading("正在校验明细入库状态,请稍候..."); |
| | | const invalidLedgers = await validateLedgersStockedBeforeDelivery( |
| | | selectedRows.value |
| | | ); |
| | | proxy.$modal.closeLoading(); |
| | | if (invalidLedgers.length > 0) { |
| | | try { |
| | | await ElMessageBox.alert( |
| | | `以下销售台账存在未全部入库的明细,暂不可发货:\n${invalidLedgers.join( |
| | | "\n" |
| | | )}`, |
| | | "提示", |
| | | { |
| | | type: "warning", |
| | | confirmButtonText: "知道了", |
| | | } |
| | | ); |
| | | } catch { |
| | | /* 关闭弹窗 */ |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | }; |
| | | |
| | | // 打开发货弹框(单条) |
| | | const openDeliveryForm = row => { |
| | | const openDeliveryForm = async row => { |
| | | // 只允许【未发货/审批失败】发货;已发货/审批中不允许 |
| | | const status = Number(row.deliveryStatus); |
| | | if (status !== 1 && status !== 3) { |
| | | proxy.$modal.msgWarning("只有发货状态为未发货或审批失败的记录才可以发货"); |
| | | return; |
| | | } |
| | | |
| | | proxy.$modal.loading("正在校验明细入库状态,请稍候..."); |
| | | const invalidLedgers = await validateLedgersStockedBeforeDelivery([row]); |
| | | proxy.$modal.closeLoading(); |
| | | if (invalidLedgers.length > 0) { |
| | | try { |
| | | await ElMessageBox.alert( |
| | | `当前销售台账存在未全部入库的明细,暂不可发货:\n${invalidLedgers[0]}`, |
| | | "提示", |
| | | { |
| | | type: "warning", |
| | | confirmButtonText: "知道了", |
| | | } |
| | | ); |
| | | } catch { |
| | | /* 关闭弹窗 */ |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .ledger-qr-dialog { |
| | | text-align: center; |
| | | padding-bottom: 8px; |
| | | } |
| | | |
| | | .ledger-qr-composite-img { |
| | | max-width: 100%; |
| | | height: auto; |
| | | display: block; |
| | | margin: 0 auto 28px; |
| | | } |
| | | |
| | | .ledger-qr-save-btn { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .approver-nodes-container { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | padding: 16px; |
| | | background-color: #f8f9fa; |
| | | border-radius: 4px; |
| | | border: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | .approver-node-item { |
| | | flex: 0 0 calc(33.333% - 12px); |
| | | min-width: 200px; |
| | | padding: 12px; |
| | | background-color: #fff; |
| | | border-radius: 4px; |
| | | border: 1px solid #dcdfe6; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .approver-node-item:hover { |
| | | border-color: #409eff; |
| | | box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1); |
| | | } |
| | | |
| | | .approver-node-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .approver-node-label { |
| | | font-size: 13px; |
| | | font-weight: 500; |
| | | color: #606266; |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | | .approver-node-item { |
| | | flex: 0 0 calc(50% - 8px); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 768px) { |
| | | .approver-node-item { |
| | | flex: 0 0 100%; |
| | | } |
| | | } |
| | | </style> |