| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501 |
- <template>
- <div class="p-2">
- <!-- 返回按钮 -->
- <div class="mb-4 flex items-center">
- <el-button link icon="ArrowLeft" @click="goBack">返回</el-button>
- </div>
- <!-- 申请入池表单头部 -->
- <el-card shadow="hover" class="mb-[10px]">
- <el-form :model="auditForm" label-width="100px">
- <!-- 第一行:产品池类型 + 产品册选择 + 申请类型 -->
- <el-row :gutter="20">
- <el-col :span="8">
- <el-form-item label="产品池类型" prop="type">
- <el-select v-model="auditForm.type" placeholder="请选择产品池类型" style="width: 100%" :disabled="typeDisabled">
- <el-option label="自营产品池" value="0" />
- <el-option label="标准产品池" value="1" />
- <el-option label="协议产品池" value="2" />
- <el-option label="项目产品池" value="3" />
- <el-option label="营销产品池" value="4" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <!-- type 4:营销产品池 -->
- <el-form-item v-if="auditForm.type === '4'" label="产品池" prop="poolId">
- <el-select v-model="auditForm.poolId" placeholder="请选择产品池" clearable filterable style="width: 100%">
- <el-option v-for="item in poolOptions" :key="item.id" :label="item.name" :value="item.id" />
- </el-select>
- </el-form-item>
- <!-- type 2:协议产品池 -->
- <el-form-item v-else-if="auditForm.type === '2'" label="协议" prop="protocolId">
- <el-select v-model="auditForm.protocolId" placeholder="请选择协议" clearable filterable style="width: 100%">
- <el-option v-for="item in protocolOptions" :key="item.id" :label="`${item.customerName}`" :value="item.id" />
- </el-select>
- </el-form-item>
- <!-- type 3:项目产品池 -->
- <el-form-item v-else-if="auditForm.type === '3'" label="项目" prop="itemId">
- <el-select v-model="auditForm.itemId" placeholder="请选择项目" clearable filterable style="width: 100%">
- <el-option v-for="item in itemOptions" :key="item.id" :label="item.itemName" :value="item.id" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <!-- 第二行:备注 -->
- <el-row>
- <el-col :span="16">
- <el-form-item label="备注" prop="remark">
- <el-input v-model="auditForm.remark" placeholder="请输入备注" style="width: 100%" />
- </el-form-item>
- </el-col>
- </el-row>
- <!-- 第三行:附件 -->
- <el-row>
- <el-col :span="8">
- <el-form-item v-if="auditForm.type !== '0'" label="申请类型" prop="applyType">
- <el-radio-group v-model="auditForm.applyType">
- <el-radio :value="0">入池</el-radio>
- <el-radio :value="1">出池</el-radio>
- </el-radio-group>
- </el-form-item>
- </el-col>
- <el-col :span="24">
- <el-form-item label="附件" prop="attachment">
- <file-upload v-model="auditForm.attachment" :file-size="20" :limit="5" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- </el-card>
- <el-card shadow="never">
- <template #header>
- <div class="flex justify-between items-center">
- <span class="font-bold">入池清单</span>
- <div class="flex gap-2">
- <el-button icon="Upload" @click="handleImport">导入商品</el-button>
- <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
- <el-button type="danger" icon="Delete" @click="handleClearPool">清空产品池</el-button>
- </div>
- </div>
- </template>
- <el-table v-loading="loading" border :data="productList">
- <el-table-column label="商品编号" align="center" prop="productNo" width="120" fixed="left">
- <template #default="scope">
- <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.productNo }}</el-link>
- </template>
- </el-table-column>
- <el-table-column label="商品图片" align="center" prop="productImage" width="100">
- <template #default="scope">
- <image-preview :src="scope.row.productImage" :width="60" :height="60" />
- </template>
- </el-table-column>
- <el-table-column label="商品信息" align="center" min-width="250">
- <template #default="scope">
- <div class="text-left">
- <div style="white-space: normal; word-break: break-all; line-height: 1.4">{{ scope.row.itemName }}</div>
- <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
- <div class="text-gray-500" style="font-size: 12px">
- 分类: {{ scope.row.topCategoryName + '-' + scope.row.mediumCategoryName + '-' + scope.row.bottomCategoryName }}
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="单位" align="center" width="100">
- <template #default="scope">
- <div class="text-left">
- <div class="text-gray-500" style="font-size: 12px">单位: {{ scope.row.unitName || '-' }}</div>
- <div class="text-gray-500" style="font-size: 12px">起订量: {{ scope.row.minOrderQuantity || '-' }}</div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="价格信息" align="center" width="120">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">市场价:</span>
- <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">官网价:</span>
- <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">最低价:</span>
- <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column v-if="auditForm.type === '2'" label="协议价" align="center" width="140">
- <template #default="scope">
- <el-input-number
- v-model="tempAgreementPrices[scope.row.id]"
- :min="0"
- :precision="2"
- :controls="false"
- size="small"
- style="width: 120px"
- placeholder="请输入协议价"
- @blur="handleAgreementPriceBlur(scope.row)"
- />
- </template>
- </el-table-column>
- <el-table-column label="采购信息" align="center" width="150">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">采购价:</span>
- <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">暂估毛利率:</span>
- <span
- >{{
- scope.row.memberPrice
- ? (((scope.row.memberPrice - (scope.row.purchasingPrice || 0)) / scope.row.memberPrice) * 100).toFixed(2)
- : '0.00'
- }}%</span
- >
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column v-if="auditForm.type === '3'" label="第三方售价" align="center" width="100">
- <template #default="scope">
- <span class="text-red-500">¥{{ tempProjectProductInfo[scope.row.id]?.negotiatedPrice?.toFixed(2) || '0.00' }}</span>
- </template>
- </el-table-column>
- <el-table-column v-if="auditForm.type === '3'" label="计价规则" align="center" width="90">
- <template #default="scope">
- {{
- tempProjectProductInfo[scope.row.id]?.pricingRule === '0'
- ? '一品一率'
- : tempProjectProductInfo[scope.row.id]?.pricingRule === '1'
- ? '折扣率'
- : '-'
- }}
- </template>
- </el-table-column>
- <el-table-column v-if="auditForm.type === '3'" label="第三方分类" align="center" width="160">
- <template #default="scope">
- {{ tempProjectProductInfo[scope.row.id]?.categoryName || '-' }}
- </template>
- </el-table-column>
- <el-table-column label="商品类型" align="center" prop="productReviewStatus" width="90">
- <template #default="scope">
- <span v-if="scope.row.productReviewStatus === 0">待采购审核</span>
- <span v-else-if="scope.row.productReviewStatus === 1">审核通过</span>
- <span v-else-if="scope.row.productReviewStatus === 2">驳回</span>
- <span v-else-if="scope.row.productReviewStatus === 3">待营销审核</span>
- <span v-else>-</span>
- </template>
- </el-table-column>
- <el-table-column label="上下架状态" align="center" prop="productStatus" width="100">
- <template #default="scope">
- <el-tag v-if="scope.row.productStatus === 1" type="success">已上架</el-tag>
- <el-tag v-else-if="scope.row.productStatus === 0" type="warning">下架</el-tag>
- <el-tag v-else-if="scope.row.productStatus === 2" type="info">上架中</el-tag>
- <el-tag v-else type="info">未知</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="120" fixed="right">
- <template #default="scope">
- <div class="flex flex-col gap-1">
- <el-link type="danger" :underline="false" @click="handleRemoveProduct(scope.row)">移除</el-link>
- </div>
- </template>
- </el-table-column>
- </el-table>
- <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
- </el-card>
- <!-- 底部操作按钮 -->
- <div class="mt-4 flex justify-end gap-2">
- <el-button @click="goBack">取消</el-button>
- <el-button type="primary" @click="handleSubmitAudit">确定</el-button>
- </div>
- <!-- 导入商品对话框 -->
- <el-dialog v-model="importDialog.open" title="导入商品" width="400px" append-to-body @close="handleImportDialogClose">
- <el-upload
- ref="uploadRef"
- :limit="1"
- accept=".xlsx, .xls"
- :auto-upload="false"
- :before-upload="() => false"
- :on-change="handleFileChange"
- :on-remove="() => (importDialog.selectedFile = null)"
- drag
- >
- <el-icon class="el-icon--upload"><i-ep-upload-filled /></el-icon>
- <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
- <template #tip>
- <div class="text-center el-upload__tip">
- <span>仅允许导入 xls、xlsx 格式文件。</span>
- <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="downloadTemplate">下载模板</el-link>
- </div>
- </template>
- </el-upload>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitFileForm" :loading="importDialog.isUploading">确 定</el-button>
- <el-button @click="importDialog.open = false">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 添加商品对话框 -->
- <el-dialog title="添加商品" v-model="addProductDialog.visible" width="1400px" append-to-body top="5vh">
- <div class="add-product-dialog">
- <!-- 搜索区域 -->
- <el-form :model="addProductQuery" :inline="true" class="mb-4">
- <el-form-item label="商品名称:" label-width="80px">
- <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 200px" />
- </el-form-item>
- <el-form-item label="商品编号:" label-width="80px">
- <el-input v-model="addProductQuery.productNo" placeholder="商品编号" clearable style="width: 200px" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleSearchProducts">搜索</el-button>
- </el-form-item>
- <el-form-item v-if="auditForm.type !== '2' && auditForm.type !== '3'">
- <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
- </el-form-item>
- </el-form>
- <!-- 商品列表 -->
- <el-table
- ref="addProductTableRef"
- v-loading="addProductDialog.loading"
- :data="addProductDialog.productList"
- border
- @selection-change="handleSelectionChange"
- max-height="500"
- >
- <el-table-column type="selection" width="55" align="center" />
- <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
- <el-table-column label="商品图片" align="center" prop="productImage" width="100">
- <template #default="scope">
- <image-preview :src="scope.row.productImage" :width="60" :height="60" />
- </template>
- </el-table-column>
- <el-table-column label="商品信息" align="center" min-width="250">
- <template #default="scope">
- <div class="text-left">
- <div style="white-space: normal; word-break: break-all; line-height: 1.4">{{ scope.row.itemName }}</div>
- <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
- <div class="text-gray-500" style="font-size: 12px">
- 分类: {{ scope.row.topCategoryName + '-' + scope.row.mediumCategoryName + '-' + scope.row.bottomCategoryName }}
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="单位" align="center" width="100">
- <template #default="scope">
- <div class="text-left">
- <div class="text-gray-500" style="font-size: 12px">单位: {{ scope.row.unitName || '-' }}</div>
- <div class="text-gray-500" style="font-size: 12px">起订量: {{ scope.row.minOrderQuantity || '-' }}</div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="价格信息" align="center" width="120">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">市场价:</span>
- <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">官网价:</span>
- <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">最低价:</span>
- <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="采购信息" align="center" width="150">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">采购价:</span>
- <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">暂估毛利率:</span>
- <span
- >{{
- scope.row.memberPrice
- ? (((scope.row.memberPrice - (scope.row.purchasingPrice || 0)) / scope.row.memberPrice) * 100).toFixed(2)
- : '0.00'
- }}%</span
- >
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="库存情况" align="center" width="140">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">总库存:</span>
- <span>{{ scope.row.totalInventory ?? '-' }}</span>
- </div>
- <div>
- <span class="text-gray-500">现有库存:</span>
- <span>{{ scope.row.nowInventory ?? '-' }}</span>
- </div>
- <div>
- <span class="text-gray-500">虚拟库存:</span>
- <span>{{ scope.row.virtualInventory ?? '-' }}</span>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="是否自营" align="center" width="80">
- <template #default="scope">
- <span v-if="scope.row.isSelf === 1">是</span>
- <span v-else-if="scope.row.isSelf === 0">否</span>
- <span v-else>-</span>
- </template>
- </el-table-column>
- <el-table-column label="上下架状态" align="center" prop="productStatus" width="100">
- <template #default="scope">
- <el-tag v-if="scope.row.productStatus === 1" type="success">已上架</el-tag>
- <el-tag v-else-if="scope.row.productStatus === 0" type="warning">下架</el-tag>
- <el-tag v-else-if="scope.row.productStatus === 2" type="info">上架中</el-tag>
- <el-tag v-else type="info">未知</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="供应情况" align="center" width="150">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>供应商数量:{{ scope.row.supplierCount || 0 }}</div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="120" fixed="right">
- <template #default="scope">
- <el-tag v-if="isProductSelected(scope.row.id)" type="success" size="small">已选({{ getPoolTypeLabel() }})</el-tag>
- <el-link v-else type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)">加入清单</el-link>
- </template>
- </el-table-column>
- </el-table>
- <!-- 游标分页控制 -->
- <pagination
- v-show="addProductDialog.productList.length > 0"
- v-model:page="addProductQuery.pageNum"
- v-model:limit="addProductQuery.pageSize"
- v-model:way="addProductQuery.way"
- :cursor-mode="true"
- :has-more="addProductHasMore"
- @pagination="getProductList"
- />
- </div>
- </el-dialog>
- <!-- 选择第三方产品分类弹框(项目产品池) -->
- <el-dialog title="选择第三方产品分类" v-model="thirdPartyDialog.visible" width="520px" append-to-body :destroy-on-close="true">
- <el-form label-width="130px">
- <el-form-item label="价格模式:">
- <el-radio-group v-model="thirdPartyDialog.pricingRule">
- <el-radio label="0">一品一率</el-radio>
- <el-radio label="1">折扣率</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="* 第三方产品分类:">
- <el-cascader
- ref="categoryRef"
- v-model="thirdPartyDialog.categoryValue"
- :props="categoryLazyProps"
- style="width: 100%"
- placeholder="请选择"
- clearable
- @change="handleCategoryChange"
- />
- </el-form-item>
- <el-form-item v-if="thirdPartyDialog.pricingRule === '1'" label="折扣率:">
- <span>{{ thirdPartyDialog.discountRate !== undefined ? thirdPartyDialog.discountRate : '-' }}</span>
- </el-form-item>
- <el-form-item label="起订量:">
- <el-input-number v-model="thirdPartyDialog.minOrderQuantity" :min="1" controls-position="right" style="width: 100%" />
- </el-form-item>
- <el-form-item label="* 第三方平台售价:">
- <el-input-number
- v-model="thirdPartyDialog.negotiatedPrice"
- :disabled="thirdPartyDialog.pricingRule === '1'"
- :precision="2"
- :min="0"
- controls-position="right"
- style="width: 100%"
- @blur="handleThirdPartyPriceBlur"
- />
- </el-form-item>
- <el-form-item label="市场价:">
- <span>{{ thirdPartyDialog.row?.marketPrice || 0 }}</span>
- </el-form-item>
- <el-form-item label="官网价:">
- <span>{{ thirdPartyDialog.row?.memberPrice || 0 }}</span>
- </el-form-item>
- <el-form-item label="最低售价:">
- <span class="text-red-500">¥{{ thirdPartyDialog.row?.minSellingPrice || 0 }}</span>
- </el-form-item>
- </el-form>
- <div class="mx-1 mb-3 px-3 py-2 rounded flex items-center gap-1 text-sm" style="background: #fff9f0; border: 1px solid #ffd591; color: #d48806">
- <el-icon><i-ep-warning /></el-icon>
- <span>(第三方平台售价不能低于最低售价,不高于官网价)</span>
- </div>
- <template #footer>
- <el-button @click="thirdPartyDialog.visible = false">返回</el-button>
- <el-button type="primary" @click="handleConfirmThirdParty">确认</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup name="PoolLinkAudit" lang="ts">
- import { useRouter, useRoute } from 'vue-router';
- import { categoryTree, listBase } from '@/api/product/base';
- import { getPoolAuditProducts } from '@/api/product/baseAudit';
- import { BaseVO, BaseQuery } from '@/api/product/base/types';
- import { getPoolAudit, updatePoolAudit, addPoolAudit } from '@/api/product/poolAudit';
- import { listInfo } from '@/api/customer/supplierInfo';
- import { InfoVO } from '@/api/customer/supplierInfo/types';
- import { listBrand } from '@/api/product/brand';
- import { BrandVO } from '@/api/product/brand/types';
- import { PoolAuditVO, PoolAuditForm } from '@/api/product/poolAudit/types';
- import { getItem, listItem } from '@/api/external/item';
- import { ItemVO } from '@/api/external/item/types';
- import { getInfo as getProtocolInfo, listInfo as listProtocolInfo } from '@/api/product/protocolInfo';
- import { InfoVO as ProtocolInfoVO } from '@/api/product/protocolInfo/types';
- import { getPool, listPool } from '@/api/product/pool';
- import { PoolVO } from '@/api/product/pool/types';
- import * as XLSX from 'xlsx';
- import { listProductCategory } from '@/api/external/productCategory';
- import { ProductCategoryVO } from '@/api/external/productCategory/types';
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const router = useRouter();
- const route = useRoute();
- const productList = ref<BaseVO[]>([]);
- const loading = ref(false);
- const total = ref(0);
- const auditInfoLoading = ref(false);
- const auditInfo = ref<(PoolAuditVO & { createTime?: string; auditTime?: string }) | undefined>();
- const itemInfo = ref<ItemVO | undefined>();
- const protocolInfo = ref<ProtocolInfoVO | undefined>();
- const poolInfo = ref<PoolVO | undefined>();
- // 申请入池表单数据
- const auditForm = reactive<PoolAuditForm>({
- id: undefined,
- type: undefined,
- name: undefined,
- poolId: undefined,
- protocolId: undefined,
- itemId: undefined,
- remark: undefined,
- attachment: undefined,
- productIds: [],
- applyType: 0
- });
- /** 下拉选项数据 */
- const poolOptions = ref<PoolVO[]>([]);
- const protocolOptions = ref<ProtocolInfoVO[]>([]);
- const itemOptions = ref<ItemVO[]>([]);
- /** 根据产品池类型加载对应下拉选项 */
- const loadSelectOptions = async (type?: string | number) => {
- const t = String(type ?? auditForm.type ?? '');
- if (t === '4') {
- const res = await listPool({ type: Number(t) } as any);
- poolOptions.value = res.rows ?? [];
- } else if (t === '2') {
- const res = await listProtocolInfo();
- protocolOptions.value = res.rows ?? [];
- } else if (t === '3') {
- const res = await listItem();
- itemOptions.value = res.rows ?? [];
- }
- };
- // 回显时屏蔽 watch 副作用
- const isInitializing = ref(false);
- /** 切换产品池类型时,清空关联选择并重新加载选项,同时清空商品列表 */
- watch(
- () => auditForm.type,
- (newType) => {
- if (isInitializing.value) return;
- auditForm.poolId = undefined;
- auditForm.protocolId = undefined;
- auditForm.itemId = undefined;
- auditForm.name = undefined;
- // 切换类型时清空商品列表
- tempProductIds.value = '';
- Object.keys(tempAgreementPrices).forEach((key) => delete tempAgreementPrices[key]);
- productList.value = [];
- total.value = 0;
- if (newType !== undefined && newType !== null) {
- loadSelectOptions(newType);
- }
- }
- );
- /** 切换具体产品池/协议/项目时,清空商品列表 */
- watch(
- () => [auditForm.poolId, auditForm.protocolId, auditForm.itemId] as const,
- (newVal, oldVal) => {
- if (isInitializing.value) return;
- const changed = newVal.some((v, i) => v !== oldVal[i]);
- if (changed) {
- tempProductIds.value = '';
- Object.keys(tempAgreementPrices).forEach((key) => delete tempAgreementPrices[key]);
- productList.value = [];
- total.value = 0;
- }
- }
- );
- const queryParams = ref({
- pageNum: 1,
- pageSize: 10,
- poolAuditId: (route.params.id || route.query.id) as string | number,
- productNo: undefined,
- itemName: undefined,
- brandId: undefined,
- productStatus: undefined,
- bottomCategoryId: undefined,
- supplier: undefined,
- dateRange: undefined
- });
- /** 路由传入的产品池类型(存在时禁用类型下拉) */
- const routeType = computed(() => route.query.type as string | undefined);
- const typeDisabled = computed(() => !!routeType.value && routeType.value !== 'undefined' && routeType.value !== '-1');
- const categoryOptions = ref<any[]>([]);
- const categoryMap = ref<Map<string | number, any>>(new Map());
- const supplierOptions = ref<InfoVO[]>([]);
- const brandOptions = ref<BrandVO[]>([]);
- const brandLoading = ref(false);
- let brandSearchTimer: ReturnType<typeof setTimeout> | null = null;
- // 导入商品对话框
- const importDialog = reactive({
- open: false,
- isUploading: false,
- selectedFile: null as File | null
- });
- const uploadRef = ref<any>();
- // 添加商品对话框
- const addProductDialog = reactive({
- visible: false,
- loading: false,
- productList: [] as BaseVO[],
- total: 0
- });
- // 游标分页相关变量
- const addProductHasMore = ref(true);
- const addProductPageHistory = ref<Array<{ firstId: string | number; lastId: string | number }>>([]);
- // 添加商品查询参数
- const addProductQuery = ref<BaseQuery>({
- pageNum: 1,
- pageSize: 10,
- way: undefined,
- productNo: undefined,
- itemName: undefined,
- lastSeenId: undefined
- });
- // 选中的商品
- const selectedProducts = ref<BaseVO[]>([]);
- const addProductTableRef = ref<any>();
- // 协议价输入映射(type=2时使用,项为对话框内商品id)
- const priceInputMap = reactive<Record<string | number, number | undefined>>({});
- // 临时展示用:本地暂存的入池商品 ID 列表(逗号分隔的字符串)
- const tempProductIds = ref<string>('');
- // 临时展示用:本地暂存的协议价(type=2)
- const tempAgreementPrices = reactive<Record<string | number, number | undefined>>({});
- // 第三方产品分类弹框状态(type=3 项目产品池时使用)
- const thirdPartyDialog = reactive({
- visible: false,
- row: null as BaseVO | null,
- pricingRule: '0' as '0' | '1',
- categoryValue: [] as (string | number)[],
- discountRate: undefined as number | undefined,
- negotiatedPrice: 0 as number,
- minOrderQuantity: 1 as number
- });
- // 分类折扣率缓存(categoryId -> discountRate)
- const categoryDiscountMap = ref<Record<string | number, number | undefined>>({});
- // 项目产品池商品额外信息(type=3 时存储第三方售价、计价规则、分类)
- const tempProjectProductInfo = reactive<
- Record<
- string | number,
- {
- negotiatedPrice: number;
- pricingRule: '0';
- categoryId: string | number | null;
- categoryName: string;
- minOrderQuantity: number;
- }
- >
- >({});
- // 第三方分类级联选择器 ref
- const categoryRef = ref<any>();
- // 分类懒加载配置(最多3级,itemId 来自 auditForm.itemId)
- const categoryLazyProps = {
- lazy: true,
- lazyLoad: (node: any, resolve: (data: any[]) => void) => {
- const { level, value } = node;
- const parentId = level === 0 ? 0 : value;
- listProductCategory({ itemId: auditForm.itemId as any, parentId } as any)
- .then((res) => {
- const data = (res.rows || []) as ProductCategoryVO[];
- data.forEach((item) => {
- categoryDiscountMap.value[item.id] = item.discountRate;
- });
- resolve(
- data.map((item) => ({
- value: item.id,
- label: item.categoryName,
- leaf: (item.classLevel !== undefined && item.classLevel >= 3) || !item.hasChildren
- }))
- );
- })
- .catch(() => resolve([]));
- }
- };
- /** 获取审核状态标签文字 */
- const getStatusLabel = (status?: string): string => {
- const map: Record<string, string> = { '0': '待提交', '1': '待审核', '2': '审核通过', '3': '审核驳回' };
- return status !== undefined ? map[status] || status : '-';
- };
- /** 获取审核状态标签类型 */
- const getStatusTagType = (status?: string): 'success' | 'warning' | 'info' | 'danger' | 'primary' => {
- const map: Record<string, 'success' | 'warning' | 'info' | 'danger' | 'primary'> = {
- '0': 'info',
- '1': 'warning',
- '2': 'success',
- '3': 'danger'
- };
- return status !== undefined ? map[status] || 'primary' : 'primary';
- };
- /** 加载审核池信息(编辑回显) */
- const loadAuditInfo = async () => {
- const id = route.params.id || route.query.id || route.query.poolAuditId;
- if (!id) return;
- auditInfoLoading.value = true;
- try {
- // 调用 getPoolAudit 获取审核信息
- const res = await getPoolAudit(id as string | number);
- const data = res.data as PoolAuditVO;
- auditInfo.value = data as any;
- // 回显表单,先设标志位防止 watch 清空字段
- isInitializing.value = true;
- auditForm.id = data.id;
- auditForm.type = data.type !== undefined && data.type !== null ? String(data.type) : undefined;
- auditForm.name = data.name;
- auditForm.poolId = data.poolId;
- auditForm.protocolId = data.protocolId;
- auditForm.itemId = data.itemId;
- auditForm.remark = data.remark;
- auditForm.attachment = data.attachment;
- auditForm.applyType = data.applyType;
- // 加载对应下拉选项
- if (auditForm.type) {
- await loadSelectOptions(auditForm.type);
- }
- // 修复类型不匹配导致 el-select 无法回显的问题(接口返回的 id 类型可能与选项列表不一致)
- if (auditForm.type === '2' && auditForm.protocolId) {
- const match = protocolOptions.value.find((item) => String(item.id) === String(auditForm.protocolId));
- if (match) auditForm.protocolId = match.id;
- } else if (auditForm.type === '3' && auditForm.itemId) {
- const match = itemOptions.value.find((item) => String(item.id) === String(auditForm.itemId));
- if (match) auditForm.itemId = match.id;
- } else if (auditForm.type === '4' && auditForm.poolId) {
- const match = poolOptions.value.find((item) => String(item.id) === String(auditForm.poolId));
- if (match) auditForm.poolId = match.id;
- }
- // 所有回显赋值完成后再关闭标志位,避免 watch 异步回调清空字段
- isInitializing.value = false;
- // 从 products 初始化商品 ID 列表和协议价
- const products: Array<{ productId: string | number; negotiatedPrice?: number; agreementPrice?: number }> = (data as any).products || [];
- tempProductIds.value = products.map((p: any) => p.productId).join(',');
- products.forEach((p: any) => {
- const price = p.negotiatedPrice ?? p.agreementPrice;
- if (price !== undefined && price !== null) {
- tempAgreementPrices[p.productId] = price;
- }
- });
- // 刷新商品列表
- await getList();
- // 加载关联详情信息
- const type = auditForm.type;
- if (type === '3' && data.itemId) {
- const itemRes = await getItem(data.itemId);
- itemInfo.value = itemRes.data;
- } else if (type === '2' && data.protocolId) {
- const protocolRes = await getProtocolInfo(data.protocolId);
- protocolInfo.value = protocolRes.data;
- } else if ((type === '0' || type === '4') && data.poolId) {
- const poolRes = await getPool(data.poolId);
- poolInfo.value = poolRes.data;
- }
- } catch (error) {
- console.error('加载信息失败:', error);
- } finally {
- auditInfoLoading.value = false;
- }
- };
- /** 获取分类树 */
- const getCategoryTree = async () => {
- try {
- const res = await categoryTree();
- categoryOptions.value = res.data || [];
- buildCategoryMap(categoryOptions.value);
- } catch (error) {
- console.error('获取分类树失败:', error);
- }
- };
- /** 获取供应商列表 */
- const getSupplierList = async () => {
- try {
- const res = await listInfo();
- supplierOptions.value = res.data || res.rows || [];
- } catch (error) {
- console.error('获取供应商列表失败:', error);
- }
- };
- /** 获取供应商名称 */
- const getSupplierName = (supplierId: string | number | undefined): string => {
- if (!supplierId) return '-';
- const supplier = supplierOptions.value.find((item) => item.id === supplierId);
- return supplier?.enterpriseName || supplier?.shortName || '-';
- };
- /** 加载品牌选项(默认100条) */
- const loadBrandOptions = async (keyword?: string) => {
- brandLoading.value = true;
- try {
- const res = await listBrand({ pageNum: 1, pageSize: 100, brandName: keyword });
- brandOptions.value = res.rows || [];
- } catch (error) {
- console.error('加载品牌列表失败:', error);
- } finally {
- brandLoading.value = false;
- }
- };
- /** 品牌远程搜索(防抖) */
- const handleBrandSearch = (query: string) => {
- if (brandSearchTimer) clearTimeout(brandSearchTimer);
- brandSearchTimer = setTimeout(() => {
- loadBrandOptions(query || undefined);
- }, 300);
- };
- /** 构建分类映射 */
- const buildCategoryMap = (categories: any[], parentPath = '') => {
- categories.forEach((category) => {
- const fullPath = parentPath ? `${parentPath} > ${category.label}` : category.label;
- categoryMap.value.set(category.id, { ...category, fullPath });
- if (category.children && category.children.length > 0) {
- buildCategoryMap(category.children, fullPath);
- }
- });
- };
- /** 获取分类完整路径 */
- const getCategoryFullPath = (categoryId: string | number): string => {
- const category = categoryMap.value.get(categoryId);
- return category?.fullPath || '';
- };
- /** 查询商品列表(将本地暂存的商品 ID 传给后端查询) */
- const getList = async () => {
- // 没有临时商品时直接显示空列表
- if (!tempProductIds.value) {
- productList.value = [];
- total.value = 0;
- return;
- }
- loading.value = true;
- try {
- const params: any = {
- pageNum: queryParams.value.pageNum,
- pageSize: queryParams.value.pageSize,
- ids: tempProductIds.value
- };
- const res = await listBase(params);
- productList.value = (res.rows || res.data || []) as any;
- total.value = res.total || tempProductIds.value.split(',').filter((id) => id).length;
- } catch (error) {
- console.error('获取商品列表失败:', error);
- productList.value = [];
- total.value = 0;
- } finally {
- loading.value = false;
- }
- };
- /** 返回 */
- const goBack = () => {
- router.back();
- };
- /** 搜索 */
- const handleQuery = () => {
- queryParams.value.pageNum = 1;
- getList();
- };
- /** 申请入池(待提交 -> 待审核) */
- const handleApplyPool = async () => {
- await proxy?.$modal.confirm('确认要申请入池吗?提交后状态将变为待审核。');
- const id = auditInfo.value?.id;
- if (!id) return;
- await updatePoolAudit({ id, productReviewStatus: '0' });
- proxy?.$modal.msgSuccess('申请成功,状态已变为待审核');
- await loadAuditInfo();
- };
- /** 确定按钮 - 提交产品池审核单据 */
- const handleSubmitAudit = async () => {
- if (!auditForm.type) {
- proxy?.$modal.msgWarning('请选择产品池类型');
- return;
- }
- if (auditForm.type === '4' && !auditForm.poolId) {
- proxy?.$modal.msgWarning('请选择产品池');
- return;
- }
- if (auditForm.type === '2' && !auditForm.protocolId) {
- proxy?.$modal.msgWarning('请选择协议');
- return;
- }
- if (auditForm.type === '3' && !auditForm.itemId) {
- proxy?.$modal.msgWarning('请选择项目');
- return;
- }
- if (!tempProductIds.value) {
- proxy?.$modal.msgWarning('请先添加商品到入池清单');
- return;
- }
- // type=2 协议产品池:提交前校验所有商品的协议价
- if (auditForm.type === '2') {
- for (const product of productList.value) {
- const price = tempAgreementPrices[product.id];
- if (!price || price <= 0) {
- proxy?.$modal.msgWarning(`商品「${product.itemName || product.productNo}」的协议价不能为0,请检查后重试`);
- return;
- }
- const minPrice = Number(product.minSellingPrice) || 0;
- const maxPrice = Number(product.memberPrice) || 0;
- if (minPrice > 0 && price < minPrice) {
- proxy?.$modal.msgWarning(`商品「${product.itemName || product.productNo}」的协议价不能低于最低售价(¥${minPrice})`);
- return;
- }
- if (maxPrice > 0 && price > maxPrice) {
- proxy?.$modal.msgWarning(`商品「${product.itemName || product.productNo}」的协议价不能高于官网价(¥${maxPrice})`);
- return;
- }
- }
- }
- await proxy?.$modal.confirm('确认要提交产品池审核单据吗?');
- try {
- // 提交整个审核单据(包含本地暂存的商品 ID 及协议价)
- const submitData: PoolAuditForm = {
- ...auditForm,
- products: tempProductIds.value
- .split(',')
- .filter((id) => id)
- .map((id) => ({
- productId: id,
- negotiatedPrice: tempAgreementPrices[id],
- ...(auditForm.type === '3' && tempProjectProductInfo[id]
- ? {
- negotiatedPrice: tempProjectProductInfo[id].negotiatedPrice,
- pricingRule: tempProjectProductInfo[id].pricingRule,
- categoryId: tempProjectProductInfo[id].categoryId,
- categoryName: tempProjectProductInfo[id].categoryName
- }
- : {})
- })),
- productReviewStatus: '0' // 提交后状态为待申请
- };
- if (!auditForm.id) {
- await addPoolAudit(submitData);
- } else {
- await updatePoolAudit(submitData);
- }
- proxy?.$modal.msgSuccess('提交成功');
- goBack();
- } catch (error) {
- console.error('提交失败:', error);
- }
- };
- /** 清空入池清单 */
- const handleClearPool = async () => {
- await proxy?.$modal.confirm('确认要清空入池清单中的所有商品吗?');
- tempProductIds.value = '';
- Object.keys(tempAgreementPrices).forEach((key) => delete tempAgreementPrices[key]);
- productList.value = [];
- total.value = 0;
- proxy?.$modal.msgSuccess('清空成功');
- };
- /** 导入商品按钮 */
- const handleImport = () => {
- if (!checkPoolSelected()) return;
- importDialog.open = true;
- importDialog.isUploading = false;
- importDialog.selectedFile = null;
- nextTick(() => {
- uploadRef.value?.clearFiles();
- });
- };
- /** 关闭导入对话框时清理 */
- const handleImportDialogClose = () => {
- importDialog.selectedFile = null;
- importDialog.isUploading = false;
- uploadRef.value?.clearFiles();
- };
- /** 下载导入模板 */
- const downloadTemplate = () => {
- const url = new URL('./商品导入模版.xlsx', import.meta.url).href;
- const a = document.createElement('a');
- a.href = url;
- a.download = '商品导入模版.xlsx';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- };
- /** 选择文件时暂存 */
- const handleFileChange = (file: any) => {
- importDialog.selectedFile = file.raw as File;
- };
- /** 解析 Excel 文件,读取商品编号和协议价格两列 */
- const parseExcelData = (file: File): Promise<Array<{ productNo: string; negotiatedPrice?: number }>> => {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (e) => {
- try {
- const data = new Uint8Array(e.target!.result as ArrayBuffer);
- const workbook = XLSX.read(data, { type: 'array' });
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
- const rows = XLSX.utils.sheet_to_json<any[]>(sheet, { header: 1 });
- const headerRow: any[] = rows[0] || [];
- // 查找「商品编号」列索引
- let noColIndex = headerRow.findIndex((h: any) => String(h).trim() === '商品编号');
- if (noColIndex === -1) noColIndex = 0;
- // 查找「协议价格」列索引
- const priceColIndex = headerRow.findIndex((h: any) => String(h).trim() === '协议价格');
- const result: Array<{ productNo: string; negotiatedPrice?: number }> = [];
- rows.slice(1).forEach((row: any[]) => {
- const val = row[noColIndex];
- if (val !== undefined && val !== null && String(val).trim() !== '') {
- const negotiatedPrice =
- priceColIndex !== -1 && row[priceColIndex] !== undefined && row[priceColIndex] !== null ? Number(row[priceColIndex]) : undefined;
- result.push({
- productNo: String(val).trim(),
- negotiatedPrice: !isNaN(negotiatedPrice as number) ? negotiatedPrice : undefined
- });
- }
- });
- resolve(result);
- } catch (err) {
- reject(err);
- }
- };
- reader.onerror = reject;
- reader.readAsArrayBuffer(file);
- });
- };
- /** 提交文件:前端解析 Excel,再查询匹配商品 ID */
- const submitFileForm = async () => {
- if (!importDialog.selectedFile) {
- proxy?.$modal.msgWarning('请先选择要导入的文件');
- return;
- }
- importDialog.isUploading = true;
- try {
- const parsedRows = await parseExcelData(importDialog.selectedFile);
- if (parsedRows.length === 0) {
- proxy?.$modal.msgWarning('未在文件中找到有效的商品编号,请检查模板格式');
- return;
- }
- const productNos = parsedRows.map((r) => r.productNo);
- // 按商品编号批量查询商品,获取商品对象
- const res = await listBase({ productNos, pageNum: 1, pageSize: productNos.length } as any);
- const products: BaseVO[] = res.rows || res.data || [];
- if (products.length === 0) {
- proxy?.$modal.msgWarning(`共读取 ${productNos.length} 个编号,但未匹配到任何商品`);
- return;
- }
- // 建立 productNo -> negotiatedPrice 映射
- const priceMap = new Map<string, number | undefined>();
- parsedRows.forEach((r) => priceMap.set(r.productNo, r.negotiatedPrice));
- const isProtocol = auditForm.type === '2';
- let addedCount = 0;
- products.forEach((p) => {
- const idStr = String(p.id);
- if (!tempProductIds.value.split(',').includes(idStr)) {
- tempProductIds.value = tempProductIds.value ? `${tempProductIds.value},${idStr}` : idStr;
- addedCount++;
- }
- // 无论是否已存在,有协议价时都更新
- if (isProtocol && p.productNo) {
- const price = priceMap.get(p.productNo);
- if (price !== undefined) {
- tempAgreementPrices[p.id] = price;
- }
- }
- });
- const skipped = productNos.length - products.length;
- importDialog.open = false;
- const msg = skipped > 0 ? `成功导入 ${addedCount} 个商品,${skipped} 个编号未匹配` : `成功导入 ${addedCount} 个商品到入池清单`;
- proxy?.$modal.msgSuccess(msg);
- await getList();
- } catch (err) {
- console.error('导入失败:', err);
- proxy?.$modal.msgError('解析文件失败,请检查文件格式');
- } finally {
- importDialog.isUploading = false;
- }
- };
- /** 检查营销/协议/项目产品池是否已选择对应池 */
- const checkPoolSelected = (): boolean => {
- if (auditForm.type === '4' && !auditForm.poolId) {
- proxy?.$modal.msgWarning('请先选择产品池');
- return false;
- }
- if (auditForm.type === '2' && !auditForm.protocolId) {
- proxy?.$modal.msgWarning('请先选择协议');
- return false;
- }
- if (auditForm.type === '3' && !auditForm.itemId) {
- proxy?.$modal.msgWarning('请先选择项目');
- return false;
- }
- return true;
- };
- /** 添加商品 */
- const handleAddProduct = () => {
- if (!checkPoolSelected()) return;
- addProductDialog.visible = true;
- addProductQuery.value = {
- pageNum: 1,
- pageSize: 10,
- way: undefined,
- productNo: undefined,
- itemName: undefined,
- lastSeenId: undefined
- };
- addProductPageHistory.value = [];
- addProductHasMore.value = true;
- selectedProducts.value = [];
- // 清空协议价映射
- Object.keys(priceInputMap).forEach((key) => delete priceInputMap[key]);
- getProductList();
- };
- /** 获取商品列表 */
- const getProductList = async () => {
- addProductDialog.loading = true;
- try {
- // 出池模式:使用 getPoolAuditProducts 接口
- if (auditForm.applyType === 1) {
- const outParams: any = {
- pageNum: addProductQuery.value.pageNum,
- pageSize: addProductQuery.value.pageSize,
- poolId: auditForm.poolId,
- itemId: auditForm.itemId,
- type: auditForm.type,
- protocolId: auditForm.protocolId,
- productNo: addProductQuery.value.productNo,
- productName: addProductQuery.value.itemName
- };
- const res = await getPoolAuditProducts(outParams);
- if (res.rows) {
- addProductDialog.productList = res.rows;
- addProductDialog.total = res.total || 0;
- } else if (res.data) {
- addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
- addProductDialog.total = addProductDialog.productList.length;
- } else {
- addProductDialog.productList = [];
- addProductDialog.total = 0;
- }
- addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
- } else {
- // 入池模式:使用 listBase 接口
- const params = { ...addProductQuery.value };
- // 只查询已上架的商品
- params.productStatus = 1;
- // 自营池(type=0)查询非自营商品;标准产品池(type=1)或协议产品池(type=2)查询自营商品
- if (auditForm.type === '0') {
- params.isSelf = 0;
- } else if (auditForm.type === '1' || auditForm.type === '2') {
- params.isSelf = 1;
- }
- const currentPageNum = addProductQuery.value.pageNum;
- if (currentPageNum === 1) {
- delete params.lastSeenId;
- delete params.firstSeenId;
- delete params.way;
- } else {
- if (addProductQuery.value.way === 0) {
- const nextPageHistory = addProductPageHistory.value[currentPageNum];
- if (nextPageHistory) {
- params.firstSeenId = nextPageHistory.firstId;
- params.way = 0;
- }
- } else {
- const prevPageHistory = addProductPageHistory.value[currentPageNum - 1];
- if (prevPageHistory) {
- params.lastSeenId = prevPageHistory.lastId;
- params.way = 1;
- }
- }
- }
- const res = await listBase(params);
- if (res.rows) {
- addProductDialog.productList = res.rows;
- addProductDialog.total = res.total || 0;
- } else if (res.data) {
- addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
- addProductDialog.total = addProductDialog.productList.length;
- } else {
- addProductDialog.productList = [];
- addProductDialog.total = 0;
- }
- addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
- if (addProductDialog.productList.length > 0) {
- const firstItem = addProductDialog.productList[0];
- const lastItem = addProductDialog.productList[addProductDialog.productList.length - 1];
- if (addProductPageHistory.value.length <= currentPageNum) {
- addProductPageHistory.value[currentPageNum] = {
- firstId: firstItem.id,
- lastId: lastItem.id
- };
- }
- }
- }
- // 初始化协议价(只对type=2有意义,默认用官网价)
- if (auditInfo.value?.type === '2') {
- addProductDialog.productList.forEach((product) => {
- if (priceInputMap[product.id] === undefined) {
- priceInputMap[product.id] = product.standardPrice ?? product.midRangePrice ?? 0;
- }
- });
- }
- } catch (error) {
- console.error('获取商品列表失败:', error);
- addProductDialog.productList = [];
- addProductDialog.total = 0;
- } finally {
- addProductDialog.loading = false;
- }
- };
- /** 搜索商品 */
- const handleSearchProducts = () => {
- addProductQuery.value.pageNum = 1;
- addProductQuery.value.lastSeenId = undefined;
- addProductPageHistory.value = [];
- addProductHasMore.value = true;
- getProductList();
- };
- /** 选择变化 */
- const handleSelectionChange = (selection: BaseVO[]) => {
- selectedProducts.value = selection;
- };
- /** 批量加入清单(本地暂存,不调接口) */
- const handleBatchAdd = async () => {
- if (selectedProducts.value.length === 0) {
- proxy?.$modal.msgWarning('请先选择要添加的商品');
- return;
- }
- const isProtocol = auditForm.type === '2';
- let addedCount = 0;
- selectedProducts.value.forEach((product) => {
- const idStr = String(product.id);
- if (!tempProductIds.value.split(',').includes(idStr)) {
- tempProductIds.value = tempProductIds.value ? `${tempProductIds.value},${idStr}` : idStr;
- // 存储协议价(type=2 时取用户输入的值,否则取官网价)
- tempAgreementPrices[product.id] = isProtocol ? (priceInputMap[product.id] ?? product.standardPrice ?? product.midRangePrice) : undefined;
- addedCount++;
- }
- });
- if (addedCount === 0) {
- proxy?.$modal.msgWarning('选中的商品已全部在入池清单中');
- return;
- }
- proxy?.$modal.msgSuccess(`成功添加 ${addedCount} 个商品到入池清单`);
- addProductDialog.visible = false;
- selectedProducts.value = [];
- if (addProductTableRef.value) {
- addProductTableRef.value.clearSelection();
- }
- await getList();
- };
- /** 添加单个商品(本地暂存,不调接口) */
- const handleAddSingleProduct = async (row: BaseVO) => {
- const idStr = String(row.id);
- if (tempProductIds.value.split(',').includes(idStr)) {
- proxy?.$modal.msgWarning('该商品已在入池清单中');
- return;
- }
- // 项目产品池:弹出第三方产品分类弹框
- if (auditForm.type === '3') {
- thirdPartyDialog.row = row;
- thirdPartyDialog.pricingRule = '0';
- thirdPartyDialog.categoryValue = [];
- thirdPartyDialog.discountRate = undefined;
- thirdPartyDialog.negotiatedPrice = 0;
- thirdPartyDialog.minOrderQuantity = Number(row.minOrderQuantity) || 1;
- thirdPartyDialog.visible = true;
- return;
- }
- const isProtocol = auditForm.type === '2';
- tempProductIds.value = tempProductIds.value ? `${tempProductIds.value},${idStr}` : idStr;
- tempAgreementPrices[row.id] = isProtocol ? (priceInputMap[row.id] ?? row.standardPrice ?? row.midRangePrice) : undefined;
- proxy?.$modal.msgSuccess('添加成功');
- await getList();
- };
- /** 处理第三方产品分类选择变化 */
- const handleCategoryChange = (value: (string | number)[]) => {
- if (!value || value.length === 0) {
- thirdPartyDialog.discountRate = undefined;
- return;
- }
- const lastId = value[value.length - 1];
- thirdPartyDialog.discountRate = categoryDiscountMap.value[lastId];
- if (thirdPartyDialog.pricingRule === '1') {
- const memberPrice = Number(thirdPartyDialog.row?.memberPrice) || 0;
- const rate = thirdPartyDialog.discountRate || 0;
- thirdPartyDialog.negotiatedPrice = parseFloat((memberPrice * rate).toFixed(2));
- }
- };
- /** 切换价格模式时清空价格并重新计算 */
- watch(
- () => thirdPartyDialog.pricingRule,
- (mode) => {
- // 切换模式时先清空价格
- thirdPartyDialog.negotiatedPrice = 0;
- // 折扣率模式:自动计算 第三方平台售价 = 官网价 × 折扣率
- if (mode === '1') {
- const memberPrice = Number(thirdPartyDialog.row?.memberPrice) || 0;
- const rate = thirdPartyDialog.discountRate || 0;
- thirdPartyDialog.negotiatedPrice = parseFloat((memberPrice * rate).toFixed(2));
- }
- }
- );
- /** 第三方售价失焦校验:不低于最低售价,不高于官网价 */
- const handleThirdPartyPriceBlur = () => {
- const price = thirdPartyDialog.negotiatedPrice;
- const row = thirdPartyDialog.row;
- if (!row || !price || price <= 0) return;
- const minPrice = Number(row.minSellingPrice) || 0;
- const maxPrice = Number(row.memberPrice) || 0;
- if (minPrice > 0 && price < minPrice) {
- proxy?.$modal.msgWarning(`第三方售价不能低于最低售价(¥${minPrice})`);
- thirdPartyDialog.negotiatedPrice = minPrice;
- return;
- }
- if (maxPrice > 0 && price > maxPrice) {
- proxy?.$modal.msgWarning(`第三方售价不能高于官网价(¥${maxPrice})`);
- thirdPartyDialog.negotiatedPrice = maxPrice;
- }
- };
- /** 确认选择第三方产品分类,加入入池清单 */
- const handleConfirmThirdParty = async () => {
- if (!thirdPartyDialog.categoryValue || thirdPartyDialog.categoryValue.length === 0) {
- proxy?.$modal.msgWarning('请选择第三方产品分类');
- return;
- }
- if (thirdPartyDialog.negotiatedPrice <= 0) {
- proxy?.$modal.msgWarning('请输入有效的第三方平台售价');
- return;
- }
- const row = thirdPartyDialog.row!;
- const idStr = String(row.id);
- const categoryId = thirdPartyDialog.categoryValue[thirdPartyDialog.categoryValue.length - 1] ?? null;
- // 从级联选择器获取路径标签
- let categoryName = '';
- const checkedNodes = categoryRef.value?.getCheckedNodes(true);
- if (checkedNodes && checkedNodes.length > 0) {
- categoryName = (checkedNodes[0].pathLabels || []).join(' / ');
- }
- // 加入临时 ID 列表
- tempProductIds.value = tempProductIds.value ? `${tempProductIds.value},${idStr}` : idStr;
- // 存储项目商品额外信息
- tempProjectProductInfo[row.id] = {
- negotiatedPrice: thirdPartyDialog.negotiatedPrice,
- pricingRule: thirdPartyDialog.pricingRule,
- categoryId,
- categoryName,
- minOrderQuantity: thirdPartyDialog.minOrderQuantity
- };
- thirdPartyDialog.visible = false;
- proxy?.$modal.msgSuccess('添加成功');
- await getList();
- };
- /** 获取分类名称 */
- const getCategoryName = (row: BaseVO): string => {
- if (row.bottomCategoryId) {
- return getCategoryFullPath(row.bottomCategoryId);
- }
- return '-';
- };
- /** 判断商品是否已在入池清单中 */
- const isProductSelected = (id: string | number): boolean => {
- if (!tempProductIds.value) return false;
- return tempProductIds.value.split(',').includes(String(id));
- };
- /** 获取当前产品池类型标签 */
- const getPoolTypeLabel = (): string => {
- const typeMap: Record<string, string> = {
- '0': '自营池',
- '1': '标准池',
- '2': '协议池',
- '3': '项目池',
- '4': '营销池'
- };
- return typeMap[auditForm.type || ''] || '';
- };
- /** 协议价失焦校验 */
- const handleAgreementPriceBlur = (row: BaseVO) => {
- const price = tempAgreementPrices[row.id];
- if (price === undefined || price === null || price <= 0) {
- proxy?.$modal.msgWarning('协议价不能为0');
- tempAgreementPrices[row.id] = undefined;
- return;
- }
- const minPrice = Number(row.minSellingPrice) || 0;
- const maxPrice = Number(row.memberPrice) || 0;
- if (minPrice > 0 && price < minPrice) {
- proxy?.$modal.msgWarning(`协议价不能低于最低售价(¥${minPrice})`);
- tempAgreementPrices[row.id] = minPrice;
- return;
- }
- if (maxPrice > 0 && price > maxPrice) {
- proxy?.$modal.msgWarning(`协议价不能高于官网价(¥${maxPrice})`);
- tempAgreementPrices[row.id] = maxPrice;
- }
- };
- /** 移除商品(本地移除) */
- const handleRemoveProduct = async (row: BaseVO) => {
- await proxy?.$modal.confirm('确认要移除该商品吗?');
- const idStr = String(row.id);
- const idsArray = tempProductIds.value.split(',').filter((id) => id);
- const idx = idsArray.indexOf(idStr);
- if (idx !== -1) {
- idsArray.splice(idx, 1);
- tempProductIds.value = idsArray.join(',');
- delete tempAgreementPrices[row.id];
- }
- proxy?.$modal.msgSuccess('移除成功');
- await getList();
- };
- onMounted(async () => {
- // 新增场景:有路由 type 但无 id,自动填充并加载选项
- const hasId = route.params.id || route.query.id;
- if (!hasId && typeDisabled.value && routeType.value) {
- isInitializing.value = true;
- auditForm.type = routeType.value;
- isInitializing.value = false;
- await loadSelectOptions(routeType.value);
- }
- loadAuditInfo();
- getCategoryTree();
- getSupplierList();
- getList();
- loadBrandOptions();
- });
- </script>
- <style scoped lang="scss">
- .add-product-dialog {
- :deep(.el-form--inline .el-form-item) {
- margin-right: 10px;
- }
- }
- </style>
|