index.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. <template>
  2. <div class="p-2">
  3. <!-- 返回按钮 -->
  4. <div class="mb-4 flex items-center">
  5. <el-button link icon="ArrowLeft" @click="goBack">返回</el-button>
  6. <span class="ml-2 text-lg font-bold">商品配置</span>
  7. </div>
  8. <!-- 搜索区域 -->
  9. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  10. <div v-show="showSearch" class="mb-[10px]">
  11. <el-card shadow="hover">
  12. <el-form ref="queryFormRef" :model="queryParams" label-width="90px">
  13. <el-row :gutter="20">
  14. <el-col :span="6">
  15. <el-form-item label="商品编号" prop="productNo">
  16. <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable @keyup.enter="handleQuery" />
  17. </el-form-item>
  18. </el-col>
  19. <el-col :span="6">
  20. <el-form-item label="商品名称" prop="itemName">
  21. <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable @keyup.enter="handleQuery" />
  22. </el-form-item>
  23. </el-col>
  24. <el-col :span="6">
  25. <el-form-item label="商品品牌" prop="brandId">
  26. <el-input v-model="queryParams.brandId" placeholder="请选择" clearable />
  27. </el-form-item>
  28. </el-col>
  29. <el-col :span="6">
  30. <el-form-item label="上下架状态" prop="productStatus">
  31. <el-select v-model="queryParams.productStatus" placeholder="请选择" clearable>
  32. <el-option label="上架" value="1" />
  33. <el-option label="下架" value="0" />
  34. </el-select>
  35. </el-form-item>
  36. </el-col>
  37. </el-row>
  38. <el-row :gutter="20">
  39. <el-col :span="6">
  40. <el-form-item label="商品类别" prop="categoryId">
  41. <el-tree-select
  42. v-model="queryParams.categoryId"
  43. :data="categoryOptions"
  44. :props="{ value: 'id', label: 'label', children: 'children' }"
  45. check-strictly
  46. :render-after-expand="false"
  47. clearable
  48. placeholder="请选择商品类别"
  49. >
  50. <template #default="{ data }">
  51. <span>{{ getCategoryFullPath(data.id) }}</span>
  52. </template>
  53. </el-tree-select>
  54. </el-form-item>
  55. </el-col>
  56. <el-col :span="6">
  57. <el-form-item label="创建供应商" prop="supplier">
  58. <el-input v-model="queryParams.supplier" placeholder="请选择创建供应商" clearable />
  59. </el-form-item>
  60. </el-col>
  61. <el-col :span="6">
  62. <el-form-item label="入池时间" prop="dateRange">
  63. <el-date-picker
  64. v-model="queryParams.dateRange"
  65. type="daterange"
  66. range-separator="至"
  67. start-placeholder="开始时间"
  68. end-placeholder="结束时间"
  69. style="width: 100%"
  70. />
  71. </el-form-item>
  72. </el-col>
  73. <el-col :span="6" class="text-left">
  74. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  75. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  76. </el-col>
  77. </el-row>
  78. </el-form>
  79. </el-card>
  80. </div>
  81. </transition>
  82. <el-card shadow="never">
  83. <template #header>
  84. <div class="flex justify-between items-center">
  85. <span class="font-bold">商品列表信息列表</span>
  86. <div class="flex gap-2">
  87. <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
  88. </div>
  89. </div>
  90. </template>
  91. <el-table v-loading="loading" border :data="productList">
  92. <el-table-column type="index" label="序号" width="60" align="center" />
  93. <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
  94. <el-table-column label="商品图片" align="center" prop="productImageUrl" width="100">
  95. <template #default="scope">
  96. <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
  97. </template>
  98. </el-table-column>
  99. <el-table-column label="商品信息" align="center" min-width="200">
  100. <template #default="scope">
  101. <div class="text-left" style="font-size: 12px;">
  102. <div>{{ scope.row.itemName }}</div>
  103. <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
  104. <div class="text-gray-500">库存:{{ scope.row.stock || '999' }}</div>
  105. </div>
  106. </template>
  107. </el-table-column>
  108. <el-table-column label="商品类别" align="center" width="150">
  109. <template #default="scope">
  110. <div class="text-left" style="font-size: 12px;">
  111. <div>{{ scope.row.categoryName || '办公设备+扫描设备+平板式扫描仪' }}</div>
  112. </div>
  113. </template>
  114. </el-table-column>
  115. <el-table-column label="单位" align="center" prop="unitName" width="80" />
  116. <el-table-column label="SKU价格" align="center" width="150">
  117. <template #default="scope">
  118. <div class="text-left" style="font-size: 12px;">
  119. <div>
  120. <span class="text-gray-500">市场价:</span>
  121. <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
  122. </div>
  123. <div>
  124. <span class="text-gray-500">平台售价:</span>
  125. <span class="text-red-500">¥{{ scope.row.platformPrice || '0.00' }}</span>
  126. </div>
  127. <div>
  128. <span class="text-gray-500">最低售价:</span>
  129. <span class="text-red-500">¥{{ scope.row.minPrice || '0.00' }}</span>
  130. </div>
  131. </div>
  132. </template>
  133. </el-table-column>
  134. <el-table-column label="成本数据" align="center" width="150">
  135. <template #default="scope">
  136. <div class="text-left" style="font-size: 12px;">
  137. <div>
  138. <span class="text-gray-500">采购价:</span>
  139. <span>¥{{ scope.row.purchasePrice || '0.00' }}</span>
  140. </div>
  141. <div>
  142. <span class="text-gray-500">暂估毛利率:</span>
  143. <span>{{ scope.row.grossMargin || '0.00' }}%</span>
  144. </div>
  145. </div>
  146. </template>
  147. </el-table-column>
  148. <el-table-column label="项目平台价" align="center" prop="platformPrice" width="100" />
  149. <el-table-column label="商品状态" align="center" width="80">
  150. <template #default="scope">
  151. <el-tag v-if="scope.row.productStatus === '1'" type="success">上架</el-tag>
  152. <el-tag v-else type="warning">下架</el-tag>
  153. </template>
  154. </el-table-column>
  155. <el-table-column label="入池时间" align="center" prop="createTime" width="120" />
  156. <el-table-column label="创建供应商" align="center" prop="supplier" width="100" />
  157. <el-table-column label="操作" align="center" width="120" fixed="right">
  158. <template #default="scope">
  159. <div class="flex flex-col gap-1">
  160. <el-link type="primary" :underline="false" @click="handlePriceMaintain(scope.row)">价格维护</el-link>
  161. <el-link type="primary" :underline="false" @click="handleInventoryMaintain(scope.row)">修改库存</el-link>
  162. <el-link type="danger" :underline="false" @click="handleRemoveProduct(scope.row)">移除商品池</el-link>
  163. </div>
  164. </template>
  165. </el-table-column>
  166. </el-table>
  167. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  168. </el-card>
  169. <!-- 价格维护对话框 -->
  170. <el-dialog title="价格维护" v-model="priceDialog.visible" width="500px" append-to-body>
  171. <el-form ref="priceFormRef" :model="priceDialog.form" :rules="priceRules" label-width="100px">
  172. <el-form-item label="商品名称">
  173. <span>{{ priceDialog.row?.itemName }}</span>
  174. </el-form-item>
  175. <el-form-item label="市场价" prop="marketPrice">
  176. <el-input-number v-model="priceDialog.form.marketPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
  177. </el-form-item>
  178. <el-form-item label="平台售价" prop="platformPrice">
  179. <el-input-number v-model="priceDialog.form.platformPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
  180. </el-form-item>
  181. <el-form-item label="最低售价" prop="minPrice">
  182. <el-input-number v-model="priceDialog.form.minPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
  183. </el-form-item>
  184. <el-form-item label="产品价格" prop="productPrice">
  185. <el-input-number v-model="priceDialog.form.productPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
  186. </el-form-item>
  187. </el-form>
  188. <template #footer>
  189. <el-button @click="priceDialog.visible = false">取 消</el-button>
  190. <el-button type="primary" @click="submitPriceForm">确 定</el-button>
  191. </template>
  192. </el-dialog>
  193. <!-- 修改库存对话框 -->
  194. <el-dialog title="修改库存" v-model="stockDialog.visible" width="500px" append-to-body>
  195. <el-form ref="stockFormRef" :model="stockDialog.form" :rules="stockRules" label-width="100px">
  196. <el-form-item label="商品名称">
  197. <span>{{ stockDialog.row?.itemName }}</span>
  198. </el-form-item>
  199. <el-form-item label="当前库存">
  200. <span>{{ stockDialog.row?.stock || 0 }}</span>
  201. </el-form-item>
  202. <el-form-item label="库存数量" prop="stock">
  203. <el-input-number v-model="stockDialog.form.stock" :min="0" :precision="0" controls-position="right" style="width: 100%" />
  204. </el-form-item>
  205. </el-form>
  206. <template #footer>
  207. <el-button @click="stockDialog.visible = false">取 消</el-button>
  208. <el-button type="primary" @click="submitStockForm">确 定</el-button>
  209. </template>
  210. </el-dialog>
  211. <!-- 添加商品对话框 -->
  212. <el-dialog title="添加商品" v-model="addProductDialog.visible" width="1400px" append-to-body top="5vh">
  213. <div class="add-product-dialog">
  214. <!-- 搜索区域 -->
  215. <el-form :model="addProductQuery" :inline="true" class="mb-4">
  216. <el-form-item>
  217. <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
  218. </el-form-item>
  219. <el-form-item label="商品名称:">
  220. <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 200px" />
  221. </el-form-item>
  222. <el-form-item label="商品编号:">
  223. <el-input v-model="addProductQuery.productNo" placeholder="商品编号" clearable style="width: 200px" />
  224. </el-form-item>
  225. <el-form-item>
  226. <el-button type="primary" icon="Search" @click="handleSearchProducts">搜索</el-button>
  227. </el-form-item>
  228. </el-form>
  229. <!-- 商品列表 -->
  230. <el-table
  231. ref="addProductTableRef"
  232. v-loading="addProductDialog.loading"
  233. :data="addProductDialog.productList"
  234. border
  235. @selection-change="handleSelectionChange"
  236. max-height="500"
  237. >
  238. <el-table-column type="selection" width="55" align="center" />
  239. <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
  240. <el-table-column label="商品图片" align="center" prop="productImageUrl" width="100">
  241. <template #default="scope">
  242. <image-preview :src="scope.row.productImageUrl" :width="60" :height="60"/>
  243. </template>
  244. </el-table-column>
  245. <el-table-column label="商品信息" align="center" min-width="200">
  246. <template #default="scope">
  247. <div class="text-left" style="font-size: 12px;">
  248. <div>{{ scope.row.itemName }}</div>
  249. <div class="text-gray-500">品牌:{{ scope.row.brandName || '雅唐' }}</div>
  250. </div>
  251. </template>
  252. </el-table-column>
  253. <el-table-column label="商品分类" align="center" width="150">
  254. <template #default="scope">
  255. <div class="text-left" style="font-size: 12px;">
  256. <div>{{ getCategoryName(scope.row) }}</div>
  257. </div>
  258. </template>
  259. </el-table-column>
  260. <el-table-column label="单位" align="center" width="100">
  261. <template #default="scope">
  262. <div class="text-left" style="font-size: 12px;">
  263. <div>单位:{{ scope.row.unitName || '件' }}</div>
  264. <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
  265. </div>
  266. </template>
  267. </el-table-column>
  268. <el-table-column label="SKU价格" align="center" width="150">
  269. <template #default="scope">
  270. <div class="text-left" style="font-size: 12px;">
  271. <div>
  272. <span class="text-gray-500">市场价:</span>
  273. <span>¥{{ scope.row.midRangePrice || '0.00' }}</span>
  274. </div>
  275. <div>
  276. <span class="text-gray-500">平台价:</span>
  277. <span class="text-red-500">¥{{ scope.row.standardPrice || '0.00' }}</span>
  278. </div>
  279. <div>
  280. <span class="text-gray-500">最低价:</span>
  281. <span>¥{{ scope.row.certificatePrice || '0.00' }}</span>
  282. </div>
  283. </div>
  284. </template>
  285. </el-table-column>
  286. <el-table-column label="库存情况" align="center" width="150">
  287. <template #default="scope">
  288. <div class="text-left" style="font-size: 12px;">
  289. <div class="text-red-500">库存总数:{{ scope.row.stock || 0 }}</div>
  290. <div>现有库存:{{ scope.row.availableStock || 0 }}</div>
  291. <div>虚拟库存:{{ scope.row.virtualStock || 0 }}</div>
  292. <div class="text-orange-500">[现有库存不足时]</div>
  293. </div>
  294. </template>
  295. </el-table-column>
  296. <el-table-column label="供应情况" align="center" width="150">
  297. <template #default="scope">
  298. <div class="text-left" style="font-size: 12px;">
  299. <div>供应商数量:{{ scope.row.supplierCount || 0 }}</div>
  300. </div>
  301. </template>
  302. </el-table-column>
  303. <el-table-column label="协议价" align="center" prop="agreementPrice" width="100" />
  304. <el-table-column label="操作" align="center" width="100" fixed="right">
  305. <template #default="scope">
  306. <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)">加入清单</el-link>
  307. </template>
  308. </el-table-column>
  309. </el-table>
  310. <!-- 游标分页控制 -->
  311. <pagination
  312. v-show="addProductDialog.productList.length > 0"
  313. v-model:page="addProductQuery.pageNum"
  314. v-model:limit="addProductQuery.pageSize"
  315. v-model:way="addProductQuery.way"
  316. :cursor-mode="true"
  317. :has-more="addProductHasMore"
  318. @pagination="getProductList"
  319. />
  320. </div>
  321. </el-dialog>
  322. </div>
  323. </template>
  324. <script setup name="PoolLink" lang="ts">
  325. import { useRouter, useRoute } from 'vue-router';
  326. import { categoryTree, listBase } from '@/api/pmsProduct/base';
  327. import { BaseVO, BaseQuery } from '@/api/pmsProduct/base/types';
  328. import { listPoolLink, batchAddProducts, BatchAddProductData, editPrice, editStock, delPoolLink, PoolLinkForm } from '@/api/pmsProduct/poolLink';
  329. import { PoolLinkQuery, PoolLinkVO } from '@/api/pmsProduct/poolLink/types';
  330. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  331. const router = useRouter();
  332. const route = useRoute();
  333. const productList = ref<PoolLinkVO[]>([]);
  334. const loading = ref(false);
  335. const showSearch = ref(true);
  336. const total = ref(0);
  337. const queryFormRef = ref<ElFormInstance>();
  338. const queryParams = ref({
  339. pageNum: 1,
  340. pageSize: 10,
  341. poolId: (route.params.id || route.query.poolId) as string | number,
  342. productNo: undefined,
  343. itemName: undefined,
  344. brandId: undefined,
  345. productStatus: undefined,
  346. categoryId: undefined,
  347. supplier: undefined,
  348. dateRange: undefined,
  349. });
  350. const categoryOptions = ref<any[]>([]);
  351. const categoryMap = ref<Map<string | number, any>>(new Map());
  352. // 添加商品对话框
  353. const addProductDialog = reactive({
  354. visible: false,
  355. loading: false,
  356. productList: [] as BaseVO[],
  357. total: 0,
  358. });
  359. // 游标分页相关变量
  360. const addProductHasMore = ref(true); // 是否还有更多数据
  361. const addProductPageHistory = ref<Array<{ firstId: string | number; lastId: string | number }>>([]);
  362. // 添加商品查询参数
  363. const addProductQuery = ref<BaseQuery>({
  364. pageNum: 1,
  365. pageSize: 10,
  366. way: undefined,
  367. productNo: undefined,
  368. itemName: undefined,
  369. lastSeenId: undefined, // 游标分页的lastSeenId
  370. });
  371. // 选中的商品
  372. const selectedProducts = ref<BaseVO[]>([]);
  373. const addProductTableRef = ref<any>();
  374. /** 获取分类树 */
  375. const getCategoryTree = async () => {
  376. try {
  377. const res = await categoryTree();
  378. categoryOptions.value = res.data || [];
  379. // 构建分类映射
  380. buildCategoryMap(categoryOptions.value);
  381. } catch (error) {
  382. console.error('获取分类树失败:', error);
  383. }
  384. };
  385. /** 构建分类映射 */
  386. const buildCategoryMap = (categories: any[], parentPath = '') => {
  387. categories.forEach(category => {
  388. const fullPath = parentPath ? `${parentPath} > ${category.label}` : category.label;
  389. categoryMap.value.set(category.id, { ...category, fullPath });
  390. if (category.children && category.children.length > 0) {
  391. buildCategoryMap(category.children, fullPath);
  392. }
  393. });
  394. };
  395. /** 获取分类完整路径 */
  396. const getCategoryFullPath = (categoryId: string | number): string => {
  397. const category = categoryMap.value.get(categoryId);
  398. return category?.fullPath || '';
  399. };
  400. /** 查询商品列表 */
  401. const getList = async () => {
  402. loading.value = true;
  403. try {
  404. const query: PoolLinkQuery = {
  405. pageNum: queryParams.value.pageNum,
  406. pageSize: queryParams.value.pageSize,
  407. poolId: queryParams.value.poolId as string | number,
  408. productNo: queryParams.value.productNo,
  409. itemName: queryParams.value.itemName,
  410. brandId: queryParams.value.brandId,
  411. categoryId: queryParams.value.categoryId,
  412. productStatus: queryParams.value.productStatus,
  413. supplier: queryParams.value.supplier
  414. };
  415. // 处理日期范围
  416. if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
  417. query.params = {
  418. beginCreateTime: queryParams.value.dateRange[0],
  419. endCreateTime: queryParams.value.dateRange[1]
  420. };
  421. }
  422. const res = await listPoolLink(query);
  423. productList.value = res.rows || [];
  424. total.value = res.total || 0;
  425. } catch (error) {
  426. console.error('获取商品列表失败:', error);
  427. productList.value = [];
  428. total.value = 0;
  429. } finally {
  430. loading.value = false;
  431. }
  432. }
  433. /** 返回 */
  434. const goBack = () => {
  435. router.back();
  436. }
  437. /** 搜索 */
  438. const handleQuery = () => {
  439. queryParams.value.pageNum = 1;
  440. getList();
  441. }
  442. /** 重置 */
  443. const resetQuery = () => {
  444. queryFormRef.value?.resetFields();
  445. handleQuery();
  446. }
  447. /** 清空产品池 */
  448. const handleClearPool = async () => {
  449. await proxy?.$modal.confirm('确认要清空该产品池的所有商品吗?');
  450. // TODO: 调用清空API
  451. proxy?.$modal.msgSuccess('清空成功');
  452. await getList();
  453. }
  454. /** 添加商品 */
  455. const handleAddProduct = () => {
  456. addProductDialog.visible = true;
  457. // 重置查询条件
  458. addProductQuery.value = {
  459. pageNum: 1,
  460. pageSize: 10,
  461. way: undefined,
  462. productNo: undefined,
  463. itemName: undefined,
  464. lastSeenId: undefined,
  465. };
  466. // 重置游标分页状态
  467. addProductPageHistory.value = [];
  468. addProductHasMore.value = true;
  469. selectedProducts.value = [];
  470. getProductList();
  471. };
  472. /** 获取商品列表 */
  473. const getProductList = async () => {
  474. addProductDialog.loading = true;
  475. try {
  476. const params = { ...addProductQuery.value };
  477. const currentPageNum = addProductQuery.value.pageNum;
  478. // 第一页不需要游标参数
  479. if (currentPageNum === 1) {
  480. delete params.lastSeenId;
  481. delete params.firstSeenId;
  482. delete params.way;
  483. } else {
  484. // way参数:0=上一页,1=下一页
  485. if (addProductQuery.value.way === 0) {
  486. // 上一页:使用目标页的firstId
  487. const nextPageHistory = addProductPageHistory.value[currentPageNum];
  488. if (nextPageHistory) {
  489. params.firstSeenId = nextPageHistory.firstId;
  490. params.way = 0;
  491. }
  492. } else {
  493. // 下一页:使用前一页的lastId作为lastSeenId
  494. const prevPageHistory = addProductPageHistory.value[currentPageNum - 1];
  495. if (prevPageHistory) {
  496. params.lastSeenId = prevPageHistory.lastId;
  497. params.way = 1;
  498. }
  499. }
  500. }
  501. const res = await listBase(params);
  502. // 兼容两种返回结构
  503. if (res.rows) {
  504. addProductDialog.productList = res.rows;
  505. addProductDialog.total = res.total || 0;
  506. } else if (res.data) {
  507. addProductDialog.productList = Array.isArray(res.data) ? res.data : [];
  508. addProductDialog.total = addProductDialog.productList.length;
  509. } else {
  510. addProductDialog.productList = [];
  511. addProductDialog.total = 0;
  512. }
  513. // 判断是否还有更多数据
  514. addProductHasMore.value = addProductDialog.productList.length === addProductQuery.value.pageSize;
  515. // 记录当前页的第一个id和最后一个id
  516. if (addProductDialog.productList.length > 0) {
  517. const firstItem = addProductDialog.productList[0];
  518. const lastItem = addProductDialog.productList[addProductDialog.productList.length - 1];
  519. // 如果长度小于currentPageNum则创建
  520. if (addProductPageHistory.value.length <= currentPageNum) {
  521. addProductPageHistory.value[currentPageNum] = {
  522. firstId: firstItem.id,
  523. lastId: lastItem.id
  524. };
  525. }
  526. }
  527. } catch (error) {
  528. console.error('获取商品列表失败:', error);
  529. addProductDialog.productList = [];
  530. addProductDialog.total = 0;
  531. } finally {
  532. addProductDialog.loading = false;
  533. }
  534. };
  535. /** 搜索商品 */
  536. const handleSearchProducts = () => {
  537. addProductQuery.value.pageNum = 1;
  538. addProductQuery.value.lastSeenId = undefined;
  539. addProductPageHistory.value = []; // 重置页面历史
  540. addProductHasMore.value = true;
  541. getProductList();
  542. };
  543. /** 选择变化 */
  544. const handleSelectionChange = (selection: BaseVO[]) => {
  545. selectedProducts.value = selection;
  546. };
  547. /** 批量加入清单 */
  548. const handleBatchAdd = async () => {
  549. if (selectedProducts.value.length === 0) {
  550. proxy?.$modal.msgWarning('请先选择要添加的商品');
  551. return;
  552. }
  553. try {
  554. // 构造批量添加的数据
  555. const batchData: BatchAddProductData = {
  556. poolId: queryParams.value.poolId,
  557. products: selectedProducts.value.map(product => ({
  558. productId: product.id,
  559. agreementPrice: product.standardPrice || product.midRangePrice // 使用平台价或市场价作为协议价
  560. }))
  561. };
  562. await batchAddProducts(batchData);
  563. proxy?.$modal.msgSuccess(`成功添加 ${selectedProducts.value.length} 个商品到商品池`);
  564. addProductDialog.visible = false;
  565. // 清空选中项
  566. selectedProducts.value = [];
  567. if (addProductTableRef.value) {
  568. addProductTableRef.value.clearSelection();
  569. }
  570. await getList();
  571. } catch (error) {
  572. console.error('添加商品失败:', error);
  573. }
  574. };
  575. /** 添加单个商品 */
  576. const handleAddSingleProduct = async (row: BaseVO) => {
  577. try {
  578. // 构造单个商品添加的数据
  579. const batchData: BatchAddProductData = {
  580. poolId: queryParams.value.poolId,
  581. products: [{
  582. productId: row.id,
  583. agreementPrice: row.standardPrice || row.midRangePrice // 使用平台价或市场价作为协议价
  584. }]
  585. };
  586. await batchAddProducts(batchData);
  587. proxy?.$modal.msgSuccess('添加成功');
  588. // 不关闭对话框,允许继续添加
  589. await getList();
  590. } catch (error) {
  591. console.error('添加商品失败:', error);
  592. }
  593. };
  594. /** 获取分类名称 */
  595. const getCategoryName = (row: BaseVO): string => {
  596. // 优先使用完整路径
  597. if (row.bottomCategoryId) {
  598. return getCategoryFullPath(row.bottomCategoryId);
  599. }
  600. return '-';
  601. };
  602. /** 导出商品 */
  603. const handleExportProduct = () => {
  604. // TODO: 导出功能
  605. proxy?.$modal.msg('导出商品功能');
  606. }
  607. /** 导入商品 */
  608. const handleImportProduct = () => {
  609. // TODO: 导入功能
  610. proxy?.$modal.msg('导入商品功能');
  611. }
  612. // ========== 价格维护相关 ==========
  613. const priceDialog = reactive({
  614. visible: false,
  615. row: null as PoolLinkVO | null,
  616. form: {
  617. marketPrice: 0,
  618. platformPrice: 0,
  619. minPrice: 0,
  620. productPrice: 0
  621. }
  622. });
  623. const priceFormRef = ref<ElFormInstance>();
  624. const priceRules = reactive({
  625. productPrice: [{ required: true, message: '请输入产品价格', trigger: 'blur' }]
  626. });
  627. /** 价格维护 */
  628. const handlePriceMaintain = (row: PoolLinkVO) => {
  629. priceDialog.row = row;
  630. priceDialog.form = {
  631. marketPrice: row.marketPrice || 0,
  632. platformPrice: row.platformPrice || 0,
  633. minPrice: row.minPrice || 0,
  634. productPrice: row.productPrice || 0
  635. };
  636. priceDialog.visible = true;
  637. };
  638. /** 提交价格表单 */
  639. const submitPriceForm = async () => {
  640. await priceFormRef.value?.validate();
  641. const data: PoolLinkForm = {
  642. id: priceDialog.row?.id,
  643. productPrice: priceDialog.form.productPrice
  644. };
  645. await editPrice(data);
  646. proxy?.$modal.msgSuccess('价格修改成功');
  647. priceDialog.visible = false;
  648. await getList();
  649. };
  650. // ========== 库存维护相关 ==========
  651. const stockDialog = reactive({
  652. visible: false,
  653. row: null as PoolLinkVO | null,
  654. form: {
  655. stock: 0
  656. }
  657. });
  658. const stockFormRef = ref<ElFormInstance>();
  659. const stockRules = reactive({
  660. stock: [{ required: true, message: '请输入库存数量', trigger: 'blur' }]
  661. });
  662. /** 修改库存 */
  663. const handleInventoryMaintain = (row: PoolLinkVO) => {
  664. stockDialog.row = row;
  665. stockDialog.form = {
  666. stock: row.stock || 0
  667. };
  668. stockDialog.visible = true;
  669. };
  670. /** 提交库存表单 */
  671. const submitStockForm = async () => {
  672. await stockFormRef.value?.validate();
  673. const data: PoolLinkForm = {
  674. id: stockDialog.row?.id,
  675. stock: stockDialog.form.stock
  676. };
  677. await editStock(data);
  678. proxy?.$modal.msgSuccess('库存修改成功');
  679. stockDialog.visible = false;
  680. await getList();
  681. };
  682. /** 移除商品池 */
  683. const handleRemoveProduct = async (row: PoolLinkVO) => {
  684. await proxy?.$modal.confirm('确认要移除该商品吗?');
  685. await delPoolLink(row.id);
  686. proxy?.$modal.msgSuccess('移除成功');
  687. await getList();
  688. }
  689. onMounted(() => {
  690. getCategoryTree();
  691. getList();
  692. });
  693. </script>
  694. <style scoped lang="scss">
  695. .add-product-dialog {
  696. :deep(.el-form--inline .el-form-item) {
  697. margin-right: 10px;
  698. }
  699. }
  700. </style>