deploy.bat
@@ -3,12 +3,12 @@ setlocal enabledelayedexpansion :: ================= Config ================= set "USER=root" set "PASS=Xdrjkj@10086#" set "IP=36.213.180.217 set "PORT=65022" set "USER=dell" set "PASS=qweasdzxc2580@.." set "IP=114.132.189.42 set "PORT=9100" set "DIST_DIR=dist" set "REMOTE_DIR=/data/project/pim/vue/xjgm" set "REMOTE_DIR=/home/vue/tjbm-product" set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10" :: =========================================== src/views/basicData/tankInfo/index.vue
@@ -85,7 +85,7 @@ <el-table-column label="罐容" prop="capacity" min-width="120" /> <el-table-column label="库存余量(m³)" <el-table-column label="库存余量" prop="remaining" min-width="140" /> <el-table-column label="是否启用" @@ -205,7 +205,7 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="库存余量(m³):" <el-form-item label="库存余量:" prop="remaining"> <el-input-number v-model="form.remaining" :precision="2" @@ -247,9 +247,16 @@ trigger: 'blur', } ]"> <el-input v-model="form.tenantInfo" placeholder="请输入租客信息" clearable /> <el-select v-model="form.tenantInfo" placeholder="请选择租客" clearable filterable style="width: 100%"> <el-option v-for="item in tenantSelectOptions" :key="item.id" :label="item.customerName" :value="item.customerName" /> </el-select> </el-form-item> </el-col> </el-row> @@ -261,7 +268,7 @@ <script setup> import FormDialog from "@/components/Dialog/FormDialog.vue"; import pagination from "@/components/PIMTable/Pagination.vue"; import { ref, reactive, toRefs, watch, getCurrentInstance } from "vue"; import { ref, reactive, toRefs, watch, computed, getCurrentInstance } from "vue"; import { Search } from "@element-plus/icons-vue"; import { ElMessageBox } from "element-plus"; import { @@ -270,8 +277,10 @@ updateTankInfo, delTankInfo, } from "@/api/basicData/tankInfo.js"; import { listCustomer } from "@/api/basicData/customer.js"; const { proxy } = getCurrentInstance(); const { customer_type } = proxy.useDict("customer_type"); const tableData = ref([]); const selectedRows = ref([]); const tableLoading = ref(false); @@ -311,6 +320,33 @@ // 用途文案:1 租赁 / 2 自用 const getUsageTypeText = value => ({ 1: "租赁", 2: "自用" })[value] || "--"; // 租客只能从「代储客户」分类里选;字典的 value 未必就是中文标签,按 label 反查真实取值 const tenantOptions = ref([]); let loadedTenantType = null; const loadTenantOptions = () => { const hit = (customer_type.value || []).find( item => item.label === "代储客户" ); const customerType = hit ? hit.value : "代储客户"; if (customerType === loadedTenantType) return; loadedTenantType = customerType; listCustomer({ current: -1, size: -1, customerType }).then(res => { tenantOptions.value = res.data.records || []; }); }; // 字典异步到达后再拉一次;字典命中缓存时 immediate 这一次就够了 watch(customer_type, loadTenantOptions, { immediate: true }); // 历史数据是手工填的租客名,未必还在代储客户列表里,补一个临时选项保证回显 const tenantSelectOptions = computed(() => { const list = tenantOptions.value; const current = form.value.tenantInfo; if (current && !list.some(item => item.customerName === current)) { return [...list, { id: `legacy-${current}`, customerName: current }]; } return list; }); // 未启用时用途与租客信息一律清空;选自用时租客信息自动清空(租赁才需要租客) watch( () => [form.value.isEnabled, form.value.usageType], src/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue
@@ -10,13 +10,14 @@ <div class="detail-content"> <div class="detail-table-wrapper"> <el-tabs v-model="detailTab" class="detail-tabs"> class="detail-tabs" @tab-change="handleTabChange"> <el-tab-pane label="库存信息" name="main"> <el-table :data="tableData" :data="inventoryData" border v-loading="tableLoading" v-loading="inventoryLoading" style="width: 100%" height="100%" > @@ -92,9 +93,9 @@ <el-tab-pane label="入库信息" name="in"> <el-table :data="tableData" :data="inboundData" border v-loading="tableLoading" v-loading="inboundLoading" style="width: 100%" height="100%" > @@ -102,6 +103,8 @@ align="center" :header-cell-style="groupHeaderStyle"> <el-table-column label="油品" prop="productName" min-width="90" show-overflow-tooltip /> <el-table-column label="批号" prop="batchNo" min-width="150" show-overflow-tooltip /> <el-table-column label="入库数量(吨)" prop="stockInNum" min-width="110" show-overflow-tooltip /> <el-table-column label="入库名称" prop="oilDepotName" min-width="130" show-overflow-tooltip /> <el-table-column label="入库类别" prop="inboundCategory" min-width="100" show-overflow-tooltip /> <el-table-column label="储罐号" prop="tankNo" min-width="100" show-overflow-tooltip /> @@ -168,9 +171,9 @@ <el-tab-pane label="出库信息" name="out"> <el-table :data="tableData" :data="outboundData" border v-loading="tableLoading" v-loading="outboundLoading" style="width: 100%" height="100%" > @@ -178,21 +181,22 @@ align="center" :header-cell-style="groupHeaderStyle"> <el-table-column label="出库类别" prop="outCategory" min-width="90" show-overflow-tooltip /> <el-table-column label="储库名称" prop="outOilDepotName" min-width="130" show-overflow-tooltip /> <el-table-column label="储罐号" prop="outTankNo" min-width="100" show-overflow-tooltip /> <el-table-column label="批号" prop="batchNo" min-width="150" show-overflow-tooltip /> <el-table-column label="储库名称" prop="oilDepotName" min-width="130" show-overflow-tooltip /> <el-table-column label="储罐号" prop="tankNo" min-width="100" show-overflow-tooltip /> <el-table-column label="出库日期" prop="outDate" min-width="110" show-overflow-tooltip /> <el-table-column label="油品" prop="productName" min-width="90" show-overflow-tooltip /> <el-table-column label="出库吨位(吨)" prop="outTonnage" min-width="110" show-overflow-tooltip /> <el-table-column label="客户名称" prop="outCustomerName" min-width="120" show-overflow-tooltip /> <el-table-column label="客户对接人" prop="outContactPerson" min-width="100" show-overflow-tooltip /> <el-table-column label="对接人电话" prop="outContactPhone" min-width="120" show-overflow-tooltip /> <el-table-column label="客户名称" prop="customerName" min-width="120" show-overflow-tooltip /> <el-table-column label="客户对接人" prop="contactPerson" min-width="100" show-overflow-tooltip /> <el-table-column label="对接人电话" prop="contactPhone" min-width="120" show-overflow-tooltip /> </el-table-column> <el-table-column label="装油操作信息" align="center" :header-cell-style="groupHeaderStyle"> <el-table-column label="装油检验" prop="loadingInspectionStatus" min-width="90" show-overflow-tooltip /> <el-table-column label="装车鹤位" prop="loadingCranePosition" min-width="100" show-overflow-tooltip /> <el-table-column label="密度" prop="outDensity" min-width="80" show-overflow-tooltip /> <el-table-column label="密度" prop="density" min-width="80" show-overflow-tooltip /> <el-table-column label="装油拍照" prop="loadingPhotos" align="center" min-width="90"> <template #default="scope"> <el-image @@ -210,17 +214,17 @@ <el-table-column label="出库过磅数据" align="center" :header-cell-style="groupHeaderStyle"> <el-table-column label="毛重(吨)" prop="outGrossWeight" min-width="90" show-overflow-tooltip /> <el-table-column label="皮重(吨)" prop="outTareWeight" min-width="90" show-overflow-tooltip /> <el-table-column label="净重(吨)" prop="outNetWeight" min-width="90" show-overflow-tooltip /> <el-table-column label="亏量(吨)" prop="outLossQuantity" min-width="90" show-overflow-tooltip /> <el-table-column label="实际入库体积(升)" prop="outActualVolume" min-width="130" show-overflow-tooltip /> <el-table-column label="磅单" prop="outPoundPhotos" align="center" min-width="90"> <el-table-column label="毛重(吨)" prop="grossWeight" min-width="90" show-overflow-tooltip /> <el-table-column label="皮重(吨)" prop="tareWeight" min-width="90" show-overflow-tooltip /> <el-table-column label="净重(吨)" prop="netWeight" min-width="90" show-overflow-tooltip /> <el-table-column label="亏量(吨)" prop="lossQuantity" min-width="90" show-overflow-tooltip /> <el-table-column label="实际出库体积(升)" prop="actualVolume" min-width="130" show-overflow-tooltip /> <el-table-column label="磅单" prop="poundPhotos" align="center" min-width="90"> <template #default="scope"> <el-image v-if="toPhotoArray(scope.row.outPoundPhotos).length" :src="getPhotoUrl(toPhotoArray(scope.row.outPoundPhotos)[0])" :preview-src-list="getPhotoUrls(scope.row.outPoundPhotos)" v-if="toPhotoArray(scope.row.poundPhotos).length" :src="getPhotoUrl(toPhotoArray(scope.row.poundPhotos)[0])" :preview-src-list="getPhotoUrls(scope.row.poundPhotos)" preview-teleported fit="cover" style="width: 40px; height: 40px; border-radius: 4px" @@ -232,24 +236,43 @@ <el-table-column label="出库物流信息" align="center" :header-cell-style="groupHeaderStyle"> <el-table-column label="司机姓名" prop="outDriverName" min-width="100" show-overflow-tooltip /> <el-table-column label="司机电话" prop="outDriverPhone" min-width="120" show-overflow-tooltip /> <el-table-column label="押运员姓名" prop="outEscortName" min-width="100" show-overflow-tooltip /> <el-table-column label="押运员电话" prop="outEscortPhone" min-width="120" show-overflow-tooltip /> <el-table-column label="车牌号" prop="outTruckPlateNo" min-width="110" show-overflow-tooltip /> <el-table-column label="车挂牌号" prop="outTrailerPlateNo" min-width="110" show-overflow-tooltip /> <el-table-column label="司机姓名" prop="driverName" min-width="100" show-overflow-tooltip /> <el-table-column label="司机电话" prop="driverPhone" min-width="120" show-overflow-tooltip /> <el-table-column label="押运员姓名" prop="escortName" min-width="100" show-overflow-tooltip /> <el-table-column label="押运员电话" prop="escortPhone" min-width="120" show-overflow-tooltip /> <el-table-column label="车牌号" prop="truckPlateNo" min-width="110" show-overflow-tooltip /> <el-table-column label="车挂牌号" prop="trailerPlateNo" min-width="110" show-overflow-tooltip /> </el-table-column> </el-table> </el-tab-pane> </el-tabs> </div> <pagination v-show="total > 0" :total="total" v-if="detailTab === 'main'" v-show="inventoryTotal > 0" :total="inventoryTotal" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" :page="inventoryPage.current" :limit="inventoryPage.size" @pagination="handleInventoryPageChange" /> <pagination v-if="detailTab === 'in'" v-show="inboundTotal > 0" :total="inboundTotal" layout="total, sizes, prev, pager, next, jumper" :page="inboundPage.current" :limit="inboundPage.size" @pagination="handleInboundPageChange" /> <pagination v-if="detailTab === 'out'" v-show="outboundTotal > 0" :total="outboundTotal" layout="total, sizes, prev, pager, next, jumper" :page="outboundPage.current" :limit="outboundPage.size" @pagination="handleOutboundPageChange" /> </div> </el-dialog> @@ -259,6 +282,8 @@ import pagination from "@/components/PIMTable/Pagination.vue"; import { computed, reactive, ref, watch } from "vue"; import { getStockInventoryBatchNoQty } from "@/api/inventoryManagement/stockInventory.js"; import { getStockInRecordListPage } from "@/api/inventoryManagement/stockInRecord.js"; import { getStockOutPage } from "@/api/inventoryManagement/stockOut.js"; const props = defineProps({ visible: { @@ -282,36 +307,112 @@ }, }); const tableData = ref([]); const tableLoading = ref(false); const total = ref(0); // 三个页签各自独立查询、各自分页:库存信息走批号明细,入库/出库走各自的记录列表 const detailTab = ref("main"); const page = reactive({ const inventoryData = ref([]); const inventoryLoading = ref(false); const inventoryTotal = ref(0); const inventoryPage = reactive({ current: 1, size: 20, }); const inboundData = ref([]); const inboundLoading = ref(false); const inboundTotal = ref(0); const inboundPage = reactive({ current: 1, size: 20, }); const inboundLoaded = ref(false); const outboundData = ref([]); const outboundLoading = ref(false); const outboundTotal = ref(0); const outboundPage = reactive({ current: 1, size: 20, }); const outboundLoaded = ref(false); const getList = () => { if (!props.record?.productId || !props.record?.productModelId) { tableData.value = []; total.value = 0; inventoryData.value = []; inventoryTotal.value = 0; return; } tableLoading.value = true; inventoryLoading.value = true; getStockInventoryBatchNoQty({ current: page.current, size: page.size, current: inventoryPage.current, size: inventoryPage.size, productId: props.record.productId, productModelId: props.record.productModelId, }) .then((res) => { tableData.value = res.data?.records || []; total.value = res.data?.total || 0; inventoryData.value = res.data?.records || []; inventoryTotal.value = res.data?.total || 0; }) .finally(() => { tableLoading.value = false; inventoryLoading.value = false; }); }; const getInboundList = () => { if (!props.record?.productModelId) { inboundData.value = []; inboundTotal.value = 0; return; } inboundLoading.value = true; getStockInRecordListPage({ current: inboundPage.current, size: inboundPage.size, productModelId: props.record.productModelId, }) .then((res) => { inboundData.value = res.data?.records || []; inboundTotal.value = res.data?.total || 0; inboundLoaded.value = true; }) .finally(() => { inboundLoading.value = false; }); }; const getOutboundList = () => { if (!props.record?.productModelId) { outboundData.value = []; outboundTotal.value = 0; return; } outboundLoading.value = true; getStockOutPage({ current: outboundPage.current, size: outboundPage.size, productModelId: props.record.productModelId, }) .then((res) => { outboundData.value = res.data?.records || []; outboundTotal.value = res.data?.total || 0; outboundLoaded.value = true; }) .finally(() => { outboundLoading.value = false; }); }; // 点哪个页签查哪个:入库/出库首次进入时才请求,避免打开弹窗就发三个请求 const handleTabChange = (name) => { if (name === "in" && !inboundLoaded.value) { getInboundList(); } if (name === "out" && !outboundLoaded.value) { getOutboundList(); } }; // 图片列:后端返回为 JSON 字符串,兼容数组/字符串两种形态 @@ -349,10 +450,22 @@ fontSize: "13px", }; const paginationChange = (obj) => { page.current = obj.page; page.size = obj.limit; const handleInventoryPageChange = (obj) => { inventoryPage.current = obj.page; inventoryPage.size = obj.limit; getList(); }; const handleInboundPageChange = (obj) => { inboundPage.current = obj.page; inboundPage.size = obj.limit; getInboundList(); }; const handleOutboundPageChange = (obj) => { outboundPage.current = obj.page; outboundPage.size = obj.limit; getOutboundList(); }; const handleSubtract = (row) => { @@ -369,10 +482,24 @@ const closeModal = () => { isShow.value = false; page.current = 1; page.size = 20; tableData.value = []; total.value = 0; detailTab.value = "main"; inventoryPage.current = 1; inventoryPage.size = 20; inventoryData.value = []; inventoryTotal.value = 0; inboundPage.current = 1; inboundPage.size = 20; inboundData.value = []; inboundTotal.value = 0; inboundLoaded.value = false; outboundPage.current = 1; outboundPage.size = 20; outboundData.value = []; outboundTotal.value = 0; outboundLoaded.value = false; }; watch( @@ -381,7 +508,12 @@ if (!visible) { return; } page.current = 1; detailTab.value = "main"; inventoryPage.current = 1; inboundPage.current = 1; outboundPage.current = 1; inboundLoaded.value = false; outboundLoaded.value = false; getList(); }, { immediate: true } src/views/inventoryManagement/stockManagement/OilStockInDialog.vue
@@ -10,15 +10,16 @@ ref="formRef"> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="产品大类" <el-form-item label="产品" prop="productId" :rules="[ { required: true, message: '请选择产品大类', message: '请选择产品', trigger: 'change', } ]"> <!-- 油品由「产品 + 规格型号」两级选出,不单独维护 --> <el-tree-select v-model="formState.productId" placeholder="请选择" clearable @@ -206,10 +207,11 @@ <el-col :span="12"> <el-form-item label="油品" prop="oilProduct" :rules="[{ required: true, message: '请选择油品', trigger: 'change' }]"> <el-input :model-value="formState.oilProduct" readonly placeholder="--" /> :rules="[{ required: true, message: '请输入油品', trigger: 'change' }]"> <!-- 选完「产品 + 规格型号」自动写入,也能自己改 --> <el-input v-model="formState.oilProduct" placeholder="选完产品与规格型号后自动生成" @input="handleOilProductInput" /> </el-form-item> </el-col> </el-row> @@ -315,7 +317,6 @@ prop="trailerPlateNo" :rules="[{ required: true, message: '请输入车挂牌号', trigger: 'blur' }]"> <el-input v-model="formState.trailerPlateNo" :readonly="!!formState.vehicleId" placeholder="请输入" clearable /> </el-form-item> @@ -407,7 +408,8 @@ prop="unloadPhotos" :rules="[{ required: true, message: '请上传卸油关键环节与车辆关键位置拍照', trigger: 'change' }]"> <ImageUpload v-model:file-list="formState.unloadPhotos" :limit="10" /> :limit="10" permanent /> </el-form-item> </el-col> </el-row> @@ -495,7 +497,8 @@ prop="poundPhotos" :rules="[{ required: true, message: '请上传磅单', trigger: 'change' }]"> <ImageUpload v-model:file-list="formState.poundPhotos" :limit="10" /> :limit="10" permanent /> </el-form-item> </el-col> </el-row> @@ -679,6 +682,26 @@ const productOptions = ref([]); const modelOptions = ref([]); // 油品默认由「产品 + 规格型号」两级拼出来(两级都选完才有值)。 // autoOilProduct 记住自动写进去的值,用来判断用户有没有手改过——手改过就不再覆盖 const autoOilProduct = ref(""); const oilProductLabel = (productName, model) => [productName, model].filter(Boolean).join(" / "); const syncOilProduct = next => { const current = formState.value.oilProduct; if (!current || current === autoOilProduct.value) { formState.value.oilProduct = next || undefined; } autoOilProduct.value = next || ""; }; // 手改过之后 autoOilProduct 就对不上了,后面的产品/规格联动不再覆盖它 const handleOilProductInput = () => { autoOilProduct.value = ""; }; let { proxy } = getCurrentInstance(); const closeModal = () => { @@ -708,19 +731,17 @@ } }; // 车辆选择改变:车牌号、车挂牌号与司机信息由车辆主数据带出,司机可手动覆盖 // 车辆选择改变:车牌号与司机信息由车辆主数据带出,司机可手动覆盖。 // 车挂牌号是挂车自己的牌、不在车辆主数据里,始终手填,不参与联动。 const vehicleChange = id => { const vehicle = vehicleList.value.find(item => item.id === id); if (vehicle) { formState.value.truckPlateNo = vehicle.plateNumber || ""; // 车挂牌号与车牌号是同一个牌,选了车辆就带出并置为只读,避免手填出两个不一致的值 formState.value.trailerPlateNo = vehicle.plateNumber || ""; formState.value.driverName = vehicle.driverName || ""; formState.value.driverPhone = vehicle.driverPhone || ""; } else { if (!vehicle) { formState.value.truckPlateNo = ""; formState.value.trailerPlateNo = ""; return; } formState.value.truckPlateNo = vehicle.plateNumber || ""; formState.value.driverName = vehicle.driverName || ""; formState.value.driverPhone = vehicle.driverPhone || ""; }; const getProductOptions = () => @@ -746,7 +767,8 @@ formState.value.productName = ""; formState.value.productModelName = ""; formState.value.unit = ""; formState.value.oilProduct = undefined; // 油品是产品 + 规格拼出来的,换了产品就一起作废,手改过的不动 syncOilProduct(""); modelOptions.value = []; if (!value) { return; @@ -756,14 +778,14 @@ }); }; // 规格型号改变:产品名称/规格/单位由规格主数据带出,油品取产品名称 // 规格型号改变:产品名称/规格/单位由规格主数据带出 const handleProductModelChange = value => { const item = modelOptions.value.find(row => row.id === value); formState.value.productName = item?.productName || ""; formState.value.productModelName = item?.model || ""; formState.value.unit = item?.unit || ""; // 本租户「油品」就是产品名称(库存明细/出库台账的油品列绑的都是 productName) formState.value.oilProduct = item?.productName || undefined; // 本租户「油品」按「产品名称 / 规格型号」展示,库存明细、出库台账的油品列取的就是它 syncOilProduct(oilProductLabel(item?.productName, item?.model)); proxy.$refs["formRef"]?.validateField("productModelId"); }; src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue
@@ -8,155 +8,6 @@ :model="formState" label-position="top" ref="formRef"> <div class="form-section-title">出库信息</div> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="出库类别" prop="outCategory" :rules="[{ required: true, message: '请选择出库类别', trigger: 'change' }]"> <el-select v-model="formState.outCategory" placeholder="请选择" clearable :disabled="hasBoundLedger" style="width: 100%"> <el-option label="客存" value="客存" /> <el-option label="代储" value="代储" /> <el-option label="销售" value="销售" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="储库名称" prop="oilDepotId" :rules="[{ required: true, message: '请选择储库名称', trigger: 'change' }]"> <el-select v-model="formState.oilDepotId" placeholder="请选择" clearable :disabled="isStorageBound" popper-class="oil-depot-select-popper" style="width: 100%" @change="depotChange"> <el-option v-for="item in oilDepotList" :key="item.id" :label="`${item.depotName}-${item.company || ''}`" :value="item.id"> <div class="oil-depot-option"> <div class="oil-depot-option-title">{{ item.depotName }}-{{ item.company }}</div> <div class="oil-depot-option-line">负责人:{{ item.chargePerson }}</div> <div class="oil-depot-option-line">联系电话:{{ item.phone }}</div> <div class="oil-depot-option-line">启用日期:{{ item.enableDate }}</div> </div> </el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="储罐号" prop="tankId" :rules="[{ required: true, message: '请选择储罐号', trigger: 'change' }]"> <el-select v-model="formState.tankId" placeholder="请选择" clearable :disabled="isStorageBound" popper-class="tank-select-popper" style="width: 100%" @change="tankChange"> <el-option v-for="item in tankList" :key="item.id" :label="item.tankNo" :value="item.id"> <div class="tank-option"> <div class="tank-option-title">{{ item.tankNo }}</div> <div class="tank-option-line">大类:{{ item.category }}</div> <div class="tank-option-line">油品:{{ item.oilProduct }}</div> <div class="tank-option-line">罐容:{{ item.capacity }}</div> <div class="tank-option-line">库存余量(m³):{{ item.remaining }}</div> <div class="tank-option-line">租客信息:{{ item.tenantInfo || '--' }}</div> </div> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="出库日期" prop="outDate" :rules="[{ required: true, message: '请选择出库日期', trigger: 'change' }]"> <el-date-picker v-model="formState.outDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="请选择" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="油品" prop="oilProduct" :rules="isStorageBound ? [] : [{ required: true, message: '请选择油品', trigger: 'change' }]"> <el-input :model-value="formState.oilProduct" readonly placeholder="--" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="客户名称" prop="customerId" :rules="[{ required: true, message: '请选择客户名称', trigger: 'change' }]"> <el-select v-model="formState.customerId" placeholder="请选择" clearable filterable :disabled="hasBoundLedger" popper-class="customer-select-popper" style="width: 100%" @change="customerChange"> <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id"> <div class="customer-option"> <div class="customer-option-title">{{ item.customerName }}</div> <div class="customer-option-line">银行基本户:{{ item.basicBankAccount }}</div> <div class="customer-option-line">银行账号:{{ item.bankAccount }}</div> <div class="customer-option-line">地址及联系方式:{{ item.addressPhone }}</div> </div> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="客户对接人" prop="contactPerson" :rules="[{ required: true, message: '请输入客户对接人', trigger: 'blur' }]"> <el-input v-model="formState.contactPerson" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="对接人电话" prop="contactPhone" :rules="[ { required: true, message: '请输入对接人电话', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的11位手机号码', trigger: 'blur' } ]"> <el-input v-model="formState.contactPhone" placeholder="请输入11位数字" maxlength="11" @input="phoneInput('contactPhone')" clearable /> </el-form-item> </el-col> </el-row> <!-- 一次出库可分摊给多个客户:各台账明细都要含本次出库规格,分摊数量之和 = 出库吨位 --> <div class="form-section-title">销售台账绑定</div> <el-row v-for="(ledger, index) in ledgerRows" @@ -299,7 +150,6 @@ </el-form-item> </el-col> </el-row> <!-- 批次候选是按产品规格去库存里捞的,所以放在规格后面,选完规格再选批次 --> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="单位" @@ -308,10 +158,110 @@ disabled /> </el-form-item> </el-col> </el-row> <div class="form-section-title">出库信息</div> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="出库类别" prop="outCategory" :rules="[{ required: true, message: '请选择出库类别', trigger: 'change' }]"> <el-select v-model="formState.outCategory" placeholder="请选择" clearable :disabled="hasBoundLedger" style="width: 100%"> <el-option label="客存" value="客存" /> <el-option label="代储" value="代储" /> <el-option label="销售" value="销售" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="储库名称" prop="oilDepotId" :rules="[{ required: true, message: '请选择储库名称', trigger: 'change' }]"> <el-select v-model="formState.oilDepotId" placeholder="请选择" clearable :disabled="isStorageBound" popper-class="oil-depot-select-popper" style="width: 100%" @change="depotChange"> <el-option v-for="item in oilDepotList" :key="item.id" :label="`${item.depotName}-${item.company || ''}`" :value="item.id"> <div class="oil-depot-option"> <div class="oil-depot-option-title">{{ item.depotName }}-{{ item.company }}</div> <div class="oil-depot-option-line">负责人:{{ item.chargePerson }}</div> <div class="oil-depot-option-line">联系电话:{{ item.phone }}</div> <div class="oil-depot-option-line">启用日期:{{ item.enableDate }}</div> </div> </el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="储罐号" prop="tankId" :rules="[{ required: true, message: '请选择储罐号', trigger: 'change' }]"> <el-select v-model="formState.tankId" placeholder="请选择" clearable :disabled="isStorageBound" popper-class="tank-select-popper" style="width: 100%" @change="tankChange"> <el-option v-for="item in tankList" :key="item.id" :label="item.tankNo" :value="item.id"> <div class="tank-option"> <div class="tank-option-title">{{ item.tankNo }}</div> <div class="tank-option-line">大类:{{ item.category }}</div> <div class="tank-option-line">油品:{{ item.oilProduct }}</div> <div class="tank-option-line">罐容:{{ item.capacity }}</div> <div class="tank-option-line">库存余量(m³):{{ item.remaining }}</div> <div class="tank-option-line">租客信息:{{ item.tenantInfo || '--' }}</div> </div> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="出库日期" prop="outDate" :rules="[{ required: true, message: '请选择出库日期', trigger: 'change' }]"> <el-date-picker v-model="formState.outDate" type="date" value-format="YYYY-MM-DD" format="YYYY-MM-DD" placeholder="请选择" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="油品" prop="oilProduct" :rules="isStorageBound ? [] : [{ required: true, message: '请输入油品', trigger: 'change' }]"> <!-- 选完「产品 + 规格型号」自动写入,也能自己改 --> <el-input v-model="formState.oilProduct" placeholder="选完产品与规格型号后自动生成" @input="handleOilProductInput" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="出库批次" prop="batchNo" :rules="[{ required: true, message: '油品出库必须选择出库批次', trigger: 'change' }]"> <!-- 批次候选是按产品规格去库存里捞的,选完上面的规格这里才有得选 --> <el-select v-model="formState.batchNo" placeholder="请选择" clearable @@ -322,6 +272,57 @@ :label="item" :value="item" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="8"> <el-form-item label="客户名称" prop="customerId" :rules="[{ required: true, message: '请选择客户名称', trigger: 'change' }]"> <el-select v-model="formState.customerId" placeholder="请选择" clearable filterable :disabled="hasBoundLedger" popper-class="customer-select-popper" style="width: 100%" @change="customerChange"> <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id"> <div class="customer-option"> <div class="customer-option-title">{{ item.customerName }}</div> <div class="customer-option-line">银行基本户:{{ item.basicBankAccount }}</div> <div class="customer-option-line">银行账号:{{ item.bankAccount }}</div> <div class="customer-option-line">地址及联系方式:{{ item.addressPhone }}</div> </div> </el-option> </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="客户对接人" prop="contactPerson" :rules="[{ required: true, message: '请输入客户对接人', trigger: 'blur' }]"> <el-input v-model="formState.contactPerson" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="对接人电话" prop="contactPhone" :rules="[ { required: true, message: '请输入对接人电话', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入有效的11位手机号码', trigger: 'blur' } ]"> <el-input v-model="formState.contactPhone" placeholder="请输入11位数字" maxlength="11" @input="phoneInput('contactPhone')" clearable /> </el-form-item> </el-col> </el-row> @@ -361,7 +362,8 @@ prop="loadingPhotos" :rules="[{ required: true, message: '请上传装油关键环节与车辆关键位置拍照', trigger: 'change' }]"> <ImageUpload v-model:file-list="formState.loadingPhotos" :limit="10" /> :limit="10" permanent /> </el-form-item> </el-col> </el-row> @@ -444,7 +446,8 @@ prop="poundPhotos" :rules="[{ required: true, message: '请上传磅单', trigger: 'change' }]"> <ImageUpload v-model:file-list="formState.poundPhotos" :limit="10" /> :limit="10" permanent /> </el-form-item> </el-col> </el-row> @@ -484,7 +487,6 @@ ? [{ required: true, message: '请输入车挂牌号', trigger: 'blur' }] : []"> <el-input v-model="formState.trailerPlateNo" :readonly="!!formState.vehicleId" placeholder="请输入" clearable /> </el-form-item> @@ -645,6 +647,27 @@ // 未绑定卖油台账时走「产品大类树 + 规格型号」,与销售台账选产品的方式一致 const productOptions = ref([]); const modelOptions = ref([]); // 油品默认由「产品 + 规格型号」两级拼出来(两级都选完才有值)。 // autoOilProduct 记住自动写进去的值,用来判断用户有没有手改过——手改过就不再覆盖 const autoOilProduct = ref(""); const oilProductLabel = (productName, model) => [productName, model].filter(Boolean).join(" / "); const syncOilProduct = next => { const current = formState.value.oilProduct; if (!current || current === autoOilProduct.value) { formState.value.oilProduct = next || undefined; } autoOilProduct.value = next || ""; }; // 手改过之后 autoOilProduct 就对不上了,后面的产品/规格联动不再覆盖它 const handleOilProductInput = () => { autoOilProduct.value = ""; }; // 出库批次候选取该规格的入库批次 const batchNoOptions = ref([]); // 销售类出库的可用量提示(上次剩余 + 当前库存) @@ -888,19 +911,17 @@ .catch(() => {}); }; // 车辆选择改变:车牌号、车挂牌号与司机信息由车辆主数据带出,出库时司机可手动覆盖 // 车辆选择改变:车牌号与司机信息由车辆主数据带出,出库时司机可手动覆盖。 // 车挂牌号是挂车自己的牌、不在车辆主数据里,始终手填,不参与联动。 const vehicleChange = id => { const vehicle = vehicleList.value.find(item => item.id === id); if (vehicle) { formState.value.truckPlateNo = vehicle.plateNumber || ""; // 车挂牌号与车牌号是同一个牌,选了车辆就带出并置为只读,避免手填出两个不一致的值 formState.value.trailerPlateNo = vehicle.plateNumber || ""; formState.value.driverName = vehicle.driverName || ""; formState.value.driverPhone = vehicle.driverPhone || ""; } else { if (!vehicle) { formState.value.truckPlateNo = ""; formState.value.trailerPlateNo = ""; return; } formState.value.truckPlateNo = vehicle.plateNumber || ""; formState.value.driverName = vehicle.driverName || ""; formState.value.driverPhone = vehicle.driverPhone || ""; }; const salesLedgerLabel = item => @@ -1104,7 +1125,7 @@ }; const ledgerProductLabel = item => [item.productName, item.model].filter(Boolean).join(" / "); oilProductLabel(item.productName, item.model); const clearProduct = () => { formState.value.productId = undefined; @@ -1112,7 +1133,8 @@ formState.value.productName = ""; formState.value.model = ""; formState.value.unit = ""; formState.value.oilProduct = undefined; // 油品是产品 + 规格拼出来的,产品一清空它也跟着清空,手改过的不动 syncOilProduct(""); formState.value.batchNo = undefined; batchNoOptions.value = []; }; @@ -1129,7 +1151,7 @@ formState.value.productName = item.productName; formState.value.model = item.model; formState.value.unit = item.unit; formState.value.oilProduct = item.productName || item.model || undefined; syncOilProduct(oilProductLabel(item.productName, item.model)); formState.value.batchNo = undefined; fetchBatchNoOptions(); proxy.$refs["formRef"]?.validateField("productModelId"); @@ -1148,13 +1170,14 @@ }); }; // 规格型号改变:产品名称/规格/单位由规格主数据带出,油品取产品名称 // 规格型号改变:产品名称/规格/单位由规格主数据带出 const handleProductModelChange = value => { const item = modelOptions.value.find(row => row.id === value); formState.value.productName = item?.productName || ""; formState.value.model = item?.model || ""; formState.value.unit = item?.unit || ""; formState.value.oilProduct = item?.productName || undefined; // 本租户「油品」按「产品名称 / 规格型号」展示,库存明细、出库台账的油品列取的就是它 syncOilProduct(oilProductLabel(item?.productName, item?.model)); formState.value.batchNo = undefined; fetchBatchNoOptions(); proxy.$refs["formRef"]?.validateField("productModelId"); src/views/procurementManagement/procurementLedger/index.vue
@@ -99,20 +99,8 @@ prop="specificationModel" /> <el-table-column label="单位" prop="unit" /> <el-table-column label="入库审核状态" prop="stockInApprovalStatus" width="120"> <template #default="scope"> <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)" size="small"> {{ scope.row.stockInApprovalStatus || '--' }} </el-tag> </template> </el-table-column> <el-table-column label="数量" prop="quantity" /> <el-table-column label="销售发货数量" prop="shippedQuantity" /> <el-table-column label="可用数量" prop="availableQuality" /> <el-table-column label="退货数量" @@ -191,17 +179,6 @@ <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small"> {{ approvalStatusText[scope.row.approvalStatus] || '未知状态' }} </el-tag> </template> </el-table-column> <el-table-column label="入库状态" prop="stockInStatus" width="100" show-overflow-tooltip> <template #default="scope"> <el-tag :type="getStockInStatusType(scope.row.stockInStatus)" size="small"> {{ scope.row.stockInStatus || '--' }} </el-tag> </template> </el-table-column> @@ -587,16 +564,6 @@ </el-tag> </template> </el-table-column> <el-table-column label="入库审核状态" prop="stockInApprovalStatus" width="120"> <template #default="scope"> <el-tag :type="getStockInApprovalStatusType(scope.row.stockInApprovalStatus)" size="small"> {{ scope.row.stockInApprovalStatus || '--' }} </el-tag> </template> </el-table-column> <el-table-column fixed="right" label="操作" min-width="60" @@ -604,8 +571,7 @@ <template #default="scope"> <el-button link type="primary" @click="openProductForm('edit', scope.row, scope.$index)" :disabled="scope.row.stockInApprovalStatus === '完全入库'">编辑 @click="openProductForm('edit', scope.row, scope.$index)">编辑 </el-button> </template> </el-table-column> @@ -917,26 +883,6 @@ 2: "warning", // 审批中 - 橙色 3: "success", // 审批通过 - 绿色 4: "danger", // 审批失败 - 红色 }; return typeMap[status] || ""; }; // 获取入库状态标签类型 const getStockInStatusType = status => { const typeMap = { 待入库: "info", // 待入库 - 灰色 入库中: "warning", // 入库中 - 橙色 完全入库: "success", // 完全入库 - 绿色 }; return typeMap[status] || ""; }; // 获取入库审核状态标签类型 const getStockInApprovalStatusType = status => { const typeMap = { 待入库: "info", // 待入库 - 灰色 入库中: "warning", // 入库中 - 橙色 完全入库: "success", // 完全入库 - 绿色 }; return typeMap[status] || ""; }; @@ -1447,14 +1393,6 @@ }; // 打开弹框 const openForm = async (type, row) => { // 编辑时检查入库状态,完全入库时不能编辑 if (type === "edit" && row) { if (row.stockInStatus === "完全入库") { proxy.$modal.msgWarning("完全入库状态的记录不能编辑"); return; } } await getTemplateList(); await userListNoPage().then(res => { userList.value = res.data; @@ -1619,12 +1557,6 @@ }; // 打开产品弹框 const openProductForm = async (type, row, index) => { // 编辑时检查产品入库审核状态,完全入库时不能编辑 if (type === "edit" && row && row.stockInApprovalStatus === "完全入库") { proxy.$modal.msgWarning("完全入库状态的产品不能编辑"); return; } productOperationType.value = type; productOperationIndex.value = index; productForm.value = {}; @@ -1809,14 +1741,6 @@ } if (productSelectedRows.value.length === 0) { proxy.$modal.msgWarning("请选择数据"); return; } // 检查选中的产品中是否有完全入库的 const hasFullyStocked = productSelectedRows.value.some( row => row.stockInApprovalStatus === "完全入库" ); if (hasFullyStocked) { proxy.$modal.msgWarning("选中的产品中包含完全入库的产品,无法删除"); return; } if (operationType.value === "add") { src/views/procurementManagement/purchaseApplication/index.vue
@@ -413,17 +413,6 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="合同金额(元):" prop="contractAmount"> <el-input-number v-model="form.contractAmount" :precision="2" :step="0.1" :min="0" placeholder="请输入" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="采购数量(吨):" prop="purchaseQuantity"> <el-input-number v-model="form.purchaseQuantity" src/views/reportAnalysis/PSIDataAnalysis/components/ProductTypeSwitch.vue
@@ -5,7 +5,6 @@ @change="handleChange" > <el-radio-button :label="1">原材料</el-radio-button> <el-radio-button :label="3">半成品</el-radio-button> <el-radio-button :label="2">成品</el-radio-button> </el-radio-group> </template> @@ -16,7 +15,7 @@ const props = defineProps({ modelValue: { type: Number, default: 1, // 默认选中"原材料" default: 2, // 默认选中"成品" }, }) src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -30,7 +30,9 @@ import ProductTypeSwitch from './ProductTypeSwitch.vue' import { productInOutAnalysis } from '@/api/viewIndex.js' const productType = ref(1) // 1=原材料 2=半成品 3=成品 // 1=原材料 2=成品 3=半成品。 // 默认成品:本租户「原材料」大类和「半成品」大类下都没有挂产品型号,选它们必然是一条全 0 的平线 const productType = ref(2) const chartStyle = { width: '100%', height: '130%' } src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -13,7 +13,7 @@ :tooltip="pieTooltip" :color="pieColors" :options="pieOptions" style="height: 320px" style="height: 100%" /> </div> </div> @@ -67,9 +67,9 @@ })) return { orient: 'vertical', top: 'center', left: '52%', orient: 'horizontal', bottom: 0, left: 'center', itemGap: 30, data: data, formatter: function (name) { @@ -116,7 +116,7 @@ name: '产品销售金额分析', type: 'pie', radius: '60%', center: ['25%', '50%'], center: ['50%', '45%'], itemStyle: { borderColor: '#0a1c3a', borderWidth: 2, @@ -145,8 +145,8 @@ const { init: initBackground, cleanup: cleanupBackground } = useChartBackground({ wrapperRef: pieWrapperRef, backgroundRef: pieBackgroundRef, left: '25%', // 图表中心 X 是 25% top: '50%', // 图表中心 Y 是 50% left: '50%', // 图表中心 X 是 50% top: '45%', // 图表中心 Y 是 45%(下面留出图例的位置) offsetX: '-51.5%', // X 轴偏移 offsetY: '-50%', // Y 轴偏移 watchData: pieDatas // 监听数据变化,自动调整位置 @@ -199,21 +199,24 @@ gap: 20px; } /* 左栏只剩这一块面板,高度撑满整栏,图表跟着把剩余空间占掉 */ .panel-item-customers { border: 1px solid #1a58b0; padding: 18px; width: 100%; height: 449px; flex: 1; min-height: 0; } .pie-chart-wrapper { position: relative; width: 100%; height: 320px; flex: 1; min-height: 0; } .pie-background { position: absolute; left: 25%; top: 50%; left: 50%; top: 45%; transform: translate(-51.5%, -50%); width: 310px; height: 310px; src/views/reportAnalysis/PSIDataAnalysis/index.vue
@@ -21,8 +21,6 @@ <!-- 左侧区域 --> <div class="left-panel"> <LeftTop /> <LeftBottom /> </div> <!-- 中间区域 --> @@ -34,8 +32,9 @@ <!-- 右侧区域 --> <div class="right-panel"> <RightBottom /> <RightTop /> <!-- 右侧两块沿用 dataDashboard 的组件,这里默认看季度 --> <RightBottom :default-date-type="3" /> <RightTop :default-date-type="3" /> </div> </div> </div> @@ -45,7 +44,6 @@ <script setup> import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue' import autofit from 'autofit.js' import LeftBottom from './components/left-bottom.vue' import CenterCenter from './components/center-center.vue' import RightTop from '../dataDashboard/components/basic/right-top.vue' import RightBottom from '../dataDashboard/components/basic/right-bottom.vue' @@ -295,6 +293,14 @@ width: 520px; } /* 左栏只有一块面板了,让它撑满整栏,避免下半栏留白 */ .left-panel > * { flex: 1; min-height: 0; display: flex; flex-direction: column; } .center-panel { flex: 1.5; display: flex; src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
@@ -47,7 +47,7 @@ import { customerRevenueAnalysis } from '@/api/viewIndex.js' import { listCustomer } from '@/api/basicData/customer.js' const dateType = ref(1) // 1=周 2=月 3=季度 const dateType = ref(3) // 1=周 2=月 3=季度 const customerValue = ref(null) const customerOptions = ref([]) src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -33,7 +33,15 @@ height: '100%', } const dateType = ref(1) // 1=周 2=月 3=季度 // 复用本组件的页面可指定初始周期,不传按「周」展示 const props = defineProps({ defaultDateType: { type: Number, default: 1, // 1=周 2=月 3=季度 }, }) const dateType = ref(props.defaultDateType) // 飞机图标 SVG path(与 right-top 一致) const aircraft = src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -32,7 +32,15 @@ height: '100%', } const radio1 = ref(1) // 复用本组件的页面可指定初始周期,不传按「周」展示 const props = defineProps({ defaultDateType: { type: Number, default: 1, // 1=周 2=月 3=季度 }, }) const radio1 = ref(props.defaultDateType) // 飞机图标 SVG path const aircraft = src/views/reportAnalysis/dataDashboard/index.vue
@@ -34,9 +34,9 @@ <!-- 右侧区域 --> <div class="right-panel"> <RightTop /> <RightTop :default-date-type="3" /> <RightBottom /> <RightBottom :default-date-type="3" /> </div> </div> </div> src/views/salesManagement/salesLedger/index.vue
@@ -115,31 +115,6 @@ <el-table-column label="产品大类" prop="productCategory" /> <el-table-column label="规格型号" prop="specificationModel" /> <el-table-column label="单位" prop="unit" /> <el-table-column label="审批中数量" width="120" align="center"> <template #default="scope"> <span>{{ scope.row.pendingApprovalQuantity || 0 }}</span> </template> </el-table-column> <el-table-column label="发货车牌" minWidth="100px" align="center"> <template #default="scope"> <div> <el-tag type="success" v-if="scope.row.shippingCarNumber" >{{ scope.row.shippingCarNumber }} </el-tag> <el-tag v-else type="info">- </el-tag> </div> </template> </el-table-column> <el-table-column label="发货日期" minWidth="100px" align="center"> <template #default="scope"> <div> <div v-if="scope.row.shippingDate"> {{ scope.row.shippingDate }} </div> <el-tag v-else type="info">- </el-tag> </div> </template> </el-table-column> <el-table-column label="数量" prop="quantity" /> <el-table-column label="退货数量" prop="returnQuantity" /> <el-table-column label="税率(%)" prop="taxRate" />