wip: tiered deliver price

This commit is contained in:
Kroytz committed 2026-07-03 19:17:26 +08:00
1 parent 8423007350
commit a0c13b91da
23 files changed
+426 -39

No files matched your search

@@ -0,0 +1,14 @@
-- ============================================================
-- 门店配送规则(阶梯配送费)升级脚本
-- 将固定的"配送价格"扩展为基于距离区间的"配送规则"
-- 配送规则存为 JSON 数组: [{minKm,maxKm,price}],区间左闭右开
-- 无规则匹配时回退 delivery_price(默认配送费)
-- ============================================================
ALTER TABLE `yshop_store_shop`
ADD COLUMN `delivery_rules` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '配送规则JSON:[{minKm,maxKm,price}]' AFTER `delivery_price`;
-- 可选:为已有支持外卖的门店初始化一条示例规则(请按需调整)
-- UPDATE `yshop_store_shop`
-- SET `delivery_rules` = '[{"minKm":0,"maxKm":3,"price":8},{"minKm":3,"maxKm":5,"price":12}]'
-- WHERE `distance` > 0 AND `delivery_rules` IS NULL;
+3 -2
View File
@@ -13698,6 +13698,7 @@ CREATE TABLE `yshop_store_shop` (
`distance` int NOT NULL DEFAULT '0' COMMENT '外卖配送距离,单位为千米。0表示不送外卖',
`min_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '起送价钱',
`delivery_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '配送价格',
`delivery_rules` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '配送规则JSON:[{minKm,maxKm,price}]',
`notice` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '公告',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否营业:0=否,1=是',
`admin_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '管理员id',
@@ -13714,8 +13715,8 @@ CREATE TABLE `yshop_store_shop` (
-- Records of yshop_store_shop
-- ----------------------------
BEGIN;
INSERT INTO `yshop_store_shop` VALUES (2, '意向餐饮店', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '源汇区人民东路', '河南省漯河市源汇区团结路50号-20', '2023-08-19 09:00:00', '2023-08-19 23:00:00', '114.04877877439665', '33.57093831826829', 10, 10.00, 20.00, '新店开张', 1, '126', '1', '2023-08-06 11:48:43', '2023-09-14 17:30:59', '1', '1', b'0');
INSERT INTO `yshop_store_shop` VALUES (3, 'yshop', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '67号', '北京市东城区', '2023-08-06 13:35:02', '2023-08-06 15:35:02', '116.40403593222032', '39.91643892263258', 0, 1.00, 2.00, '2222', 1, '100', '2', '2023-08-06 13:14:53', '2024-03-26 15:42:20', '1', '1', b'0');
INSERT INTO `yshop_store_shop` VALUES (2, '意向餐饮店', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '源汇区人民东路', '河南省漯河市源汇区团结路50号-20', '2023-08-19 09:00:00', '2023-08-19 23:00:00', '114.04877877439665', '33.57093831826829', 10, 10.00, 20.00, '[{"minKm":0,"maxKm":3,"price":8},{"minKm":3,"maxKm":5,"price":12},{"minKm":5,"maxKm":10,"price":18}]', '新店开张', 1, '126', '1', '2023-08-06 11:48:43', '2023-09-14 17:30:59', '1', '1', b'0');
INSERT INTO `yshop_store_shop` VALUES (3, 'yshop', '18000000000', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', 'https://apidc.yixiang.co/admin-api/infra/file/4/get/44723283500f656cdd5906e13f2997cb292d17f516065ff4b3b38f9810bce4cb.jpg', '67号', '北京市东城区', '2023-08-06 13:35:02', '2023-08-06 15:35:02', '116.40403593222032', '39.91643892263258', 0, 1.00, 2.00, NULL, '2222', 1, '100', '2', '2023-08-06 13:14:53', '2024-03-26 15:42:20', '1', '1', b'0');
COMMIT;
-- ----------------------------
@@ -36,6 +36,7 @@ public interface ErrorCodeConstants {
ErrorCode STORE_ORDER_DESK_NOT = new ErrorCode(1008007023, "当前桌号不存在");
ErrorCode STORE_ORDER_OUT_OF_DELIVERY_RANGE = new ErrorCode(1008007024, "当前收货地址不在配送范围内");
@@ -49,6 +49,7 @@ import co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProduc
import co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import co.yixiang.yshop.module.store.service.storeshop.AppStoreShopService;
import co.yixiang.yshop.module.store.service.storeshop.DeliveryCalculator;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -180,8 +181,26 @@ public class AppStoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper,Store
BigDecimal sumPrice = BigDecimal.ZERO;
BigDecimal couponPrice = BigDecimal.ZERO;
BigDecimal postagePrice = storeShopDO.getDeliveryPrice();
BigDecimal deductionPrice = BigDecimal.ZERO;
// 计算配送费:外卖按收货地址距离匹配配送规则,自取不收配送费
BigDecimal postagePrice = BigDecimal.ZERO;
UserAddressDO userAddress = null;
if (OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(param.getOrderType())) {
if (StrUtil.isEmpty(param.getAddressId())) {
throw exception(SELECT_ADDRESS);
}
userAddress = appUserAddressService.getById(param.getAddressId());
if (ObjectUtil.isNull(userAddress)) {
throw exception(USER_ADDRESS_NOT_EXISTS);
}
double distanceKm = DeliveryCalculator.calcDistanceKm(
storeShopDO.getLng(), storeShopDO.getLat(),
userAddress.getLongitude(), userAddress.getLatitude());
postagePrice = DeliveryCalculator.calcDeliveryPrice(storeShopDO, distanceKm);
if (postagePrice == null) {
throw exception(STORE_ORDER_OUT_OF_DELIVERY_RANGE);
}
}
//对库存检查加锁
RLock lock = redissonClient.getLock(LOCK_KEY);
@@ -264,13 +283,6 @@ public class AppStoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper,Store
storeOrder.setOrderId(orderSn);
//处理如果是外卖 地址
if(OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(param.getOrderType())){
if (StrUtil.isEmpty(param.getAddressId())) {
throw exception(SELECT_ADDRESS);
}
UserAddressDO userAddress = appUserAddressService.getById(param.getAddressId());
if (ObjectUtil.isNull(userAddress)) {
throw exception(USER_ADDRESS_NOT_EXISTS);
}
storeOrder.setRealName(userAddress.getRealName());
storeOrder.setUserPhone(userAddress.getPhone());
storeOrder.setUserAddress(userAddress.getAddress() + " " + userAddress.getDetail());
@@ -278,12 +290,12 @@ public class AppStoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper,Store
storeOrder.setCartId(StrUtil.join(",", cartIds));
storeOrder.setTotalNum(totalNum);
storeOrder.setTotalPrice(sumPrice);
storeOrder.setTotalPostage(storeShopDO.getDeliveryPrice());
storeOrder.setTotalPostage(postagePrice);
storeOrder.setCouponId(StrUtil.isBlank(param.getCouponId()) ? 0 : Integer.valueOf(param.getCouponId()));
storeOrder.setCouponPrice(couponPrice);
storeOrder.setPayPrice(payPrice);
storeOrder.setPayPostage(storeShopDO.getDeliveryPrice());
storeOrder.setPayPostage(postagePrice);
storeOrder.setDeductionPrice(deductionPrice);
storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_0.getValue());
storeOrder.setPayType(param.getPayType());
@@ -1,5 +1,6 @@
package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.DeliveryRule;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
@@ -60,9 +61,12 @@ public class StoreShopBaseVO {
@Schema(description = "起送价钱", required = true, example = "15157")
private BigDecimal minPrice;
@Schema(description = "配送价格", required = true, example = "11771")
@Schema(description = "默认配送费(未配置规则或无匹配时兜底)", example = "8")
private BigDecimal deliveryPrice;
@Schema(description = "配送规则(按距离区间阶梯计费)", example = "[{\"minKm\":1,\"maxKm\":3,\"price\":8}]")
private List<DeliveryRule> deliveryRules;
@Schema(description = "公告", required = true)
@NotNull(message = "公告不能为空")
private String notice;
@@ -10,6 +10,7 @@ package co.yixiang.yshop.module.store.controller.app.storeshop;
import co.yixiang.yshop.framework.common.pojo.CommonResult;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.DeliveryFeeRespVO;
import co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import co.yixiang.yshop.module.store.service.storeshop.AppStoreShopService;
@@ -75,6 +76,14 @@ public class AppStoreController {
return success(StoreShopConvert.INSTANCE.convert02(storeShopDO));
}
@GetMapping("/delivery-fee")
@Operation(summary = "预估配送费")
public CommonResult<DeliveryFeeRespVO> getDeliveryFee(@RequestParam("shopId") Long shopId,
@RequestParam("lng") double lng,
@RequestParam("lat") double lat) {
return success(appStoreShopService.getDeliveryFee(shopId, lng, lat));
}
}
@@ -54,6 +54,9 @@ public class AppStoreShopVO {
@Schema(description = "配送价格", required = true, example = "11771")
private BigDecimal deliveryPrice;
@Schema(description = "配送规则JSON文本", example = "[{\"minKm\":1,\"maxKm\":3,\"price\":8}]")
private String deliveryRules;
@Schema(description = "公告", required = true)
private String notice;
@@ -0,0 +1,25 @@
package co.yixiang.yshop.module.store.controller.app.storeshop.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
* 预估配送费 Response VO
*
* @author yshop
*/
@Data
public class DeliveryFeeRespVO {
@Schema(description = "收货地址到门店的距离(千米)")
private Double distanceKm;
@Schema(description = "配送费(元),超出配送范围时为 0")
private BigDecimal deliveryPrice;
@Schema(description = "是否超出配送范围")
private Boolean outOfRange;
}
@@ -3,11 +3,15 @@ package co.yixiang.yshop.module.store.convert.storeshop;
import java.util.*;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.framework.common.util.json.JsonUtils;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
import co.yixiang.yshop.module.store.controller.admin.storeshop.vo.*;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.DeliveryRule;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
/**
@@ -26,6 +30,7 @@ public interface StoreShopConvert {
StoreShopRespVO convert(StoreShopDO bean);
@Mapping(source = "deliveryRules", target = "deliveryRules", qualifiedByName = "rulesToJson")
AppStoreShopVO convert02(StoreShopDO bean);
List<StoreShopRespVO> convertList(List<StoreShopDO> list);
@@ -34,4 +39,12 @@ public interface StoreShopConvert {
List<StoreShopExcelVO> convertList02(List<StoreShopDO> list);
/**
* 将配送规则 List 序列化为 JSON 文本,供小程序端解析展示
*/
@Named("rulesToJson")
default String rulesToJson(List<DeliveryRule> rules) {
return rules == null ? null : JsonUtils.toJsonString(rules);
}
}
@@ -0,0 +1,37 @@
package co.yixiang.yshop.module.store.dal.dataobject.storeshop;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 配送规则 DO
* <p>
* 描述一段距离区间对应的配送费,区间为左闭右开 [minKm, maxKm)。
* 例如:minKm=1, maxKm=3, price=8 表示 1km(含) ~ 3km(不含) 配送费 8 元。
*
* @author yshop
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeliveryRule {
/**
* 最小距离(单位:千米,含)
*/
private BigDecimal minKm;
/**
* 最大距离(单位:千米,不含)
*/
private BigDecimal maxKm;
/**
* 配送费(单位:元)
*/
private BigDecimal price;
}
@@ -1,6 +1,7 @@
package co.yixiang.yshop.module.store.dal.dataobject.storeshop;
import co.yixiang.yshop.framework.mybatis.core.type.StringListTypeHandler;
import co.yixiang.yshop.module.store.dal.type.DeliveryRuleListTypeHandler;
import lombok.*;
import java.util.*;
import java.math.BigDecimal;
@@ -74,9 +75,15 @@ public class StoreShopDO extends BaseDO {
*/
private BigDecimal minPrice;
/**
* 配送价格
* 配送价格(默认/兜底配送费)
*/
private BigDecimal deliveryPrice;
/**
* 配送规则(按距离区间阶梯计费),JSON 数组:[{minKm,maxKm,price}]
* 区间左闭右开,无匹配时回退 deliveryPrice
*/
@TableField(typeHandler = DeliveryRuleListTypeHandler.class)
private List<DeliveryRule> deliveryRules;
/**
* 公告
*/
@@ -0,0 +1,33 @@
package co.yixiang.yshop.module.store.dal.type;
import co.yixiang.yshop.framework.common.util.json.JsonUtils;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.DeliveryRule;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
/**
* List<DeliveryRule> 的类型转换器实现类,对应数据库的 text 类型
* <p>
* 参考 {@link com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler} 实现,
* 使用 TypeReference 保证反序列化为 List<DeliveryRule> 时泛型不丢失。
* 放置于 store 模块以避免框架层反向依赖业务模块。
*
* @author yshop
*/
public class DeliveryRuleListTypeHandler extends AbstractJsonTypeHandler<Object> {
private static final TypeReference<List<DeliveryRule>> TYPE_REFERENCE = new TypeReference<List<DeliveryRule>>(){};
@Override
protected Object parse(String json) {
return JsonUtils.parseObject(json, TYPE_REFERENCE);
}
@Override
protected String toJson(Object obj) {
return JsonUtils.toJsonString(obj);
}
}
@@ -1,6 +1,7 @@
package co.yixiang.yshop.module.store.service.storeshop;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.DeliveryFeeRespVO;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import com.baomidou.mybatisplus.extension.service.IService;
@@ -22,4 +23,13 @@ public interface AppStoreShopService extends IService<StoreShopDO> {
*/
List<AppStoreShopVO> getStoreList(double lon,double lat,String name,Integer shopId);
/**
* 预估配送费
* @param shopId 门店id
* @param lng 收货地址经度
* @param lat 收货地址纬度
* @return 配送费及是否超出配送范围
*/
DeliveryFeeRespVO getDeliveryFee(Long shopId, double lng, double lat);
}
@@ -1,6 +1,7 @@
package co.yixiang.yshop.module.store.service.storeshop;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;
import co.yixiang.yshop.module.store.controller.app.storeshop.vo.DeliveryFeeRespVO;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -8,6 +9,7 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -34,4 +36,24 @@ public class AppStoreShopServiceImpl extends ServiceImpl<StoreShopMapper, StoreS
public List<AppStoreShopVO> getStoreList(double lon, double lat,String name,Integer shopId) {
return shopMapper.getStoreList(lon,lat,name,shopId);
}
@Override
public DeliveryFeeRespVO getDeliveryFee(Long shopId, double lng, double lat) {
StoreShopDO shop = getById(shopId);
DeliveryFeeRespVO vo = new DeliveryFeeRespVO();
vo.setOutOfRange(true);
vo.setDeliveryPrice(BigDecimal.ZERO);
if (shop == null) {
return vo;
}
double distanceKm = DeliveryCalculator.calcDistanceKm(
shop.getLng(), shop.getLat(), String.valueOf(lng), String.valueOf(lat));
BigDecimal price = DeliveryCalculator.calcDeliveryPrice(shop, distanceKm);
vo.setDistanceKm(distanceKm);
if (price != null) {
vo.setOutOfRange(false);
vo.setDeliveryPrice(price);
}
return vo;
}
}
@@ -0,0 +1,86 @@
package co.yixiang.yshop.module.store.service.storeshop;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.DeliveryRule;
import co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;
import java.math.BigDecimal;
import java.util.List;
/**
* 配送费计算工具
* <p>
* 1. {@link #calcDistanceKm} 使用 Haversine 公式计算两个经纬度之间的球面距离(千米),
* 与 StoreShopMapper.getStoreList 中的 SQL 计算口径一致(地球半径 6378.138km)。
* 2. {@link #calcDeliveryPrice} 根据距离匹配门店的配送规则(左闭右开区间),
* 无匹配时回退 deliveryPrice(兜底);超出配送范围返回 null。
*
* @author yshop
*/
public class DeliveryCalculator {
/** 地球半径(千米),与 Mapper SQL 保持一致 */
private static final double EARTH_RADIUS_KM = 6378.138;
private DeliveryCalculator() {
}
/**
* 计算两个经纬度之间的球面距离(千米)
*
* @param lng1 起点-经度
* @param lat1 起点-纬度
* @param lng2 终点-经度
* @param lat2 终点-纬度
* @return 距离(千米)
*/
public static double calcDistanceKm(String lng1, String lat1, String lng2, String lat2) {
double lon1 = Double.parseDouble(lng1);
double latt1 = Double.parseDouble(lat1);
double lon2 = Double.parseDouble(lng2);
double latt2 = Double.parseDouble(lat2);
double radLat1 = Math.toRadians(latt1);
double radLat2 = Math.toRadians(latt2);
double a = radLat1 - radLat2;
double b = Math.toRadians(lon1) - Math.toRadians(lon2);
double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
distance = distance * EARTH_RADIUS_KM;
return Math.round(distance * 1000.0) / 1000.0;
}
/**
* 根据距离匹配配送规则计算配送费
*
* @param shop 门店
* @param distanceKm 收货地址到门店的距离(千米)
* @return 配送费;返回 null 表示不在配送范围内(不可配送)
*/
public static BigDecimal calcDeliveryPrice(StoreShopDO shop, double distanceKm) {
// distance<=0 表示不送外卖
Integer maxDistance = shop.getDistance();
if (maxDistance == null || maxDistance <= 0) {
return null;
}
// 超出配送范围
if (distanceKm > maxDistance) {
return null;
}
// 匹配阶梯规则(左闭右开)
List<DeliveryRule> rules = shop.getDeliveryRules();
if (rules != null && !rules.isEmpty()) {
for (DeliveryRule rule : rules) {
if (rule.getMinKm() == null || rule.getMaxKm() == null || rule.getPrice() == null) {
continue;
}
double min = rule.getMinKm().doubleValue();
double max = rule.getMaxKm().doubleValue();
if (distanceKm >= min && distanceKm < max) {
return rule.getPrice();
}
}
}
// 无规则匹配,回退默认配送费
return shop.getDeliveryPrice();
}
}
+7
View File
@@ -13,3 +13,10 @@ export function menuGoods(data) {
return api.get('/product/products', data, { login: false })
}
/**
* 预估配送费(根据收货地址距离匹配配送规则)
*/
export function getDeliveryFee(data) {
return api.get('/store/delivery-fee', data, { login: false })
}
+3 -3
View File
@@ -1,5 +1,5 @@
const DEV_API_URL = 'http://localhost:48081/app-api'
const PROD_API_URL = 'https://apidc.yixiang.co/app-api'
const DEV_API_URL = 'http://maitre.imomoe.cn/api/app-api'
const PROD_API_URL = 'https://maitre.imomoe.cn/api/app-api'
// #ifdef APP-PLUS || H5 || MP-WEIXIN
export const VUE_APP_API_URL =
@@ -8,7 +8,7 @@ export const VUE_APP_API_URL =
export const VUE_APP_RESOURCES_URL = 'https://h5.yixiang.co/static'
export const VUE_APP_UPLOAD_URL = VUE_APP_API_URL + '/infra/file/upload'
export const APP_ID = 'wxdbdbc123c8c30b45'
export const APP_ID = 'wx3548436813df3f60'
const orderListStatus = {}
+12 -12
View File
@@ -431,18 +431,18 @@
"router" : {
"base" : "/h5/"
},
"sdkConfigs" : {
// 使用地图或位置相关功能必须填写其一
"maps" : {
"bmap" : {
// 百度地图秘钥(HBuilderX 3.99+)http://lbsyun.baidu.com/apiconsole/key#/home
"key" : ""
},
"qqmap" : {
"key" : "OGABZ-Y5OCF-5UWJ5-N7DHH-VFIG7-DHFEB"
}
}
}
"sdkConfigs" : {
// 使用地图或位置相关功能必须填写其一
"maps" : {
"bmap" : {
// 百度地图秘钥(HBuilderX 3.99+)http://lbsyun.baidu.com/apiconsole/key#/home
"key" : ""
},
"qqmap" : {
"key" : "OGABZ-Y5OCF-5UWJ5-N7DHH-VFIG7-DHFEB"
}
}
}
}
}
/* 模块配置 *//* 应用发布信息 *//* android打包配置 */
@@ -125,7 +125,7 @@
<view class="flex-fill d-flex justify-content-end align-items-center">
<view>
总计¥{{ total }}
<text v-if="orderType == 'takeout'">,配送费¥{{ store.deliveryPrice }}</text>
<text v-if="orderType == 'takeout'">,配送费¥{{ deliveryFee }}</text>
<text v-if="coupon.value">,¥-{{ coupon.value }}</text>
,实付
</view>
@@ -219,6 +219,7 @@
import {
ref,
computed,
watch,
nextTick
} from 'vue'
import { useMainStore } from '@/store/store'
@@ -235,12 +236,15 @@ import {
import {
couponCount
} from '@/api/coupon'
import { getDeliveryFee } from '@/api/goods'
// #ifdef H5
import * as jweixin from 'weixin-js-sdk'
// #endif
const main = useMainStore()
const { orderType,address, store,location,isLogin,member,mycoupon } = storeToRefs(main)
const active = ref(false)
const deliveryFee = ref(0)
const outOfRange = ref(false)
const title = ref('支付')
const jsStr = ref('')
const cart = ref([])
@@ -302,8 +306,8 @@ const total = computed(() =>{
const amount = computed(() =>{
let amount = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);
// 加配送费
if (store.value.distance > 0 && orderType.value == 'takeout') {
amount += parseFloat(store.value.deliveryPrice);
if (orderType.value == 'takeout') {
amount += deliveryFee.value;
}
@@ -317,6 +321,33 @@ const amount = computed(() =>{
return amount.toFixed(2);
})
// 选地址/切换外卖后预估配送费(基于收货地址距离匹配配送规则)
watch([address, store, orderType], async () => {
if (orderType.value !== 'takeout' || !store.value.id) {
deliveryFee.value = 0
outOfRange.value = false
return
}
// 未选地址时显示默认配送费
if (!address.value || !address.value.longitude) {
deliveryFee.value = parseFloat(store.value.deliveryPrice) || 0
outOfRange.value = false
return
}
try {
const res = await getDeliveryFee({
shopId: store.value.id,
lng: address.value.longitude,
lat: address.value.latitude
})
outOfRange.value = !!res.outOfRange
deliveryFee.value = parseFloat(res.deliveryPrice) || 0
} catch (e) {
deliveryFee.value = parseFloat(store.value.deliveryPrice) || 0
outOfRange.value = false
}
}, { immediate: true })
onShow(() => {
coupon.value = main.mycoupon
let date = new Date(new Date().getTime() + 3600000); // 一个小时后
@@ -494,6 +525,11 @@ const submit = () => {
}
}
const pay = async() => {
// 外卖时校验收货地址是否在配送范围
if (orderType.value == 'takeout' && outOfRange.value) {
uToast.value.show({message:'当前收货地址不在配送范围内',type: 'error'});
return;
}
let that = this;
// // #ifdef MP-WEIXIN
// await new Promise(function(revolve) {
@@ -14,7 +14,7 @@
<view class="shop-page__body">
<view class="shop-page__info">
<view>距离您 {{kmUnit(item.dis)}}</view>
<view v-if="item.distance > 0">配送距离:{{item.distance + 'km '}} & 配送费:{{item.delivery_price}}</view>
<view v-if="item.distance > 0">配送距离:{{item.distance + 'km '}} & 配送费:{{deliveryRuleText(item)}}</view>
<view v-else>外卖不配送</view>
<view>{{item.addressMap + ' ' + item.address}}</view>
<view>营业时间 {{formatDateTime(item.startTime,'hh:mm')}} - {{formatDateTime(item.endTime,'hh:mm')}}</view>
@@ -74,6 +74,20 @@ const getShop = async(keywork = '') => {
}
}
}
// 配送规则摘要文本
const deliveryRuleText = (item) => {
let rules = []
try {
rules = typeof item.deliveryRules === 'string' && item.deliveryRules
? JSON.parse(item.deliveryRules) : (item.deliveryRules || [])
} catch (e) {
rules = []
}
if (rules && rules.length) {
return rules.map(r => r.minKm + '-' + r.maxKm + 'km ¥' + r.price).join(' / ')
}
return '¥' + (item.deliveryPrice || 0)
}
//打开定位
const openLocation = (shop) => {
//console.log(shop);
+4 -4
View File
@@ -366,12 +366,12 @@ const getCartGoodsPrice = computed(() =>{ //计算购物车总价
return parseFloat(price).toFixed(2);
})
const disabledPay = computed(() => { //是否达到起送价
return orderType.value == 'takeout' && (getCartGoodsPrice < parseFloat(store.value.min_price)) ? true :
return orderType.value == 'takeout' && (getCartGoodsPrice < parseFloat(store.value.minPrice)) ? true :
false
})
const spread = computed(() => { //差多少元起送
if (orderType.value != 'takeout') return
return parseFloat((store.value.min_price - getCartGoodsPrice).toFixed(2))
return parseFloat((store.value.minPrice - getCartGoodsPrice).toFixed(2))
})
// 监听自定义事件
@@ -750,8 +750,8 @@ const toPay = () => {
uToast.value.show({message:'本店不支持外卖',type: 'error'});
return;
}
// 判断当前是否在配送范围内
if (orderType.value == 'takeout' && store.value.distance < store.value.far) {
// 判断当前是否在配送范围内(基于用户定位距离粗略判断,最终以下单时收货地址校验为准)
if (orderType.value == 'takeout' && store.value.dis && store.value.distance < (store.value.dis / 1000)) {
uToast.value.show({message:'选中的地址不在配送范围',type: 'error'});
return;
}
@@ -1,5 +1,11 @@
import request from '@/config/axios'
export interface DeliveryRule {
minKm: number
maxKm: number
price: number
}
export interface ShopVO {
id: number
name: string
@@ -13,6 +19,7 @@ export interface ShopVO {
distance: number
minPrice: number
deliveryPrice: number
deliveryRules: DeliveryRule[]
notice: string
status: boolean
adminId: string
@@ -46,8 +46,23 @@
<el-form-item label="起送价钱" prop="minPrice">
<el-input v-model="formData.minPrice" placeholder="请输入起送价钱" />
</el-form-item>
<el-form-item label="配送价格" prop="deliveryPrice">
<el-input v-model="formData.deliveryPrice" placeholder="请输入配送价格" />
<el-form-item label="默认配送费" prop="deliveryPrice">
<el-input v-model="formData.deliveryPrice" placeholder="未配置规则或无匹配时的兜底配送费" />
</el-form-item>
<el-form-item label="配送规则">
<div style="width: 100%;">
<div v-for="(rule, index) in formData.deliveryRules" :key="index" class="delivery-rule-row">
<el-input v-model="rule.minKm" placeholder="最小km" style="width: 110px;" />
<span class="delivery-rule-sep">~</span>
<el-input v-model="rule.maxKm" placeholder="最大km" style="width: 110px;" />
<span class="delivery-rule-unit">km</span>
<el-input v-model="rule.price" placeholder="配送费" style="width: 110px;" />
<span class="delivery-rule-unit">元</span>
<el-button type="danger" link @click="removeRule(index)">删除</el-button>
</div>
<el-button type="primary" link @click="addRule">+ 添加规则</el-button>
<div class="delivery-rule-tip">区间左闭右开,如 1~3km 含 1 不含 3;不配置规则时使用默认配送费</div>
</div>
</el-form-item>
<el-form-item label="公告" prop="notice">
<el-input type="textarea" rows="5" v-model="formData.notice" placeholder="请输入公告" />
@@ -111,6 +126,7 @@ const formData = ref({
distance: undefined,
minPrice: undefined,
deliveryPrice: undefined,
deliveryRules: [],
notice: undefined,
status: undefined,
adminId: undefined,
@@ -159,6 +175,7 @@ const open = async (type: string, id?: number) => {
try {
formData.value = await ShopApi.getShop(id)
formData.value.adminId = formData.value.adminId.map(Number)
formData.value.deliveryRules = formData.value.deliveryRules || []
} finally {
formLoading.value = false
}
@@ -197,6 +214,19 @@ const openForm = () => {
formRef1.value.open()
}
/** 新增一条配送规则 */
const addRule = () => {
if (!formData.value.deliveryRules) {
formData.value.deliveryRules = []
}
formData.value.deliveryRules.push({ minKm: undefined, maxKm: undefined, price: undefined })
}
/** 删除一条配送规则 */
const removeRule = (index: number) => {
formData.value.deliveryRules.splice(index, 1)
}
const mapConfirm = (mapResult) => {
console.log('mapResult:',mapResult)
formData.value.addressMap = mapResult.address
@@ -233,6 +263,7 @@ const resetForm = () => {
distance: undefined,
minPrice: undefined,
deliveryPrice: undefined,
deliveryRules: [],
notice: undefined,
status: 1,
adminId: undefined,
@@ -248,4 +279,19 @@ const resetForm = () => {
width: 100%;
height: 300px;
}
.delivery-rule-row {
display: flex;
align-items: center;
margin-bottom: 8px;
gap: 6px;
}
.delivery-rule-sep,
.delivery-rule-unit {
color: #606266;
}
.delivery-rule-tip {
color: #999;
font-size: 12px;
margin-top: 4px;
}
</style>