Compare commits

2 Commits

Author SHA1 Message Date
wuweihua f82d9c667f Merge branch 'test' of https://git.lionquexpress.com/wuweihua/BMS into test 2026-11-11 14:50:58 +08:00
wuweihua 7e1041391a 新增打单产品渠道查询与替换功能 2026-11-11 14:50:34 +08:00
20 changed files with 603 additions and 109 deletions
@@ -208,6 +208,39 @@ public class LabelPackageController extends BaseController
return success(payProductService.selectPayProductDetail(productId)); return success(payProductService.selectPayProductDetail(productId));
} }
/** 查询打单产品已绑定渠道(默认应收 AR) */
@PreAuthorize("@ss.hasPermi('price:label:query')")
@GetMapping("/product/{productId}/channels")
public AjaxResult productChannels(@PathVariable Long productId,
@RequestParam(value = "payType", required = false) String payType)
{
String type = (payType == null || payType.trim().isEmpty()) ? "AR" : payType.trim();
return success(payProductService.listProductChannelBinds(productId, type));
}
/** 覆盖保存打单产品渠道绑定,写入 srv_channel_product_map */
@PreAuthorize("@ss.hasPermi('price:label:edit')")
@Log(title = "打单产品绑定渠道", businessType = BusinessType.UPDATE)
@PutMapping("/product/{productId}/channels")
public AjaxResult replaceProductChannels(@PathVariable Long productId, @RequestBody Map<String, Object> body)
{
String payType = body == null || body.get("payType") == null ? "AR" : String.valueOf(body.get("payType")).trim();
List<String> channelIds = new ArrayList<String>();
Object rawIds = body == null ? null : body.get("channelIds");
if (rawIds instanceof List<?>)
{
for (Object item : (List<?>) rawIds)
{
if (item != null && !String.valueOf(item).trim().isEmpty())
{
channelIds.add(String.valueOf(item).trim());
}
}
}
String msg = payProductService.replaceProductChannelBinds(productId, payType, channelIds);
return AjaxResult.success(msg);
}
@PreAuthorize("@ss.hasPermi('price:label:remove')") @PreAuthorize("@ss.hasPermi('price:label:remove')")
@Log(title = "打单套餐移除产品", businessType = BusinessType.DELETE) @Log(title = "打单套餐移除产品", businessType = BusinessType.DELETE)
@DeleteMapping("/{priceSetId}/products/{productIds}") @DeleteMapping("/{priceSetId}/products/{productIds}")
@@ -120,6 +120,12 @@ public class PayProduct extends BaseEntity
@Excel(name = "附加费数", width = 12) @Excel(name = "附加费数", width = 12)
private Integer surchargeCount; private Integer surchargeCount;
/** 已绑定渠道展示文案(非表字段,列表回显) */
private String boundChannelText;
/** 已绑定渠道数量(非表字段) */
private Integer boundChannelCount;
@Excel(name = "其他配置数", width = 12) @Excel(name = "其他配置数", width = 12)
private Integer extraCount; private Integer extraCount;
@@ -84,9 +84,10 @@ public class PrcOrderBill extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date bizDate; private Date bizDate;
private Long tmsId; private Long tmsId;
/** 渠道编码(上游 TAB_ORDER.PRODUCT_FLOOR_CODE) */
private String matchSource; private String productFloorCode;
private String matchMessage; /** 渠道名称(上游 TAB_ORDER.PRODUCT_FLOOR) */
private String productFloor;
private Long priceSetId; private Long priceSetId;
private String priceSetCode; private String priceSetCode;
private String priceSetName; private String priceSetName;
@@ -43,8 +43,10 @@ public class PrcBillingBillListExportRow implements Serializable
private BigDecimal incomeTotal; private BigDecimal incomeTotal;
@Excel(name = "应付总额", scale = 2, width = 14) @Excel(name = "应付总额", scale = 2, width = 14)
private BigDecimal payTotal; private BigDecimal payTotal;
@Excel(name = "计价来源", width = 16) @Excel(name = "渠道编码", width = 16)
private String matchSourceLabel; private String productFloorCode;
@Excel(name = "渠道名称", width = 20)
private String productFloor;
@Excel(name = "订单状态", width = 12) @Excel(name = "订单状态", width = 12)
private String orderStatusLabel; private String orderStatusLabel;
@Excel(name = "计费状态", width = 14) @Excel(name = "计费状态", width = 14)
@@ -34,6 +34,10 @@ public class TmsPriceQuoteResult implements Serializable
private String productCode; private String productCode;
private String productName; private String productName;
/** 渠道编码 */
private String productFloorCode;
/** 渠道名称 */
private String productFloor;
private String countryCode; private String countryCode;
private String countryName; private String countryName;
private Long zoneId; private Long zoneId;
@@ -10,6 +10,11 @@ public interface ChannelProductMapMapper
{ {
ChannelProductMap selectByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType); ChannelProductMap selectByChannelAndType(@Param("channelId") String channelId, @Param("payType") String payType);
java.util.List<ChannelProductMap> selectByProductIdAndType(@Param("productId") Long productId,
@Param("payType") String payType);
java.util.List<ChannelProductMap> selectByProductIdsAndType(@Param("productIds") java.util.List<Long> productIds,
@Param("payType") String payType);
int upsert(ChannelProductMap row); int upsert(ChannelProductMap row);
@@ -5,6 +5,7 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import com.bms.price.domain.PrcOrderBill;
import com.bms.price.domain.PrcOrderBillExt; import com.bms.price.domain.PrcOrderBillExt;
/** /**
@@ -14,6 +15,18 @@ public final class OracleOrderBillExtFiller
{ {
private OracleOrderBillExtFiller() {} private OracleOrderBillExtFiller() {}
/** 从上游 TAB_ORDER 写入 prc_order_bill 渠道编码/名称。 */
public static void fillBillChannelFromRow(PrcOrderBill bill, Map<String, Object> row)
{
if (bill == null || row == null || row.isEmpty())
{
return;
}
bill.setProductFloorCode(str(row, "PRODUCT_FLOOR_CODE"));
bill.setProductFloor(str(row, "PRODUCT_FLOOR"));
}
public static void fillFromRow(PrcOrderBillExt ext, Map<String, Object> row) public static void fillFromRow(PrcOrderBillExt ext, Map<String, Object> row)
{ {
if (ext == null || row == null || row.isEmpty()) if (ext == null || row == null || row.isEmpty())
@@ -61,6 +61,16 @@ public interface IPayProductService
/** 批量绑定渠道应收/应付产品;未传的产品不改原绑定 */ /** 批量绑定渠道应收/应付产品;未传的产品不改原绑定 */
String batchBindChannel(java.util.List<String> channelIds, Long apProductId, Long arProductId); String batchBindChannel(java.util.List<String> channelIds, Long apProductId, Long arProductId);
/** 查询产品已绑定的渠道(按收付类型) */
java.util.List<com.bms.price.domain.ChannelProductMap> listProductChannelBinds(Long productId, String payType);
/**
* 覆盖保存产品的渠道绑定:勾选渠道写入 srv_channel_product_map,
* 取消勾选且当前仍绑该产品的渠道会解绑。
*/
String replaceProductChannelBinds(Long productId, String payType, java.util.List<String> channelIds);
/** /**
* 打单套餐预览:解析复用应付解析器,但「已存在」只按本套餐 price_set_id 判断, * 打单套餐预览:解析复用应付解析器,但「已存在」只按本套餐 price_set_id 判断,
* 不与应付公共模板互相挡导入。 * 不与应付公共模板互相挡导入。
@@ -1101,9 +1101,104 @@ public class PayProductServiceImpl implements IPayProductService
product.setSurchargeCount(term.getSurchargeCount() == null ? product.getSurchargeCount() : term.getSurchargeCount()); product.setSurchargeCount(term.getSurchargeCount() == null ? product.getSurchargeCount() : term.getSurchargeCount());
list.add(product); list.add(product);
} }
fillLabelBoundChannels(list);
return list; return list;
} }
/** 打单产品列表回填已绑定渠道(收款 AR) */
private void fillLabelBoundChannels(List<PayProduct> list)
{
if (list == null || list.isEmpty())
{
return;
}
java.util.LinkedHashSet<Long> ids = new java.util.LinkedHashSet<Long>();
for (PayProduct product : list)
{
if (product != null && product.getProductId() != null)
{
ids.add(product.getProductId());
}
}
if (ids.isEmpty())
{
return;
}
List<ChannelProductMap> binds = channelProductMapMapper.selectByProductIdsAndType(
new ArrayList<Long>(ids), PAY_TYPE_AR);
java.util.Map<Long, java.util.List<ChannelProductMap>> byProduct =
new java.util.HashMap<Long, java.util.List<ChannelProductMap>>();
if (binds != null)
{
for (ChannelProductMap row : binds)
{
if (row == null || row.getProductId() == null)
{
continue;
}
java.util.List<ChannelProductMap> bucket = byProduct.get(row.getProductId());
if (bucket == null)
{
bucket = new ArrayList<ChannelProductMap>();
byProduct.put(row.getProductId(), bucket);
}
bucket.add(row);
}
}
for (PayProduct product : list)
{
if (product == null || product.getProductId() == null)
{
continue;
}
java.util.List<ChannelProductMap> bucket = byProduct.get(product.getProductId());
if (bucket == null || bucket.isEmpty())
{
product.setBoundChannelCount(Integer.valueOf(0));
product.setBoundChannelText(null);
continue;
}
product.setBoundChannelCount(Integer.valueOf(bucket.size()));
StringBuilder sb = new StringBuilder();
for (ChannelProductMap row : bucket)
{
if (row == null)
{
continue;
}
String code = row.getChannelCode();
String name = row.getChannelName();
String label;
if (!isBlank(code) && !isBlank(name) && !code.equals(name))
{
label = code + "/" + name;
}
else if (!isBlank(code))
{
label = code;
}
else if (!isBlank(name))
{
label = name;
}
else
{
label = row.getChannelId();
}
if (isBlank(label))
{
continue;
}
if (sb.length() > 0)
{
sb.append("、");
}
sb.append(label);
}
product.setBoundChannelText(sb.length() == 0 ? null : sb.toString());
}
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public int removeLabelProducts(Long priceSetId, Long[] productIds) public int removeLabelProducts(Long priceSetId, Long[] productIds)
@@ -1701,6 +1796,54 @@ public class PayProductServiceImpl implements IPayProductService
return "已为 " + n + " 个渠道绑定产品"; return "已为 " + n + " 个渠道绑定产品";
} }
@Override
public List<ChannelProductMap> listProductChannelBinds(Long productId, String payType)
{
Long root = resolveRoot(productId);
String type = payTypeOf(payType);
List<ChannelProductMap> list = channelProductMapMapper.selectByProductIdAndType(root, type);
return list == null ? new ArrayList<ChannelProductMap>() : list;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String replaceProductChannelBinds(Long productId, String payType, List<String> channelIds)
{
Long root = resolveRoot(productId);
String type = payTypeOf(payType);
java.util.LinkedHashSet<String> selected = new java.util.LinkedHashSet<String>();
if (channelIds != null)
{
for (String channelId : channelIds)
{
if (!isBlank(channelId))
{
selected.add(channelId.trim());
}
}
}
List<ChannelProductMap> existing = channelProductMapMapper.selectByProductIdAndType(root, type);
if (existing != null)
{
for (ChannelProductMap row : existing)
{
if (row == null || isBlank(row.getChannelId()))
{
continue;
}
if (!selected.contains(row.getChannelId().trim()))
{
channelProductMapMapper.deleteByChannelAndType(row.getChannelId().trim(), type);
}
}
}
for (String channelId : selected)
{
bindChannel(channelId, root, type);
}
return "已绑定 " + selected.size() + " 个渠道";
}
@Override @Override
public void unbindChannel(String channelId, String payType) public void unbindChannel(String channelId, String payType)
{ {
@@ -2061,9 +2061,6 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
bill.setWeight(weight); bill.setWeight(weight);
bill.setBizDate(bizDate); bill.setBizDate(bizDate);
bill.setTmsId(request.getTmsId() == null ? null : Long.valueOf(request.getTmsId())); bill.setTmsId(request.getTmsId() == null ? null : Long.valueOf(request.getTmsId()));
// 不在订单账单上持久化 match_source / match_message。
bill.setMatchSource(null);
bill.setMatchMessage(null);
bill.setPriceSetId(result.getPriceSetId()); bill.setPriceSetId(result.getPriceSetId());
bill.setPriceSetCode(result.getPriceSetCode()); bill.setPriceSetCode(result.getPriceSetCode());
bill.setPriceSetName(result.getPriceSetName()); bill.setPriceSetName(result.getPriceSetName());
@@ -2614,8 +2611,6 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
r.setCustomerCode(bill.getCustomerCode()); r.setCustomerCode(bill.getCustomerCode());
fillResultOrder(r, bill); fillResultOrder(r, bill);
r.setFromCache(Boolean.TRUE); r.setFromCache(Boolean.TRUE);
r.setMatchSource(bill.getMatchSource());
r.setMatchMessage(bill.getMatchMessage());
r.setCustomerId(bill.getCustomerId()); r.setCustomerId(bill.getCustomerId());
r.setPriceSetId(bill.getPriceSetId()); r.setPriceSetId(bill.getPriceSetId());
r.setPriceSetCode(bill.getPriceSetCode()); r.setPriceSetCode(bill.getPriceSetCode());
@@ -2627,6 +2622,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
r.setPsdId(bill.getPsdId()); r.setPsdId(bill.getPsdId());
r.setProductCode(bill.getProductCode()); r.setProductCode(bill.getProductCode());
r.setProductName(bill.getProductName()); r.setProductName(bill.getProductName());
r.setProductFloorCode(bill.getProductFloorCode());
r.setProductFloor(bill.getProductFloor());
r.setCountryCode(bill.getCountryCode()); r.setCountryCode(bill.getCountryCode());
r.setCountryName(bill.getCountryName()); r.setCountryName(bill.getCountryName());
r.setWeightMin(bill.getWeightMin()); r.setWeightMin(bill.getWeightMin());
@@ -3589,6 +3586,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
bill.setProductName(request.getProductName()); bill.setProductName(request.getProductName());
bill.setCountryCode(request.getCountryCode()); bill.setCountryCode(request.getCountryCode());
bill.setCountryName(request.getCountryName()); bill.setCountryName(request.getCountryName());
if (request.getUpstreamRow() != null && !request.getUpstreamRow().isEmpty())
{
OracleOrderBillExtFiller.fillBillChannelFromRow(bill, request.getUpstreamRow());
}
} }
/** /**
@@ -3656,7 +3657,5 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
failText = failText.substring(0, 1000); failText = failText.substring(0, 1000);
} }
bill.setErrorMessage(failText); bill.setErrorMessage(failText);
bill.setMatchMessage(null);
bill.setMatchSource(null);
} }
} }
@@ -192,7 +192,8 @@ public final class OrderBillExportSupport
row.setReceiveWeight(bill.getReceiveWeight()); row.setReceiveWeight(bill.getReceiveWeight());
row.setIncomeTotal(bill.getIncomeTotal()); row.setIncomeTotal(bill.getIncomeTotal());
row.setPayTotal(bill.getPayTotal()); row.setPayTotal(bill.getPayTotal());
row.setMatchSourceLabel(matchSourceLabel(bill.getMatchSource())); row.setProductFloorCode(bill.getProductFloorCode());
row.setProductFloor(bill.getProductFloor());
row.setOrderStatusLabel(orderStatusLabel(bill.getOrderStatus())); row.setOrderStatusLabel(orderStatusLabel(bill.getOrderStatus()));
row.setBillingFailStatusLabel(billingFailStatusLabel(bill.getBillingFailStatus())); row.setBillingFailStatusLabel(billingFailStatusLabel(bill.getBillingFailStatus()));
row.setCustomerActiveStatusLabel(customerActiveLabel(bill.getCustomerActiveStatus())); row.setCustomerActiveStatusLabel(customerActiveLabel(bill.getCustomerActiveStatus()));
@@ -270,30 +271,6 @@ public final class OrderBillExportSupport
} }
} }
public static String matchSourceLabel(String source)
{
if (StringUtils.isEmpty(source))
{
return "";
}
if ("PACKAGE_FIXED".equals(source) || "FIXED".equals(source))
{
return "客户固定价套餐";
}
if ("PACKAGE_DISCOUNT".equals(source) || "DISCOUNT".equals(source))
{
return "客户折扣套餐";
}
if ("PRODUCT_DEFAULT".equals(source))
{
return "产品默认价";
}
if ("BILLING_FAIL".equals(source))
{
return "计费失败";
}
return source;
}
public static String customerActiveLabel(String status) public static String customerActiveLabel(String status)
{ {
@@ -13,6 +13,44 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
limit 1 limit 1
</select> </select>
<select id="selectByProductIdAndType" resultType="com.bms.price.domain.ChannelProductMap">
select m.id,
m.channel_id as channelId,
m.channel_code as channelCode,
m.product_id as productId,
c.channel_name as channelName,
m.pay_type as payType,
m.create_by as createBy,
m.create_time as createTime,
m.update_by as updateBy,
m.update_time as updateTime
from srv_channel_product_map m
left join srv_service_channel c on c.id = m.channel_id
where m.product_id = #{productId}
and m.pay_type = #{payType}
order by m.channel_code asc, m.channel_id asc
</select>
<select id="selectByProductIdsAndType" resultType="com.bms.price.domain.ChannelProductMap">
select m.id,
m.channel_id as channelId,
m.channel_code as channelCode,
m.product_id as productId,
c.channel_name as channelName,
m.pay_type as payType,
m.create_by as createBy,
m.create_time as createTime,
m.update_by as updateBy,
m.update_time as updateTime
from srv_channel_product_map m
left join srv_service_channel c on c.id = m.channel_id
where m.pay_type = #{payType}
and m.product_id in
<foreach collection="productIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
order by m.product_id asc, m.channel_code asc, m.channel_id asc
</select>
<insert id="upsert"> <insert id="upsert">
insert into srv_channel_product_map ( insert into srv_channel_product_map (
@@ -42,8 +42,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="weight" column="weight" /> <result property="weight" column="weight" />
<result property="bizDate" column="biz_date" /> <result property="bizDate" column="biz_date" />
<result property="tmsId" column="tms_id" /> <result property="tmsId" column="tms_id" />
<result property="matchSource" column="match_source" /> <result property="productFloorCode" column="product_floor_code" />
<result property="matchMessage" column="match_message" /> <result property="productFloor" column="product_floor" />
<result property="priceSetId" column="price_set_id" /> <result property="priceSetId" column="price_set_id" />
<result property="priceSetCode" column="price_set_code" /> <result property="priceSetCode" column="price_set_code" />
<result property="priceSetName" column="price_set_name" /> <result property="priceSetName" column="price_set_name" />
@@ -93,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
pieces, actual_weight, receive_weight, volume, volume_weight, pkg_length, pkg_width, pkg_height, pieces, actual_weight, receive_weight, volume, volume_weight, pkg_length, pkg_width, pkg_height,
shipper_name, consignee_name, dest_postcode, warehouse_code, order_time, order_remark, shipper_name, consignee_name, dest_postcode, warehouse_code, order_time, order_remark,
product_code, product_name, country_code, country_name, weight, biz_date, tms_id, product_code, product_name, country_code, country_name, weight, biz_date, tms_id,
match_source, match_message, price_set_id, price_set_code, price_set_name, product_floor_code, product_floor, price_set_id, price_set_code, price_set_name,
set_type, bind_priority, customer_priceset_id, psv_id, psd_id, set_type, bind_priority, customer_priceset_id, psv_id, psd_id,
weight_min, weight_max, weight_label, effective_date, charge_weight, weight_min, weight_max, weight_label, effective_date, charge_weight,
freight_rate, base_freight_rate, discount_percent, registration_fee, freight_rate, base_freight_rate, discount_percent, registration_fee,
@@ -112,7 +112,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
p.pieces, p.actual_weight, p.receive_weight, p.volume, p.volume_weight, p.pkg_length, p.pkg_width, p.pkg_height, p.pieces, p.actual_weight, p.receive_weight, p.volume, p.volume_weight, p.pkg_length, p.pkg_width, p.pkg_height,
p.shipper_name, p.consignee_name, p.dest_postcode, p.warehouse_code, p.order_time, p.order_status, p.bl_pickup, p.pickup_date, p.order_remark, p.shipper_name, p.consignee_name, p.dest_postcode, p.warehouse_code, p.order_time, p.order_status, p.bl_pickup, p.pickup_date, p.order_remark,
p.product_code, p.product_name, p.country_code, p.country_name, p.weight, p.biz_date, p.tms_id, p.product_code, p.product_name, p.country_code, p.country_name, p.weight, p.biz_date, p.tms_id,
p.match_source, p.match_message, p.price_set_id, p.price_set_code, p.price_set_name, p.product_floor_code, p.product_floor, p.price_set_id, p.price_set_code, p.price_set_name,
p.set_type, p.bind_priority, p.customer_priceset_id, p.psv_id, p.psd_id, p.set_type, p.bind_priority, p.customer_priceset_id, p.psv_id, p.psd_id,
p.weight_min, p.weight_max, p.weight_label, p.effective_date, p.charge_weight, p.weight_min, p.weight_max, p.weight_label, p.effective_date, p.charge_weight,
p.freight_rate, p.base_freight_rate, p.discount_percent, p.registration_fee, p.freight_rate, p.base_freight_rate, p.discount_percent, p.registration_fee,
@@ -542,8 +542,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="countryCode != null and countryCode != ''"> <if test="countryCode != null and countryCode != ''">
AND p.country_code = #{countryCode} AND p.country_code = #{countryCode}
</if> </if>
<if test="matchSource != null and matchSource != ''"> <if test="productFloorCode != null and productFloorCode != ''">
AND p.match_source = #{matchSource} AND p.product_floor_code like concat('%', #{productFloorCode}, '%')
</if> </if>
<if test="bizDate != null"> <if test="bizDate != null">
AND p.biz_date = #{bizDate} AND p.biz_date = #{bizDate}
@@ -844,7 +844,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
pieces, actual_weight, receive_weight, volume, volume_weight, pkg_length, pkg_width, pkg_height, pieces, actual_weight, receive_weight, volume, volume_weight, pkg_length, pkg_width, pkg_height,
shipper_name, consignee_name, dest_postcode, warehouse_code, order_time, order_status, bl_pickup, pickup_date, order_remark, shipper_name, consignee_name, dest_postcode, warehouse_code, order_time, order_status, bl_pickup, pickup_date, order_remark,
product_code, product_name, country_code, country_name, weight, biz_date, tms_id, product_code, product_name, country_code, country_name, weight, biz_date, tms_id,
match_source, match_message, price_set_id, price_set_code, price_set_name, product_floor_code, product_floor, price_set_id, price_set_code, price_set_name,
set_type, bind_priority, customer_priceset_id, psv_id, psd_id, set_type, bind_priority, customer_priceset_id, psv_id, psd_id,
weight_min, weight_max, weight_label, effective_date, charge_weight, weight_min, weight_max, weight_label, effective_date, charge_weight,
freight_rate, base_freight_rate, discount_percent, registration_fee, freight_rate, base_freight_rate, discount_percent, registration_fee,
@@ -858,7 +858,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{pieces}, #{actualWeight}, #{receiveWeight}, #{volume}, #{volumeWeight}, #{pkgLength}, #{pkgWidth}, #{pkgHeight}, #{pieces}, #{actualWeight}, #{receiveWeight}, #{volume}, #{volumeWeight}, #{pkgLength}, #{pkgWidth}, #{pkgHeight},
#{shipperName}, #{consigneeName}, #{destPostcode}, #{warehouseCode}, #{orderTime}, #{orderStatus}, #{blPickup}, #{pickupDate}, #{orderRemark}, #{shipperName}, #{consigneeName}, #{destPostcode}, #{warehouseCode}, #{orderTime}, #{orderStatus}, #{blPickup}, #{pickupDate}, #{orderRemark},
#{productCode}, #{productName}, #{countryCode}, #{countryName}, #{weight}, #{bizDate}, #{tmsId}, #{productCode}, #{productName}, #{countryCode}, #{countryName}, #{weight}, #{bizDate}, #{tmsId},
#{matchSource}, #{matchMessage}, #{priceSetId}, #{priceSetCode}, #{priceSetName}, #{productFloorCode}, #{productFloor}, #{priceSetId}, #{priceSetCode}, #{priceSetName},
#{setType}, #{bindPriority}, #{customerPricesetId}, #{psvId}, #{psdId}, #{setType}, #{bindPriority}, #{customerPricesetId}, #{psvId}, #{psdId},
#{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight}, #{weightMin}, #{weightMax}, #{weightLabel}, #{effectiveDate}, #{chargeWeight},
#{freightRate}, #{baseFreightRate}, #{discountPercent}, #{registrationFee}, #{freightRate}, #{baseFreightRate}, #{discountPercent}, #{registrationFee},
@@ -908,8 +908,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
weight = #{weight}, weight = #{weight},
biz_date = #{bizDate}, biz_date = #{bizDate},
tms_id = #{tmsId}, tms_id = #{tmsId},
match_source = #{matchSource}, product_floor_code = #{productFloorCode},
match_message = #{matchMessage}, product_floor = #{productFloor},
price_set_id = #{priceSetId}, price_set_id = #{priceSetId},
price_set_code = #{priceSetCode}, price_set_code = #{priceSetCode},
price_set_name = #{priceSetName}, price_set_name = #{priceSetName},
+2 -2
View File
@@ -924,8 +924,8 @@ CREATE TABLE `prc_order_bill` (
`weight` decimal(10,3) DEFAULT NULL COMMENT '计费重量KG', `weight` decimal(10,3) DEFAULT NULL COMMENT '计费重量KG',
`biz_date` datetime NOT NULL COMMENT '计费时间', `biz_date` datetime NOT NULL COMMENT '计费时间',
`tms_id` bigint(20) DEFAULT NULL COMMENT '多租户系统ID', `tms_id` bigint(20) DEFAULT NULL COMMENT '多租户系统ID',
`match_source` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中来源', `product_floor_code` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渠道编码',
`match_message` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中说明', `product_floor` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '渠道名称',
`price_set_id` bigint(20) DEFAULT NULL COMMENT '价格套餐ID', `price_set_id` bigint(20) DEFAULT NULL COMMENT '价格套餐ID',
`price_set_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '套餐编码', `price_set_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '套餐编码',
`price_set_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '套餐名称', `price_set_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '套餐名称',
@@ -0,0 +1,30 @@
-- prc_order_bill: drop match_source / match_message.
-- Idempotent.
SET NAMES utf8mb4;
SET @sql_drop_match_source = (
SELECT IF(
COUNT(*) > 0,
'ALTER TABLE prc_order_bill DROP COLUMN match_source',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'match_source'
);
PREPARE stmt FROM @sql_drop_match_source;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql_drop_match_message = (
SELECT IF(
COUNT(*) > 0,
'ALTER TABLE prc_order_bill DROP COLUMN match_message',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'match_message'
);
PREPARE stmt FROM @sql_drop_match_message;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -0,0 +1,30 @@
-- prc_order_bill: add product_floor_code / product_floor from upstream.
-- Idempotent.
SET NAMES utf8mb4;
SET @sql_add_floor_code = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE prc_order_bill ADD COLUMN product_floor_code varchar(64) DEFAULT NULL COMMENT ''渠道编码'' AFTER tms_id',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'product_floor_code'
);
PREPARE stmt FROM @sql_add_floor_code;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql_add_floor_name = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE prc_order_bill ADD COLUMN product_floor varchar(200) DEFAULT NULL COMMENT ''渠道名称'' AFTER product_floor_code',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_order_bill' AND COLUMN_NAME = 'product_floor'
);
PREPARE stmt FROM @sql_add_floor_name;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
+16
View File
@@ -162,3 +162,19 @@ export function removeLabelProducts(priceSetId, productIds) {
}) })
} }
export function listLabelProductChannels(productId, payType = 'AR') {
return request({
url: '/price/label/product/' + productId + '/channels',
method: 'get',
params: { payType }
})
}
export function replaceLabelProductChannels(productId, data) {
return request({
url: '/price/label/product/' + productId + '/channels',
method: 'put',
data
})
}
+2 -1
View File
@@ -62,7 +62,8 @@ export const BILLING_BILL_EXPORT_FIELDS = [
{ prop: 'receiveWeight', label: '收货重' }, { prop: 'receiveWeight', label: '收货重' },
{ prop: 'incomeTotal', label: '应收总额' }, { prop: 'incomeTotal', label: '应收总额' },
{ prop: 'payTotal', label: '应付总额' }, { prop: 'payTotal', label: '应付总额' },
{ prop: 'matchSourceLabel', label: '计价来源' }, { prop: 'productFloorCode', label: '渠道编码' },
{ prop: 'productFloor', label: '渠道名称' },
{ prop: 'orderStatusLabel', label: '订单状态' }, { prop: 'orderStatusLabel', label: '订单状态' },
{ prop: 'billingFailStatusLabel', label: '计费状态' }, { prop: 'billingFailStatusLabel', label: '计费状态' },
{ prop: 'customerActiveStatusLabel', label: '客户状态' }, { prop: 'customerActiveStatusLabel', label: '客户状态' },
@@ -80,9 +80,8 @@
<el-descriptions-item label="原计费重(kg)">{{ formatWeight(detail.weight) }}</el-descriptions-item> <el-descriptions-item label="原计费重(kg)">{{ formatWeight(detail.weight) }}</el-descriptions-item>
<el-descriptions-item label="重量段">{{ detail.weightLabel || '-' }}</el-descriptions-item> <el-descriptions-item label="重量段">{{ detail.weightLabel || '-' }}</el-descriptions-item>
<el-descriptions-item label="计费时间">{{ detail.bizDate || '-' }}</el-descriptions-item> <el-descriptions-item label="计费时间">{{ detail.bizDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="计价来源"> <el-descriptions-item label="渠道编码">{{ detail.productFloorCode || '-' }}</el-descriptions-item>
<el-tag size="small" :type="sourceTagType(detail.matchSource)">{{ sourceLabel(detail.matchSource) }}</el-tag> <el-descriptions-item label="渠道名称">{{ detail.productFloor || '-' }}</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item label="计费状态"> <el-descriptions-item label="计费状态">
<el-tag size="small" :type="billingFailTagType(detail.billingFailStatus)">{{ billingFailLabel(detail.billingFailStatus) }}</el-tag> <el-tag size="small" :type="billingFailTagType(detail.billingFailStatus)">{{ billingFailLabel(detail.billingFailStatus) }}</el-tag>
</el-descriptions-item> </el-descriptions-item>
@@ -96,10 +95,6 @@
<el-descriptions-item label="运费单价" >{{ formatRate(detail.freightRate) }}</el-descriptions-item> <el-descriptions-item label="运费单价" >{{ formatRate(detail.freightRate) }}</el-descriptions-item>
<el-descriptions-item label="挂号费" >{{ formatMoney(detail.registrationFee) }}</el-descriptions-item> <el-descriptions-item label="挂号费" >{{ formatMoney(detail.registrationFee) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div v-if="detail.matchMessage" class="match-note">
<span class="match-note__label">命中说明</span>
<span>{{ detail.matchMessage }}</span>
</div>
</section> </section>
<section class="detail-block"> <section class="detail-block">
@@ -422,18 +417,6 @@ function billingFailTagType(status) {
return 'success' return 'success'
} }
function sourceLabel(source) {
const map = {
PACKAGE_FIXED: '客户固定价套餐',
PACKAGE_DISCOUNT: '客户折扣套餐',
PRODUCT_DEFAULT: '产品默认价',
BILLING_FAIL: '计费失败',
FIXED: '客户固定价套餐',
DISCOUNT: '客户折扣套餐'
}
return map[source] || source || '-'
}
function roundModeLabel(mode) { function roundModeLabel(mode) {
const map = { UP: '向上取值', DOWN: '向下取整', HALF_UP: '四舍五入' } const map = { UP: '向上取值', DOWN: '向下取整', HALF_UP: '四舍五入' }
return map[mode] || mode || '四舍五入' return map[mode] || mode || '四舍五入'
@@ -466,14 +449,6 @@ function displayFeeName(row) {
return name || typeLabel || row.feeType || '-' return name || typeLabel || row.feeType || '-'
} }
function sourceTagType(source) {
if (source === 'PACKAGE_FIXED' || source === 'FIXED') return 'success'
if (source === 'PACKAGE_DISCOUNT' || source === 'DISCOUNT') return 'warning'
if (source === 'PRODUCT_DEFAULT') return 'info'
if (source === 'BILLING_FAIL') return 'danger'
return ''
}
function refundLabel(status) { function refundLabel(status) {
const map = { NONE: '未退费', REFUNDED: '已退费' } const map = { NONE: '未退费', REFUNDED: '已退费' }
return map[status] || status || '未退费' return map[status] || status || '未退费'
@@ -8,8 +8,7 @@
@close="emit('update:visible', false)" @close="emit('update:visible', false)"
> >
<div class="toolbar"> <div class="toolbar">
<el-input v-model="keyword" clearable placeholder="产品名称 / 工作表 / 国家" style="width: 260px" /> <el-input v-model="keyword" clearable :placeholder="phSearch" style="width: 300px" />
<el-button type="danger" plain :disabled="!selectedIds.length" @click="handleRemove" v-hasPermi="['price:label:remove']">移除</el-button>
</div> </div>
<el-table <el-table
v-loading="loading" v-loading="loading"
@@ -17,30 +16,26 @@
border border
height="calc(100vh - 220px)" height="calc(100vh - 220px)"
row-key="termId" row-key="termId"
@selection-change="onSelect"
> >
<el-table-column type="selection" width="48" align="center" /> <el-table-column prop="productId" :label="colId" width="90" align="center" />
<el-table-column prop="productId" label="产品编号" width="90" align="center" /> <el-table-column prop="productName" :label="colName" min-width="150" show-overflow-tooltip />
<el-table-column prop="productName" label="产品名称" min-width="150" show-overflow-tooltip /> <el-table-column :label="colTerm" min-width="180" align="center">
<el-table-column prop="supplierName" label="价卡归属" width="100" align="center" />
<el-table-column prop="countryScope" label="国家/口岸" min-width="120" show-overflow-tooltip />
<el-table-column prop="chargeType" label="费用类型" width="110" align="center" />
<el-table-column prop="sheetName" label="报价工作表" min-width="140" show-overflow-tooltip />
<el-table-column prop="versionNo" label="版本" width="70" align="center" />
<el-table-column label="有效期" min-width="180" align="center">
<template #default="{ row }">{{ termText(row) }}</template> <template #default="{ row }">{{ termText(row) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="状态" width="90" align="center"> <el-table-column :label="colStatus" width="90" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="termStatusTag(row.termStatus)" size="small">{{ termStatusText(row.termStatus) }}</el-tag> <el-tag :type="termStatusTag(row.termStatus)" size="small">{{ termStatusText(row.termStatus) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="weightCount" label="重量段" width="80" align="center" /> <el-table-column prop="weightCount" :label="colWeight" width="80" align="center" />
<el-table-column prop="surchargeCount" label="附加费" width="80" align="center" /> <el-table-column prop="surchargeCount" :label="colSc" width="80" align="center" />
<el-table-column label="操作" width="160" align="center" fixed="right"> <el-table-column :label="colBoundChannels" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ boundChannelText(row) }}</template>
</el-table-column>
<el-table-column :label="colAction" width="160" align="center" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" @click="openProduct(row)" v-hasPermi="['price:label:query']">查看</el-button> <el-button link type="primary" @click="openProduct(row)" v-hasPermi="['price:label:query']">查看</el-button>
<el-button link type="danger" @click="handleRemove([row.productId])" v-hasPermi="['price:label:remove']">移除</el-button> <el-button link type="primary" @click="openBindList(row)" v-hasPermi="['price:label:edit']">绑定</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -51,11 +46,89 @@
<el-button @click="detailVisible = false">关闭</el-button> <el-button @click="detailVisible = false">关闭</el-button>
</template> </template>
</el-drawer> </el-drawer>
<el-drawer
v-model="bindListVisible"
:title="bindListTitle"
size="520px"
append-to-body
destroy-on-close
@closed="onBindListClosed"
>
<div class="bind-list-toolbar">
<el-button type="primary" plain @click="openAddChannels" v-hasPermi="['price:label:edit']">添加渠道</el-button>
</div>
<el-table
v-loading="bindListLoading"
:data="boundChannels"
border
height="calc(100vh - 240px)"
:empty-text="emptyBound"
>
<el-table-column prop="channelCode" :label="colChannelCode" min-width="120" show-overflow-tooltip />
<el-table-column prop="channelName" :label="colChannelName" min-width="160" show-overflow-tooltip />
<el-table-column :label="colAction" width="90" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="danger" @click="removeBoundChannel(row)" v-hasPermi="['price:label:edit']">移除</el-button>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="bindListVisible = false">关闭</el-button>
</template>
</el-drawer>
<el-dialog
v-model="addVisible"
:title="addTitle"
width="560px"
append-to-body
destroy-on-close
@closed="onAddClosed"
>
<div v-loading="addLoading">
<el-form label-width="88px">
<el-form-item :label="lblProduct">
<span>{{ bindProductName }}</span>
</el-form-item>
<el-form-item :label="lblChannels">
<el-select
v-model="addChannelIds"
multiple
filterable
clearable
collapse-tags
collapse-tags-tooltip
:placeholder="phChannels"
style="width: 100%"
>
<el-option
v-for="item in addableChannelOptions"
:key="item.id"
:label="channelOptionLabel(item)"
:value="String(item.id)"
/>
</el-select>
</el-form-item>
<div class="bind-hint">{{ bindHint }}</div>
</el-form>
</div>
<template #footer>
<el-button @click="addVisible = false">取消</el-button>
<el-button type="primary" :loading="addSaving" @click="submitAddChannels">保存</el-button>
</template>
</el-dialog>
</el-drawer> </el-drawer>
</template> </template>
<script setup> <script setup>
import { listLabelProducts, getLabelProduct, removeLabelProducts } from '@/api/price/label' import {
listLabelProducts,
getLabelProduct,
listLabelProductChannels,
replaceLabelProductChannels
} from '@/api/price/label'
import { listChannelOptions } from '@/api/masterdata/serviceChannel'
import PayConfigDetail from '@/views/price/basic/pay/components/PayConfigDetail.vue' import PayConfigDetail from '@/views/price/basic/pay/components/PayConfigDetail.vue'
const props = defineProps({ const props = defineProps({
@@ -69,11 +142,40 @@ const { proxy } = getCurrentInstance()
const loading = ref(false) const loading = ref(false)
const rows = ref([]) const rows = ref([])
const keyword = ref('') const keyword = ref('')
const selectedIds = ref([])
const detailVisible = ref(false) const detailVisible = ref(false)
const detailLoading = ref(false) const detailLoading = ref(false)
const detail = ref(null) const detail = ref(null)
const bindListVisible = ref(false)
const bindListLoading = ref(false)
const bindProductId = ref(null)
const bindProductName = ref('')
const boundChannels = ref([])
const channelOptions = ref([])
const addVisible = ref(false)
const addLoading = ref(false)
const addSaving = ref(false)
const addChannelIds = ref([])
const phSearch = '产品名称 / 绑定渠道'
const colId = '产品编号'
const colName = '产品名称'
const colTerm = '有效期'
const colStatus = '状态'
const colWeight = '重量段'
const colSc = '附加费'
const colBoundChannels = '绑定渠道'
const colAction = '操作'
const colChannelCode = '渠道编码'
const colChannelName = '渠道名称'
const lblProduct = '产品'
const lblChannels = '添加渠道'
const phChannels = '请选择要添加的渠道(可多选)'
const bindHint = '绑定为收款(AR),落库 srv_channel_product_map;一个渠道同一收付类型仅保留一条产品映射。'
const emptyBound = '暂无绑定渠道'
const addTitle = '添加渠道'
const title = computed(() => { const title = computed(() => {
const v = props.version || {} const v = props.version || {}
const label = v.versionLabel || (v.versionNo != null ? `V${v.versionNo}` : '') const label = v.versionLabel || (v.versionNo != null ? `V${v.versionNo}` : '')
@@ -86,15 +188,24 @@ const detailTitle = computed(() => {
return `${detail.value.productName || '产品'} ${no}`.trim() return `${detail.value.productName || '产品'} ${no}`.trim()
}) })
const bindListTitle = computed(() => {
return bindProductName.value ? `绑定渠道 · ${bindProductName.value}` : '绑定渠道'
})
const filteredRows = computed(() => { const filteredRows = computed(() => {
const kw = keyword.value.trim().toLowerCase() const kw = keyword.value.trim().toLowerCase()
if (!kw) return rows.value if (!kw) return rows.value
return rows.value.filter(row => { return rows.value.filter(row => {
const text = [row.productName, row.sheetName, row.countryScope, row.supplierName].join(' ').toLowerCase() const text = [row.productName, boundChannelText(row)].join(' ').toLowerCase()
return text.includes(kw) return text.includes(kw)
}) })
}) })
const addableChannelOptions = computed(() => {
const bound = new Set((boundChannels.value || []).map(item => String(item.channelId)))
return (channelOptions.value || []).filter(item => item && item.id != null && !bound.has(String(item.id)))
})
function formatHour(val) { function formatHour(val) {
if (!val) return '' if (!val) return ''
const text = String(val).replace('T', ' ') const text = String(val).replace('T', ' ')
@@ -120,8 +231,17 @@ function termStatusTag(status) {
return map[String(status)] || 'info' return map[String(status)] || 'info'
} }
function onSelect(selection) { function channelOptionLabel(item) {
selectedIds.value = (selection || []).map(item => item.productId).filter(Boolean) const code = item?.channelCode || item?.id || ''
const name = item?.channelName || ''
if (code && name && code !== name) return code + ' / ' + name
return code || name || '-'
}
function boundChannelText(row) {
if (!row) return '-'
if (row.boundChannelText) return row.boundChannelText
return '-'
} }
async function load() { async function load() {
@@ -150,16 +270,96 @@ async function openProduct(row) {
} }
} }
function handleRemove(ids) { async function ensureChannelOptions() {
const list = (Array.isArray(ids) ? ids : selectedIds.value).filter(Boolean) if (channelOptions.value.length) return
if (!list.length || !props.priceSetId) return const res = await listChannelOptions({ blOpen: '1' })
proxy.$modal.confirm(`确认从本套餐移除选中的 ${list.length} 个产品?`).then(() => { channelOptions.value = res.data || []
return removeLabelProducts(props.priceSetId, list.join(',')) }
}).then(() => {
proxy.$modal.msgSuccess('已移除') async function loadBoundChannels() {
load() if (!bindProductId.value) {
emit('changed') boundChannels.value = []
}).catch(() => {}) return
}
bindListLoading.value = true
try {
const res = await listLabelProductChannels(bindProductId.value, 'AR')
boundChannels.value = res.data || []
} finally {
bindListLoading.value = false
}
}
async function openBindList(row) {
if (!row?.productId) return
bindProductId.value = row.productId
bindProductName.value = row.productName || String(row.productId)
boundChannels.value = []
bindListVisible.value = true
await loadBoundChannels()
}
function onBindListClosed() {
bindProductId.value = null
bindProductName.value = ''
boundChannels.value = []
addVisible.value = false
addChannelIds.value = []
}
async function openAddChannels() {
addChannelIds.value = []
addVisible.value = true
addLoading.value = true
try {
await ensureChannelOptions()
} finally {
addLoading.value = false
}
}
function onAddClosed() {
addChannelIds.value = []
}
function currentBoundIds() {
return (boundChannels.value || []).map(item => String(item.channelId)).filter(Boolean)
}
function submitAddChannels() {
if (!bindProductId.value) return
const selected = (addChannelIds.value || []).map(String).filter(Boolean)
if (!selected.length) {
proxy.$modal.msgWarning('请先选择要添加的渠道')
return
}
const merged = Array.from(new Set(currentBoundIds().concat(selected)))
addSaving.value = true
replaceLabelProductChannels(bindProductId.value, {
payType: 'AR',
channelIds: merged
}).then((res) => {
proxy.$modal.msgSuccess((res && res.msg) || '添加成功')
addVisible.value = false
return loadBoundChannels()
}).then(() => load()).catch(() => {}).finally(() => {
addSaving.value = false
})
}
function removeBoundChannel(row) {
if (!bindProductId.value || !row?.channelId) return
const label = channelOptionLabel(row)
proxy.$modal.confirm(`确认移除渠道「${label}」?`).then(() => {
const remain = currentBoundIds().filter(id => id !== String(row.channelId))
return replaceLabelProductChannels(bindProductId.value, {
payType: 'AR',
channelIds: remain
})
}).then((res) => {
proxy.$modal.msgSuccess((res && res.msg) || '已移除')
return loadBoundChannels()
}).then(() => load()).catch(() => {})
} }
watch(() => [props.visible, props.priceSetId, props.version?.versionNo], ([vis]) => { watch(() => [props.visible, props.priceSetId, props.version?.versionNo], ([vis]) => {
@@ -174,4 +374,15 @@ watch(() => [props.visible, props.priceSetId, props.version?.versionNo], ([vis])
margin-bottom: 12px; margin-bottom: 12px;
align-items: center; align-items: center;
} }
.bind-list-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 12px;
}
.bind-hint {
margin: -4px 0 0 88px;
color: #909399;
font-size: 12px;
line-height: 1.5;
}
</style> </style>