productConfig.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. <template>
  2. <div class="p-2">
  3. <!-- 返回按钮 + 标题 -->
  4. <el-card shadow="never" class="mb-2">
  5. <div class="flex items-center">
  6. <el-button link @click="handleBack">
  7. <el-icon><ArrowLeft /></el-icon>
  8. 返回
  9. </el-button>
  10. <el-divider direction="vertical" />
  11. <span class="text-base font-medium">商品配置</span>
  12. </div>
  13. </el-card>
  14. <!-- 搜索区域 -->
  15. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  16. <div v-show="showSearch" class="mb-[10px]">
  17. <el-card shadow="hover">
  18. <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="70px">
  19. <el-form-item label="商品编号" prop="productNo">
  20. <el-input v-model="queryParams.productNo" placeholder="请输入商品编号" clearable style="width: 180px" @keyup.enter="handleQuery" />
  21. </el-form-item>
  22. <el-form-item label="商品名称" prop="itemName">
  23. <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable style="width: 180px" @keyup.enter="handleQuery" />
  24. </el-form-item>
  25. <el-form-item label="商品品牌" prop="brandId">
  26. <el-select v-model="queryParams.brandId" placeholder="请选择" clearable style="width: 120px">
  27. <el-option v-for="item in brandOptions" :key="item.id" :label="item.brandName" :value="item.id" />
  28. </el-select>
  29. </el-form-item>
  30. <el-form-item label="商品类别" prop="categoryId">
  31. <el-select v-model="queryParams.topCategoryId" placeholder="请选择" clearable style="width: 100px" @change="handleTopCategoryChange">
  32. <el-option v-for="item in topCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
  33. </el-select>
  34. <el-select v-model="queryParams.mediumCategoryId" placeholder="请选择" clearable style="width: 100px; margin-left: 5px" @change="handleMediumCategoryChange">
  35. <el-option v-for="item in mediumCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
  36. </el-select>
  37. <el-select v-model="queryParams.bottomCategoryId" placeholder="请选择" clearable style="width: 100px; margin-left: 5px">
  38. <el-option v-for="item in bottomCategoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
  39. </el-select>
  40. </el-form-item>
  41. <el-form-item>
  42. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  43. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  44. </el-form-item>
  45. </el-form>
  46. </el-card>
  47. </div>
  48. </transition>
  49. <!-- 表格区域 -->
  50. <el-card shadow="never">
  51. <template #header>
  52. <div class="flex justify-between items-center">
  53. <span class="font-bold text-[#409EFF]">商品列表信息列表</span>
  54. <div class="flex gap-2">
  55. <el-button type="primary" icon="Plus" @click="handleAddProduct">添加商品</el-button>
  56. <el-button type="primary" icon="Upload" @click="handleImportProduct">导入商品</el-button>
  57. <el-button type="primary" icon="Download" @click="handleExportProduct">导出商品</el-button>
  58. </div>
  59. </div>
  60. </template>
  61. <el-table v-loading="loading" border :data="productList">
  62. <el-table-column label="商品编号" align="center" prop="productNo" width="100" />
  63. <el-table-column label="商品图片" align="center" width="100">
  64. <template #default="scope">
  65. <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
  66. </template>
  67. </el-table-column>
  68. <el-table-column label="商品信息" align="center" min-width="400">
  69. <template #default="scope">
  70. <div class="text-left" style="font-size: 12px;">
  71. <div>{{ scope.row.itemName }}</div>
  72. <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
  73. </div>
  74. </template>
  75. </el-table-column>
  76. <el-table-column label="商品类别" align="center" prop="categoryName" width="120" />
  77. <el-table-column label="单位" align="center" width="100">
  78. <template #default="scope">
  79. <div style="font-size: 12px;">
  80. <div>单位:{{ scope.row.unitName || '个' }}</div>
  81. <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
  82. </div>
  83. </template>
  84. </el-table-column>
  85. <el-table-column label="SKU价格" align="center" width="130">
  86. <template #default="scope">
  87. <div class="text-left" style="font-size: 12px;">
  88. <div>市场价:¥{{ scope.row.marketPrice || '0.00' }}</div>
  89. <div class="text-[#f56c6c]">平台售价:¥{{ scope.row.platformPrice || '0.00' }}</div>
  90. <div>最低售价:¥{{ scope.row.minPrice || '0.00' }}</div>
  91. </div>
  92. </template>
  93. </el-table-column>
  94. <el-table-column label="采购价" align="center" prop="purchasePrice" width="100" />
  95. <el-table-column label="协议价" align="center" prop="agreementPrice" width="100" />
  96. <el-table-column label="毛利率" align="center" width="130">
  97. <template #default="scope">
  98. {{ calcGrossMargin(scope.row) }}
  99. </template>
  100. </el-table-column>
  101. <el-table-column label="商品状态" align="center" width="100">
  102. <template #default="scope">
  103. <el-tag v-if="scope.row.productStatus === '1'" type="success" size="small">上架</el-tag>
  104. <el-tag v-else type="info" size="small">下架</el-tag>
  105. </template>
  106. </el-table-column>
  107. <el-table-column label="操作" align="center" width="100" fixed="right">
  108. <template #default="scope">
  109. <div class="flex flex-col items-center gap-1">
  110. <el-link type="primary" :underline="false" @click="handlePriceEdit(scope.row)">价格修改</el-link>
  111. <el-link type="danger" :underline="false" @click="handleDelete(scope.row)">删 除</el-link>
  112. </div>
  113. </template>
  114. </el-table-column>
  115. </el-table>
  116. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  117. </el-card>
  118. <!-- 价格修改弹窗 -->
  119. <el-dialog title="价格修改" v-model="priceDialog.visible" width="500px" append-to-body>
  120. <el-form ref="priceFormRef" :model="priceDialog.form" :rules="priceRules" label-width="100px">
  121. <el-form-item label="商品名称">
  122. <span>{{ priceDialog.row?.itemName }}</span>
  123. </el-form-item>
  124. <el-form-item label="协议价" prop="agreementPrice">
  125. <el-input-number v-model="priceDialog.form.agreementPrice" :precision="2" :min="0" controls-position="right" style="width: 100%" />
  126. </el-form-item>
  127. </el-form>
  128. <template #footer>
  129. <el-button @click="priceDialog.visible = false">取 消</el-button>
  130. <el-button type="primary" @click="submitPriceForm">确 定</el-button>
  131. </template>
  132. </el-dialog>
  133. <!-- 导入商品弹窗 -->
  134. <el-dialog title="导入商品" v-model="importDialog.visible" width="500px" append-to-body>
  135. <el-form label-width="100px">
  136. <el-form-item label="导入模板">
  137. <el-button type="primary" link icon="Download" @click="handleDownloadTemplate">下载导入模板</el-button>
  138. </el-form-item>
  139. <el-form-item label="选择文件">
  140. <el-upload
  141. ref="importUploadRef"
  142. action="#"
  143. :auto-upload="false"
  144. :limit="1"
  145. accept=".xlsx,.xls"
  146. :on-change="handleImportFileChange"
  147. :on-remove="() => (importDialog.file = null)"
  148. >
  149. <el-button type="primary" icon="Upload">选择文件</el-button>
  150. <template #tip>
  151. <div class="el-upload__tip">只支持 .xlsx / .xls 格式文件</div>
  152. </template>
  153. </el-upload>
  154. </el-form-item>
  155. </el-form>
  156. <template #footer>
  157. <el-button @click="importDialog.visible = false">取 消</el-button>
  158. <el-button type="primary" :loading="importDialog.loading" @click="handleConfirmImport">确 定</el-button>
  159. </template>
  160. </el-dialog>
  161. <!-- 添加商品弹窗 -->
  162. <el-dialog title="添加商品" v-model="addProductDialog.visible" width="1500px" append-to-body top="5vh">
  163. <div class="add-product-dialog">
  164. <el-form :model="addProductQuery" :inline="true" class="mb-4">
  165. <el-form-item>
  166. <el-button type="primary" icon="Plus" @click="handleBatchAdd">加入清单</el-button>
  167. </el-form-item>
  168. <el-form-item label="商品名称:">
  169. <el-input v-model="addProductQuery.itemName" placeholder="商品名称" clearable style="width: 180px" />
  170. </el-form-item>
  171. <el-form-item label="商品编号:">
  172. <el-input v-model="addProductQuery.productNo" placeholder="商品编号" clearable style="width: 180px" />
  173. </el-form-item>
  174. <el-form-item>
  175. <el-button type="primary" icon="Search" @click="handleSearchProducts">搜索</el-button>
  176. </el-form-item>
  177. </el-form>
  178. <el-table
  179. ref="addProductTableRef"
  180. v-loading="addProductDialog.loading"
  181. :data="addProductDialog.productList"
  182. border
  183. @selection-change="handleSelectionChange"
  184. max-height="500"
  185. >
  186. <el-table-column type="selection" width="55" align="center" />
  187. <el-table-column label="商品编号" align="center" prop="productNo" width="100" />
  188. <el-table-column label="商品图片" align="center" width="100">
  189. <template #default="scope">
  190. <image-preview :src="scope.row.productImage" :width="60" :height="60"/>
  191. </template>
  192. </el-table-column>
  193. <el-table-column label="商品信息" align="center" min-width="400">
  194. <template #default="scope">
  195. <div class="text-left" style="font-size: 12px;">
  196. <div>{{ scope.row.itemName }}</div>
  197. <div class="text-gray-500">品牌:{{ scope.row.brandName || '-' }}</div>
  198. </div>
  199. </template>
  200. </el-table-column>
  201. <el-table-column label="商品分类" align="center" prop="categoryName" width="120" />
  202. <el-table-column label="单位" align="center" width="100">
  203. <template #default="scope">
  204. <div class="text-left" style="font-size: 12px;">
  205. <div>单位:{{ scope.row.unitName || '个' }}</div>
  206. <div>起订量:{{ scope.row.minOrderQuantity || 1 }}</div>
  207. </div>
  208. </template>
  209. </el-table-column>
  210. <el-table-column label="SKU价格" align="center" width="130">
  211. <template #default="scope">
  212. <div class="text-left" style="font-size: 12px;">
  213. <div>市场价:¥{{ scope.row.midRangePrice || '0.00' }}</div>
  214. <div class="text-[#f56c6c]">平台价:¥{{ scope.row.standardPrice || '0.00' }}</div>
  215. <div>最低价:¥{{ scope.row.certificatePrice || '0.00' }}</div>
  216. </div>
  217. </template>
  218. </el-table-column>
  219. <el-table-column label="库存情况" align="center" width="130">
  220. <template #default="scope">
  221. <div class="text-left" style="font-size: 12px;">
  222. <div class="text-[#f56c6c]">库存总数:{{ scope.row.stock || 0 }}</div>
  223. <div>现有库存:{{ scope.row.availableStock || 0 }}</div>
  224. <div>虚拟库存:{{ scope.row.virtualStock || 0 }}</div>
  225. </div>
  226. </template>
  227. </el-table-column>
  228. <el-table-column label="采购价" align="center" prop="purchasingPrice" width="80" />
  229. <el-table-column label="商品状态" align="center" prop="productStatus" width="80" >
  230. <template #default="scope">
  231. <el-tag v-if="scope.row.productStatus === '1'" type="success" size="small">上架</el-tag>
  232. <el-tag v-else type="info" size="small">下架</el-tag>
  233. </template>
  234. </el-table-column>
  235. <el-table-column label="协议价" align="center" width="150">
  236. <template #default="scope">
  237. <el-input-number
  238. v-model="scope.row.agreementPrice"
  239. :precision="2"
  240. :min="0"
  241. controls-position="right"
  242. style="width: 120px"
  243. placeholder="请输入"
  244. />
  245. </template>
  246. </el-table-column>
  247. <el-table-column label="操作" align="center" width="100" fixed="right">
  248. <template #default="scope">
  249. <el-link type="primary" :underline="false" @click="handleAddSingleProduct(scope.row)">加入清单</el-link>
  250. </template>
  251. </el-table-column>
  252. </el-table>
  253. <pagination
  254. v-show="addProductDialog.productList.length > 0"
  255. v-model:page="addProductQuery.pageNum"
  256. v-model:limit="addProductQuery.pageSize"
  257. :total="addProductDialog.total"
  258. @pagination="getProductList"
  259. />
  260. </div>
  261. </el-dialog>
  262. </div>
  263. </template>
  264. <script setup name="ProductConfig" lang="ts">
  265. import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
  266. import type { ComponentInternalInstance } from 'vue';
  267. import type { FormInstance } from 'element-plus';
  268. import { useRoute, useRouter } from 'vue-router';
  269. import { ArrowLeft } from '@element-plus/icons-vue';
  270. import { listBase } from '@/api/pmsProduct/base';
  271. import { listProductCategory } from '@/api/customerOperation/customerBlacklist';
  272. import { listBrand } from '@/api/product/brand';
  273. import {
  274. addSiteProduct,
  275. getSiteProductPage,
  276. exportSiteProductData,
  277. importSiteProductData,
  278. getSiteProductImportTemplate,
  279. updateSiteProduct,
  280. delSiteProduct
  281. } from '@/api/product/siteProduct/index';
  282. import FileSaver from 'file-saver';
  283. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  284. const route = useRoute();
  285. const router = useRouter();
  286. const productList = ref<any[]>([]);
  287. const loading = ref(false);
  288. const showSearch = ref(true);
  289. const total = ref(0);
  290. const queryFormRef = ref<ElFormInstance>();
  291. // 从路由获取站点信息
  292. const siteId = ref<string | number | undefined>(undefined);
  293. const siteName = ref<string>('');
  294. // 品牌选项
  295. const brandOptions = ref<any[]>([]);
  296. // 分类三级联动
  297. const topCategoryOptions = ref<any[]>([]);
  298. const mediumCategoryOptions = ref<any[]>([]);
  299. const bottomCategoryOptions = ref<any[]>([]);
  300. const allCategoryList = ref<any[]>([]);
  301. // 查询参数
  302. const queryParams = ref({
  303. pageNum: 1,
  304. pageSize: 10,
  305. siteId: undefined as string | number | undefined,
  306. productNo: undefined as string | undefined,
  307. itemName: undefined as string | undefined,
  308. brandId: undefined as number | undefined,
  309. topCategoryId: undefined as number | undefined,
  310. mediumCategoryId: undefined as number | undefined,
  311. bottomCategoryId: undefined as number | undefined
  312. });
  313. // 价格修改弹窗
  314. const priceDialog = reactive({
  315. visible: false,
  316. row: null as any,
  317. form: {
  318. id: undefined as number | undefined,
  319. agreementPrice: 0
  320. }
  321. });
  322. const priceFormRef = ref<FormInstance>();
  323. const priceRules = {
  324. agreementPrice: [{ required: true, message: '请输入协议价', trigger: 'blur' }]
  325. };
  326. // 添加商品弹窗
  327. const addProductDialog = reactive({
  328. visible: false,
  329. loading: false,
  330. productList: [] as any[],
  331. total: 0
  332. });
  333. const addProductQuery = ref({
  334. pageNum: 1,
  335. pageSize: 10,
  336. isSelf: 1,
  337. productNo: undefined as string | undefined,
  338. itemName: undefined as string | undefined
  339. });
  340. const selectedProducts = ref<any[]>([]);
  341. const addProductTableRef = ref<any>();
  342. // 导入商品弹窗
  343. const importDialog = reactive({
  344. visible: false,
  345. loading: false,
  346. file: null as File | null
  347. });
  348. const importUploadRef = ref<any>();
  349. /** 初始化 */
  350. const initData = () => {
  351. siteId.value = route.query.siteId as string;
  352. siteName.value = route.query.siteName as string || '';
  353. queryParams.value.siteId = siteId.value;
  354. getList();
  355. };
  356. /** 查询商品列表 */
  357. const getList = async () => {
  358. loading.value = true;
  359. try {
  360. const res = await getSiteProductPage({
  361. ...queryParams.value,
  362. bottomCategoryId: queryParams.value.bottomCategoryId || queryParams.value.mediumCategoryId || queryParams.value.topCategoryId
  363. });
  364. productList.value = res.rows || [];
  365. total.value = res.total || 0;
  366. } catch (error) {
  367. console.error('获取商品列表失败:', error);
  368. } finally {
  369. loading.value = false;
  370. }
  371. };
  372. /** 加载品牌列表 */
  373. const loadBrandOptions = async () => {
  374. try {
  375. const res = await listBrand({ pageNum: 1, pageSize: 1000 });
  376. brandOptions.value = res.rows || [];
  377. } catch (error) {
  378. console.error('获取品牌列表失败:', error);
  379. }
  380. };
  381. /** 加载商品分类 */
  382. const loadCategoryOptions = async () => {
  383. try {
  384. const res = await listProductCategory({ pageNum: 1, pageSize: 1000, dataSource: 'A10' });
  385. allCategoryList.value = res.rows || [];
  386. topCategoryOptions.value = allCategoryList.value.filter((item: any) => !item.parentId || item.parentId === 0);
  387. } catch (error) {
  388. console.error('加载分类失败:', error);
  389. }
  390. };
  391. /** 顶级分类变化 */
  392. const handleTopCategoryChange = (val: number | undefined) => {
  393. queryParams.value.mediumCategoryId = undefined;
  394. queryParams.value.bottomCategoryId = undefined;
  395. bottomCategoryOptions.value = [];
  396. if (val) {
  397. mediumCategoryOptions.value = allCategoryList.value.filter((item: any) => item.parentId === val);
  398. } else {
  399. mediumCategoryOptions.value = [];
  400. }
  401. };
  402. /** 中级分类变化 */
  403. const handleMediumCategoryChange = (val: number | undefined) => {
  404. queryParams.value.bottomCategoryId = undefined;
  405. if (val) {
  406. bottomCategoryOptions.value = allCategoryList.value.filter((item: any) => item.parentId === val);
  407. } else {
  408. bottomCategoryOptions.value = [];
  409. }
  410. };
  411. /** 计算毛利率:(协议价 - 采购价) ÷ 采购价 × 100% */
  412. const calcGrossMargin = (row: any): string => {
  413. const agreement = parseFloat(row.agreementPrice);
  414. const purchase = parseFloat(row.purchasePrice || row.purchasingPrice);
  415. if (!purchase || purchase === 0 || isNaN(agreement) || isNaN(purchase)) return '-';
  416. const margin = ((agreement - purchase) / purchase) * 100;
  417. return `${margin.toFixed(2)}%`;
  418. };
  419. /** 搜索 */
  420. const handleQuery = () => {
  421. queryParams.value.pageNum = 1;
  422. getList();
  423. };
  424. /** 重置 */
  425. const resetQuery = () => {
  426. queryFormRef.value?.resetFields();
  427. queryParams.value = {
  428. pageNum: 1,
  429. pageSize: 10,
  430. siteId: siteId.value,
  431. productNo: undefined,
  432. itemName: undefined,
  433. brandId: undefined,
  434. topCategoryId: undefined,
  435. mediumCategoryId: undefined,
  436. bottomCategoryId: undefined
  437. };
  438. mediumCategoryOptions.value = [];
  439. bottomCategoryOptions.value = [];
  440. handleQuery();
  441. };
  442. /** 返回 */
  443. const handleBack = () => {
  444. router.push('/customerOperation/vipSite');
  445. };
  446. /** 价格修改 */
  447. const handlePriceEdit = (row: any) => {
  448. priceDialog.row = row;
  449. priceDialog.form = {
  450. id: row.id,
  451. agreementPrice: row.agreementPrice || 0
  452. };
  453. priceDialog.visible = true;
  454. };
  455. /** 提交价格修改 */
  456. const submitPriceForm = async () => {
  457. await priceFormRef.value?.validate();
  458. await updateSiteProduct({
  459. id: priceDialog.form.id,
  460. agreementPrice: String(priceDialog.form.agreementPrice)
  461. });
  462. proxy?.$modal.msgSuccess('价格修改成功');
  463. priceDialog.visible = false;
  464. await getList();
  465. };
  466. /** 删除商品 */
  467. const handleDelete = async (row: any) => {
  468. await proxy?.$modal.confirm(`确认要删除商品"${row.itemName}"吗?`);
  469. await delSiteProduct(row.id);
  470. proxy?.$modal.msgSuccess('删除成功');
  471. await getList();
  472. };
  473. /** 添加商品 */
  474. const handleAddProduct = () => {
  475. addProductDialog.visible = true;
  476. addProductQuery.value = {
  477. pageNum: 1,
  478. pageSize: 10,
  479. isSelf: 1,
  480. productNo: undefined,
  481. itemName: undefined
  482. };
  483. selectedProducts.value = [];
  484. getProductList();
  485. };
  486. /** 获取可添加的商品列表 */
  487. const getProductList = async () => {
  488. addProductDialog.loading = true;
  489. try {
  490. const res = await listBase(addProductQuery.value);
  491. addProductDialog.productList = res.rows || [];
  492. addProductDialog.total = res.total || 0;
  493. } catch (error) {
  494. console.error('获取商品列表失败:', error);
  495. addProductDialog.productList = [];
  496. addProductDialog.total = 0;
  497. } finally {
  498. addProductDialog.loading = false;
  499. }
  500. };
  501. /** 搜索商品 */
  502. const handleSearchProducts = () => {
  503. addProductQuery.value.pageNum = 1;
  504. getProductList();
  505. };
  506. /** 选择变化 */
  507. const handleSelectionChange = (selection: any[]) => {
  508. selectedProducts.value = selection;
  509. };
  510. /** 批量加入清单 */
  511. const handleBatchAdd = async () => {
  512. if (selectedProducts.value.length === 0) {
  513. proxy?.$modal.msgWarning('请先选择要添加的商品');
  514. return;
  515. }
  516. // 校验是否填写协议价
  517. const noPrice = (selectedProducts.value as any[]).filter((item: any) => !item.agreementPrice || Number(item.agreementPrice) <= 0);
  518. if (noPrice.length > 0) {
  519. proxy?.$modal.msgWarning(`有 ${noPrice.length} 个商品未填写协议价,请填写后再加入清单`);
  520. return;
  521. }
  522. try {
  523. await Promise.all(
  524. (selectedProducts.value as any[]).map((item: any) =>
  525. addSiteProduct({
  526. siteId: siteId.value,
  527. productId: item.id,
  528. productNo: item.productNo,
  529. agreementPrice: String(item.agreementPrice)
  530. })
  531. )
  532. );
  533. proxy?.$modal.msgSuccess(`成功添加 ${selectedProducts.value.length} 个商品`);
  534. addProductDialog.visible = false;
  535. selectedProducts.value = [];
  536. if (addProductTableRef.value) {
  537. addProductTableRef.value.clearSelection();
  538. }
  539. await getList();
  540. } catch (error) {
  541. console.error('添加商品失败:', error);
  542. }
  543. };
  544. /** 添加单个商品 */
  545. const handleAddSingleProduct = async (row: any) => {
  546. if (!row.agreementPrice || Number(row.agreementPrice) <= 0) {
  547. proxy?.$modal.msgWarning('请先填写协议价');
  548. return;
  549. }
  550. try {
  551. await addSiteProduct({
  552. siteId: siteId.value,
  553. productId: row.id,
  554. productNo: row.productNo,
  555. agreementPrice: String(row.agreementPrice)
  556. });
  557. proxy?.$modal.msgSuccess('添加成功');
  558. await getList();
  559. } catch (error) {
  560. console.error('添加商品失败:', error);
  561. }
  562. };
  563. /** 下载导入模板 */
  564. const handleDownloadTemplate = async () => {
  565. try {
  566. const res = await getSiteProductImportTemplate();
  567. const blob = new Blob([res as any], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  568. FileSaver.saveAs(blob, '站点商品导入模板.xlsx');
  569. } catch (error) {
  570. console.error('下载模板失败:', error);
  571. }
  572. };
  573. /** 导入文件选择 */
  574. const handleImportFileChange = (file: any) => {
  575. importDialog.file = file.raw;
  576. };
  577. /** 确认导入 */
  578. const handleConfirmImport = async () => {
  579. if (!importDialog.file) {
  580. proxy?.$modal.msgWarning('请先选择要导入的文件');
  581. return;
  582. }
  583. importDialog.loading = true;
  584. try {
  585. await importSiteProductData(importDialog.file);
  586. proxy?.$modal.msgSuccess('导入成功');
  587. importDialog.visible = false;
  588. importDialog.file = null;
  589. if (importUploadRef.value) importUploadRef.value.clearFiles();
  590. await getList();
  591. } catch (error) {
  592. console.error('导入失败:', error);
  593. } finally {
  594. importDialog.loading = false;
  595. }
  596. };
  597. /** 导入商品 */
  598. const handleImportProduct = () => {
  599. importDialog.visible = true;
  600. importDialog.file = null;
  601. if (importUploadRef.value) importUploadRef.value.clearFiles();
  602. };
  603. /** 导出商品 */
  604. const handleExportProduct = async () => {
  605. try {
  606. const res = await exportSiteProductData({ siteId: siteId.value });
  607. const blob = new Blob([res as any], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  608. FileSaver.saveAs(blob, `站点商品数据_${new Date().getTime()}.xlsx`);
  609. } catch (error) {
  610. console.error('导出失败:', error);
  611. }
  612. };
  613. onMounted(() => {
  614. initData();
  615. loadBrandOptions();
  616. loadCategoryOptions();
  617. });
  618. </script>
  619. <style scoped lang="scss">
  620. .add-product-dialog {
  621. :deep(.el-form--inline .el-form-item) {
  622. margin-right: 10px;
  623. }
  624. }
  625. </style>