套餐加分区功能
This commit is contained in:
@@ -70,6 +70,7 @@ price.set.detail.country.empty=固定价明细必须填写国家
|
|||||||
price.set.detail.weight.empty=固定价明细必须填写重量段
|
price.set.detail.weight.empty=固定价明细必须填写重量段
|
||||||
price.set.detail.freight.empty=固定价明细必须填写运费单价
|
price.set.detail.freight.empty=固定价明细必须填写运费单价
|
||||||
price.set.detail.registration.empty=固定价明细必须填写挂号费
|
price.set.detail.registration.empty=固定价明细必须填写挂号费
|
||||||
|
price.set.detail.zone.empty=固定价明细必须选择分区
|
||||||
price.set.detail.roundMode.invalid=取值方式仅支持 UP / DOWN / HALF_UP
|
price.set.detail.roundMode.invalid=取值方式仅支持 UP / DOWN / HALF_UP
|
||||||
price.set.customer.notExists=客户绑定不存在
|
price.set.customer.notExists=客户绑定不存在
|
||||||
price.set.customer.unbindAudit=审核中的套餐不允许解绑客户
|
price.set.customer.unbindAudit=审核中的套餐不允许解绑客户
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ public class BmsProductCountry implements Serializable
|
|||||||
private String productCode;
|
private String productCode;
|
||||||
private String countryName;
|
private String countryName;
|
||||||
private String countryCode;
|
private String countryCode;
|
||||||
|
/** prc_zone.zone_id; null when product country has no zone */
|
||||||
|
private Long zoneId;
|
||||||
|
/** e.g. Z1; null when no zone */
|
||||||
|
private String zoneCode;
|
||||||
|
/** e.g. Australia(Z1); null when no zone */
|
||||||
|
private String zoneName;
|
||||||
private BigDecimal weightMin;
|
private BigDecimal weightMin;
|
||||||
private BigDecimal weightMax;
|
private BigDecimal weightMax;
|
||||||
private BigDecimal freightPrice;
|
private BigDecimal freightPrice;
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ public class PrcPriceSetDetail implements Serializable
|
|||||||
private String productName;
|
private String productName;
|
||||||
private String countryCode;
|
private String countryCode;
|
||||||
private String countryName;
|
private String countryName;
|
||||||
|
/** prc_zone.zone_id; null when country has no zone */
|
||||||
|
private Long zoneId;
|
||||||
|
/** e.g. Z1; null when no zone */
|
||||||
|
private String zoneCode;
|
||||||
|
/** zone display name; null when no zone */
|
||||||
|
private String zoneName;
|
||||||
private Long zdId;
|
private Long zdId;
|
||||||
private Long wsdId;
|
private Long wsdId;
|
||||||
private String weightLabel;
|
private String weightLabel;
|
||||||
|
|||||||
+2
-1
@@ -27,12 +27,13 @@ public interface PrcPriceSetDetailMapper
|
|||||||
|
|
||||||
public int countByPriceSetId(Long priceSetId);
|
public int countByPriceSetId(Long priceSetId);
|
||||||
|
|
||||||
/** FIXED:按产品+国家+关联产品国家明细/重量段命中 */
|
/** FIXED: product + country + zone + productCountryId / weight */
|
||||||
public PrcPriceSetDetail selectFixedMatch(
|
public PrcPriceSetDetail selectFixedMatch(
|
||||||
@Param("priceSetId") Long priceSetId,
|
@Param("priceSetId") Long priceSetId,
|
||||||
@Param("productCode") String productCode,
|
@Param("productCode") String productCode,
|
||||||
@Param("countryCode") String countryCode,
|
@Param("countryCode") String countryCode,
|
||||||
@Param("countryName") String countryName,
|
@Param("countryName") String countryName,
|
||||||
|
@Param("zoneCode") String zoneCode,
|
||||||
@Param("productCountryId") Long productCountryId,
|
@Param("productCountryId") Long productCountryId,
|
||||||
@Param("weightLabel") String weightLabel);
|
@Param("weightLabel") String weightLabel);
|
||||||
|
|
||||||
|
|||||||
+97
-38
@@ -34,10 +34,13 @@ import com.ruoyi.common.utils.MessageUtils;
|
|||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.price.domain.BmsProduct;
|
import com.ruoyi.price.domain.BmsProduct;
|
||||||
import com.ruoyi.price.domain.BmsProductCountry;
|
import com.ruoyi.price.domain.BmsProductCountry;
|
||||||
|
import com.ruoyi.price.domain.PrcZone;
|
||||||
import com.ruoyi.price.mapper.BmsProductCountryMapper;
|
import com.ruoyi.price.mapper.BmsProductCountryMapper;
|
||||||
import com.ruoyi.price.mapper.BmsProductMapper;
|
import com.ruoyi.price.mapper.BmsProductMapper;
|
||||||
|
import com.ruoyi.price.mapper.PrcZoneMapper;
|
||||||
import com.ruoyi.price.service.IBmsProductService;
|
import com.ruoyi.price.service.IBmsProductService;
|
||||||
import com.ruoyi.price.service.IPrcSurchargeRelService;
|
import com.ruoyi.price.service.IPrcSurchargeRelService;
|
||||||
|
import com.ruoyi.price.util.ZoneCodeAbbrevs;
|
||||||
import com.ruoyi.system.domain.BmsRegion;
|
import com.ruoyi.system.domain.BmsRegion;
|
||||||
import com.ruoyi.system.mapper.BmsRegionMapper;
|
import com.ruoyi.system.mapper.BmsRegionMapper;
|
||||||
|
|
||||||
@@ -74,6 +77,9 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private BmsRegionMapper regionMapper;
|
private BmsRegionMapper regionMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PrcZoneMapper zoneMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPrcSurchargeRelService surchargeRelService;
|
private IPrcSurchargeRelService surchargeRelService;
|
||||||
|
|
||||||
@@ -248,6 +254,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
throw new ServiceException(MessageUtils.message("price.product.import.regionEmpty"));
|
throw new ServiceException(MessageUtils.message("price.product.import.regionEmpty"));
|
||||||
}
|
}
|
||||||
List<BmsRegion> euRegions = loadEuRegions(regionByNameCn);
|
List<BmsRegion> euRegions = loadEuRegions(regionByNameCn);
|
||||||
|
Map<String, PrcZone> zoneByCountryAndCode = loadZoneByCountryAndCode();
|
||||||
|
|
||||||
int successNum = 0;
|
int successNum = 0;
|
||||||
int skipNum = 0;
|
int skipNum = 0;
|
||||||
@@ -263,7 +270,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions,
|
int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions,
|
||||||
unmatchedCountries);
|
zoneByCountryAndCode, unmatchedCountries);
|
||||||
transactionManager.commit(tx);
|
transactionManager.commit(tx);
|
||||||
successNum += nums[0];
|
successNum += nums[0];
|
||||||
skipNum += nums[1];
|
skipNum += nums[1];
|
||||||
@@ -300,7 +307,8 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
|
|
||||||
/** @return int[]{added, skipped, countryRows} */
|
/** @return int[]{added, skipped, countryRows} */
|
||||||
private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName,
|
private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName,
|
||||||
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions, Set<String> unmatchedCountries)
|
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions,
|
||||||
|
Map<String, PrcZone> zoneByCountryAndCode, Set<String> unmatchedCountries)
|
||||||
{
|
{
|
||||||
product.setSourceSheet(product.getProductName());
|
product.setSourceSheet(product.getProductName());
|
||||||
product.setCreateBy(operName);
|
product.setCreateBy(operName);
|
||||||
@@ -314,7 +322,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
}
|
}
|
||||||
product.setSourceSheet(productSheet.getSheetName());
|
product.setSourceSheet(productSheet.getSheetName());
|
||||||
List<BmsProductCountry> countries = parseProductSheet(productSheet, product,
|
List<BmsProductCountry> countries = parseProductSheet(productSheet, product,
|
||||||
regionByNameCn, euRegions, unmatchedCountries);
|
regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries);
|
||||||
if (countries == null || countries.isEmpty())
|
if (countries == null || countries.isEmpty())
|
||||||
{
|
{
|
||||||
throw new ServiceException(MessageUtils.message("price.product.import.noCountry",
|
throw new ServiceException(MessageUtils.message("price.product.import.noCountry",
|
||||||
@@ -412,7 +420,8 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
{
|
{
|
||||||
throw new ServiceException(MessageUtils.message("price.product.import.needFeeAlter"));
|
throw new ServiceException(MessageUtils.message("price.product.import.needFeeAlter"));
|
||||||
}
|
}
|
||||||
if (tip.contains("expire_date") || tip.contains("uk_product_country_weight"))
|
if (tip.contains("expire_date") || tip.contains("uk_product_country_weight")
|
||||||
|
|| tip.contains("uk_product_country_zone_weight"))
|
||||||
{
|
{
|
||||||
throw new ServiceException(MessageUtils.message("price.product.import.needExpireAlter"));
|
throw new ServiceException(MessageUtils.message("price.product.import.needExpireAlter"));
|
||||||
}
|
}
|
||||||
@@ -716,7 +725,8 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<BmsProductCountry> parseProductSheet(Sheet sheet, BmsProduct product,
|
private List<BmsProductCountry> parseProductSheet(Sheet sheet, BmsProduct product,
|
||||||
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions, Set<String> unmatchedCountries)
|
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions,
|
||||||
|
Map<String, PrcZone> zoneByCountryAndCode, Set<String> unmatchedCountries)
|
||||||
{
|
{
|
||||||
if (sheet == null)
|
if (sheet == null)
|
||||||
{
|
{
|
||||||
@@ -739,7 +749,6 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
int colSize = findColByHeader(headerRow, "\u5c3a\u5bf8", colCountry + 1);
|
int colSize = findColByHeader(headerRow, "\u5c3a\u5bf8", colCountry + 1);
|
||||||
int colLastMile = findColByHeader(headerRow, "\u5c3e\u7a0b", colCountry + 1);
|
int colLastMile = findColByHeader(headerRow, "\u5c3e\u7a0b", colCountry + 1);
|
||||||
int colLeadTime = findColByHeader(headerRow, "\u65f6\u6548", colCountry + 1);
|
int colLeadTime = findColByHeader(headerRow, "\u65f6\u6548", colCountry + 1);
|
||||||
// Fallback offsets when header text missing (Weishi layout: B=country ... H=lead time)
|
|
||||||
if (colWeight < 0)
|
if (colWeight < 0)
|
||||||
{
|
{
|
||||||
colWeight = colCountry + 1;
|
colWeight = colCountry + 1;
|
||||||
@@ -765,7 +774,7 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
colLeadTime = colCountry + 6;
|
colLeadTime = colCountry + 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
String currentCountry = null;
|
String currentCountryRaw = null;
|
||||||
String currentSize = null;
|
String currentSize = null;
|
||||||
String currentLastMile = null;
|
String currentLastMile = null;
|
||||||
String currentLeadTime = null;
|
String currentLeadTime = null;
|
||||||
@@ -785,12 +794,12 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(countryCell))
|
if (StringUtils.isNotEmpty(countryCell))
|
||||||
{
|
{
|
||||||
currentCountry = normalizeCountryName(countryCell);
|
currentCountryRaw = countryCell;
|
||||||
currentSize = emptyToNull(cellString(row.getCell(colSize)));
|
currentSize = emptyToNull(cellString(row.getCell(colSize)));
|
||||||
currentLastMile = emptyToNull(cellString(row.getCell(colLastMile)));
|
currentLastMile = emptyToNull(cellString(row.getCell(colLastMile)));
|
||||||
currentLeadTime = emptyToNull(cellString(row.getCell(colLeadTime)));
|
currentLeadTime = emptyToNull(cellString(row.getCell(colLeadTime)));
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(currentCountry))
|
if (StringUtils.isEmpty(currentCountryRaw))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -803,14 +812,23 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
BigDecimal registration = cellDecimal(row.getCell(colRegister));
|
BigDecimal registration = cellDecimal(row.getCell(colRegister));
|
||||||
Date eff = sheetEffective != null ? sheetEffective : product.getEffectiveDate();
|
Date eff = sheetEffective != null ? sheetEffective : product.getEffectiveDate();
|
||||||
|
|
||||||
BmsRegion region = resolveRegion(currentCountry, regionByNameCn);
|
ZoneCodeAbbrevs.CountryZoneParts parts = ZoneCodeAbbrevs.splitCountryZone(currentCountryRaw);
|
||||||
|
if (parts == null || StringUtils.isEmpty(parts.countryName))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String baseCountry = parts.countryName;
|
||||||
|
String zoneLabel = parts.zoneLabel;
|
||||||
|
|
||||||
|
BmsRegion region = resolveRegion(baseCountry, regionByNameCn);
|
||||||
if (region != null)
|
if (region != null)
|
||||||
{
|
{
|
||||||
list.add(buildCountryRow(region.getCountryNameCn(), region.getCountryCode2(),
|
list.add(buildCountryRow(region.getCountryNameCn(), region.getCountryCode2(),
|
||||||
|
zoneLabel, zoneByCountryAndCode,
|
||||||
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isEuZoneName(currentCountry))
|
if (isEuZoneName(baseCountry) && StringUtils.isEmpty(zoneLabel))
|
||||||
{
|
{
|
||||||
if (euRegions == null || euRegions.isEmpty())
|
if (euRegions == null || euRegions.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -819,23 +837,26 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
for (BmsRegion eu : euRegions)
|
for (BmsRegion eu : euRegions)
|
||||||
{
|
{
|
||||||
list.add(buildCountryRow(eu.getCountryNameCn(), eu.getCountryCode2(),
|
list.add(buildCountryRow(eu.getCountryNameCn(), eu.getCountryCode2(),
|
||||||
|
null, zoneByCountryAndCode,
|
||||||
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
unmatchedCountries.add(currentCountry);
|
unmatchedCountries.add(currentCountryRaw.trim().replace("\n", " ").replace("\r", " "));
|
||||||
list.add(buildCountryRow(currentCountry, null,
|
list.add(buildCountryRow(baseCountry, null, zoneLabel, zoneByCountryAndCode,
|
||||||
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BmsProductCountry buildCountryRow(String countryName, String countryCode, BigDecimal[] range,
|
private BmsProductCountry buildCountryRow(String countryName, String countryCode, String zoneLabel,
|
||||||
|
Map<String, PrcZone> zoneByCountryAndCode, BigDecimal[] range,
|
||||||
BigDecimal freight, BigDecimal registration, String sizeLimit, String lastMile, String leadTime, Date eff)
|
BigDecimal freight, BigDecimal registration, String sizeLimit, String lastMile, String leadTime, Date eff)
|
||||||
{
|
{
|
||||||
BmsProductCountry c = new BmsProductCountry();
|
BmsProductCountry c = new BmsProductCountry();
|
||||||
c.setCountryName(countryName);
|
c.setCountryName(countryName);
|
||||||
c.setCountryCode(countryCode);
|
c.setCountryCode(countryCode);
|
||||||
|
applyZoneFields(c, countryCode, zoneLabel, zoneByCountryAndCode);
|
||||||
c.setWeightMin(range[0]);
|
c.setWeightMin(range[0]);
|
||||||
c.setWeightMax(range[1]);
|
c.setWeightMax(range[1]);
|
||||||
c.setFreightPrice(freight);
|
c.setFreightPrice(freight);
|
||||||
@@ -847,6 +868,68 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyZoneFields(BmsProductCountry c, String countryCode, String zoneLabel,
|
||||||
|
Map<String, PrcZone> zoneByCountryAndCode)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(zoneLabel))
|
||||||
|
{
|
||||||
|
c.setZoneId(null);
|
||||||
|
c.setZoneCode(null);
|
||||||
|
c.setZoneName(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String zoneCode = ZoneCodeAbbrevs.toAbbrev(zoneLabel);
|
||||||
|
c.setZoneCode(zoneCode);
|
||||||
|
PrcZone zone = null;
|
||||||
|
if (StringUtils.isNotEmpty(countryCode) && zoneByCountryAndCode != null)
|
||||||
|
{
|
||||||
|
zone = zoneByCountryAndCode.get(zoneKey(countryCode, zoneCode));
|
||||||
|
}
|
||||||
|
if (zone != null)
|
||||||
|
{
|
||||||
|
c.setZoneId(zone.getZoneId());
|
||||||
|
c.setZoneCode(zone.getZoneCode());
|
||||||
|
c.setZoneName(zone.getZoneName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c.setZoneId(null);
|
||||||
|
c.setZoneName(StringUtils.isNotEmpty(c.getCountryName())
|
||||||
|
? c.getCountryName() + "(" + zoneCode + ")"
|
||||||
|
: zoneCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PrcZone> loadZoneByCountryAndCode()
|
||||||
|
{
|
||||||
|
Map<String, PrcZone> map = new HashMap<>();
|
||||||
|
List<PrcZone> list = zoneMapper.selectPrcZoneList(new PrcZone());
|
||||||
|
if (list == null)
|
||||||
|
{
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (PrcZone zone : list)
|
||||||
|
{
|
||||||
|
if (zone == null || StringUtils.isEmpty(zone.getZoneCode()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String code2 = zone.getCountryCode();
|
||||||
|
if (StringUtils.isEmpty(code2))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
map.put(zoneKey(code2, zone.getZoneCode()), zone);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String zoneKey(String countryCode, String zoneCode)
|
||||||
|
{
|
||||||
|
return String.valueOf(countryCode).trim().toUpperCase(Locale.ROOT)
|
||||||
|
+ "|" + String.valueOf(zoneCode).trim().toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load bms_region map: country_name_cn -> region (for country_code_2).
|
* Load bms_region map: country_name_cn -> region (for country_code_2).
|
||||||
*/
|
*/
|
||||||
@@ -907,7 +990,6 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
{
|
{
|
||||||
return candidates.get(0);
|
return candidates.get(0);
|
||||||
}
|
}
|
||||||
// Prefer longest Chinese name match (e.g. 捷克共和国 over 捷克)
|
|
||||||
BmsRegion best = candidates.get(0);
|
BmsRegion best = candidates.get(0);
|
||||||
for (int i = 1; i < candidates.size(); i++)
|
for (int i = 1; i < candidates.size(); i++)
|
||||||
{
|
{
|
||||||
@@ -920,29 +1002,6 @@ public class BmsProductServiceImpl implements IBmsProductService
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Strip Excel footnotes like "澳大利亚(1区)\\n分区点击" */
|
|
||||||
private static String normalizeCountryName(String name)
|
|
||||||
{
|
|
||||||
if (name == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String n = name.trim();
|
|
||||||
int cut = n.length();
|
|
||||||
int nl = n.indexOf('\n');
|
|
||||||
if (nl >= 0 && nl < cut)
|
|
||||||
{
|
|
||||||
cut = nl;
|
|
||||||
}
|
|
||||||
int cr = n.indexOf('\r');
|
|
||||||
if (cr >= 0 && cr < cut)
|
|
||||||
{
|
|
||||||
cut = cr;
|
|
||||||
}
|
|
||||||
n = n.substring(0, cut).trim();
|
|
||||||
return n.isEmpty() ? null : n;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal[] parseWeightRange(String label)
|
private BigDecimal[] parseWeightRange(String label)
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(label))
|
if (StringUtils.isEmpty(label))
|
||||||
|
|||||||
+130
-12
@@ -248,6 +248,9 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
|||||||
nd.setProductName(d.getProductName());
|
nd.setProductName(d.getProductName());
|
||||||
nd.setCountryCode(d.getCountryCode());
|
nd.setCountryCode(d.getCountryCode());
|
||||||
nd.setCountryName(d.getCountryName());
|
nd.setCountryName(d.getCountryName());
|
||||||
|
nd.setZoneId(d.getZoneId());
|
||||||
|
nd.setZoneCode(d.getZoneCode());
|
||||||
|
nd.setZoneName(d.getZoneName());
|
||||||
nd.setZdId(d.getZdId());
|
nd.setZdId(d.getZdId());
|
||||||
nd.setWsdId(d.getWsdId());
|
nd.setWsdId(d.getWsdId());
|
||||||
nd.setWeightLabel(d.getWeightLabel());
|
nd.setWeightLabel(d.getWeightLabel());
|
||||||
@@ -659,6 +662,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
|||||||
{
|
{
|
||||||
throw new ServiceException(MessageUtils.message("price.set.detail.country.empty"));
|
throw new ServiceException(MessageUtils.message("price.set.detail.country.empty"));
|
||||||
}
|
}
|
||||||
|
requireZoneIfNeeded(detail);
|
||||||
if (detail.getWsdId() == null && StringUtils.isEmpty(detail.getWeightLabel()))
|
if (detail.getWsdId() == null && StringUtils.isEmpty(detail.getWeightLabel()))
|
||||||
{
|
{
|
||||||
throw new ServiceException(MessageUtils.message("price.set.detail.weight.empty"));
|
throw new ServiceException(MessageUtils.message("price.set.detail.weight.empty"));
|
||||||
@@ -692,6 +696,29 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When product country rows for this country have zones, zone is required.
|
||||||
|
*/
|
||||||
|
private void requireZoneIfNeeded(PrcPriceSetDetail detail)
|
||||||
|
{
|
||||||
|
if (detail.getZoneId() != null || StringUtils.isNotEmpty(detail.getZoneCode()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<BmsProductCountry> rows = listOpenProductCountries(detail.getProductCode());
|
||||||
|
for (BmsProductCountry row : rows)
|
||||||
|
{
|
||||||
|
if (!matchDetailCountryOnly(detail, row))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (row.getZoneId() != null || StringUtils.isNotEmpty(row.getZoneCode()))
|
||||||
|
{
|
||||||
|
throw new ServiceException(MessageUtils.message("price.set.detail.zone.empty"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve / verify product_country_id from bms_product_country when missing.
|
* Resolve / verify product_country_id from bms_product_country when missing.
|
||||||
*/
|
*/
|
||||||
@@ -699,25 +726,16 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
|||||||
{
|
{
|
||||||
if (detail.getProductCountryId() != null)
|
if (detail.getProductCountryId() != null)
|
||||||
{
|
{
|
||||||
|
syncZoneFromProductCountry(detail);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(detail.getProductCode()) || StringUtils.isEmpty(detail.getWeightLabel()))
|
if (StringUtils.isEmpty(detail.getProductCode()) || StringUtils.isEmpty(detail.getWeightLabel()))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BmsProductCountry query = new BmsProductCountry();
|
List<BmsProductCountry> rows = listOpenProductCountries(detail.getProductCode());
|
||||||
query.setProductCode(detail.getProductCode());
|
|
||||||
List<BmsProductCountry> rows = productCountryMapper.selectBmsProductCountryList(query);
|
|
||||||
if (rows == null || rows.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (BmsProductCountry row : rows)
|
for (BmsProductCountry row : rows)
|
||||||
{
|
{
|
||||||
if (row.getExpireDate() != null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!matchDetailCountry(detail, row))
|
if (!matchDetailCountry(detail, row))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -730,12 +748,80 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
|||||||
&& detail.getWeightLabel().contains(stripTrailingZeros(row.getWeightMax()))))
|
&& detail.getWeightLabel().contains(stripTrailingZeros(row.getWeightMax()))))
|
||||||
{
|
{
|
||||||
detail.setProductCountryId(row.getDetailId());
|
detail.setProductCountryId(row.getDetailId());
|
||||||
|
applyZoneFromRow(detail, row);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchDetailCountry(PrcPriceSetDetail detail, BmsProductCountry row)
|
private void syncZoneFromProductCountry(PrcPriceSetDetail detail)
|
||||||
|
{
|
||||||
|
if (detail.getProductCountryId() == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (detail.getZoneId() != null || StringUtils.isNotEmpty(detail.getZoneCode()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BmsProductCountry query = new BmsProductCountry();
|
||||||
|
query.setProductCode(detail.getProductCode());
|
||||||
|
List<BmsProductCountry> rows = productCountryMapper.selectBmsProductCountryList(query);
|
||||||
|
if (rows == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (BmsProductCountry row : rows)
|
||||||
|
{
|
||||||
|
if (detail.getProductCountryId().equals(row.getDetailId()))
|
||||||
|
{
|
||||||
|
applyZoneFromRow(detail, row);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BmsProductCountry> listOpenProductCountries(String productCode)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(productCode))
|
||||||
|
{
|
||||||
|
return java.util.Collections.emptyList();
|
||||||
|
}
|
||||||
|
BmsProductCountry query = new BmsProductCountry();
|
||||||
|
query.setProductCode(productCode);
|
||||||
|
List<BmsProductCountry> rows = productCountryMapper.selectBmsProductCountryList(query);
|
||||||
|
if (rows == null || rows.isEmpty())
|
||||||
|
{
|
||||||
|
return java.util.Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<BmsProductCountry> open = new ArrayList<BmsProductCountry>();
|
||||||
|
for (BmsProductCountry row : rows)
|
||||||
|
{
|
||||||
|
if (row.getExpireDate() == null)
|
||||||
|
{
|
||||||
|
open.add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyZoneFromRow(PrcPriceSetDetail detail, BmsProductCountry row)
|
||||||
|
{
|
||||||
|
if (detail.getZoneId() == null)
|
||||||
|
{
|
||||||
|
detail.setZoneId(row.getZoneId());
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(detail.getZoneCode()))
|
||||||
|
{
|
||||||
|
detail.setZoneCode(row.getZoneCode());
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(detail.getZoneName()))
|
||||||
|
{
|
||||||
|
detail.setZoneName(row.getZoneName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean matchDetailCountryOnly(PrcPriceSetDetail detail, BmsProductCountry row)
|
||||||
{
|
{
|
||||||
if (StringUtils.isNotEmpty(detail.getCountryCode()) && StringUtils.isNotEmpty(row.getCountryCode()))
|
if (StringUtils.isNotEmpty(detail.getCountryCode()) && StringUtils.isNotEmpty(row.getCountryCode()))
|
||||||
{
|
{
|
||||||
@@ -745,6 +831,38 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
|
|||||||
&& detail.getCountryName().equals(row.getCountryName());
|
&& detail.getCountryName().equals(row.getCountryName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean matchDetailCountry(PrcPriceSetDetail detail, BmsProductCountry row)
|
||||||
|
{
|
||||||
|
if (!matchDetailCountryOnly(detail, row))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return matchDetailZone(detail, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean matchDetailZone(PrcPriceSetDetail detail, BmsProductCountry row)
|
||||||
|
{
|
||||||
|
boolean detailHas = detail.getZoneId() != null || StringUtils.isNotEmpty(detail.getZoneCode());
|
||||||
|
boolean rowHas = row.getZoneId() != null || StringUtils.isNotEmpty(row.getZoneCode());
|
||||||
|
if (!detailHas && !rowHas)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (detailHas != rowHas)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (detail.getZoneId() != null && row.getZoneId() != null)
|
||||||
|
{
|
||||||
|
return detail.getZoneId().equals(row.getZoneId());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotEmpty(detail.getZoneCode()) && StringUtils.isNotEmpty(row.getZoneCode()))
|
||||||
|
{
|
||||||
|
return detail.getZoneCode().equalsIgnoreCase(row.getZoneCode());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static String formatWeightLabel(BigDecimal min, BigDecimal max)
|
private static String formatWeightLabel(BigDecimal min, BigDecimal max)
|
||||||
{
|
{
|
||||||
return stripTrailingZeros(min) + "<W\u2264" + stripTrailingZeros(max);
|
return stripTrailingZeros(min) + "<W\u2264" + stripTrailingZeros(max);
|
||||||
|
|||||||
+4
-75
@@ -31,6 +31,7 @@ import com.ruoyi.price.domain.PrcZoneRegion;
|
|||||||
import com.ruoyi.price.mapper.PrcZoneDetailMapper;
|
import com.ruoyi.price.mapper.PrcZoneDetailMapper;
|
||||||
import com.ruoyi.price.mapper.PrcZoneMapper;
|
import com.ruoyi.price.mapper.PrcZoneMapper;
|
||||||
import com.ruoyi.price.service.IPrcZoneService;
|
import com.ruoyi.price.service.IPrcZoneService;
|
||||||
|
import com.ruoyi.price.util.ZoneCodeAbbrevs;
|
||||||
import com.ruoyi.system.domain.BmsRegion;
|
import com.ruoyi.system.domain.BmsRegion;
|
||||||
import com.ruoyi.system.mapper.BmsRegionMapper;
|
import com.ruoyi.system.mapper.BmsRegionMapper;
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ public class PrcZoneServiceImpl implements IPrcZoneService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(zone.getZoneCode()))
|
if (StringUtils.isNotEmpty(zone.getZoneCode()))
|
||||||
{
|
{
|
||||||
zone.setZoneCode(toZoneCodeAbbrev(zone.getZoneCode()));
|
zone.setZoneCode(ZoneCodeAbbrevs.toAbbrev(zone.getZoneCode()));
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(zone.getZoneName()))
|
if (StringUtils.isEmpty(zone.getZoneName()))
|
||||||
{
|
{
|
||||||
@@ -156,7 +157,7 @@ public class PrcZoneServiceImpl implements IPrcZoneService
|
|||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(zone.getZoneCode()))
|
if (StringUtils.isNotEmpty(zone.getZoneCode()))
|
||||||
{
|
{
|
||||||
zone.setZoneCode(toZoneCodeAbbrev(zone.getZoneCode()));
|
zone.setZoneCode(ZoneCodeAbbrevs.toAbbrev(zone.getZoneCode()));
|
||||||
}
|
}
|
||||||
if (zone.getZoneSort() == null && StringUtils.isNotEmpty(zone.getZoneCode()))
|
if (zone.getZoneSort() == null && StringUtils.isNotEmpty(zone.getZoneCode()))
|
||||||
{
|
{
|
||||||
@@ -293,7 +294,7 @@ public class PrcZoneServiceImpl implements IPrcZoneService
|
|||||||
String countryName = region.getCountryNameCn();
|
String countryName = region.getCountryNameCn();
|
||||||
for (Map.Entry<String, List<String>> entry : entries)
|
for (Map.Entry<String, List<String>> entry : entries)
|
||||||
{
|
{
|
||||||
String zoneCode = toZoneCodeAbbrev(entry.getKey());
|
String zoneCode = ZoneCodeAbbrevs.toAbbrev(entry.getKey());
|
||||||
PrcZone zone = new PrcZone();
|
PrcZone zone = new PrcZone();
|
||||||
zone.setRegionId(regionId);
|
zone.setRegionId(regionId);
|
||||||
zone.setCountryCode(region.getCountryCode2());
|
zone.setCountryCode(region.getCountryCode2());
|
||||||
@@ -868,78 +869,6 @@ public class PrcZoneServiceImpl implements IPrcZoneService
|
|||||||
return (countryName == null ? "" : countryName) + "(" + zoneCode + ")";
|
return (countryName == null ? "" : countryName) + "(" + zoneCode + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Map Excel/local zone labels to English abbreviations for zone_code.
|
|
||||||
*/
|
|
||||||
private static String toZoneCodeAbbrev(String raw)
|
|
||||||
{
|
|
||||||
if (StringUtils.isEmpty(raw))
|
|
||||||
{
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
String key = raw.replace('\r', ' ').replace('\n', ' ').replaceAll("\\s+", " ").trim();
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case "1\u533a":
|
|
||||||
return "Z1";
|
|
||||||
case "2\u533a":
|
|
||||||
return "Z2";
|
|
||||||
case "3\u533a":
|
|
||||||
return "Z3";
|
|
||||||
case "4\u533a":
|
|
||||||
return "Z4";
|
|
||||||
case "5\u533a":
|
|
||||||
return "Z5";
|
|
||||||
case "Mondial Relay":
|
|
||||||
return "MR";
|
|
||||||
case "\u6c96\u7e04\u770c":
|
|
||||||
return "OKN";
|
|
||||||
case "\u5317\u7231\u5c14\u5170":
|
|
||||||
return "NI";
|
|
||||||
case "\u9ad8\u5730\u548c\u5c9b\u5c7f":
|
|
||||||
return "HI";
|
|
||||||
case "\u6d77\u5ce1\u7fa4\u5c9b(\u542b\u9521\u5229\u5c9b)":
|
|
||||||
return "CI";
|
|
||||||
case "\u9a6c\u6069\u5c9b":
|
|
||||||
return "IM";
|
|
||||||
case "Beckton":
|
|
||||||
return "BEC";
|
|
||||||
case "Bristol":
|
|
||||||
return "BRS";
|
|
||||||
case "Cambridge":
|
|
||||||
return "CAM";
|
|
||||||
case "Collier Wood":
|
|
||||||
return "CW";
|
|
||||||
case "Guernsey":
|
|
||||||
return "GG";
|
|
||||||
case "Isle of Man Bailiwick of Guernsey States of Jersey":
|
|
||||||
return "IM_GG_JE";
|
|
||||||
case "Lakeside":
|
|
||||||
return "LKS";
|
|
||||||
case "London Congestion Zone":
|
|
||||||
return "LCZ";
|
|
||||||
case "London Congestion Zone - Gemini":
|
|
||||||
return "LCZ_GEM";
|
|
||||||
case "London ULEZ - Wimbledon":
|
|
||||||
return "ULEZ_WIM";
|
|
||||||
case "Northern Ireland Isles of Scilly":
|
|
||||||
return "NI_SCILLY";
|
|
||||||
case "Old Kent Road Cargo Bikes":
|
|
||||||
return "OKR_CB";
|
|
||||||
case "Oxford":
|
|
||||||
return "OXF";
|
|
||||||
case "The Highlands Islands of Scotland":
|
|
||||||
return "HIGHLANDS";
|
|
||||||
default:
|
|
||||||
// already abbrev or unknown: keep trimmed original (spaces -> underscore)
|
|
||||||
if (key.matches(".*[\\u4e00-\\u9fff].*"))
|
|
||||||
{
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
return key.replace(' ', '_').toUpperCase(Locale.ROOT).replaceAll("_+", "_");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int extractZoneSort(String zoneCode)
|
private static int extractZoneSort(String zoneCode)
|
||||||
{
|
{
|
||||||
if (StringUtils.isEmpty(zoneCode))
|
if (StringUtils.isEmpty(zoneCode))
|
||||||
|
|||||||
+2
-1
@@ -248,7 +248,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
|
|||||||
|
|
||||||
PrcPriceSetDetail detail = detailMapper.selectFixedMatch(
|
PrcPriceSetDetail detail = detailMapper.selectFixedMatch(
|
||||||
set.getPriceSetId(), productCode,
|
set.getPriceSetId(), productCode,
|
||||||
base.getCountryCode(), base.getCountryName(), base.getDetailId(),
|
base.getCountryCode(), base.getCountryName(), base.getZoneCode(),
|
||||||
|
base.getDetailId(),
|
||||||
formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
|
formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
|
||||||
if (detail == null)
|
if (detail == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.ruoyi.price.util;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Excel country cell with optional zone suffix, map labels to English abbreviations.
|
||||||
|
*/
|
||||||
|
public final class ZoneCodeAbbrevs
|
||||||
|
{
|
||||||
|
// fullwidth () and ASCII ()
|
||||||
|
private static final Pattern ZONE_SUFFIX = Pattern.compile(
|
||||||
|
"^(.+?)[\\(\\uFF08]([^\\)\\uFF09]+)[\\)\\uFF09]\\s*$");
|
||||||
|
|
||||||
|
private ZoneCodeAbbrevs()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class CountryZoneParts
|
||||||
|
{
|
||||||
|
public final String countryName;
|
||||||
|
public final String zoneLabel;
|
||||||
|
|
||||||
|
public CountryZoneParts(String countryName, String zoneLabel)
|
||||||
|
{
|
||||||
|
this.countryName = countryName;
|
||||||
|
this.zoneLabel = zoneLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split country cell like Country(zoneLabel); no parenthesis -> zoneLabel null.
|
||||||
|
*/
|
||||||
|
public static CountryZoneParts splitCountryZone(String raw)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(raw))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String n = raw.trim();
|
||||||
|
int cut = n.length();
|
||||||
|
int nl = n.indexOf('\n');
|
||||||
|
if (nl >= 0 && nl < cut)
|
||||||
|
{
|
||||||
|
cut = nl;
|
||||||
|
}
|
||||||
|
int cr = n.indexOf('\r');
|
||||||
|
if (cr >= 0 && cr < cut)
|
||||||
|
{
|
||||||
|
cut = cr;
|
||||||
|
}
|
||||||
|
n = n.substring(0, cut).trim();
|
||||||
|
if (n.isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Matcher m = ZONE_SUFFIX.matcher(n);
|
||||||
|
if (m.matches())
|
||||||
|
{
|
||||||
|
String country = m.group(1).trim();
|
||||||
|
String zone = m.group(2).trim();
|
||||||
|
if (StringUtils.isEmpty(country))
|
||||||
|
{
|
||||||
|
return new CountryZoneParts(n, null);
|
||||||
|
}
|
||||||
|
return new CountryZoneParts(country, StringUtils.isEmpty(zone) ? null : zone);
|
||||||
|
}
|
||||||
|
return new CountryZoneParts(n, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toAbbrev(String raw)
|
||||||
|
{
|
||||||
|
if (StringUtils.isEmpty(raw))
|
||||||
|
{
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
String key = raw.replace('\r', ' ').replace('\n', ' ').replaceAll("\\s+", " ").trim();
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "1\u533a":
|
||||||
|
return "Z1";
|
||||||
|
case "2\u533a":
|
||||||
|
return "Z2";
|
||||||
|
case "3\u533a":
|
||||||
|
return "Z3";
|
||||||
|
case "4\u533a":
|
||||||
|
return "Z4";
|
||||||
|
case "5\u533a":
|
||||||
|
return "Z5";
|
||||||
|
case "Mondial Relay":
|
||||||
|
return "MR";
|
||||||
|
case "\u6c96\u7e04\u770c":
|
||||||
|
return "OKN";
|
||||||
|
case "\u5317\u7231\u5c14\u5170":
|
||||||
|
return "NI";
|
||||||
|
case "\u9ad8\u5730\u548c\u5c9b\u5c7f":
|
||||||
|
return "HI";
|
||||||
|
case "\u6d77\u5ce1\u7fa4\u5c9b(\u542b\u9521\u5229\u5c9b)":
|
||||||
|
return "CI";
|
||||||
|
case "\u9a6c\u6069\u5c9b":
|
||||||
|
return "IM";
|
||||||
|
case "Beckton":
|
||||||
|
return "BEC";
|
||||||
|
case "Bristol":
|
||||||
|
return "BRS";
|
||||||
|
case "Cambridge":
|
||||||
|
return "CAM";
|
||||||
|
case "Collier Wood":
|
||||||
|
return "CW";
|
||||||
|
case "Guernsey":
|
||||||
|
return "GG";
|
||||||
|
case "Isle of Man Bailiwick of Guernsey States of Jersey":
|
||||||
|
return "IM_GG_JE";
|
||||||
|
case "Lakeside":
|
||||||
|
return "LKS";
|
||||||
|
case "London Congestion Zone":
|
||||||
|
return "LCZ";
|
||||||
|
case "London Congestion Zone - Gemini":
|
||||||
|
return "LCZ_GEM";
|
||||||
|
case "London ULEZ - Wimbledon":
|
||||||
|
return "ULEZ_WIM";
|
||||||
|
case "Northern Ireland Isles of Scilly":
|
||||||
|
return "NI_SCILLY";
|
||||||
|
case "Old Kent Road Cargo Bikes":
|
||||||
|
return "OKR_CB";
|
||||||
|
case "Oxford":
|
||||||
|
return "OXF";
|
||||||
|
case "The Highlands Islands of Scotland":
|
||||||
|
return "HIGHLANDS";
|
||||||
|
default:
|
||||||
|
if (key.matches("(?i)Z[1-9]\\d*"))
|
||||||
|
{
|
||||||
|
return key.toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
if (key.matches(".*[\\u4e00-\\u9fff].*"))
|
||||||
|
{
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
return key.replace(' ', '_').toUpperCase(Locale.ROOT).replaceAll("_+", "_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="productCode" column="product_code" />
|
<result property="productCode" column="product_code" />
|
||||||
<result property="countryName" column="country_name" />
|
<result property="countryName" column="country_name" />
|
||||||
<result property="countryCode" column="country_code" />
|
<result property="countryCode" column="country_code" />
|
||||||
|
<result property="zoneId" column="zone_id" />
|
||||||
|
<result property="zoneCode" column="zone_code" />
|
||||||
|
<result property="zoneName" column="zone_name" />
|
||||||
<result property="weightMin" column="weight_min" />
|
<result property="weightMin" column="weight_min" />
|
||||||
<result property="weightMax" column="weight_max" />
|
<result property="weightMax" column="weight_max" />
|
||||||
<result property="freightPrice" column="freight_price" />
|
<result property="freightPrice" column="freight_price" />
|
||||||
@@ -26,7 +29,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectVo">
|
<sql id="selectVo">
|
||||||
select detail_id, product_id, product_code, country_name, country_code, weight_min, weight_max,
|
select detail_id, product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name,
|
||||||
|
weight_min, weight_max,
|
||||||
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||||
effective_date, expire_date, status, create_time, update_time
|
effective_date, expire_date, status, create_time, update_time
|
||||||
from bms_product_country
|
from bms_product_country
|
||||||
@@ -47,6 +51,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="countryCode != null and countryCode != ''">
|
<if test="countryCode != null and countryCode != ''">
|
||||||
AND country_code = #{countryCode}
|
AND country_code = #{countryCode}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="zoneId != null">
|
||||||
|
AND zone_id = #{zoneId}
|
||||||
|
</if>
|
||||||
|
<if test="zoneCode != null and zoneCode != ''">
|
||||||
|
AND zone_code = #{zoneCode}
|
||||||
|
</if>
|
||||||
<if test="weightMin != null">
|
<if test="weightMin != null">
|
||||||
AND weight_min = #{weightMin}
|
AND weight_min = #{weightMin}
|
||||||
</if>
|
</if>
|
||||||
@@ -66,16 +76,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
AND status = #{effectStatus}
|
AND status = #{effectStatus}
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
order by effective_date desc, country_name asc, weight_min asc, detail_id asc
|
order by effective_date desc, country_name asc, zone_code asc, weight_min asc, detail_id asc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insertBmsProductCountry" parameterType="BmsProductCountry" useGeneratedKeys="true" keyProperty="detailId">
|
<insert id="insertBmsProductCountry" parameterType="BmsProductCountry" useGeneratedKeys="true" keyProperty="detailId">
|
||||||
insert into bms_product_country (
|
insert into bms_product_country (
|
||||||
product_id, product_code, country_name, country_code, weight_min, weight_max,
|
product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name,
|
||||||
|
weight_min, weight_max,
|
||||||
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
freight_price, registration_fee, size_limit, last_mile, lead_time,
|
||||||
effective_date, expire_date, status, create_time
|
effective_date, expire_date, status, create_time
|
||||||
) values (
|
) values (
|
||||||
#{productId}, #{productCode}, #{countryName}, #{countryCode}, #{weightMin}, #{weightMax},
|
#{productId}, #{productCode}, #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName},
|
||||||
|
#{weightMin}, #{weightMax},
|
||||||
#{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime},
|
#{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime},
|
||||||
#{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), sysdate()
|
#{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), sysdate()
|
||||||
)
|
)
|
||||||
@@ -162,7 +174,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="bizDate == null">
|
<if test="bizDate == null">
|
||||||
and expire_date is null
|
and expire_date is null
|
||||||
</if>
|
</if>
|
||||||
order by effective_date desc, weight_min asc, detail_id asc
|
order by
|
||||||
|
case when zone_code is null or zone_code = '' then 0 else 1 end,
|
||||||
|
effective_date desc, weight_min asc, detail_id asc
|
||||||
limit 1
|
limit 1
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="productName" column="product_name" />
|
<result property="productName" column="product_name" />
|
||||||
<result property="countryCode" column="country_code" />
|
<result property="countryCode" column="country_code" />
|
||||||
<result property="countryName" column="country_name" />
|
<result property="countryName" column="country_name" />
|
||||||
|
<result property="zoneId" column="zone_id" />
|
||||||
|
<result property="zoneCode" column="zone_code" />
|
||||||
|
<result property="zoneName" column="zone_name" />
|
||||||
<result property="zdId" column="zd_id" />
|
<result property="zdId" column="zd_id" />
|
||||||
<result property="wsdId" column="wsd_id" />
|
<result property="wsdId" column="wsd_id" />
|
||||||
<result property="weightLabel" column="weight_label" />
|
<result property="weightLabel" column="weight_label" />
|
||||||
@@ -26,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<sql id="selectPrcPriceSetDetailVo">
|
<sql id="selectPrcPriceSetDetailVo">
|
||||||
select psd_id, price_set_id, product_code, product_name, country_code, country_name,
|
select psd_id, price_set_id, product_code, product_name, country_code, country_name,
|
||||||
|
zone_id, zone_code, zone_name,
|
||||||
zd_id, wsd_id, weight_label, product_country_id, fv_value, fv_registration_fee,
|
zd_id, wsd_id, weight_label, product_country_id, fv_value, fv_registration_fee,
|
||||||
discount_value, round_mode, pr_id, create_time, update_time
|
discount_value, round_mode, pr_id, create_time, update_time
|
||||||
from prc_price_set_detail
|
from prc_price_set_detail
|
||||||
@@ -48,6 +52,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="countryCode != null and countryCode != ''">
|
<if test="countryCode != null and countryCode != ''">
|
||||||
AND country_code = #{countryCode}
|
AND country_code = #{countryCode}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="zoneCode != null and zoneCode != ''">
|
||||||
|
AND zone_code = #{zoneCode}
|
||||||
|
</if>
|
||||||
</where>
|
</where>
|
||||||
order by psd_id asc
|
order by psd_id asc
|
||||||
</select>
|
</select>
|
||||||
@@ -67,6 +74,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="productName != null">product_name,</if>
|
<if test="productName != null">product_name,</if>
|
||||||
<if test="countryCode != null">country_code,</if>
|
<if test="countryCode != null">country_code,</if>
|
||||||
<if test="countryName != null">country_name,</if>
|
<if test="countryName != null">country_name,</if>
|
||||||
|
<if test="zoneId != null">zone_id,</if>
|
||||||
|
<if test="zoneCode != null">zone_code,</if>
|
||||||
|
<if test="zoneName != null">zone_name,</if>
|
||||||
<if test="zdId != null">zd_id,</if>
|
<if test="zdId != null">zd_id,</if>
|
||||||
<if test="wsdId != null">wsd_id,</if>
|
<if test="wsdId != null">wsd_id,</if>
|
||||||
<if test="weightLabel != null">weight_label,</if>
|
<if test="weightLabel != null">weight_label,</if>
|
||||||
@@ -83,6 +93,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="productName != null">#{productName},</if>
|
<if test="productName != null">#{productName},</if>
|
||||||
<if test="countryCode != null">#{countryCode},</if>
|
<if test="countryCode != null">#{countryCode},</if>
|
||||||
<if test="countryName != null">#{countryName},</if>
|
<if test="countryName != null">#{countryName},</if>
|
||||||
|
<if test="zoneId != null">#{zoneId},</if>
|
||||||
|
<if test="zoneCode != null">#{zoneCode},</if>
|
||||||
|
<if test="zoneName != null">#{zoneName},</if>
|
||||||
<if test="zdId != null">#{zdId},</if>
|
<if test="zdId != null">#{zdId},</if>
|
||||||
<if test="wsdId != null">#{wsdId},</if>
|
<if test="wsdId != null">#{wsdId},</if>
|
||||||
<if test="weightLabel != null">#{weightLabel},</if>
|
<if test="weightLabel != null">#{weightLabel},</if>
|
||||||
@@ -103,6 +116,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="productName != null">product_name = #{productName},</if>
|
<if test="productName != null">product_name = #{productName},</if>
|
||||||
country_code = #{countryCode},
|
country_code = #{countryCode},
|
||||||
country_name = #{countryName},
|
country_name = #{countryName},
|
||||||
|
zone_id = #{zoneId},
|
||||||
|
zone_code = #{zoneCode},
|
||||||
|
zone_name = #{zoneName},
|
||||||
zd_id = #{zdId},
|
zd_id = #{zdId},
|
||||||
wsd_id = #{wsdId},
|
wsd_id = #{wsdId},
|
||||||
weight_label = #{weightLabel},
|
weight_label = #{weightLabel},
|
||||||
@@ -149,6 +165,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
1 = 0
|
1 = 0
|
||||||
</otherwise>
|
</otherwise>
|
||||||
</choose>
|
</choose>
|
||||||
|
<if test="zoneCode != null and zoneCode != ''">
|
||||||
|
and (zone_code is null or upper(zone_code) = upper(#{zoneCode}))
|
||||||
|
</if>
|
||||||
<if test="weightLabel != null and weightLabel != ''">
|
<if test="weightLabel != null and weightLabel != ''">
|
||||||
and (weight_label is null or weight_label = #{weightLabel} or weight_label like concat('%', #{weightLabel}, '%'))
|
and (weight_label is null or weight_label = #{weightLabel} or weight_label like concat('%', #{weightLabel}, '%'))
|
||||||
</if>
|
</if>
|
||||||
@@ -156,6 +175,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
)
|
)
|
||||||
order by
|
order by
|
||||||
case when #{productCountryId} is not null and product_country_id = #{productCountryId} then 0 else 1 end,
|
case when #{productCountryId} is not null and product_country_id = #{productCountryId} then 0 else 1 end,
|
||||||
|
case when #{zoneCode} is not null and upper(ifnull(zone_code,'')) = upper(#{zoneCode}) then 0 else 1 end,
|
||||||
|
case when zone_code is null then 1 else 0 end,
|
||||||
psd_id asc
|
psd_id asc
|
||||||
limit 1
|
limit 1
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -38,9 +38,12 @@ CREATE TABLE IF NOT EXISTS prc_price_set_detail (
|
|||||||
price_set_id BIGINT(20) NOT NULL COMMENT '价格套餐id',
|
price_set_id BIGINT(20) NOT NULL COMMENT '价格套餐id',
|
||||||
product_code VARCHAR(10) NOT NULL COMMENT '产品代码',
|
product_code VARCHAR(10) NOT NULL COMMENT '产品代码',
|
||||||
product_name VARCHAR(50) DEFAULT NULL COMMENT '产品名称',
|
product_name VARCHAR(50) DEFAULT NULL COMMENT '产品名称',
|
||||||
country_code CHAR(2) DEFAULT NULL COMMENT '国家二字码',
|
country_code CHAR(2) DEFAULT NULL COMMENT 'country ISO2',
|
||||||
country_name VARCHAR(50) DEFAULT NULL COMMENT '国家名称',
|
country_name VARCHAR(50) DEFAULT NULL COMMENT 'country name',
|
||||||
zd_id BIGINT(20) DEFAULT NULL COMMENT '收费区划id',
|
zone_id BIGINT(20) DEFAULT NULL COMMENT 'prc_zone.zone_id',
|
||||||
|
zone_code VARCHAR(50) DEFAULT NULL COMMENT 'zone code e.g. Z1',
|
||||||
|
zone_name VARCHAR(100) DEFAULT NULL COMMENT 'zone display name',
|
||||||
|
zd_id BIGINT(20) DEFAULT NULL COMMENT 'legacy zone division id',
|
||||||
wsd_id BIGINT(20) DEFAULT NULL COMMENT '重量段明细id',
|
wsd_id BIGINT(20) DEFAULT NULL COMMENT '重量段明细id',
|
||||||
weight_label VARCHAR(50) DEFAULT NULL COMMENT '重量段展示',
|
weight_label VARCHAR(50) DEFAULT NULL COMMENT '重量段展示',
|
||||||
product_country_id BIGINT(20) DEFAULT NULL COMMENT '关联 bms_product_country.detail_id',
|
product_country_id BIGINT(20) DEFAULT NULL COMMENT '关联 bms_product_country.detail_id',
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
-- Add zone fields to prc_price_set_detail (nullable when country has no zone)
|
||||||
|
-- Idempotent: safe to re-run
|
||||||
|
-- charset: utf8mb4
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
-- zone_id
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND COLUMN_NAME = 'zone_id'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE prc_price_set_detail ADD COLUMN zone_id BIGINT(20) DEFAULT NULL COMMENT ''prc_zone.zone_id'' AFTER country_name',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- zone_code
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND COLUMN_NAME = 'zone_code'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE prc_price_set_detail ADD COLUMN zone_code VARCHAR(50) DEFAULT NULL COMMENT ''zone code e.g. Z1'' AFTER zone_id',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- zone_name
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND COLUMN_NAME = 'zone_name'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE prc_price_set_detail ADD COLUMN zone_name VARCHAR(100) DEFAULT NULL COMMENT ''zone display name'' AFTER zone_code',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- index
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND INDEX_NAME = 'idx_psd_zone'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE prc_price_set_detail ADD INDEX idx_psd_zone (zone_id)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'prc_price_set_detail' AND INDEX_NAME = 'idx_psd_country_zone'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE prc_price_set_detail ADD INDEX idx_psd_country_zone (price_set_id, product_code, country_code, zone_code)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
@@ -34,6 +34,9 @@ CREATE TABLE IF NOT EXISTS bms_product_country (
|
|||||||
product_code varchar(20) NOT NULL COMMENT '产品代码',
|
product_code varchar(20) NOT NULL COMMENT '产品代码',
|
||||||
country_name varchar(50) NOT NULL COMMENT '国家中文名',
|
country_name varchar(50) NOT NULL COMMENT '国家中文名',
|
||||||
country_code char(2) DEFAULT NULL COMMENT 'ISO2,可空待补齐',
|
country_code char(2) DEFAULT NULL COMMENT 'ISO2,可空待补齐',
|
||||||
|
zone_id bigint DEFAULT NULL COMMENT 'prc_zone.zone_id',
|
||||||
|
zone_code varchar(50) DEFAULT NULL COMMENT 'zone code e.g. Z1',
|
||||||
|
zone_name varchar(100) DEFAULT NULL COMMENT 'zone display name',
|
||||||
weight_min decimal(10,3) DEFAULT NULL COMMENT '重量段最小值KG(含)',
|
weight_min decimal(10,3) DEFAULT NULL COMMENT '重量段最小值KG(含)',
|
||||||
weight_max decimal(10,3) DEFAULT NULL COMMENT '重量段最大值KG(含)',
|
weight_max decimal(10,3) DEFAULT NULL COMMENT '重量段最大值KG(含)',
|
||||||
freight_price decimal(10,4) DEFAULT NULL COMMENT '运费 RMB/KG',
|
freight_price decimal(10,4) DEFAULT NULL COMMENT '运费 RMB/KG',
|
||||||
@@ -47,9 +50,11 @@ CREATE TABLE IF NOT EXISTS bms_product_country (
|
|||||||
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (detail_id),
|
PRIMARY KEY (detail_id),
|
||||||
UNIQUE KEY uk_product_country_weight_eff (product_code, country_name, weight_min, weight_max, effective_date),
|
UNIQUE KEY uk_product_country_zone_weight_eff (product_code, country_name, zone_code, weight_min, weight_max, effective_date),
|
||||||
KEY idx_product_country_pid (product_id),
|
KEY idx_product_country_pid (product_id),
|
||||||
KEY idx_country_code (country_code),
|
KEY idx_country_code (country_code),
|
||||||
|
KEY idx_product_country_zone (zone_id),
|
||||||
|
KEY idx_product_country_zone_code (country_code, zone_code),
|
||||||
KEY idx_weight_match (product_code, country_code, weight_min, weight_max),
|
KEY idx_weight_match (product_code, country_code, weight_min, weight_max),
|
||||||
KEY idx_product_eff (product_code, effective_date, expire_date)
|
KEY idx_product_eff (product_code, effective_date, expire_date)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品国家明细-重量段+运费挂号费+尺寸/尾程/时效';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品国家明细-重量段+运费挂号费+尺寸/尾程/时效';
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
-- Add zone fields to bms_product_country (nullable when no zone)
|
||||||
|
-- Idempotent: safe to re-run (skip if column/index already exists)
|
||||||
|
-- charset: utf8mb4
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
-- zone_id
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND COLUMN_NAME = 'zone_id'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE bms_product_country ADD COLUMN zone_id BIGINT(20) DEFAULT NULL COMMENT ''prc_zone.zone_id'' AFTER country_code',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- zone_code
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND COLUMN_NAME = 'zone_code'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE bms_product_country ADD COLUMN zone_code VARCHAR(50) DEFAULT NULL COMMENT ''zone code e.g. Z1'' AFTER zone_id',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- zone_name
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND COLUMN_NAME = 'zone_name'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE bms_product_country ADD COLUMN zone_name VARCHAR(100) DEFAULT NULL COMMENT ''zone display name'' AFTER zone_code',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- indexes
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND INDEX_NAME = 'idx_product_country_zone'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE bms_product_country ADD INDEX idx_product_country_zone (zone_id)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND INDEX_NAME = 'idx_product_country_zone_code'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE bms_product_country ADD INDEX idx_product_country_zone_code (country_code, zone_code)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- unique key: drop old, add new
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND INDEX_NAME = 'uk_product_country_weight_eff'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has > 0,
|
||||||
|
'ALTER TABLE bms_product_country DROP INDEX uk_product_country_weight_eff',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @has := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'bms_product_country' AND INDEX_NAME = 'uk_product_country_zone_weight_eff'
|
||||||
|
);
|
||||||
|
SET @sql := IF(@has = 0,
|
||||||
|
'ALTER TABLE bms_product_country ADD UNIQUE KEY uk_product_country_zone_weight_eff (product_code, country_name, zone_code, weight_min, weight_max, effective_date)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer v-model="visible" size="920px" :title="title" append-to-body>
|
<el-drawer v-model="visible" size="80%" :title="title" append-to-body>
|
||||||
<div class="mb8" style="display: flex; align-items: center; gap: 12px">
|
<div class="mb8" style="display: flex; align-items: center; gap: 12px">
|
||||||
<el-input v-model="keyword" placeholder="搜索国家(中文名 / ISO2)" clearable style="width: 260px" />
|
<el-input v-model="keyword" placeholder="搜索国家/分区(中文名 / ISO2 / 分区)" clearable style="width: 260px" />
|
||||||
</div>
|
</div>
|
||||||
<simple-table
|
<simple-table
|
||||||
:data="filteredList"
|
:data="filteredList"
|
||||||
@@ -12,6 +12,9 @@
|
|||||||
empty-text="暂无国家明细"
|
empty-text="暂无国家明细"
|
||||||
:span-method="spanMethod"
|
:span-method="spanMethod"
|
||||||
>
|
>
|
||||||
|
<template #col-countryName="{ row }">
|
||||||
|
<span class="pre-wrap">{{ row.countryName }} ({{ row.countryCode }})</span>
|
||||||
|
</template>
|
||||||
<template #col-sizeLimit="{ row }">
|
<template #col-sizeLimit="{ row }">
|
||||||
<span class="pre-wrap">{{ row.sizeLimit }}</span>
|
<span class="pre-wrap">{{ row.sizeLimit }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -28,8 +31,9 @@ import { listProductCountries } from '@/api/price/product'
|
|||||||
import { formatWeightLabel } from '@/views/price/set/utils/priceSet'
|
import { formatWeightLabel } from '@/views/price/set/utils/priceSet'
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ prop: 'countryName', label: '国家', width: 90, showOverflowTooltip: true },
|
{ prop: 'countryName', label: '国家', width: 120, showOverflowTooltip: true },
|
||||||
{ prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
|
// { prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
|
||||||
|
{ prop: 'zoneName', label: '分区名称', width: 140, showOverflowTooltip: true, formatter: (row) => row.zoneName || '-' },
|
||||||
{
|
{
|
||||||
prop: 'weightMin',
|
prop: 'weightMin',
|
||||||
label: '重量段(KG)',
|
label: '重量段(KG)',
|
||||||
@@ -61,7 +65,9 @@ const filteredList = computed(() => {
|
|||||||
const st = status.value
|
const st = status.value
|
||||||
return list.value.filter(r => {
|
return list.value.filter(r => {
|
||||||
if (kw && !(String(r.countryName || '').toLowerCase().includes(kw) ||
|
if (kw && !(String(r.countryName || '').toLowerCase().includes(kw) ||
|
||||||
String(r.countryCode || '').toLowerCase().includes(kw))) return false
|
String(r.countryCode || '').toLowerCase().includes(kw) ||
|
||||||
|
String(r.zoneCode || '').toLowerCase().includes(kw) ||
|
||||||
|
String(r.zoneName || '').toLowerCase().includes(kw))) return false
|
||||||
if (st && String(r.status) !== String(st)) return false
|
if (st && String(r.status) !== String(st)) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -81,23 +87,29 @@ function open(row) {
|
|||||||
status.value = ''
|
status.value = ''
|
||||||
listProductCountries(row.productCode, formatEffDate(row.effectiveDate), row.productId).then(res => {
|
listProductCountries(row.productCode, formatEffDate(row.effectiveDate), row.productId).then(res => {
|
||||||
list.value = (res.data || []).slice().sort((a, b) => {
|
list.value = (res.data || []).slice().sort((a, b) => {
|
||||||
return String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
|
const c = String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
|
||||||
|
if (c !== 0) return c
|
||||||
|
return String(a.zoneCode || '').localeCompare(String(b.zoneCode || ''))
|
||||||
})
|
})
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rowGroupKey(row) {
|
||||||
|
return String(row?.countryCode || '') + '|' + String(row?.zoneCode || '') + '|' + String(row?.countryName || '')
|
||||||
|
}
|
||||||
|
|
||||||
function spanMethod({ column, rowIndex }) {
|
function spanMethod({ column, rowIndex }) {
|
||||||
if (column.property !== 'countryName' && column.property !== 'countryCode') return
|
if (!['countryName', 'countryCode', 'zoneCode', 'zoneName', 'zoneId'].includes(column.property)) return
|
||||||
const rows = filteredList.value
|
const rows = filteredList.value
|
||||||
if (!rows.length) return
|
if (!rows.length) return
|
||||||
const cur = rows[rowIndex]?.countryCode
|
const cur = rowGroupKey(rows[rowIndex])
|
||||||
if (rowIndex > 0 && rows[rowIndex - 1]?.countryCode === cur) {
|
if (rowIndex > 0 && rowGroupKey(rows[rowIndex - 1]) === cur) {
|
||||||
return { rowspan: 0, colspan: 0 }
|
return { rowspan: 0, colspan: 0 }
|
||||||
}
|
}
|
||||||
let count = 1
|
let count = 1
|
||||||
while (rowIndex + count < rows.length && rows[rowIndex + count]?.countryCode === cur) count++
|
while (rowIndex + count < rows.length && rowGroupKey(rows[rowIndex + count]) === cur) count++
|
||||||
return { rowspan: count, colspan: 1 }
|
return { rowspan: count, colspan: 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="visible" size="720px" append-to-body>
|
||||||
|
<template #header>
|
||||||
|
<div class="drawer-head">
|
||||||
|
<span class="drawer-title">邮编明细</span>
|
||||||
|
<span v-if="selectedZone" class="drawer-sub">
|
||||||
|
{{ selectedZone.zoneName || selectedZone.zoneCode }}
|
||||||
|
({{ detailTotal }}条)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="drawer-toolbar">
|
||||||
|
<el-input
|
||||||
|
v-model="query.postcode"
|
||||||
|
clearable
|
||||||
|
placeholder="搜索邮编"
|
||||||
|
style="width: 200px; margin-right: 8px"
|
||||||
|
@keyup.enter="loadList"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" plain icon="Search" size="small" @click="loadList">搜索</el-button>
|
||||||
|
<el-button type="primary" plain icon="Plus" size="small" @click="handleAdd" v-hasPermi="['price:zone:add']">新增邮编</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="list" border row-key="detailId" empty-text="暂无邮编">
|
||||||
|
<el-table-column label="ID" prop="detailId" align="center" width="90" />
|
||||||
|
<el-table-column label="邮编" prop="postcode" align="center" min-width="140" />
|
||||||
|
<el-table-column label="操作" align="center" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
|
||||||
|
<el-button link type="primary" icon="Delete" @click="handleDelete(row.detailId)" v-hasPermi="['price:zone:remove']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="drawer-pager">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="query.pageNum"
|
||||||
|
v-model:page-size="query.pageSize"
|
||||||
|
:total="detailTotal"
|
||||||
|
:page-sizes="[20, 50, 100, 200]"
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
@size-change="loadList"
|
||||||
|
@current-change="loadList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<postcode-form-dialog ref="postcodeFormRef" @success="onChanged" />
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="PostcodeDrawer">
|
||||||
|
import { listDetail, delDetail } from '@/api/price/zone'
|
||||||
|
import PostcodeFormDialog from './PostcodeFormDialog.vue'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
|
const emit = defineEmits(['changed'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const selectedZone = ref(null)
|
||||||
|
const list = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const detailTotal = ref(0)
|
||||||
|
const query = ref({ pageNum: 1, pageSize: 50, postcode: undefined })
|
||||||
|
|
||||||
|
const postcodeFormRef = ref()
|
||||||
|
|
||||||
|
function open(zone) {
|
||||||
|
selectedZone.value = zone
|
||||||
|
query.value = { pageNum: 1, pageSize: 50, postcode: undefined }
|
||||||
|
visible.value = true
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadList() {
|
||||||
|
if (!selectedZone.value?.zoneId) {
|
||||||
|
list.value = []
|
||||||
|
detailTotal.value = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
listDetail({
|
||||||
|
zoneId: selectedZone.value.zoneId,
|
||||||
|
postcode: query.value.postcode || undefined,
|
||||||
|
pageNum: query.value.pageNum,
|
||||||
|
pageSize: query.value.pageSize
|
||||||
|
}).then(res => {
|
||||||
|
list.value = res.rows || []
|
||||||
|
detailTotal.value = res.total || 0
|
||||||
|
}).catch(() => {
|
||||||
|
list.value = []
|
||||||
|
detailTotal.value = 0
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAdd() {
|
||||||
|
postcodeFormRef.value.open(selectedZone.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdate(row) {
|
||||||
|
postcodeFormRef.value.open(selectedZone.value, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 邮编增删改后:刷新本列表,并向上通知(分区表需更新邮编数) */
|
||||||
|
function onChanged() {
|
||||||
|
loadList()
|
||||||
|
emit('changed')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(detailIds) {
|
||||||
|
proxy.$modal.confirm('是否确认删除邮编编号为"' + detailIds + '"的数据?').then(() => {
|
||||||
|
return delDetail(detailIds)
|
||||||
|
}).then(() => {
|
||||||
|
proxy.$modal.msgSuccess('删除成功')
|
||||||
|
onChanged()
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drawer-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.drawer-title { font-size: 16px; font-weight: 600; }
|
||||||
|
.drawer-sub { color: #909399; font-size: 13px; }
|
||||||
|
.drawer-toolbar { margin-bottom: 12px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
|
||||||
|
.drawer-pager { margin-top: 16px; display: flex; justify-content: flex-end; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="title" v-model="visible" width="420px" append-to-body>
|
||||||
|
<el-form ref="postcodeFormRef" :model="form" :rules="rules" label-width="80px">
|
||||||
|
<el-form-item label="邮编" prop="postcode">
|
||||||
|
<el-input v-model="form.postcode" placeholder="请输入邮编" maxlength="32" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="visible = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="PostcodeFormDialog">
|
||||||
|
import { addDetail, updateDetail } from '@/api/price/zone'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const title = ref('')
|
||||||
|
const postcodeFormRef = ref()
|
||||||
|
const form = ref({})
|
||||||
|
const rules = {
|
||||||
|
postcode: [{ required: true, message: '邮编不能为空', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** zone 为所属分区;row 传空为新增,传邮编行为编辑 */
|
||||||
|
function open(zone, row) {
|
||||||
|
form.value = row
|
||||||
|
? { ...row }
|
||||||
|
: {
|
||||||
|
detailId: undefined,
|
||||||
|
zoneId: zone?.zoneId,
|
||||||
|
postcode: undefined
|
||||||
|
}
|
||||||
|
title.value = row ? '编辑邮编' : '新增邮编'
|
||||||
|
visible.value = true
|
||||||
|
nextTick(() => postcodeFormRef.value?.clearValidate())
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitForm() {
|
||||||
|
postcodeFormRef.value.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
const payload = {
|
||||||
|
...form.value,
|
||||||
|
postcode: String(form.value.postcode || '').trim()
|
||||||
|
}
|
||||||
|
const req = payload.detailId != null ? updateDetail(payload) : addDetail(payload)
|
||||||
|
req.then(() => {
|
||||||
|
proxy.$modal.msgSuccess(payload.detailId != null ? '修改成功' : '新增成功')
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="visible" size="860px" append-to-body>
|
||||||
|
<template #header>
|
||||||
|
<div class="drawer-head">
|
||||||
|
<span class="drawer-title">国家分区</span>
|
||||||
|
<el-select
|
||||||
|
v-model="selectedRegionId"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="选择国家"
|
||||||
|
class="country-select"
|
||||||
|
@change="loadZoneList"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="r in regionOptions"
|
||||||
|
:key="r.regionId"
|
||||||
|
:label="regionLabel(r)"
|
||||||
|
:value="r.regionId"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span v-if="selectedCountry" class="drawer-sub">
|
||||||
|
当前:{{ selectedCountry.countryNameCn }}
|
||||||
|
<template v-if="selectedCountry.countryCode2">({{ selectedCountry.countryCode2 }})</template>
|
||||||
|
,共 {{ zoneList.length }} 个分区
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-loading="zoneLoading">
|
||||||
|
<div class="zone-toolbar">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
icon="Plus"
|
||||||
|
size="small"
|
||||||
|
:disabled="!selectedCountry"
|
||||||
|
@click="handleZoneAdd"
|
||||||
|
v-hasPermi="['price:zone:add']"
|
||||||
|
>新增分区</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
icon="Delete"
|
||||||
|
size="small"
|
||||||
|
:disabled="!selectedCountry"
|
||||||
|
@click="handleClearZones"
|
||||||
|
v-hasPermi="['price:zone:remove']"
|
||||||
|
>清空分区</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
:data="zoneList"
|
||||||
|
border
|
||||||
|
highlight-current-row
|
||||||
|
row-key="zoneId"
|
||||||
|
:empty-text="selectedCountry ? '该国家暂无分区,点击「新增分区」创建' : '请先选择国家'"
|
||||||
|
>
|
||||||
|
<el-table-column label="国家" align="center" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="zone-country-cell">
|
||||||
|
<div class="zone-title">{{ row.zoneName || ((selectedCountry?.countryNameCn || '') + '(' + row.zoneCode + ')') }}</div>
|
||||||
|
<el-button link type="primary" class="zone-click" @click="openPostcodeDrawer(row)" v-hasPermi="['price:zone:query']">分区点击</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="国家编码" prop="countryCode" align="center" width="110" />
|
||||||
|
<el-table-column label="分区代码" prop="zoneCode" align="center" width="120" />
|
||||||
|
<el-table-column label="邮编数" prop="detailCount" align="center" width="100" />
|
||||||
|
<el-table-column label="排序" prop="zoneSort" align="center" width="80" />
|
||||||
|
<el-table-column label="操作" align="center" width="220" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" icon="View" @click="openPostcodeDrawer(row)" v-hasPermi="['price:zone:query']">明细</el-button>
|
||||||
|
<el-button link type="primary" icon="Edit" @click="handleZoneUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
|
||||||
|
<el-button link type="primary" icon="Delete" @click="handleZoneDelete(row.zoneId)" v-hasPermi="['price:zone:remove']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<zone-form-dialog ref="zoneFormRef" :country="selectedCountry" @success="onZoneChanged" />
|
||||||
|
<postcode-drawer ref="postcodeDrawerRef" @changed="onZoneChanged" />
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="ZoneDrawer">
|
||||||
|
import { listRegionOptions } from '@/api/data/region'
|
||||||
|
import {
|
||||||
|
delCountryZones,
|
||||||
|
listZone, addZone, updateZone, delZone
|
||||||
|
} from '@/api/price/zone'
|
||||||
|
import ZoneFormDialog from './ZoneFormDialog.vue'
|
||||||
|
import PostcodeDrawer from './PostcodeDrawer.vue'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
|
const emit = defineEmits(['changed'])
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const selectedRegionId = ref(null)
|
||||||
|
const selectedCountry = computed(() =>
|
||||||
|
regionOptions.value.find(r => r.regionId === selectedRegionId.value) || null
|
||||||
|
)
|
||||||
|
const zoneList = ref([])
|
||||||
|
const zoneLoading = ref(false)
|
||||||
|
const regionOptions = ref([])
|
||||||
|
|
||||||
|
const zoneFormRef = ref()
|
||||||
|
const postcodeDrawerRef = ref()
|
||||||
|
|
||||||
|
function regionLabel(r) {
|
||||||
|
const cn = r.countryNameCn || r.countryNameEn || ''
|
||||||
|
return cn ? `${r.flagEmoji || ''} ${cn} (${r.countryCode2})`.trim() : r.countryCode2
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureRegions() {
|
||||||
|
if (regionOptions.value.length) return Promise.resolve()
|
||||||
|
return listRegionOptions().then(res => {
|
||||||
|
regionOptions.value = res.data || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开抽屉;传入国家行则自动选中,不传则让用户在头部下拉选择 */
|
||||||
|
function open(country) {
|
||||||
|
visible.value = true
|
||||||
|
ensureRegions().then(() => {
|
||||||
|
selectedRegionId.value = country?.regionId || null
|
||||||
|
loadZoneList(selectedRegionId.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
loadZoneList(selectedRegionId.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadZoneList(regionId) {
|
||||||
|
if (!regionId) {
|
||||||
|
zoneList.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zoneLoading.value = true
|
||||||
|
listZone({ regionId, pageNum: 1, pageSize: 200 }).then(res => {
|
||||||
|
zoneList.value = res.rows || []
|
||||||
|
}).catch(() => {
|
||||||
|
zoneList.value = []
|
||||||
|
}).finally(() => {
|
||||||
|
zoneLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleZoneAdd() {
|
||||||
|
zoneFormRef.value.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleZoneUpdate(row) {
|
||||||
|
zoneFormRef.value.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 分区增删改 / 邮编变动后:刷新分区列表,并通知父页刷新国家表格统计 */
|
||||||
|
function onZoneChanged() {
|
||||||
|
loadZoneList(selectedRegionId.value)
|
||||||
|
emit('changed')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleZoneDelete(zoneIds) {
|
||||||
|
proxy.$modal.confirm('是否确认删除分区编号为"' + zoneIds + '"的数据?将同时删除邮编明细。').then(() => {
|
||||||
|
return delZone(zoneIds)
|
||||||
|
}).then(() => {
|
||||||
|
proxy.$modal.msgSuccess('删除成功')
|
||||||
|
onZoneChanged()
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClearZones() {
|
||||||
|
if (!selectedRegionId.value) return
|
||||||
|
proxy.$modal.confirm('是否清空国家 regionId="' + selectedRegionId.value + '" 下的全部分区与邮编?(不会删除 bms_region 国家主数据)').then(() => {
|
||||||
|
return delCountryZones(selectedRegionId.value)
|
||||||
|
}).then(() => {
|
||||||
|
zoneList.value = []
|
||||||
|
proxy.$modal.msgSuccess('清空成功')
|
||||||
|
emit('changed')
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 抽屉打开期间,外部清空了某国家(国家表格「清空」按钮)时同步列表 */
|
||||||
|
function onCountryCleared(regionIds) {
|
||||||
|
if (visible.value && String(regionIds).split(',').map(String).includes(String(selectedRegionId.value))) {
|
||||||
|
zoneList.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPostcodeDrawer(row) {
|
||||||
|
postcodeDrawerRef.value.open(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, reload, onCountryCleared })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drawer-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.drawer-title { font-size: 16px; font-weight: 600; }
|
||||||
|
.drawer-sub { color: #909399; font-size: 13px; }
|
||||||
|
.country-select { width: 260px; }
|
||||||
|
.zone-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.zone-country-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.zone-title { font-weight: 500; }
|
||||||
|
.zone-click { padding: 0; height: auto; font-size: 12px; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="title" v-model="open" width="520px" append-to-body>
|
||||||
|
<el-form ref="zoneFormRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="国家">
|
||||||
|
<span>{{ country.countryNameCn }} ({{ country.countryCode2 }})</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="国家编码" prop="countryCode">
|
||||||
|
<el-input v-model="form.countryCode" placeholder="ISO2,如 AU" maxlength="10" style="text-transform: uppercase" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="分区代码" prop="zoneCode">
|
||||||
|
<el-input v-model="form.zoneCode" placeholder="如 1区" maxlength="50" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示名" prop="zoneName">
|
||||||
|
<el-input v-model="form.zoneName" placeholder="如 澳大利亚(1区)" maxlength="100" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序号" prop="zoneSort">
|
||||||
|
<el-input-number v-model="form.zoneSort" :min="0" :max="9999" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="备注" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="open = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="ZoneFormDialog">
|
||||||
|
import { addZone, updateZone } from '@/api/price/zone'
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/** 当前抽屉选中的国家(bms_region 行) */
|
||||||
|
country: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
|
const open = ref(false)
|
||||||
|
const title = ref('')
|
||||||
|
const zoneFormRef = ref()
|
||||||
|
const form = ref({})
|
||||||
|
const rules = {
|
||||||
|
countryCode: [{ required: true, message: '国家编码不能为空', trigger: 'blur' }],
|
||||||
|
zoneCode: [{ required: true, message: '分区代码不能为空', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** row 传空为新增,传分区行为编辑 */
|
||||||
|
function open2(row) {
|
||||||
|
form.value = row
|
||||||
|
? { ...row }
|
||||||
|
: {
|
||||||
|
zoneId: undefined,
|
||||||
|
regionId: props.country?.regionId,
|
||||||
|
countryCode: props.country?.countryCode2 || undefined,
|
||||||
|
zoneCode: undefined,
|
||||||
|
zoneName: undefined,
|
||||||
|
zoneSort: 0,
|
||||||
|
remark: undefined
|
||||||
|
}
|
||||||
|
title.value = row ? '编辑分区' : '新增分区'
|
||||||
|
open.value = true
|
||||||
|
nextTick(() => zoneFormRef.value?.clearValidate())
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitForm() {
|
||||||
|
zoneFormRef.value.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
const region = props.country
|
||||||
|
const cn = region?.countryNameCn || ''
|
||||||
|
const code = (form.value.countryCode || region?.countryCode2 || '')
|
||||||
|
.toString().trim().toUpperCase()
|
||||||
|
const payload = {
|
||||||
|
...form.value,
|
||||||
|
regionId: region?.regionId,
|
||||||
|
countryCode: code,
|
||||||
|
zoneName: form.value.zoneName || (cn ? (cn + '(' + form.value.zoneCode + ')') : form.value.zoneCode)
|
||||||
|
}
|
||||||
|
delete payload.detailCount
|
||||||
|
delete payload.countryNameCn
|
||||||
|
delete payload.countryCode2
|
||||||
|
const req = payload.zoneId != null ? updateZone(payload) : addZone(payload)
|
||||||
|
req.then(() => {
|
||||||
|
proxy.$modal.msgSuccess(payload.zoneId != null ? '修改成功' : '新增成功')
|
||||||
|
open.value = false
|
||||||
|
emit('success')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open: open2 })
|
||||||
|
</script>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
class="mb16"
|
class="mb16"
|
||||||
title="国家来自 bms_region。支持导入:澳大利亚分区、加拿大偏远附加费、法国MR邮编、日本附加费、英国附加费。"
|
title="国家来自 bms_region;点击「分区」在抽屉中查看/维护国家分区,抽屉内点击「分区点击」查看邮编明细。导入 Excel 时需 sheet 名以「分区」结尾且能匹配国家表。"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<basic-table
|
<basic-table
|
||||||
@@ -14,14 +14,10 @@
|
|||||||
:search-columns="countrySearchColumns"
|
:search-columns="countrySearchColumns"
|
||||||
:query="queryCountryList"
|
:query="queryCountryList"
|
||||||
row-key="regionId"
|
row-key="regionId"
|
||||||
@row-click="handleCountryRowClick"
|
|
||||||
>
|
>
|
||||||
<template #toolbar="{ selection, ids }">
|
<template #toolbar>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" plain icon="Plus" @click="handlePickRegionAddZone" v-hasPermi="['price:zone:add']">为国家新增分区</el-button>
|
<el-button type="primary" plain icon="Plus" @click="handleAddZone" v-hasPermi="['price:zone:add']">为国家新增分区</el-button>
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="danger" plain icon="Delete" :disabled="!selection.length" @click="handleCountryDelete(ids)" v-hasPermi="['price:zone:remove']">清空分区</el-button>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['price:zone:add']">导入 Excel</el-button>
|
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['price:zone:add']">导入 Excel</el-button>
|
||||||
@@ -35,169 +31,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #col-action="{ row }">
|
<template #col-action="{ row }">
|
||||||
<el-button link type="primary" icon="List" @click.stop="handleCountryRowClick(row)" v-hasPermi="['price:zone:query']">分区</el-button>
|
<el-button link type="primary" icon="List" @click.stop="openZoneDrawer(row)" v-hasPermi="['price:zone:query']">分区</el-button>
|
||||||
<el-button link type="primary" icon="Delete" @click.stop="handleCountryDelete(row.regionId)" v-hasPermi="['price:zone:remove']">清空</el-button>
|
<el-button link type="primary" icon="Delete" @click.stop="handleCountryDelete(row.regionId)" v-hasPermi="['price:zone:remove']">清空</el-button>
|
||||||
</template>
|
</template>
|
||||||
</basic-table>
|
</basic-table>
|
||||||
|
|
||||||
<el-card shadow="never" class="detail-card" v-loading="zoneLoading">
|
<zone-drawer ref="zoneDrawerRef" @changed="onZoneChanged" />
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>
|
|
||||||
国家分区
|
|
||||||
<span v-if="selectedCountry" class="sub-hint">
|
|
||||||
当前:{{ selectedCountry.countryNameCn }}
|
|
||||||
<template v-if="selectedCountry.countryCode2">({{ selectedCountry.countryCode2 }})</template>
|
|
||||||
,共 {{ zoneList.length }} 个分区
|
|
||||||
</span>
|
|
||||||
<span v-else class="sub-hint">请点击上方国家行查看分区</span>
|
|
||||||
</span>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
icon="Plus"
|
|
||||||
size="small"
|
|
||||||
:disabled="!selectedCountry"
|
|
||||||
@click="handleZoneAdd"
|
|
||||||
v-hasPermi="['price:zone:add']"
|
|
||||||
>新增分区</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
:data="zoneList"
|
|
||||||
border
|
|
||||||
highlight-current-row
|
|
||||||
row-key="zoneId"
|
|
||||||
empty-text="暂无分区数据"
|
|
||||||
>
|
|
||||||
<el-table-column label="国家" align="center" min-width="180">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<div class="zone-country-cell">
|
|
||||||
<div class="zone-title">{{ row.zoneName || ((selectedCountry?.countryNameCn || '') + '(' + row.zoneCode + ')') }}</div>
|
|
||||||
<el-button link type="primary" class="zone-click" @click="openDetailDrawer(row)" v-hasPermi="['price:zone:query']">分区点击</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="国家编码" prop="countryCode" align="center" width="110" />
|
|
||||||
<el-table-column label="分区代码" prop="zoneCode" align="center" width="120" />
|
|
||||||
<el-table-column label="邮编数" prop="detailCount" align="center" width="100" />
|
|
||||||
<el-table-column label="排序" prop="zoneSort" align="center" width="80" />
|
|
||||||
<el-table-column label="操作" align="center" width="220" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button link type="primary" icon="View" @click="openDetailDrawer(row)" v-hasPermi="['price:zone:query']">明细</el-button>
|
|
||||||
<el-button link type="primary" icon="Edit" @click="handleZoneUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
|
|
||||||
<el-button link type="primary" icon="Delete" @click="handleZoneDelete(row.zoneId)" v-hasPermi="['price:zone:remove']">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-dialog :title="zoneTitle" v-model="zoneOpen" width="520px" append-to-body>
|
|
||||||
<el-form ref="zoneFormRef" :model="zoneForm" :rules="zoneRules" label-width="100px">
|
|
||||||
<el-form-item v-if="!selectedCountry || zoneForm._pickRegion" label="国家" prop="regionId">
|
|
||||||
<el-select
|
|
||||||
v-model="zoneForm.regionId"
|
|
||||||
filterable
|
|
||||||
clearable
|
|
||||||
placeholder="从 bms_region 选择国家"
|
|
||||||
style="width: 100%"
|
|
||||||
@change="onZoneRegionChange"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="r in regionOptions"
|
|
||||||
:key="r.regionId"
|
|
||||||
:label="regionLabel(r)"
|
|
||||||
:value="r.regionId"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item v-else label="国家">
|
|
||||||
<span>{{ selectedCountry.countryNameCn }} ({{ selectedCountry.countryCode2 }})</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="国家编码" prop="countryCode">
|
|
||||||
<el-input v-model="zoneForm.countryCode" disabled placeholder="ISO2,如 AU" maxlength="10" style="text-transform: uppercase" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="分区代码" prop="zoneCode">
|
|
||||||
<el-input v-model="zoneForm.zoneCode" placeholder="如 Z1" maxlength="50" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="显示名" prop="zoneName">
|
|
||||||
<el-input v-model="zoneForm.zoneName" placeholder="如 澳大利亚(Z1)" maxlength="100" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="排序号" prop="zoneSort">
|
|
||||||
<el-input-number v-model="zoneForm.zoneSort" :min="0" :max="9999" controls-position="right" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input v-model="zoneForm.remark" type="textarea" :rows="3" placeholder="备注" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button type="primary" @click="submitZoneForm">确 定</el-button>
|
|
||||||
<el-button @click="zoneOpen = false">取 消</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-drawer v-model="detailDrawerVisible" size="720px" append-to-body>
|
|
||||||
<template #header>
|
|
||||||
<div class="drawer-head">
|
|
||||||
<span class="drawer-title">邮编明细</span>
|
|
||||||
<span v-if="selectedZone" class="drawer-sub">
|
|
||||||
{{ selectedZone.zoneName || selectedZone.zoneCode }}
|
|
||||||
({{ detailTotal }}条)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="drawer-toolbar">
|
|
||||||
<el-input
|
|
||||||
v-model="detailQuery.postcode"
|
|
||||||
clearable
|
|
||||||
placeholder="搜索邮编"
|
|
||||||
style="width: 200px; margin-right: 8px"
|
|
||||||
@keyup.enter="loadDetailList"
|
|
||||||
/>
|
|
||||||
<el-button type="primary" plain icon="Search" size="small" @click="loadDetailList">搜索</el-button>
|
|
||||||
<el-button type="primary" plain icon="Plus" size="small" @click="handleDetailAdd" v-hasPermi="['price:zone:add']">新增邮编</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table v-loading="detailLoading" :data="detailList" border row-key="detailId" empty-text="暂无邮编">
|
|
||||||
<el-table-column label="邮编" prop="postcode" align="center" min-width="140" />
|
|
||||||
<el-table-column label="操作" align="center" width="160">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button link type="primary" icon="Edit" @click="handleDetailUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
|
|
||||||
<el-button link type="primary" icon="Delete" @click="handleDetailDelete(row.detailId)" v-hasPermi="['price:zone:remove']">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<div class="drawer-pager">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="detailQuery.pageNum"
|
|
||||||
v-model:page-size="detailQuery.pageSize"
|
|
||||||
:total="detailTotal"
|
|
||||||
:page-sizes="[20, 50, 100, 200]"
|
|
||||||
layout="total, sizes, prev, pager, next"
|
|
||||||
@size-change="loadDetailList"
|
|
||||||
@current-change="loadDetailList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-drawer>
|
|
||||||
|
|
||||||
<el-dialog :title="detailTitle" v-model="detailOpen" width="420px" append-to-body>
|
|
||||||
<el-form ref="detailFormRef" :model="detailForm" :rules="detailRules" label-width="80px">
|
|
||||||
<el-form-item label="邮编" prop="postcode">
|
|
||||||
<el-input v-model="detailForm.postcode" placeholder="请输入邮编" maxlength="32" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button type="primary" @click="submitDetailForm">确 定</el-button>
|
|
||||||
<el-button @click="detailOpen = false">取 消</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<excel-import-dialog
|
<excel-import-dialog
|
||||||
ref="importRef"
|
ref="importRef"
|
||||||
@@ -212,45 +51,13 @@
|
|||||||
|
|
||||||
<script setup name="PriceZone">
|
<script setup name="PriceZone">
|
||||||
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
import ExcelImportDialog from '@/components/ExcelImportDialog'
|
||||||
import { listRegionOptions } from '@/api/data/region'
|
import { listCountry, delCountryZones } from '@/api/price/zone'
|
||||||
import {
|
import ZoneDrawer from './components/ZoneDrawer.vue'
|
||||||
listCountry, delCountryZones,
|
|
||||||
listZone, addZone, updateZone, delZone,
|
|
||||||
listDetail, addDetail, updateDetail, delDetail
|
|
||||||
} from '@/api/price/zone'
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
const countryTableRef = ref()
|
const countryTableRef = ref()
|
||||||
const selectedCountry = ref(null)
|
const zoneDrawerRef = ref()
|
||||||
const zoneList = ref([])
|
|
||||||
const zoneLoading = ref(false)
|
|
||||||
const regionOptions = ref([])
|
|
||||||
|
|
||||||
const zoneOpen = ref(false)
|
|
||||||
const zoneTitle = ref('')
|
|
||||||
const zoneFormRef = ref()
|
|
||||||
const zoneForm = ref({})
|
|
||||||
const zoneRules = {
|
|
||||||
regionId: [{ required: true, message: '请选择国家', trigger: 'change' }],
|
|
||||||
countryCode: [{ required: true, message: '国家编码不能为空', trigger: 'blur' }],
|
|
||||||
zoneCode: [{ required: true, message: '分区代码不能为空', trigger: 'blur' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const detailDrawerVisible = ref(false)
|
|
||||||
const selectedZone = ref(null)
|
|
||||||
const detailList = ref([])
|
|
||||||
const detailLoading = ref(false)
|
|
||||||
const detailTotal = ref(0)
|
|
||||||
const detailQuery = ref({ pageNum: 1, pageSize: 50, postcode: undefined })
|
|
||||||
const detailOpen = ref(false)
|
|
||||||
const detailTitle = ref('')
|
|
||||||
const detailFormRef = ref()
|
|
||||||
const detailForm = ref({})
|
|
||||||
const detailRules = {
|
|
||||||
postcode: [{ required: true, message: '邮编不能为空', trigger: 'blur' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const importRef = ref()
|
const importRef = ref()
|
||||||
|
|
||||||
const countrySearchColumns = [
|
const countrySearchColumns = [
|
||||||
@@ -272,42 +79,18 @@ function queryCountryList(params) {
|
|||||||
return listCountry(params)
|
return listCountry(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
function regionLabel(r) {
|
/** 工具栏「为国家新增分区」:不传国家,抽屉内自行选择 */
|
||||||
const cn = r.countryNameCn || r.countryNameEn || ''
|
function handleAddZone() {
|
||||||
return cn ? `${r.flagEmoji || ''} ${cn} (${r.countryCode2})`.trim() : r.countryCode2
|
zoneDrawerRef.value.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureRegions() {
|
function openZoneDrawer(row) {
|
||||||
if (regionOptions.value.length) return Promise.resolve()
|
zoneDrawerRef.value.open(row)
|
||||||
return listRegionOptions().then(res => {
|
|
||||||
regionOptions.value = res.data || []
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onZoneRegionChange(regionId) {
|
/** 抽屉内分区/邮编有变动 → 刷新国家表格的分区数/邮编数统计 */
|
||||||
const r = regionOptions.value.find(item => item.regionId === regionId)
|
function onZoneChanged() {
|
||||||
zoneForm.value.countryCode = r?.countryCode2 ? String(r.countryCode2).toUpperCase() : undefined
|
countryTableRef.value.refresh()
|
||||||
}
|
|
||||||
|
|
||||||
function handleCountryRowClick(row) {
|
|
||||||
if (!row?.regionId) return
|
|
||||||
selectedCountry.value = row
|
|
||||||
loadZoneList(row.regionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadZoneList(regionId) {
|
|
||||||
if (!regionId) {
|
|
||||||
zoneList.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
zoneLoading.value = true
|
|
||||||
listZone({ regionId, pageNum: 1, pageSize: 200 }).then(res => {
|
|
||||||
zoneList.value = res.rows || []
|
|
||||||
}).catch(() => {
|
|
||||||
zoneList.value = []
|
|
||||||
}).finally(() => {
|
|
||||||
zoneLoading.value = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCountryDelete(regionIds) {
|
function handleCountryDelete(regionIds) {
|
||||||
@@ -315,225 +98,21 @@ function handleCountryDelete(regionIds) {
|
|||||||
return delCountryZones(regionIds)
|
return delCountryZones(regionIds)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
countryTableRef.value.refresh()
|
countryTableRef.value.refresh()
|
||||||
if (selectedCountry.value && String(regionIds).split(',').map(String).includes(String(selectedCountry.value.regionId))) {
|
zoneDrawerRef.value.onCountryCleared(regionIds)
|
||||||
selectedCountry.value = null
|
|
||||||
zoneList.value = []
|
|
||||||
}
|
|
||||||
proxy.$modal.msgSuccess('清空成功')
|
proxy.$modal.msgSuccess('清空成功')
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetZoneForm(pickRegion) {
|
|
||||||
zoneForm.value = {
|
|
||||||
zoneId: undefined,
|
|
||||||
regionId: pickRegion ? undefined : selectedCountry.value?.regionId,
|
|
||||||
countryCode: pickRegion ? undefined : (selectedCountry.value?.countryCode2 || undefined),
|
|
||||||
zoneCode: undefined,
|
|
||||||
zoneName: undefined,
|
|
||||||
zoneSort: 0,
|
|
||||||
remark: undefined,
|
|
||||||
_pickRegion: !!pickRegion
|
|
||||||
}
|
|
||||||
proxy.resetForm('zoneFormRef')
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePickRegionAddZone() {
|
|
||||||
ensureRegions().then(() => {
|
|
||||||
resetZoneForm(true)
|
|
||||||
zoneOpen.value = true
|
|
||||||
zoneTitle.value = '为国家新增分区'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleZoneAdd() {
|
|
||||||
if (!selectedCountry.value) {
|
|
||||||
handlePickRegionAddZone()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resetZoneForm(false)
|
|
||||||
zoneOpen.value = true
|
|
||||||
zoneTitle.value = '新增分区'
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleZoneUpdate(row) {
|
|
||||||
resetZoneForm(false)
|
|
||||||
zoneForm.value = { ...row, _pickRegion: false }
|
|
||||||
zoneOpen.value = true
|
|
||||||
zoneTitle.value = '编辑分区'
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitZoneForm() {
|
|
||||||
proxy.$refs['zoneFormRef'].validate(valid => {
|
|
||||||
if (!valid) return
|
|
||||||
const regionId = zoneForm.value.regionId || selectedCountry.value?.regionId
|
|
||||||
const region = regionOptions.value.find(r => r.regionId === regionId)
|
|
||||||
|| selectedCountry.value
|
|
||||||
const cn = region?.countryNameCn || ''
|
|
||||||
const code = (zoneForm.value.countryCode || region?.countryCode2 || selectedCountry.value?.countryCode2 || '')
|
|
||||||
.toString().trim().toUpperCase()
|
|
||||||
const payload = {
|
|
||||||
...zoneForm.value,
|
|
||||||
regionId,
|
|
||||||
countryCode: code,
|
|
||||||
zoneName: zoneForm.value.zoneName || (cn ? (cn + '(' + zoneForm.value.zoneCode + ')') : zoneForm.value.zoneCode)
|
|
||||||
}
|
|
||||||
delete payload._pickRegion
|
|
||||||
delete payload.detailCount
|
|
||||||
delete payload.countryNameCn
|
|
||||||
delete payload.countryCode2
|
|
||||||
const req = payload.zoneId != null ? updateZone(payload) : addZone(payload)
|
|
||||||
req.then(() => {
|
|
||||||
proxy.$modal.msgSuccess(payload.zoneId != null ? '修改成功' : '新增成功')
|
|
||||||
zoneOpen.value = false
|
|
||||||
countryTableRef.value.refresh()
|
|
||||||
if (selectedCountry.value?.regionId === regionId) {
|
|
||||||
loadZoneList(regionId)
|
|
||||||
} else {
|
|
||||||
selectedCountry.value = {
|
|
||||||
regionId,
|
|
||||||
countryNameCn: cn || region?.countryNameCn,
|
|
||||||
countryCode2: region?.countryCode2
|
|
||||||
}
|
|
||||||
loadZoneList(regionId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleZoneDelete(zoneIds) {
|
|
||||||
proxy.$modal.confirm('是否确认删除分区编号为"' + zoneIds + '"的数据?将同时删除邮编明细。').then(() => {
|
|
||||||
return delZone(zoneIds)
|
|
||||||
}).then(() => {
|
|
||||||
loadZoneList(selectedCountry.value.regionId)
|
|
||||||
countryTableRef.value.refresh()
|
|
||||||
proxy.$modal.msgSuccess('删除成功')
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function openDetailDrawer(row) {
|
|
||||||
selectedZone.value = row
|
|
||||||
detailQuery.value = { pageNum: 1, pageSize: 50, postcode: undefined }
|
|
||||||
detailDrawerVisible.value = true
|
|
||||||
loadDetailList()
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDetailList() {
|
|
||||||
if (!selectedZone.value?.zoneId) {
|
|
||||||
detailList.value = []
|
|
||||||
detailTotal.value = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
detailLoading.value = true
|
|
||||||
listDetail({
|
|
||||||
zoneId: selectedZone.value.zoneId,
|
|
||||||
postcode: detailQuery.value.postcode || undefined,
|
|
||||||
pageNum: detailQuery.value.pageNum,
|
|
||||||
pageSize: detailQuery.value.pageSize
|
|
||||||
}).then(res => {
|
|
||||||
detailList.value = res.rows || []
|
|
||||||
detailTotal.value = res.total || 0
|
|
||||||
}).catch(() => {
|
|
||||||
detailList.value = []
|
|
||||||
detailTotal.value = 0
|
|
||||||
}).finally(() => {
|
|
||||||
detailLoading.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetDetailForm() {
|
|
||||||
detailForm.value = {
|
|
||||||
detailId: undefined,
|
|
||||||
zoneId: selectedZone.value?.zoneId,
|
|
||||||
postcode: undefined
|
|
||||||
}
|
|
||||||
proxy.resetForm('detailFormRef')
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDetailAdd() {
|
|
||||||
resetDetailForm()
|
|
||||||
detailOpen.value = true
|
|
||||||
detailTitle.value = '新增邮编'
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDetailUpdate(row) {
|
|
||||||
resetDetailForm()
|
|
||||||
detailForm.value = { ...row }
|
|
||||||
detailOpen.value = true
|
|
||||||
detailTitle.value = '编辑邮编'
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitDetailForm() {
|
|
||||||
proxy.$refs['detailFormRef'].validate(valid => {
|
|
||||||
if (!valid) return
|
|
||||||
const payload = {
|
|
||||||
...detailForm.value,
|
|
||||||
zoneId: selectedZone.value.zoneId,
|
|
||||||
postcode: String(detailForm.value.postcode || '').trim()
|
|
||||||
}
|
|
||||||
const req = payload.detailId != null ? updateDetail(payload) : addDetail(payload)
|
|
||||||
req.then(() => {
|
|
||||||
proxy.$modal.msgSuccess(payload.detailId != null ? '修改成功' : '新增成功')
|
|
||||||
detailOpen.value = false
|
|
||||||
loadDetailList()
|
|
||||||
loadZoneList(selectedCountry.value.regionId)
|
|
||||||
countryTableRef.value.refresh()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDetailDelete(detailIds) {
|
|
||||||
proxy.$modal.confirm('是否确认删除邮编编号为"' + detailIds + '"的数据?').then(() => {
|
|
||||||
return delDetail(detailIds)
|
|
||||||
}).then(() => {
|
|
||||||
loadDetailList()
|
|
||||||
loadZoneList(selectedCountry.value.regionId)
|
|
||||||
countryTableRef.value.refresh()
|
|
||||||
proxy.$modal.msgSuccess('删除成功')
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleImport() {
|
function handleImport() {
|
||||||
importRef.value.open()
|
importRef.value.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onImportSuccess() {
|
function onImportSuccess() {
|
||||||
countryTableRef.value.refresh()
|
countryTableRef.value.refresh()
|
||||||
if (selectedCountry.value?.regionId) {
|
zoneDrawerRef.value.reload()
|
||||||
loadZoneList(selectedCountry.value.regionId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.mb16 { margin-bottom: 16px; }
|
.mb16 { margin-bottom: 16px; }
|
||||||
.detail-card { margin-top: 16px; }
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.sub-hint {
|
|
||||||
margin-left: 10px;
|
|
||||||
color: #909399;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.zone-country-cell {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2px;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
.zone-title { font-weight: 500; }
|
|
||||||
.zone-click { padding: 0; height: auto; font-size: 12px; }
|
|
||||||
.drawer-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.drawer-title { font-size: 16px; font-weight: 600; }
|
|
||||||
.drawer-sub { color: #909399; font-size: 13px; }
|
|
||||||
.drawer-toolbar { margin-bottom: 12px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
|
|
||||||
.drawer-pager { margin-top: 16px; display: flex; justify-content: flex-end; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
class="mb12"
|
class="mb12"
|
||||||
title="FIXED 模式:可不填明细(计费取 bms_product_country 默认运费/挂号费);有明细时按产品×国家×重量段覆盖固定价。附加费来自产品关联,只读展示。"
|
title="FIXED 模式:可不填明细(计费取 bms_product_country 默认运费/挂号费);有明细时按产品×国家×分区×重量段覆盖固定价。附加费来自产品关联,只读展示。"
|
||||||
/>
|
/>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-else
|
v-else
|
||||||
@@ -64,6 +64,29 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column v-if="isFixedType(setType)" label="分区" min-width="140">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select
|
||||||
|
v-if="refData.hasZonesOf(row)"
|
||||||
|
v-model="row.zoneCode"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="选择分区"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="!row._countryKey"
|
||||||
|
@change="onZoneChange(row)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="z in refData.zoneOptionsOf(row)"
|
||||||
|
:key="z.key"
|
||||||
|
:label="z.label"
|
||||||
|
:value="z.zoneCode"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span v-else class="muted">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column v-if="isFixedType(setType)" label="重量段" min-width="150">
|
<el-table-column v-if="isFixedType(setType)" label="重量段" min-width="150">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -72,7 +95,7 @@
|
|||||||
clearable
|
clearable
|
||||||
placeholder="选择重量段"
|
placeholder="选择重量段"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:disabled="!row._countryKey"
|
:disabled="!row._countryKey || (refData.hasZonesOf(row) && !row.zoneCode)"
|
||||||
@change="onWeightChange(row)"
|
@change="onWeightChange(row)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@@ -158,7 +181,7 @@ const { bms_round_mode } = useDict('bms_round_mode')
|
|||||||
|
|
||||||
/** 参考数据来自父页面,缓存与请求去重全页面共享 */
|
/** 参考数据来自父页面,缓存与请求去重全页面共享 */
|
||||||
const { productOptions, surchargesOf, countryOptionsOf, weightOptionsOf } = props.refData
|
const { productOptions, surchargesOf, countryOptionsOf, weightOptionsOf } = props.refData
|
||||||
const { onProductChange, onCountryChange, onWeightChange } = useDetailCascade(props.refData)
|
const { onProductChange, onCountryChange, onZoneChange, onWeightChange } = useDetailCascade(props.refData)
|
||||||
|
|
||||||
function addRow() {
|
function addRow() {
|
||||||
props.details.push(createEmptyDetailRow())
|
props.details.push(createEmptyDetailRow())
|
||||||
|
|||||||
@@ -37,6 +37,21 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col v-if="hasZones" :span="12">
|
||||||
|
<el-form-item label="分区" prop="zoneCode">
|
||||||
|
<el-select
|
||||||
|
v-model="form.zoneCode"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="选择分区"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="!form._countryKey"
|
||||||
|
@change="onZoneChange(form)"
|
||||||
|
>
|
||||||
|
<el-option v-for="z in zoneOptions" :key="z.key" :label="z.label" :value="z.zoneCode" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="重量段" prop="weightLabel">
|
<el-form-item label="重量段" prop="weightLabel">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -45,7 +60,7 @@
|
|||||||
clearable
|
clearable
|
||||||
placeholder="选择重量段"
|
placeholder="选择重量段"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:disabled="!form._countryKey"
|
:disabled="!form._countryKey || (hasZones && !form.zoneCode)"
|
||||||
@change="onWeightChange(form)"
|
@change="onWeightChange(form)"
|
||||||
>
|
>
|
||||||
<el-option v-for="w in weightOptions" :key="w.value" :label="w.label" :value="w.value" />
|
<el-option v-for="w in weightOptions" :key="w.value" :label="w.label" :value="w.value" />
|
||||||
@@ -173,8 +188,8 @@ const emit = defineEmits(['success'])
|
|||||||
const { proxy } = getCurrentInstance()
|
const { proxy } = getCurrentInstance()
|
||||||
const { bms_round_mode } = useDict('bms_round_mode')
|
const { bms_round_mode } = useDict('bms_round_mode')
|
||||||
|
|
||||||
const { productOptions, surchargesOf, countryOptionsOf, weightOptionsOf } = props.refData
|
const { productOptions, surchargesOf, countryOptionsOf, zoneOptionsOf, hasZonesOf, weightOptionsOf } = props.refData
|
||||||
const { onProductChange, onCountryChange, onWeightChange } = useDetailCascade(props.refData)
|
const { onProductChange, onCountryChange, onZoneChange, onWeightChange } = useDetailCascade(props.refData)
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const title = ref('')
|
const title = ref('')
|
||||||
@@ -189,6 +204,8 @@ const rules = {
|
|||||||
|
|
||||||
const surcharges = computed(() => surchargesOf(form.value))
|
const surcharges = computed(() => surchargesOf(form.value))
|
||||||
const countryOptions = computed(() => countryOptionsOf(form.value))
|
const countryOptions = computed(() => countryOptionsOf(form.value))
|
||||||
|
const zoneOptions = computed(() => zoneOptionsOf(form.value))
|
||||||
|
const hasZones = computed(() => hasZonesOf(form.value))
|
||||||
const weightOptions = computed(() => weightOptionsOf(form.value))
|
const weightOptions = computed(() => weightOptionsOf(form.value))
|
||||||
|
|
||||||
/** 打开弹窗:row 为空表示新增 */
|
/** 打开弹窗:row 为空表示新增 */
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
<el-table-column label="国家" min-width="130">
|
<el-table-column label="国家" min-width="130">
|
||||||
<template #default="{ row }">{{ row.countryName || row.countryCode || '-' }}</template>
|
<template #default="{ row }">{{ row.countryName || row.countryCode || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="分区" min-width="120">
|
||||||
|
<template #default="{ row }">{{ row.zoneName || row.zoneCode || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="重量段" prop="weightLabel" min-width="130" />
|
<el-table-column label="重量段" prop="weightLabel" min-width="130" />
|
||||||
<el-table-column label="运费单价" min-width="130" align="right">
|
<el-table-column label="运费单价" min-width="130" align="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -119,7 +122,7 @@ const details = computed(() => props.set.details || [])
|
|||||||
const editable = computed(() => isChildEditable(props.set.status))
|
const editable = computed(() => isChildEditable(props.set.status))
|
||||||
|
|
||||||
const subTitle = computed(() => isFixedType(props.set.setType)
|
const subTitle = computed(() => isFixedType(props.set.setType)
|
||||||
? '按 产品 × 国家 × 重量段 覆盖固定价'
|
? '按 产品 × 国家 × 分区 × 重量段 覆盖固定价'
|
||||||
: '按产品限定折扣,留空走套餐默认折扣')
|
: '按产品限定折扣,留空走套餐默认折扣')
|
||||||
|
|
||||||
const hint = computed(() => isFixedType(props.set.setType)
|
const hint = computed(() => isFixedType(props.set.setType)
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import { listProduct, listProductCountries } from '@/api/price/product'
|
|||||||
import { listSurchargeByProduct } from '@/api/price/surcharge'
|
import { listSurchargeByProduct } from '@/api/price/surcharge'
|
||||||
import {
|
import {
|
||||||
buildCountryOptions,
|
buildCountryOptions,
|
||||||
|
buildZoneOptions,
|
||||||
buildWeightOptions,
|
buildWeightOptions,
|
||||||
|
countryHasZones,
|
||||||
countryKeyOf,
|
countryKeyOf,
|
||||||
formatWeightLabel,
|
formatWeightLabel,
|
||||||
matchCountry,
|
matchCountryZone,
|
||||||
toEditableDetailRow
|
toEditableDetailRow
|
||||||
} from '../utils/priceSet'
|
} from '../utils/priceSet'
|
||||||
|
|
||||||
@@ -96,6 +98,14 @@ export function usePriceSetRefData() {
|
|||||||
return buildCountryOptions(countryCache[row?.productCode] || [])
|
return buildCountryOptions(countryCache[row?.productCode] || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zoneOptionsOf(row) {
|
||||||
|
return buildZoneOptions(countryCache[row?.productCode] || [], row || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasZonesOf(row) {
|
||||||
|
return countryHasZones(countryCache[row?.productCode] || [], row || {})
|
||||||
|
}
|
||||||
|
|
||||||
/** 明细行可选重量段 */
|
/** 明细行可选重量段 */
|
||||||
function weightOptionsOf(row) {
|
function weightOptionsOf(row) {
|
||||||
return buildWeightOptions(countryCache[row?.productCode] || [], row || {})
|
return buildWeightOptions(countryCache[row?.productCode] || [], row || {})
|
||||||
@@ -109,12 +119,14 @@ export function usePriceSetRefData() {
|
|||||||
ensureSurcharges,
|
ensureSurcharges,
|
||||||
surchargesOf,
|
surchargesOf,
|
||||||
countryOptionsOf,
|
countryOptionsOf,
|
||||||
|
zoneOptionsOf,
|
||||||
|
hasZonesOf,
|
||||||
weightOptionsOf
|
weightOptionsOf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 明细行级联:产品 → 国家 → 重量段,并带出默认运费 / 挂号费
|
* 明细行级联:产品 → 国家 → 分区 → 重量段,并带出默认运费 / 挂号费
|
||||||
*
|
*
|
||||||
* @param {Object} refData usePriceSetRefData() 的返回值(页面内共享同一份)
|
* @param {Object} refData usePriceSetRefData() 的返回值(页面内共享同一份)
|
||||||
*/
|
*/
|
||||||
@@ -124,6 +136,9 @@ export function useDetailCascade(refData) {
|
|||||||
row.countryCode = undefined
|
row.countryCode = undefined
|
||||||
row.countryName = undefined
|
row.countryName = undefined
|
||||||
row._countryKey = undefined
|
row._countryKey = undefined
|
||||||
|
row.zoneId = undefined
|
||||||
|
row.zoneCode = undefined
|
||||||
|
row.zoneName = undefined
|
||||||
row.weightLabel = undefined
|
row.weightLabel = undefined
|
||||||
row.productCountryId = undefined
|
row.productCountryId = undefined
|
||||||
row.fvValue = undefined
|
row.fvValue = undefined
|
||||||
@@ -144,6 +159,20 @@ export function useDetailCascade(refData) {
|
|||||||
const opt = refData.countryOptionsOf(row).find(c => c.key === row._countryKey)
|
const opt = refData.countryOptionsOf(row).find(c => c.key === row._countryKey)
|
||||||
row.countryCode = opt ? opt.code : undefined
|
row.countryCode = opt ? opt.code : undefined
|
||||||
row.countryName = opt ? opt.name : undefined
|
row.countryName = opt ? opt.name : undefined
|
||||||
|
row.zoneId = undefined
|
||||||
|
row.zoneCode = undefined
|
||||||
|
row.zoneName = undefined
|
||||||
|
row.weightLabel = undefined
|
||||||
|
row.productCountryId = undefined
|
||||||
|
row.fvValue = undefined
|
||||||
|
row.fvRegistrationFee = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function onZoneChange(row) {
|
||||||
|
const opt = refData.zoneOptionsOf(row).find(z => z.key === row.zoneCode)
|
||||||
|
row.zoneId = opt ? opt.zoneId : undefined
|
||||||
|
row.zoneCode = opt ? opt.zoneCode : undefined
|
||||||
|
row.zoneName = opt ? opt.zoneName : undefined
|
||||||
row.weightLabel = undefined
|
row.weightLabel = undefined
|
||||||
row.productCountryId = undefined
|
row.productCountryId = undefined
|
||||||
row.fvValue = undefined
|
row.fvValue = undefined
|
||||||
@@ -158,6 +187,9 @@ export function useDetailCascade(refData) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
row.productCountryId = opt.detailId
|
row.productCountryId = opt.detailId
|
||||||
|
if (opt.zoneId != null) row.zoneId = opt.zoneId
|
||||||
|
if (opt.zoneCode) row.zoneCode = opt.zoneCode
|
||||||
|
if (opt.zoneName) row.zoneName = opt.zoneName
|
||||||
if (opt.freightPrice != null) row.fvValue = Number(opt.freightPrice)
|
if (opt.freightPrice != null) row.fvValue = Number(opt.freightPrice)
|
||||||
if (opt.registrationFee != null) row.fvRegistrationFee = Number(opt.registrationFee)
|
if (opt.registrationFee != null) row.fvRegistrationFee = Number(opt.registrationFee)
|
||||||
}
|
}
|
||||||
@@ -171,7 +203,8 @@ export function useDetailCascade(refData) {
|
|||||||
refData.ensureCountries(row.productCode).then(rows => {
|
refData.ensureCountries(row.productCode).then(rows => {
|
||||||
if (!row.weightLabel) return
|
if (!row.weightLabel) return
|
||||||
const hit = rows.find(item => {
|
const hit = rows.find(item => {
|
||||||
if (!matchCountry(row, item)) return false
|
if (row.productCountryId != null && Number(item.detailId) === Number(row.productCountryId)) return true
|
||||||
|
if (!matchCountryZone(row, item)) return false
|
||||||
return formatWeightLabel(item.weightMin, item.weightMax) === row.weightLabel
|
return formatWeightLabel(item.weightMin, item.weightMax) === row.weightLabel
|
||||||
|| (row.weightLabel.includes(String(item.weightMin)) && row.weightLabel.includes(String(item.weightMax)))
|
|| (row.weightLabel.includes(String(item.weightMin)) && row.weightLabel.includes(String(item.weightMax)))
|
||||||
})
|
})
|
||||||
@@ -181,9 +214,12 @@ export function useDetailCascade(refData) {
|
|||||||
row.productCountryId = hit.detailId
|
row.productCountryId = hit.detailId
|
||||||
if (!row.countryCode) row.countryCode = hit.countryCode
|
if (!row.countryCode) row.countryCode = hit.countryCode
|
||||||
if (!row.countryName) row.countryName = hit.countryName
|
if (!row.countryName) row.countryName = hit.countryName
|
||||||
|
if (hit.zoneId != null) row.zoneId = hit.zoneId
|
||||||
|
if (hit.zoneCode) row.zoneCode = hit.zoneCode
|
||||||
|
if (hit.zoneName) row.zoneName = hit.zoneName
|
||||||
})
|
})
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
return { resetRow, onProductChange, onCountryChange, onWeightChange, hydrateRow }
|
return { resetRow, onProductChange, onCountryChange, onZoneChange, onWeightChange, hydrateRow }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,22 @@ export function matchCountry(detail, item) {
|
|||||||
return !!detail.countryName && item.countryName === detail.countryName
|
return !!detail.countryName && item.countryName === detail.countryName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** detail row zone vs product-country row */
|
||||||
|
export function matchZone(detail, item) {
|
||||||
|
const detailHas = !!(detail.zoneCode || detail.zoneId)
|
||||||
|
const itemHas = !!(item.zoneCode || item.zoneId)
|
||||||
|
if (!detailHas && !itemHas) return true
|
||||||
|
if (detailHas !== itemHas) return false
|
||||||
|
if (detail.zoneId != null && item.zoneId != null) return Number(detail.zoneId) === Number(item.zoneId)
|
||||||
|
if (detail.zoneCode && item.zoneCode) return String(detail.zoneCode) === String(item.zoneCode)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** country + zone match */
|
||||||
|
export function matchCountryZone(detail, item) {
|
||||||
|
return matchCountry(detail, item) && matchZone(detail, item)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 国家下拉选项(按产品的国家配置去重)
|
* 国家下拉选项(按产品的国家配置去重)
|
||||||
* @param {Array} rows bms_product_country 行
|
* @param {Array} rows bms_product_country 行
|
||||||
@@ -141,9 +157,39 @@ export function buildCountryOptions(rows) {
|
|||||||
* @param {Object} row 当前明细行(含国家)
|
* @param {Object} row 当前明细行(含国家)
|
||||||
* @returns {Array<{value, label, detailId, freightPrice, registrationFee}>}
|
* @returns {Array<{value, label, detailId, freightPrice, registrationFee}>}
|
||||||
*/
|
*/
|
||||||
export function buildWeightOptions(rows, row) {
|
/**
|
||||||
|
* Zone options for selected country (from product-country rows).
|
||||||
|
* @returns {Array<{key, zoneId, zoneCode, zoneName, label}>}
|
||||||
|
*/
|
||||||
|
export function buildZoneOptions(rows, row) {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
;(rows || []).filter(item => matchCountry(row, item)).forEach(item => {
|
;(rows || []).filter(item => matchCountry(row, item)).forEach(item => {
|
||||||
|
if (!item.zoneCode && item.zoneId == null) return
|
||||||
|
const key = item.zoneCode || String(item.zoneId)
|
||||||
|
if (map.has(key)) return
|
||||||
|
map.set(key, {
|
||||||
|
key,
|
||||||
|
zoneId: item.zoneId,
|
||||||
|
zoneCode: item.zoneCode || undefined,
|
||||||
|
zoneName: item.zoneName || undefined,
|
||||||
|
label: item.zoneName || item.zoneCode || key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return [...map.values()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether selected country has any zone rows */
|
||||||
|
export function countryHasZones(rows, row) {
|
||||||
|
return buildZoneOptions(rows, row).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWeightOptions(rows, row) {
|
||||||
|
const list = rows || []
|
||||||
|
if (countryHasZones(list, row) && !(row && (row.zoneCode || row.zoneId))) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const map = new Map()
|
||||||
|
list.filter(item => matchCountryZone(row, item)).forEach(item => {
|
||||||
if (item.weightMin == null && item.weightMax == null) return
|
if (item.weightMin == null && item.weightMax == null) return
|
||||||
const value = formatWeightLabel(item.weightMin, item.weightMax)
|
const value = formatWeightLabel(item.weightMin, item.weightMax)
|
||||||
if (map.has(value)) return
|
if (map.has(value)) return
|
||||||
@@ -151,6 +197,9 @@ export function buildWeightOptions(rows, row) {
|
|||||||
value,
|
value,
|
||||||
label: value,
|
label: value,
|
||||||
detailId: item.detailId,
|
detailId: item.detailId,
|
||||||
|
zoneId: item.zoneId,
|
||||||
|
zoneCode: item.zoneCode,
|
||||||
|
zoneName: item.zoneName,
|
||||||
freightPrice: item.freightPrice,
|
freightPrice: item.freightPrice,
|
||||||
registrationFee: item.registrationFee
|
registrationFee: item.registrationFee
|
||||||
})
|
})
|
||||||
@@ -186,6 +235,9 @@ export function createEmptyDetailRow() {
|
|||||||
countryCode: undefined,
|
countryCode: undefined,
|
||||||
countryName: undefined,
|
countryName: undefined,
|
||||||
_countryKey: undefined,
|
_countryKey: undefined,
|
||||||
|
zoneId: undefined,
|
||||||
|
zoneCode: undefined,
|
||||||
|
zoneName: undefined,
|
||||||
weightLabel: undefined,
|
weightLabel: undefined,
|
||||||
productCountryId: undefined,
|
productCountryId: undefined,
|
||||||
fvValue: undefined,
|
fvValue: undefined,
|
||||||
@@ -235,6 +287,9 @@ export function toDetailPayload(d) {
|
|||||||
productName: d.productName,
|
productName: d.productName,
|
||||||
countryCode: d.countryCode || undefined,
|
countryCode: d.countryCode || undefined,
|
||||||
countryName: d.countryName || undefined,
|
countryName: d.countryName || undefined,
|
||||||
|
zoneId: d.zoneId || undefined,
|
||||||
|
zoneCode: d.zoneCode || undefined,
|
||||||
|
zoneName: d.zoneName || undefined,
|
||||||
weightLabel: d.weightLabel || undefined,
|
weightLabel: d.weightLabel || undefined,
|
||||||
productCountryId: d.productCountryId || undefined,
|
productCountryId: d.productCountryId || undefined,
|
||||||
fvValue: d.fvValue,
|
fvValue: d.fvValue,
|
||||||
|
|||||||
Reference in New Issue
Block a user