|
|
@@ -0,0 +1,1037 @@
|
|
|
+<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%">
|
|
|
+ <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.protocolNo} - ${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="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 type="index" label="序号" width="60" 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="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="text-left" style="font-size: 12px;">
|
|
|
+ <div>{{ scope.row.itemName }}</div>
|
|
|
+ <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
|
|
|
+ <div class="text-gray-500">库存:{{ scope.row.stock || '999' }}</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>{{ scope.row.categoryName || getCategoryFullPath(scope.row.bottomCategoryId) || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="单位" align="center" prop="unitName" width="80" />
|
|
|
+ <el-table-column label="SKU价格" align="center" width="150">
|
|
|
+ <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="请输入协议价"
|
|
|
+ />
|
|
|
+ </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.purchasePrice || '0.00' }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span class="text-gray-500">暂估毛利率:</span>
|
|
|
+ <span>{{ scope.row.grossMargin ?? scope.row.tempGrossMargin ?? '0.00' }}%</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="商品状态" align="center" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag v-if="String(scope.row.productStatus) === '1'" type="success">上架</el-tag>
|
|
|
+ <el-tag v-else type="warning">下架</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="入池时间" align="center" prop="createTime" width="120" />
|
|
|
+ <el-table-column label="创建供应商" align="center" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ getSupplierName(scope.row.supplier) }}</span>
|
|
|
+ </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>
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="商品名称:">
|
|
|
+ <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 200px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="商品编号:">
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <!-- 商品列表 -->
|
|
|
+ <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="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="text-left" style="font-size: 12px;">
|
|
|
+ <div>{{ scope.row.itemName }}</div>
|
|
|
+ <div class="text-gray-500">品牌:{{ scope.row.brandName || '雅唐' }}</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>{{ getCategoryName(scope.row) }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="单位" align="center" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="text-left" style="font-size: 12px;">
|
|
|
+ <div>单位:{{ scope.row.unitName || '件' }}</div>
|
|
|
+ <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="SKU价格" 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.midRangePrice || '0.00' }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span class="text-gray-500">官网价:</span>
|
|
|
+ <span class="text-red-500">¥{{ scope.row.standardPrice || '0.00' }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span class="text-gray-500">最低价:</span>
|
|
|
+ <span>¥{{ scope.row.certificatePrice || '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="priceInputMap[scope.row.id]"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ :controls="false"
|
|
|
+ size="small"
|
|
|
+ style="width: 120px"
|
|
|
+ placeholder="请输入协议价"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="库存情况" align="center" width="150">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="text-left" style="font-size: 12px;">
|
|
|
+ <div class="text-red-500">库存总数:{{ scope.row.stock || 0 }}</div>
|
|
|
+ <div>现有库存:{{ scope.row.availableStock || 0 }}</div>
|
|
|
+ <div>虚拟库存:{{ scope.row.virtualStock || 0 }}</div>
|
|
|
+ <div class="text-orange-500">[现有库存不足时]</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>供应商数量:{{ scope.row.supplierCount || 0 }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" align="center" width="100" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)" :disabled="tempProductIds.includes(scope.row.id)">加入清单</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>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="PoolLinkAudit" lang="ts">
|
|
|
+import { useRouter, useRoute } from 'vue-router';
|
|
|
+import { categoryTree, listBase } from '@/api/product/base';
|
|
|
+import { BaseVO, BaseQuery } from '@/api/product/base/types';
|
|
|
+import { getPoolAudit, updatePoolAudit, addPoolAudit } from '@/api/product/poolAudit';
|
|
|
+import { getPoolLinkAudit } from '@/api/product/poolLinkAudit';
|
|
|
+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';
|
|
|
+
|
|
|
+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>({
|
|
|
+ type: undefined,
|
|
|
+ name: undefined,
|
|
|
+ poolId: undefined,
|
|
|
+ protocolId: undefined,
|
|
|
+ itemId: undefined,
|
|
|
+ remark: undefined,
|
|
|
+ attachment: undefined,
|
|
|
+ productIds: []
|
|
|
+});
|
|
|
+
|
|
|
+/** 下拉选项数据 */
|
|
|
+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;
|
|
|
+ if (newType !== undefined && newType !== null) {
|
|
|
+ loadSelectOptions(newType);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+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 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<Array<string | number>>([]);
|
|
|
+// 临时展示用:本地暂存的协议价(type=2)
|
|
|
+const tempAgreementPrices = reactive<Record<string | number, number | undefined>>({})
|
|
|
+
|
|
|
+/** 获取审核状态标签文字 */
|
|
|
+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 {
|
|
|
+ const res = await getPoolLinkAudit(id as string | number);
|
|
|
+ const data = res.data as any;
|
|
|
+ auditInfo.value = data;
|
|
|
+
|
|
|
+ // 回显表单,先设标志位防止 watch 清空字段
|
|
|
+ isInitializing.value = true;
|
|
|
+ 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;
|
|
|
+ isInitializing.value = false;
|
|
|
+
|
|
|
+ // 加载对应下拉选项
|
|
|
+ if (auditForm.type) {
|
|
|
+ await loadSelectOptions(auditForm.type);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从 products 初始化商品 ID 列表和协议价
|
|
|
+ const products: Array<{ productId: string | number; agreementPrice?: number }> = data.products || [];
|
|
|
+ tempProductIds.value = products.map((p: any) => p.productId);
|
|
|
+ products.forEach((p: any) => {
|
|
|
+ if (p.agreementPrice !== undefined && p.agreementPrice !== null) {
|
|
|
+ tempAgreementPrices[p.productId] = p.agreementPrice;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 刷新商品列表
|
|
|
+ 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.length === 0) {
|
|
|
+ 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.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: '1' });
|
|
|
+ proxy?.$modal.msgSuccess('申请成功,状态已变为待审核');
|
|
|
+ await loadAuditInfo();
|
|
|
+};
|
|
|
+
|
|
|
+/** 确定按钮 - 提交产品池审核单据 */
|
|
|
+const handleSubmitAudit = async () => {
|
|
|
+ if (!auditForm.type) {
|
|
|
+ proxy?.$modal.msgWarning('请选择产品池类型');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ((auditForm.type === '0' || 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 (auditForm.type === '1' && !auditForm.name) {
|
|
|
+ proxy?.$modal.msgWarning('请输入产品池名称');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (tempProductIds.value.length === 0) {
|
|
|
+ proxy?.$modal.msgWarning('请先添加商品到入池清单');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await proxy?.$modal.confirm('确认要提交产品池审核单据吗?');
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 提交整个审核单据(包含本地暂存的商品ID及协议价)
|
|
|
+ const submitData: PoolAuditForm = {
|
|
|
+ ...auditForm,
|
|
|
+ products: tempProductIds.value.map(id => ({
|
|
|
+ productId: id,
|
|
|
+ negotiatedPrice: tempAgreementPrices[id]
|
|
|
+ })),
|
|
|
+ productReviewStatus: '1' // 提交后状态为待审核
|
|
|
+ };
|
|
|
+
|
|
|
+ await addPoolAudit(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 = () => {
|
|
|
+ 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;
|
|
|
+ // 查找「协议价格」列索引
|
|
|
+ let 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) => {
|
|
|
+ if (!tempProductIds.value.includes(p.id)) {
|
|
|
+ tempProductIds.value.push(p.id);
|
|
|
+ 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 handleAddProduct = () => {
|
|
|
+ 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 {
|
|
|
+ const params = { ...addProductQuery.value };
|
|
|
+ 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 => {
|
|
|
+ if (!tempProductIds.value.includes(product.id)) {
|
|
|
+ tempProductIds.value.push(product.id);
|
|
|
+ // 存储协议价(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) => {
|
|
|
+ if (tempProductIds.value.includes(row.id)) {
|
|
|
+ proxy?.$modal.msgWarning('该商品已在入池清单中');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const isProtocol = auditForm.type === '2';
|
|
|
+ tempProductIds.value.push(row.id);
|
|
|
+ tempAgreementPrices[row.id] = isProtocol
|
|
|
+ ? (priceInputMap[row.id] ?? row.standardPrice ?? row.midRangePrice)
|
|
|
+ : undefined;
|
|
|
+
|
|
|
+ proxy?.$modal.msgSuccess('添加成功');
|
|
|
+ await getList();
|
|
|
+};
|
|
|
+
|
|
|
+/** 获取分类名称 */
|
|
|
+const getCategoryName = (row: BaseVO): string => {
|
|
|
+ if (row.bottomCategoryId) {
|
|
|
+ return getCategoryFullPath(row.bottomCategoryId);
|
|
|
+ }
|
|
|
+ return '-';
|
|
|
+};
|
|
|
+
|
|
|
+/** 移除商品(本地移除) */
|
|
|
+const handleRemoveProduct = async (row: BaseVO) => {
|
|
|
+ await proxy?.$modal.confirm('确认要移除该商品吗?');
|
|
|
+ const idx = tempProductIds.value.indexOf(row.id);
|
|
|
+ if (idx !== -1) {
|
|
|
+ tempProductIds.value.splice(idx, 1);
|
|
|
+ delete tempAgreementPrices[row.id];
|
|
|
+ }
|
|
|
+ proxy?.$modal.msgSuccess('移除成功');
|
|
|
+ await getList();
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadAuditInfo();
|
|
|
+ getCategoryTree();
|
|
|
+ getSupplierList();
|
|
|
+ getList();
|
|
|
+ loadBrandOptions();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.add-product-dialog {
|
|
|
+ :deep(.el-form--inline .el-form-item) {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|