修改产品导入
This commit is contained in:
+31
-15
@@ -1,7 +1,9 @@
|
||||
package com.bms.web.controller.price;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -20,12 +22,14 @@ import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.BmsProductCountry;
|
||||
import com.bms.price.domain.BmsProductImportTask;
|
||||
import com.bms.price.service.IBmsProductService;
|
||||
|
||||
/**
|
||||
* 产品列表(含 Excel 导入)
|
||||
* Product list (async Excel import)
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@@ -45,6 +49,17 @@ public class ProductController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:product:import')")
|
||||
@GetMapping("/import/task/{taskId}")
|
||||
public AjaxResult getImportTask(@PathVariable String taskId)
|
||||
{
|
||||
BmsProductImportTask task = productService.getImportTask(taskId);
|
||||
if (task == null)
|
||||
{
|
||||
return error(MessageUtils.message("price.product.import.task.notExists"));
|
||||
}
|
||||
return success(task);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:product:query')")
|
||||
@GetMapping("/{productCode}/countries")
|
||||
@@ -64,7 +79,7 @@ public class ProductController extends BaseController
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:product:add')")
|
||||
@Log(title = "产品列表", businessType = BusinessType.INSERT)
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody BmsProduct product)
|
||||
{
|
||||
@@ -73,7 +88,7 @@ public class ProductController extends BaseController
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:product:edit')")
|
||||
@Log(title = "产品列表", businessType = BusinessType.UPDATE)
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody BmsProduct product)
|
||||
{
|
||||
@@ -81,11 +96,8 @@ public class ProductController extends BaseController
|
||||
return toAjax(productService.updateBmsProduct(product));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:product:edit')")
|
||||
@Log(title = "产品国家明细", businessType = BusinessType.UPDATE)
|
||||
@Log(title = "\u4ea7\u54c1\u56fd\u5bb6\u660e\u7ec6", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/country")
|
||||
public AjaxResult editCountry(@RequestBody BmsProductCountry country)
|
||||
{
|
||||
@@ -93,7 +105,7 @@ public class ProductController extends BaseController
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:product:remove')")
|
||||
@Log(title = "产品列表", businessType = BusinessType.DELETE)
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{productIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] productIds)
|
||||
{
|
||||
@@ -101,26 +113,30 @@ public class ProductController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入威狮B2C小包价表.xlsx(产品目录 + 国家服务明细)
|
||||
* Async import Weishi B2C workbook. Returns taskId; poll /import/task/{taskId}.
|
||||
*/
|
||||
@Log(title = "产品列表", businessType = BusinessType.IMPORT)
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.IMPORT)
|
||||
@PreAuthorize("@ss.hasPermi('price:product:import')")
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
|
||||
{
|
||||
String message = productService.importProductExcel(file, updateSupport, getUsername());
|
||||
return success(message);
|
||||
String taskId = productService.submitImportProductExcelAsync(file, updateSupport, getUsername());
|
||||
Map<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("taskId", taskId);
|
||||
return success(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a single-country linehaul cost sheet. countryCode is the destination.
|
||||
* Async single-country cost-sheet import. Returns taskId; poll /import/task/{taskId}.
|
||||
*/
|
||||
@Log(title = "\u4ea7\u54c1\u5217\u8868", businessType = BusinessType.IMPORT)
|
||||
@PreAuthorize("@ss.hasPermi('price:product:import')")
|
||||
@PostMapping("/importCountryData")
|
||||
public AjaxResult importCountryData(MultipartFile file, String countryCode) throws Exception
|
||||
{
|
||||
String message = productService.importCountryProductExcel(file, countryCode, getUsername());
|
||||
return success(message);
|
||||
String taskId = productService.submitImportCountryProductExcelAsync(file, countryCode, getUsername());
|
||||
Map<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("taskId", taskId);
|
||||
return success(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ price.product.import.countryNotFound=国家不存在或已停用:{0}
|
||||
price.product.import.countrySheetNotFound=未找到「产品目录」sheet,请上传单国专线成本价表
|
||||
price.product.import.countryNoPrice=未找到该产品的重量段价格:{0}
|
||||
price.product.import.countrySuccess=按国家导入成功({0}):新增 {1} 个产品,跳过 {2} 个(同代码同生效日不覆盖主数据);写入重量段 {3} 行。
|
||||
price.product.import.task.notExists=任务不存在或已过期
|
||||
# 报价试算 TmsPriceQuote(对外 TMS API 错误码,保持英文契约)
|
||||
price.quote.postcodeRequired=该国家/产品有分区,请填写目的邮编
|
||||
price.quote.postcodeZoneNotFound=邮编未匹配到分区,请核对目的邮编
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Async product Excel import progress (Redis).
|
||||
*/
|
||||
public class BmsProductImportTask implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String STATUS_PENDING = "PENDING";
|
||||
public static final String STATUS_RUNNING = "RUNNING";
|
||||
public static final String STATUS_SUCCESS = "SUCCESS";
|
||||
public static final String STATUS_FAIL = "FAIL";
|
||||
|
||||
private String taskId;
|
||||
private String status;
|
||||
private int percent;
|
||||
private String message;
|
||||
private String error;
|
||||
|
||||
public String getTaskId()
|
||||
{
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId)
|
||||
{
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getPercent()
|
||||
{
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(int percent)
|
||||
{
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getError()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error)
|
||||
{
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,16 @@ public interface BmsProductCountryMapper
|
||||
@Param("productCode") String productCode,
|
||||
@Param("newEffectiveDate") Date newEffectiveDate);
|
||||
|
||||
/**
|
||||
* Close open rows for one product + country where effective_date < newEffectiveDate.
|
||||
* Match by country_code when present, otherwise country_name.
|
||||
*/
|
||||
public int expireOpenByProductCodeAndCountry(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("newEffectiveDate") Date newEffectiveDate);
|
||||
|
||||
/** Recompute status from effective_date / expire_date vs today */
|
||||
public int refreshStatusByDates();
|
||||
|
||||
@@ -47,6 +57,16 @@ public interface BmsProductCountryMapper
|
||||
@Param("productCode") String productCode,
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/**
|
||||
* Delete open rows for one product + country on a given effective_date (same-day replace).
|
||||
* Needed because unique key keeps expired same-day weight bands.
|
||||
*/
|
||||
public int deleteOpenByProductCodeAndCountryOnDate(
|
||||
@Param("productCode") String productCode,
|
||||
@Param("countryCode") String countryCode,
|
||||
@Param("countryName") String countryName,
|
||||
@Param("effectiveDate") Date effectiveDate);
|
||||
|
||||
/** Open country rows (expire_date is null) for product */
|
||||
public List<BmsProductCountry> selectOpenByProductCode(@Param("productCode") String productCode);
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.BmsProductCountry;
|
||||
import com.bms.price.domain.BmsProductImportTask;
|
||||
|
||||
/**
|
||||
* 产品列表 服务层
|
||||
* Product list service
|
||||
*
|
||||
* @author bms
|
||||
*/
|
||||
@@ -21,9 +22,6 @@ public interface IBmsProductService
|
||||
|
||||
public int updateBmsProduct(BmsProduct product);
|
||||
|
||||
/**
|
||||
* 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期),状态按日期重算
|
||||
*/
|
||||
public int updateBmsProductCountry(BmsProductCountry country);
|
||||
|
||||
public int deleteBmsProductByIds(Long[] productIds);
|
||||
@@ -34,34 +32,26 @@ public interface IBmsProductService
|
||||
|
||||
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate, Long productId);
|
||||
|
||||
/**
|
||||
* 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家×重量段(含运费/挂号费/尺寸/尾程/时效)
|
||||
*
|
||||
* @param file 上传的 xlsx/xls
|
||||
* @param updateSupport unused; same code+date never overwrites, different date always inserts
|
||||
* @param operName 操作人
|
||||
* @return 导入结果说明(可含 HTML 换行)
|
||||
*/
|
||||
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception;
|
||||
|
||||
/**
|
||||
* Import a single-country cost sheet (catalog + product weight rows).
|
||||
* The destination country is chosen by the user, not read from the sheet.
|
||||
*
|
||||
* @param file uploaded xlsx/xls
|
||||
* @param countryCode ISO country code (region.country_code_2)
|
||||
* @param operName operator
|
||||
* @return HTML result message
|
||||
*/
|
||||
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName) throws Exception;
|
||||
|
||||
/** Submit async all-country import; returns taskId for polling getImportTask. */
|
||||
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName) throws Exception;
|
||||
|
||||
/** Submit async single-country import; returns taskId for polling getImportTask. */
|
||||
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName) throws Exception;
|
||||
|
||||
/** Poll Redis progress for product import task. */
|
||||
public BmsProductImportTask getImportTask(String taskId);
|
||||
|
||||
/** Sync import from bytes (used by async runner; taskId may be null). */
|
||||
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName, String taskId) throws Exception;
|
||||
|
||||
/** Sync country import from bytes (used by async runner; taskId may be null). */
|
||||
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName, String taskId) throws Exception;
|
||||
|
||||
/**
|
||||
* Parse Weishi B2C quote Excel into productCode -> country/zone/weight price rows (no persist).
|
||||
* Used by price-set detail price import.
|
||||
*/
|
||||
public java.util.Map<String, List<BmsProductCountry>> parseQuoteExcelByProduct(MultipartFile file) throws Exception;
|
||||
|
||||
/** Parse quote Excel from in-memory bytes (async import). */
|
||||
public java.util.Map<String, List<BmsProductCountry>> parseQuoteExcelByProduct(byte[] bytes, String filename) throws Exception;
|
||||
}
|
||||
|
||||
+161
-90
@@ -33,14 +33,17 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.MessageUtils;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.common.utils.uuid.IdUtils;
|
||||
import com.bms.price.domain.BmsProduct;
|
||||
import com.bms.price.domain.BmsProductCountry;
|
||||
import com.bms.price.domain.BmsProductImportTask;
|
||||
import com.bms.price.domain.PrcZone;
|
||||
import com.bms.price.mapper.BmsProductCountryMapper;
|
||||
import com.bms.price.mapper.BmsProductMapper;
|
||||
import com.bms.price.mapper.PrcZoneMapper;
|
||||
import com.bms.price.service.IBmsProductService;
|
||||
import com.bms.price.service.IPrcSurchargeRelService;
|
||||
import com.bms.price.task.BmsProductImportAsyncRunner;
|
||||
import com.bms.price.util.ZoneCodeAbbrevs;
|
||||
import com.bms.system.domain.BmsRegion;
|
||||
import com.bms.system.mapper.BmsRegionMapper;
|
||||
@@ -88,6 +91,9 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@Autowired
|
||||
private BmsProductImportAsyncRunner importAsyncRunner;
|
||||
|
||||
@Override
|
||||
public BmsProduct selectBmsProductById(Long productId)
|
||||
{
|
||||
@@ -240,18 +246,39 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
@Override
|
||||
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileEmpty"));
|
||||
}
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename == null || !(filename.toLowerCase(Locale.ROOT).endsWith(".xlsx")
|
||||
|| filename.toLowerCase(Locale.ROOT).endsWith(".xls")))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileType"));
|
||||
}
|
||||
validateImportFile(file);
|
||||
return importProductExcel(file.getBytes(), file.getOriginalFilename(), updateSupport, operName, null);
|
||||
}
|
||||
|
||||
try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is))
|
||||
@Override
|
||||
public String submitImportProductExcelAsync(MultipartFile file, boolean updateSupport, String operName) throws Exception
|
||||
{
|
||||
validateImportFile(file);
|
||||
byte[] bytes = file.getBytes();
|
||||
String filename = file.getOriginalFilename();
|
||||
String taskId = IdUtils.fastSimpleUUID();
|
||||
importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null);
|
||||
importAsyncRunner.runImportAll(taskId, bytes, filename, updateSupport, operName);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BmsProductImportTask getImportTask(String taskId)
|
||||
{
|
||||
if (StringUtils.isEmpty(taskId) || importAsyncRunner == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return importAsyncRunner.get(taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String importProductExcel(byte[] bytes, String filename, boolean updateSupport, String operName, String taskId) throws Exception
|
||||
{
|
||||
validateImportBytes(bytes, filename);
|
||||
reportImportProgress(taskId, 10, "parsing excel");
|
||||
|
||||
try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is))
|
||||
{
|
||||
Sheet catalog = workbook.getSheet(CATALOG_SHEET);
|
||||
if (catalog == null)
|
||||
@@ -265,6 +292,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.noData"));
|
||||
}
|
||||
products = dedupeCatalogProducts(products);
|
||||
reportImportProgress(taskId, 20, "parsing excel");
|
||||
|
||||
Map<String, BmsRegion> regionByNameCn = loadRegionByNameCn();
|
||||
if (regionByNameCn.isEmpty())
|
||||
@@ -279,9 +307,13 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
int countryNum = 0;
|
||||
Set<String> unmatchedCountries = new HashSet<>();
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
int total = products.size();
|
||||
int index = 0;
|
||||
|
||||
for (BmsProduct product : products)
|
||||
{
|
||||
index++;
|
||||
reportImportProductProgress(taskId, index, total, product.getProductCode());
|
||||
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionStatus tx = transactionManager.getTransaction(def);
|
||||
@@ -306,6 +338,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
}
|
||||
|
||||
reportImportProgress(taskId, 95, "finishing");
|
||||
if (failureMsg.length() > 0)
|
||||
{
|
||||
// Do not throw: each product already committed/rolled back independently
|
||||
@@ -326,16 +359,30 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
@Override
|
||||
public String importCountryProductExcel(MultipartFile file, String countryCode, String operName) throws Exception
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
validateImportFile(file);
|
||||
return importCountryProductExcel(file.getBytes(), file.getOriginalFilename(), countryCode, operName, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String submitImportCountryProductExcelAsync(MultipartFile file, String countryCode, String operName) throws Exception
|
||||
{
|
||||
validateImportFile(file);
|
||||
if (StringUtils.isEmpty(countryCode))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileEmpty"));
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.countryRequired"));
|
||||
}
|
||||
byte[] bytes = file.getBytes();
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename == null || !(filename.toLowerCase(Locale.ROOT).endsWith(".xlsx")
|
||||
|| filename.toLowerCase(Locale.ROOT).endsWith(".xls")))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileType"));
|
||||
}
|
||||
String taskId = IdUtils.fastSimpleUUID();
|
||||
importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_PENDING, 0, "queued", null);
|
||||
importAsyncRunner.runImportCountry(taskId, bytes, filename, countryCode, operName);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String importCountryProductExcel(byte[] bytes, String filename, String countryCode, String operName, String taskId) throws Exception
|
||||
{
|
||||
validateImportBytes(bytes, filename);
|
||||
if (StringUtils.isEmpty(countryCode))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.countryRequired"));
|
||||
@@ -346,8 +393,9 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.countryNotFound", countryCode));
|
||||
}
|
||||
reportImportProgress(taskId, 10, "parsing excel");
|
||||
|
||||
try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is))
|
||||
try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is))
|
||||
{
|
||||
Sheet catalog = workbook.getSheet(CATALOG_SHEET);
|
||||
if (catalog == null)
|
||||
@@ -361,6 +409,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
products = dedupeCatalogProducts(products);
|
||||
Map<String, PriceBlock> priceBlocks = parseCountryPriceBlocks(workbook);
|
||||
reportImportProgress(taskId, 20, "parsing excel");
|
||||
|
||||
int successNum = 0;
|
||||
int skipNum = 0;
|
||||
@@ -368,9 +417,13 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
String countryLabel = StringUtils.isNotEmpty(region.getCountryNameCn())
|
||||
? region.getCountryNameCn() : region.getCountryCode2();
|
||||
int total = products.size();
|
||||
int index = 0;
|
||||
|
||||
for (BmsProduct product : products)
|
||||
{
|
||||
index++;
|
||||
reportImportProductProgress(taskId, index, total, product.getProductCode());
|
||||
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionStatus tx = transactionManager.getTransaction(def);
|
||||
@@ -407,6 +460,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
}
|
||||
|
||||
reportImportProgress(taskId, 95, "finishing");
|
||||
if (failureMsg.length() > 0)
|
||||
{
|
||||
return MessageUtils.message("price.product.import.partialFail",
|
||||
@@ -417,6 +471,58 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
}
|
||||
}
|
||||
|
||||
private void validateImportFile(MultipartFile file)
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileEmpty"));
|
||||
}
|
||||
validateImportFilename(file.getOriginalFilename());
|
||||
}
|
||||
|
||||
private void validateImportBytes(byte[] bytes, String filename)
|
||||
{
|
||||
if (bytes == null || bytes.length == 0)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileEmpty"));
|
||||
}
|
||||
validateImportFilename(filename);
|
||||
}
|
||||
|
||||
private void validateImportFilename(String filename)
|
||||
{
|
||||
if (filename == null || !(filename.toLowerCase(Locale.ROOT).endsWith(".xlsx")
|
||||
|| filename.toLowerCase(Locale.ROOT).endsWith(".xls")))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.product.import.fileType"));
|
||||
}
|
||||
}
|
||||
|
||||
private void reportImportProductProgress(String taskId, int index, int total, String productCode)
|
||||
{
|
||||
if (total <= 0)
|
||||
{
|
||||
reportImportProgress(taskId, 50, "importing");
|
||||
return;
|
||||
}
|
||||
int percent = 20 + (int) Math.floor(70.0 * index / total);
|
||||
if (percent > 90)
|
||||
{
|
||||
percent = 90;
|
||||
}
|
||||
String code = StringUtils.isEmpty(productCode) ? "-" : productCode;
|
||||
reportImportProgress(taskId, percent, "importing " + code + " (" + index + "/" + total + ")");
|
||||
}
|
||||
|
||||
private void reportImportProgress(String taskId, int percent, String message)
|
||||
{
|
||||
if (StringUtils.isEmpty(taskId) || importAsyncRunner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
importAsyncRunner.put(taskId, BmsProductImportTask.STATUS_RUNNING, percent, message, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<BmsProductCountry>> parseQuoteExcelByProduct(MultipartFile file) throws Exception
|
||||
{
|
||||
@@ -546,10 +652,12 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
|
||||
|
||||
/**
|
||||
* Save country rows by effective_date:
|
||||
* - newEff > last open: expire previous open rows, copy them to newEff, then match import (hit=update, miss=insert)
|
||||
* - newEff equals last open: match update/insert on open rows
|
||||
* - newEff < last open: reject
|
||||
* Save country rows by effective_date.
|
||||
* For each product_code + country in the Excel:
|
||||
* - expire all earlier open weight bands under that country
|
||||
* - delete same-day open weight bands (unique key blocks insert if only expired)
|
||||
* - insert Excel weight bands only (no copy / merge of old bands)
|
||||
* Reject when newEff < last open effective_date.
|
||||
*/
|
||||
private int saveCountries(BmsProduct product, List<BmsProductCountry> countries)
|
||||
{
|
||||
@@ -576,69 +684,33 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
formatDate(newEff), formatDate(lastOpenEff)));
|
||||
}
|
||||
|
||||
boolean newVersionDay = lastOpenEff == null || newEff.after(lastOpenEff);
|
||||
if (newVersionDay && lastOpenEff != null)
|
||||
{
|
||||
String newStatus = resolveStoredStatus(newEff, null);
|
||||
// Copy open rows first (effective_date < newEff), then expire the previous open set.
|
||||
countryMapper.copyOpenCountries(productCode, productId, newEff, newStatus);
|
||||
countryMapper.expireOpenByProductCode(productCode, newEff);
|
||||
}
|
||||
|
||||
List<BmsProductCountry> baseRows = countryMapper.selectOpenByProductCode(productCode);
|
||||
if (baseRows == null)
|
||||
{
|
||||
baseRows = new ArrayList<>();
|
||||
}
|
||||
// keep only rows for this effective date when same-day / after copy
|
||||
Map<String, BmsProductCountry> byKey = new HashMap<>();
|
||||
for (BmsProductCountry row : baseRows)
|
||||
{
|
||||
Date rowEff = toDateOnly(row.getEffectiveDate());
|
||||
if (rowEff != null && !rowEff.equals(newEff))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
byKey.put(countryMatchKey(row), row);
|
||||
}
|
||||
|
||||
Set<String> replacedCountries = new HashSet<>();
|
||||
String rowStatus = resolveStoredStatus(newEff, null);
|
||||
int touched = 0;
|
||||
for (BmsProductCountry incoming : countries)
|
||||
{
|
||||
if (incoming == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String countryKey = countryIdentityKey(incoming);
|
||||
if (StringUtils.isNotEmpty(countryKey) && !replacedCountries.contains(countryKey))
|
||||
{
|
||||
countryMapper.expireOpenByProductCodeAndCountry(
|
||||
productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff);
|
||||
countryMapper.deleteOpenByProductCodeAndCountryOnDate(
|
||||
productCode, incoming.getCountryCode(), incoming.getCountryName(), newEff);
|
||||
replacedCountries.add(countryKey);
|
||||
}
|
||||
|
||||
incoming.setProductId(productId);
|
||||
incoming.setProductCode(productCode);
|
||||
incoming.setEffectiveDate(newEff);
|
||||
incoming.setExpireDate(null);
|
||||
incoming.setStatus(resolveStoredStatus(newEff, null));
|
||||
|
||||
String key = countryMatchKey(incoming);
|
||||
BmsProductCountry existing = byKey.get(key);
|
||||
incoming.setStatus(rowStatus);
|
||||
try
|
||||
{
|
||||
if (existing != null)
|
||||
{
|
||||
existing.setProductId(productId);
|
||||
existing.setCountryCode(incoming.getCountryCode());
|
||||
existing.setZoneId(incoming.getZoneId());
|
||||
existing.setZoneCode(incoming.getZoneCode());
|
||||
existing.setZoneName(incoming.getZoneName());
|
||||
existing.setWeightMin(incoming.getWeightMin());
|
||||
existing.setWeightMax(incoming.getWeightMax());
|
||||
existing.setFreightPrice(incoming.getFreightPrice());
|
||||
existing.setRegistrationFee(incoming.getRegistrationFee());
|
||||
existing.setSizeLimit(incoming.getSizeLimit());
|
||||
existing.setLastMile(incoming.getLastMile());
|
||||
existing.setLeadTime(incoming.getLeadTime());
|
||||
existing.setEffectiveDate(newEff);
|
||||
existing.setExpireDate(null);
|
||||
existing.setStatus(resolveStoredStatus(newEff, null));
|
||||
countryMapper.updateBmsProductCountryImport(existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
countryMapper.insertBmsProductCountry(incoming);
|
||||
byKey.put(key, incoming);
|
||||
}
|
||||
countryMapper.insertBmsProductCountry(incoming);
|
||||
touched++;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -660,23 +732,22 @@ public class BmsProductServiceImpl implements IBmsProductService
|
||||
return touched;
|
||||
}
|
||||
|
||||
/** Match key: country + zone + weight band */
|
||||
private String countryMatchKey(BmsProductCountry row)
|
||||
/** Identity for replace scope: prefer country_code, else country_name. */
|
||||
private String countryIdentityKey(BmsProductCountry row)
|
||||
{
|
||||
String country = row.getCountryName() == null ? "" : row.getCountryName().trim();
|
||||
String zone = row.getZoneCode() == null ? "" : row.getZoneCode().trim().toUpperCase(Locale.ROOT);
|
||||
String wMin = normalizeDecimal(row.getWeightMin());
|
||||
String wMax = normalizeDecimal(row.getWeightMax());
|
||||
return country + "|" + zone + "|" + wMin + "|" + wMax;
|
||||
}
|
||||
|
||||
private String normalizeDecimal(BigDecimal v)
|
||||
{
|
||||
if (v == null)
|
||||
if (row == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return v.stripTrailingZeros().toPlainString();
|
||||
if (StringUtils.isNotEmpty(row.getCountryCode()))
|
||||
{
|
||||
return "C:" + row.getCountryCode().trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(row.getCountryName()))
|
||||
{
|
||||
return "N:" + row.getCountryName().trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private Date resolveImportEffectiveDate(List<BmsProductCountry> countries)
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.bms.price.task;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.bms.common.core.redis.RedisCache;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.domain.BmsProductImportTask;
|
||||
import com.bms.price.service.IBmsProductService;
|
||||
|
||||
/**
|
||||
* Async runner for product Excel import (avoids HTTP timeout on large workbooks).
|
||||
*/
|
||||
@Component
|
||||
public class BmsProductImportAsyncRunner
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(BmsProductImportAsyncRunner.class);
|
||||
|
||||
public static final String CACHE_KEY_PREFIX = "price:product:import:task:";
|
||||
public static final int TTL_MINUTES = 60;
|
||||
|
||||
@Autowired
|
||||
private IBmsProductService productService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/** Background all-country (Weishi catalog) import. */
|
||||
@Async("threadPoolTaskExecutor")
|
||||
public void runImportAll(String taskId, byte[] bytes, String filename, boolean updateSupport, String operName)
|
||||
{
|
||||
try
|
||||
{
|
||||
put(taskId, BmsProductImportTask.STATUS_RUNNING, 5, "parsing excel", null);
|
||||
String message = productService.importProductExcel(bytes, filename, updateSupport, operName, taskId);
|
||||
put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, message, null);
|
||||
log.info("async product import done, taskId={}", taskId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("async product import failed, taskId={}", taskId, e);
|
||||
put(taskId, BmsProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
/** Background single-country cost-sheet import. */
|
||||
@Async("threadPoolTaskExecutor")
|
||||
public void runImportCountry(String taskId, byte[] bytes, String filename, String countryCode, String operName)
|
||||
{
|
||||
try
|
||||
{
|
||||
put(taskId, BmsProductImportTask.STATUS_RUNNING, 5, "parsing excel", null);
|
||||
String message = productService.importCountryProductExcel(bytes, filename, countryCode, operName, taskId);
|
||||
put(taskId, BmsProductImportTask.STATUS_SUCCESS, 100, message, null);
|
||||
log.info("async product country import done, taskId={}, country={}", taskId, countryCode);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("async product country import failed, taskId={}, country={}", taskId, countryCode, e);
|
||||
put(taskId, BmsProductImportTask.STATUS_FAIL, 100, "failed", errorMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String taskId, String status, int percent, String message, String error)
|
||||
{
|
||||
BmsProductImportTask task = new BmsProductImportTask();
|
||||
task.setTaskId(taskId);
|
||||
task.setStatus(status);
|
||||
task.setPercent(percent);
|
||||
task.setMessage(message);
|
||||
task.setError(error);
|
||||
redisCache.setCacheObject(CACHE_KEY_PREFIX + taskId, task, TTL_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public BmsProductImportTask get(String taskId)
|
||||
{
|
||||
return redisCache.getCacheObject(CACHE_KEY_PREFIX + taskId);
|
||||
}
|
||||
|
||||
private static String errorMessage(Exception e)
|
||||
{
|
||||
String msg = e.getMessage();
|
||||
if (StringUtils.isEmpty(msg))
|
||||
{
|
||||
msg = e.getClass().getSimpleName();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -171,6 +171,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and (effective_date is null or effective_date < #{newEffectiveDate})
|
||||
</update>
|
||||
|
||||
<update id="expireOpenByProductCodeAndCountry">
|
||||
update product_country
|
||||
set expire_date = #{newEffectiveDate},
|
||||
update_time = sysdate()
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (effective_date is null or effective_date < #{newEffectiveDate})
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and country_name = #{countryName}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</update>
|
||||
|
||||
<update id="refreshStatusByDates">
|
||||
update product_country
|
||||
set status = case
|
||||
@@ -190,6 +207,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</delete>
|
||||
|
||||
<delete id="deleteOpenByProductCodeAndCountryOnDate">
|
||||
delete from product_country
|
||||
where product_code = #{productCode}
|
||||
and expire_date is null
|
||||
and (
|
||||
(#{effectiveDate} is null and effective_date is null)
|
||||
or effective_date = #{effectiveDate}
|
||||
)
|
||||
<choose>
|
||||
<when test="countryCode != null and countryCode != ''">
|
||||
and upper(ifnull(country_code,'')) = upper(#{countryCode})
|
||||
</when>
|
||||
<otherwise>
|
||||
and country_name = #{countryName}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</delete>
|
||||
|
||||
<select id="selectMatchByProductCountryWeight" resultMap="BmsProductCountryResult">
|
||||
<include refid="selectVo"/>
|
||||
where c.product_code = #{productCode}
|
||||
|
||||
@@ -63,3 +63,92 @@ export function delProduct(productIds) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductImportTask(taskId) {
|
||||
return request({
|
||||
url: '/price/product/import/task/' + taskId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
function pickImportTaskId(res) {
|
||||
if (!res) return null
|
||||
const d = res.data
|
||||
if (d && typeof d === 'object' && d.taskId) return String(d.taskId)
|
||||
if (typeof d === 'string' && d.trim()) return d.trim()
|
||||
if (typeof res.msg === 'string') {
|
||||
const m = res.msg.trim()
|
||||
if (/^[a-f0-9]{32}$/i.test(m)) return m
|
||||
if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(m)) {
|
||||
return m.replace(/-/g, '')
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function pollProductImportTask(taskId, onProgress) {
|
||||
const started = Date.now()
|
||||
const maxMs = 30 * 60 * 1000
|
||||
while (Date.now() - started < maxMs) {
|
||||
const res = await getProductImportTask(taskId)
|
||||
const task = res.data || {}
|
||||
if (typeof onProgress === 'function') {
|
||||
onProgress(task)
|
||||
}
|
||||
if (task.status === 'SUCCESS') {
|
||||
return { task }
|
||||
}
|
||||
if (task.status === 'FAIL') {
|
||||
const err = new Error(task.error || task.message || 'import failed')
|
||||
err.task = task
|
||||
throw err
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
}
|
||||
throw new Error('import timeout')
|
||||
}
|
||||
|
||||
/**
|
||||
* Async all-country product import; poll until done.
|
||||
* @param {File|Blob} file
|
||||
* @param {(task: object) => void} [onProgress]
|
||||
*/
|
||||
export async function importProductExcelAsync(file, onProgress) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const submitRes = await request({
|
||||
url: '/price/product/importData?updateSupport=0',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
const taskId = pickImportTaskId(submitRes)
|
||||
if (!taskId) {
|
||||
throw new Error('missing import taskId')
|
||||
}
|
||||
return pollProductImportTask(taskId, onProgress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Async single-country product import; poll until done.
|
||||
* @param {File|Blob} file
|
||||
* @param {string} countryCode
|
||||
* @param {(task: object) => void} [onProgress]
|
||||
*/
|
||||
export async function importProductCountryExcelAsync(file, countryCode, onProgress) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const params = new URLSearchParams()
|
||||
params.set('countryCode', countryCode)
|
||||
const submitRes = await request({
|
||||
url: '/price/product/importCountryData?' + params.toString(),
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
const taskId = pickImportTaskId(submitRes)
|
||||
if (!taskId) {
|
||||
throw new Error('missing import taskId')
|
||||
}
|
||||
return pollProductImportTask(taskId, onProgress)
|
||||
}
|
||||
|
||||
@@ -60,25 +60,24 @@
|
||||
<excel-import-dialog
|
||||
ref="importRef"
|
||||
title="产品 Excel 导入"
|
||||
action="/price/product/importData"
|
||||
width="560px"
|
||||
:show-update-support="false"
|
||||
template-url="/templates/product/product-import-all-template.xlsx"
|
||||
template-file-name="产品多国家价表导入模板"
|
||||
:import-desc="allImportDesc"
|
||||
:submit-handler="runImportAllAsync"
|
||||
@success="() => tableRef.refresh()"
|
||||
/>
|
||||
<excel-import-dialog
|
||||
ref="countryImportRef"
|
||||
title="按国家导入"
|
||||
action="/price/product/importCountryData"
|
||||
width="560px"
|
||||
:show-update-support="false"
|
||||
:extra-query="countryImportQuery"
|
||||
:before-submit="validateCountryImport"
|
||||
:import-desc="countryImportDesc"
|
||||
template-url="/templates/product/product-import-country-template.xlsx"
|
||||
template-file-name="产品按国家导入模板"
|
||||
:submit-handler="runImportCountryAsync"
|
||||
@success="() => tableRef.refresh()"
|
||||
>
|
||||
<template #extra>
|
||||
@@ -96,6 +95,21 @@
|
||||
</el-form>
|
||||
</template>
|
||||
</excel-import-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="importProgressVisible"
|
||||
title="正在导入产品"
|
||||
width="420px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false"
|
||||
append-to-body
|
||||
>
|
||||
<div class="import-progress">
|
||||
<el-progress :percentage="importProgressPercent" :status="importProgressStatus" />
|
||||
<div class="import-progress-msg">{{ importProgressMessage }}</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -105,7 +119,7 @@ import ProductFormDialog from './components/ProductFormDialog.vue'
|
||||
import ProductCountryDrawer from './components/ProductCountryDrawer.vue'
|
||||
import ProductSurchargeDrawer from './components/ProductSurchargeDrawer.vue'
|
||||
import ProductExpiredDrawer from './components/ProductExpiredDrawer.vue'
|
||||
import { listProduct } from '@/api/price/product'
|
||||
import { listProduct, importProductExcelAsync, importProductCountryExcelAsync } from '@/api/price/product'
|
||||
import { listRegionOptions } from '@/api/data/region'
|
||||
|
||||
const { bms_product_effect_status } = useDict('bms_product_effect_status')
|
||||
@@ -120,15 +134,18 @@ const importRef = ref()
|
||||
const countryImportRef = ref()
|
||||
const importCountryCode = ref('')
|
||||
const regionOptions = ref([])
|
||||
const countryImportQuery = computed(() => ({ countryCode: importCountryCode.value }))
|
||||
const importProgressVisible = ref(false)
|
||||
const importProgressPercent = ref(0)
|
||||
const importProgressMessage = ref('')
|
||||
const importProgressStatus = ref(undefined)
|
||||
const allImportDesc = [
|
||||
'适用多国家价表(B2C 小包价表等),按目录 sheet 匹配国家导入。',
|
||||
'生效日期必填。新版导入时上一版失效日=本次生效日,复制上一版后按国家/分区/重量段匹配:命中更新,未命中插入。',
|
||||
'生效日期必填。产品+国家有新生效日时,先失效该国家下全部旧重量段,再插入 Excel 重量段。',
|
||||
'请在弹窗底部下载模板,按模板格式整理后上传。相同产品代码+生效日期不覆盖主数据。'
|
||||
]
|
||||
const countryImportDesc = [
|
||||
'选择目的国家后上传专线成本价表,目录中的产品都会记到该国家。',
|
||||
'生效日期必填。新版会复制上一版全部国家明细再与导入行匹配更新/插入;其他国家行会转结到新版。',
|
||||
'生效日期必填。产品+国家有新生效日时,先失效该国家下全部旧重量段,再插入 Excel 重量段。',
|
||||
'邮编、赔偿标准等 sheet 不导入。可在弹窗底部下载模板。'
|
||||
]
|
||||
|
||||
@@ -138,7 +155,6 @@ const columns = [
|
||||
{ prop: 'serviceLevel', label: '时效等级', width: 90, align: 'center' },
|
||||
{ prop: 'cargoAttr', label: '走货属性', minWidth: 120, slot: 'cargoAttr' },
|
||||
{ prop: 'destRegion', label: '目的区域', width: 90, align: 'center' },
|
||||
// { prop: 'taxMode', label: '税模式', width: 90, align: 'center', dict: 'bms_tax_mode' },
|
||||
{ prop: 'effectiveDate', label: '生效日期', width: 120, align: 'center' },
|
||||
{ prop: 'expireDate', label: '失效日期', width: 120, align: 'center' },
|
||||
{ prop: 'countryCount', label: '国家数', width: 80, align: 'center' },
|
||||
@@ -202,8 +218,80 @@ function validateCountryImport() {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function applyImportProgress(t) {
|
||||
importProgressPercent.value = Math.min(100, Number(t.percent) || 0)
|
||||
if (t.status === 'PENDING') importProgressMessage.value = '排队中…'
|
||||
else if (t.status === 'RUNNING') {
|
||||
const m = t.message || ''
|
||||
if (m === 'parsing excel') importProgressMessage.value = '正在解析价表…'
|
||||
else if (m === 'finishing') importProgressMessage.value = '收尾中…'
|
||||
else if (m === 'queued') importProgressMessage.value = '排队中…'
|
||||
else if (m.indexOf('importing ') === 0) {
|
||||
const rest = m.slice('importing '.length)
|
||||
importProgressMessage.value = '正在导入产品 ' + rest
|
||||
} else importProgressMessage.value = m || '正在导入…'
|
||||
} else if (t.status === 'SUCCESS') {
|
||||
importProgressMessage.value = t.message || '导入完成'
|
||||
importProgressStatus.value = 'success'
|
||||
} else if (t.status === 'FAIL') {
|
||||
importProgressMessage.value = t.error || t.message || '导入失败'
|
||||
importProgressStatus.value = 'exception'
|
||||
}
|
||||
}
|
||||
|
||||
async function runImportWithProgress(runner) {
|
||||
importProgressVisible.value = true
|
||||
importProgressPercent.value = 0
|
||||
importProgressMessage.value = '上传并解析中…'
|
||||
importProgressStatus.value = undefined
|
||||
try {
|
||||
const { task } = await runner(applyImportProgress)
|
||||
importProgressPercent.value = 100
|
||||
importProgressStatus.value = 'success'
|
||||
return (task && task.message) ? task.message : '导入完成'
|
||||
} catch (e) {
|
||||
importProgressStatus.value = 'exception'
|
||||
const tip = (e && e.task && e.task.error) || (e && e.message) || '导入失败'
|
||||
importProgressMessage.value = tip
|
||||
await new Promise(resolve => setTimeout(resolve, 600))
|
||||
throw e
|
||||
} finally {
|
||||
importProgressVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runImportAllAsync(uploadFile) {
|
||||
const raw = uploadFile && (uploadFile.raw || uploadFile)
|
||||
if (!raw) {
|
||||
throw new Error('请先选择文件')
|
||||
}
|
||||
return runImportWithProgress((onProgress) => importProductExcelAsync(raw, onProgress))
|
||||
}
|
||||
|
||||
async function runImportCountryAsync(uploadFile) {
|
||||
const raw = uploadFile && (uploadFile.raw || uploadFile)
|
||||
if (!raw) {
|
||||
throw new Error('请先选择文件')
|
||||
}
|
||||
if (!importCountryCode.value) {
|
||||
throw new Error('请先选择目的国家')
|
||||
}
|
||||
const code = importCountryCode.value
|
||||
return runImportWithProgress((onProgress) => importProductCountryExcelAsync(raw, code, onProgress))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb16 { margin-bottom: 16px; }
|
||||
.import-progress { padding: 8px 4px 4px; }
|
||||
.import-progress-msg {
|
||||
margin-top: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
max-height: 160px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user