gzy 4 months ago
commit acf3fb0f66

@ -4,14 +4,20 @@ import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.qccode.Base64Util;
import cn.iocoder.yudao.framework.common.util.qccode.QRCodeUtil;
import cn.iocoder.yudao.framework.common.util.qccode.vo.QRCodeConfig;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import io.swagger.v3.oas.annotations.Operation;
@ -30,6 +36,7 @@ import javax.validation.Valid;
import java.io.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -46,6 +53,12 @@ public class AppFileController {
@Resource
private DictDataService dictDataService;
@Resource
private MemberUserApi memberUserApi;
@Resource
private MemberLevelApi memberLevelApi;
@PostMapping("/upload")
@Operation(summary = "上传文件")
public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
@ -87,6 +100,17 @@ public class AppFileController {
@RequestMapping("/qrCode64")
@Operation(summary = "生成二维码64")
public CommonResult<String> qrCode64(@Valid String type) {
//查询会员当前等级
MemberUserRespDTO user = memberUserApi.getUser(WebFrameworkUtils.getLoginUserId());
// MemberLevelRespDTO user = memberLevelApi.getMemberLevel();
if(ObjectUtil.isEmpty(user.getLevelId())){
return error(1_013_018_010,"无权推广");
}else{
MemberLevelRespDTO memberLevelDO1 = memberLevelApi.getMemberLevel(user.getLevelId());
if(ObjectUtil.isEmpty(memberLevelDO1)){
return error(1_013_018_010,"无权推广");
}
}
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 假设QRCodeUtil.createQrCode是一个正确实现的方法用于生成二维码并写入到ByteArrayOutputStream中

@ -37,4 +37,12 @@ public interface TradeOrderApi {
*/
void cancelPaidOrder(Long userId, Long orderId);
// TODO 芋艿:需要优化下;
/**
*
*
* @param spuId
*/
Integer getOrderCountBySpuId(Long spuId);
}

@ -40,4 +40,9 @@ public class TradeOrderApiImpl implements TradeOrderApi {
tradeOrderUpdateService.cancelPaidOrder(userId, orderId);
}
@Override
public Integer getOrderCountBySpuId(Long spuId) {
return tradeOrderUpdateService.getOrderCountBySpuId(spuId);
}
}

@ -1,9 +1,24 @@
package cn.iocoder.yudao.module.trade.controller.app.cart;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.member.api.distributionconfig.DistributionConfigApi;
import cn.iocoder.yudao.module.member.api.distributionconfig.vo.DistributionConfigRespDTO;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.cart.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.service.cart.CartService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -16,6 +31,7 @@ import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -30,10 +46,61 @@ public class AppCartController {
@Resource
private CartService cartService;
@Resource
private DistributionConfigApi distributionConfigApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private MemberLevelApi memberLevelApi;
@Resource
private MemberUserApi memberUserApi;
@Resource
private TradeOrderUpdateService tradeOrderUpdateService;
@PostMapping("/add")
@Operation(summary = "添加购物车商品")
@PreAuthenticated
public CommonResult<Long> addCart(@Valid @RequestBody AppCartAddReqVO addCountReqVO) {
//获取用户id
Long loginUserId = WebFrameworkUtils.getLoginUserId();
//判断商品是否需要会员等级
//判断订单是否已支付
//根据商品id查询配置规则
if(ObjectUtil.isNotEmpty(addCountReqVO.getSkuId())){
//获取商品spuId
ProductSkuRespDTO sku = productSkuApi.getSku(addCountReqVO.getSkuId());
if(ObjectUtil.isNotEmpty(sku)){
ProductSpuRespDTO spu = productSpuApi.getSpu(sku.getSpuId());
if(ObjectUtil.isNotEmpty(spu)){
List<DistributionConfigRespDTO> distributionConfigDOS = distributionConfigApi.distributionConfigDOS(sku.getSpuId());
if(ObjectUtil.isNotEmpty(distributionConfigDOS)){
DistributionConfigRespDTO distributionConfigDO = distributionConfigDOS.get(0);
if(ObjectUtil.isNotEmpty(distributionConfigDO.getBuyerLevelId())){
//查询会员当前等级
MemberLevelRespDTO memberLevelDO = memberLevelApi.getMemberLevel(distributionConfigDO.getBuyerLevelId());
//查询会员当前等级
MemberUserRespDTO user = memberUserApi.getUser(loginUserId);
MemberLevelRespDTO memberLevelDO1 = memberLevelApi.getMemberLevel(user.getLevelId());
if(memberLevelDO.getLevel()>memberLevelDO1.getLevel()){
return error(1_013_018_008,"商品"+spu.getName()+"购买需要升级至会员等级"+memberLevelDO.getName());
}
Integer orderCountBySpuId = tradeOrderUpdateService.getOrderCountBySpuId(sku.getSpuId());
if(ObjectUtil.isNotEmpty(distributionConfigDO.getPurchaseNum())){
if(orderCountBySpuId>=distributionConfigDO.getPurchaseNum()){
return error(1_013_018_009,"商品"+spu.getName()+"购买已达上限");
}
}
}
}
}
}
}
return success(cartService.addCart(getLoginUserId(), addCountReqVO));
}

@ -1,11 +1,25 @@
package cn.iocoder.yudao.module.trade.controller.app.order;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.member.api.distributionconfig.DistributionConfigApi;
import cn.iocoder.yudao.module.member.api.distributionconfig.vo.DistributionConfigRespDTO;
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO;
@ -33,10 +47,13 @@ import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING;
@Tag(name = "用户 App - 交易订单")
@RestController
@ -61,10 +78,57 @@ public class AppTradeOrderController {
@Resource
private MemberUserApi memberUserApi;
@Resource
private DistributionConfigApi distributionConfigApi;
@Resource
private ProductSpuApi productSpuApi;
@Resource
private ProductSkuApi productSkuApi;
@Resource
private MemberLevelApi memberLevelApi;
@GetMapping("/settlement")
@Operation(summary = "获得订单结算信息")
@PreAuthenticated
public CommonResult<AppTradeOrderSettlementRespVO> settlementOrder(@Valid AppTradeOrderSettlementReqVO settlementReqVO) {
//获取用户id
Long loginUserId = WebFrameworkUtils.getLoginUserId();
//判断商品是否需要会员等级
//判断订单是否已支付
//根据商品id查询配置规则
if(ObjectUtil.isNotEmpty(settlementReqVO.getItems())){
for (AppTradeOrderSettlementReqVO.Item item :settlementReqVO.getItems()) {
//获取商品spuId
ProductSkuRespDTO sku = productSkuApi.getSku(item.getSkuId());
if(ObjectUtil.isNotEmpty(sku)){
ProductSpuRespDTO spu = productSpuApi.getSpu(sku.getSpuId());
if(ObjectUtil.isNotEmpty(spu)){
List<DistributionConfigRespDTO> distributionConfigDOS = distributionConfigApi.distributionConfigDOS(sku.getSpuId());
if(ObjectUtil.isNotEmpty(distributionConfigDOS)){
DistributionConfigRespDTO distributionConfigDO = distributionConfigDOS.get(0);
if(ObjectUtil.isNotEmpty(distributionConfigDO.getBuyerLevelId())){
//查询会员当前等级
MemberLevelRespDTO memberLevelDO = memberLevelApi.getMemberLevel(distributionConfigDO.getBuyerLevelId());
//查询会员当前等级
MemberUserRespDTO user = memberUserApi.getUser(loginUserId);
MemberLevelRespDTO memberLevelDO1 = memberLevelApi.getMemberLevel(user.getLevelId());
if(memberLevelDO.getLevel()>memberLevelDO1.getLevel()){
return error(1_013_018_008,"商品"+spu.getName()+"购买需要升级至会员等级"+memberLevelDO.getName());
}
Integer orderCountBySpuId = tradeOrderUpdateService.getOrderCountBySpuId(sku.getSpuId());
if(ObjectUtil.isNotEmpty(distributionConfigDO.getPurchaseNum())){
if(orderCountBySpuId>=distributionConfigDO.getPurchaseNum()){
return error(1_013_018_009,"商品"+spu.getName()+"购买已达上限");
}
}
}
}
}
}
}
}
return success(tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO));
}

@ -9,6 +9,8 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderPageRe
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.List;
@ -127,4 +129,20 @@ public interface TradeOrderMapper extends BaseMapperX<TradeOrderDO> {
default String getPickUpVerifyCode(String id){
return selectOne(TradeOrderDO::getId, id).getPickUpVerifyCode();
};
/**
*
*
* @param spuId
*/
@Select("SELECT\n" +
"\tcount( 1 ) \n" +
"FROM\n" +
"\ttrade_order o\n" +
"\tLEFT JOIN trade_order_item t ON o.id = t.order_id \n" +
"WHERE\n" +
"\to.pay_status = 1 \n" +
"\tAND t.spu_id = #{spuId} \n" +
"\tAND t.user_id = #{userId}")
Integer getOrderCountBySpuId(@Param("spuId") Long spuId, @Param("userId") Long userId);
}

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
@ -197,4 +196,11 @@ public interface TradeOrderUpdateService {
*/
void cancelPaidOrder(Long userId, Long orderId);
/**
*
*
* @param spuId
*/
Integer getOrderCountBySpuId(Long spuId);
}

@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
@ -857,6 +858,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
}
@Override
public Integer getOrderCountBySpuId(Long spuId) {
return tradeOrderMapper.getOrderCountBySpuId(spuId, WebFrameworkUtils.getLoginUserId());
}
/**
*
*

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.member.api.distributionconfig;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.distributionconfig.vo.DistributionConfigRespDTO;
import javax.validation.Valid;
import java.util.List;
/**
* Service
*
* @author
*/
public interface DistributionConfigApi {
List<DistributionConfigRespDTO> distributionConfigDOS(Long spuId);
}

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.member.api.distributionconfig.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class DistributionConfigRespDTO {
private Long id;
private Long productId;
private Long levelId;
private String status;
private LocalDateTime createTime;
private String levelName;
private String supName;
/**
*
*/
private Long buyerLevelId;
/**
*
*/
private Integer purchaseNum;
}

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.member.api.distributionconfig;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.api.distributionconfig.vo.DistributionConfigRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.distributionconfig.vo.DistributionConfigPageReqVO;
import cn.iocoder.yudao.module.member.controller.admin.distributionconfig.vo.DistributionConfigRespVO;
import cn.iocoder.yudao.module.member.controller.admin.distributionconfig.vo.DistributionConfigSaveReqVO;
import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.distributionconfig.DistributionConfigDO;
import cn.iocoder.yudao.module.member.dal.dataobject.distributionconfig.DistributionMebConfigDO;
import cn.iocoder.yudao.module.member.dal.mysql.distributionconfig.DistributionConfigMapper;
import cn.iocoder.yudao.module.member.dal.mysql.distributionconfig.DistributionMebConfigMapper;
import cn.iocoder.yudao.module.member.service.distributionconfig.DistributionConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.DISTRIBUTION_CONFIG_NOT_EXISTS;
/**
* Service
*
* @author
*/
@Service
@Validated
public class DistributionConfigApiImpl implements DistributionConfigApi {
@Resource
private DistributionConfigMapper distributionConfigMapper;
@Resource
private DistributionMebConfigMapper distributionMebConfigMapper;
@Override
public List<DistributionConfigRespDTO> distributionConfigDOS(Long spuId) {
List<DistributionConfigDO> distributionConfigDOS = distributionConfigMapper.selectList(new LambdaQueryWrapperX<DistributionConfigDO>()
.eq(DistributionConfigDO::getProductId, spuId)
.eq(DistributionConfigDO::getDeleted, 0)
.eq(DistributionConfigDO::getStatus, 0)
.orderByDesc(DistributionConfigDO::getCreateTime));
return BeanUtils.toBean(distributionConfigDOS, DistributionConfigRespDTO.class);
}
}

@ -27,4 +27,15 @@ public class DistributionConfigPageReqVO extends PageParam {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
/**
*
*/
@Schema(description = "购买人等级")
private Long buyerLevelId;
/**
*
*/
@Schema(description = "限购次数", example = "1")
private Integer purchaseNum;
}

@ -40,4 +40,15 @@ public class DistributionConfigRespVO {
@ExcelProperty("商品名称")
private String supName;
/**
*
*/
@Schema(description = "购买人等级")
private Long buyerLevelId;
/**
*
*/
@Schema(description = "限购次数", example = "1")
private Integer purchaseNum;
}

@ -25,4 +25,15 @@ public class DistributionConfigSaveReqVO {
@Schema(description = "分销配置关联会员等级列表")
private List<DistributionMebConfigDO> distributionMebConfigs;
/**
*
*/
@Schema(description = "购买人等级")
private Long buyerLevelId;
/**
*
*/
@Schema(description = "限购次数", example = "1")
private Integer purchaseNum;
}

@ -39,5 +39,13 @@ public class DistributionConfigDO extends BaseDO {
*
*/
private Long levelId;
/**
*
*/
private Long buyerLevelId;
/**
*
*/
private Integer purchaseNum;
}

@ -39,5 +39,9 @@ public class DistributionMebConfigDO extends BaseDO {
*
*/
private Integer brokerage;
/**
*
*/
private Integer repeatStatus;
}

@ -485,6 +485,13 @@ public class MemberUserServiceImpl implements MemberUserService {
if(ObjectUtil.isNotEmpty(distributionMebConfigDOS)){
for (DistributionMebConfigDO eg :distributionMebConfigDOS) {
if(eg.getLevelId() == memberUserDO.getLevelId()){
//判断商品购买次数
Integer orderCountBySpuId = tradeOrderApi.getOrderCountBySpuId(productId);
if(ObjectUtil.isNotEmpty(eg.getRepeatStatus())){
if(orderCountBySpuId>eg.getRepeatStatus()){
continue;
}
}
//添加佣金记录
BrokerageRecordDO brokerageRecordDO = new BrokerageRecordDO();
// brokerageRecordDO.setId(Math.toIntExact(brokerageUserDO.getBindUserId()));

@ -28,6 +28,16 @@
<artifactId>yudao-module-member-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-trade-biz</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-product-biz</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>

@ -32,6 +32,13 @@ import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -79,6 +86,15 @@ public class PayOrderServiceImpl implements PayOrderService {
@Resource
private MemberUserApi memberUserApi;
@Resource
private TradeOrderItemMapper tradeOrderItemMapper;
@Resource
private ProductSkuMapper productSkuMapper;
@Resource
private ProductSpuMapper productSpuMapper;
@Override
public PayOrderDO getOrder(Long id) {
return orderMapper.selectById(id);
@ -275,6 +291,13 @@ public class PayOrderServiceImpl implements PayOrderService {
PayOrderExtensionDO orderExtension = updateOrderSuccess(notify);
PayOrderDO payOrderDO = orderMapper.selectPayOrderDOById(orderExtension.getOrderId());
memberUserApi.updateUserUpgradesLevelcallback(Long.valueOf(payOrderDO.getMerchantOrderId()));
//更新商品销量
//查询商品订单信息
List<TradeOrderItemDO> tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderId(Long.valueOf(payOrderDO.getMerchantOrderId()));
if(ObjectUtil.isNotEmpty(tradeOrderItemDOS)){
productSkuMapper.update(new UpdateWrapper<ProductSkuDO>().lambda().eq(ProductSkuDO::getId,tradeOrderItemDOS.get(0).getSkuId()).setSql("sales_count = sales_count + 1"));
productSpuMapper.update(new UpdateWrapper<ProductSpuDO>().lambda().eq(ProductSpuDO::getId,tradeOrderItemDOS.get(0).getSpuId()).setSql("sales_count = sales_count + 1"));
}
return;
}
// 情况二:支付失败的回调

@ -8,6 +8,10 @@ spring:
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
# - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置
- de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
- de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
@ -29,42 +33,64 @@ spring:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 5 # 初始连接数
min-idle: 10 # 最小连接池数量
initial-size: 1 # 初始连接数
min-idle: 1 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 # 配置检测连接是否有效
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
name: zdsc
url: jdbc:mysql://221.176.140.236:23307/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
# url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
username: root
password: 123456
slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
password: hmkj@2023
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# username: SYSDBA # DM 连接的示例
# password: SYSDBA # DM 连接的示例
slave: # 模拟从库,可根据自己需要修改
name: zdsc
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://221.176.140.236:23307/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
username: root
password: 123456
password: hmkj@2023
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
# redis:
# host: 127.0.0.1 # 地址
# port: 6379 # 端口
# database: 0 # 数据库索引
# password: # 密码,建议生产环境开启
redis:
host: 400-infra.server.iocoder.cn # 地址
port: 6379 # 端口
database: 1 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
host: 120.46.159.203 # 地址
port: 16379 # 端口
database: 0 # 数据库索引
password: a8EYUSoT8wHbuRkX # 密码,建议生产环境开启
--- #################### 定时任务相关配置 ####################
# Quartz 配置项,对应 QuartzProperties 配置类
spring:
quartz:
auto-startup: true # 测试环境,需要开启 Job
auto-startup: true # 本地开发环境,尽量不要开启 Job
scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName
job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
@ -99,10 +125,11 @@ rocketmq:
spring:
# RabbitMQ 配置项,对应 RabbitProperties 配置类
rabbitmq:
host: 127.0.0.1 # RabbitMQ 服务的地址
host: 192.168.10.14 # RabbitMQ 服务的地址
port: 5672 # RabbitMQ 服务的端口
username: guest # RabbitMQ 服务的账号
password: guest # RabbitMQ 服务的密码
virtual-host: /
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
@ -140,21 +167,51 @@ spring:
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
level:
# 配置自己写的 MyBatis Mapper 打印日志
cn.iocoder.yudao.module.bpm.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info
cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info
cn.iocoder.yudao.module.pay.dal.mysql: debug
cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 JobLogMapper 的日志级别为 info
cn.iocoder.yudao.module.system.dal.mysql: debug
cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper: INFO # 配置 SensitiveWordMapper 的日志级别为 info
cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info
cn.iocoder.yudao.module.tool.dal.mysql: debug
cn.iocoder.yudao.module.member.dal.mysql: debug
cn.iocoder.yudao.module.trade.dal.mysql: debug
cn.iocoder.yudao.module.promotion.dal.mysql: debug
cn.iocoder.yudao.module.statistics.dal.mysql: debug
cn.iocoder.yudao.module.crm.dal.mysql: debug
cn.iocoder.yudao.module.erp.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
--- #################### 微信公众号相关配置 ####################
wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
mp:
# 公众号配置(必填)
app-id: wx041349c6f39b268b
secret: 5abee519483bc9f8cb37ce280e814bd0
debug: false
--- #################### 微信公众号、小程序相关配置 ####################
wx:
mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
# app-id: wx041349c6f39b268b # 测试号(牛希尧提供的)
# secret: 5abee519483bc9f8cb37ce280e814bd0
app-id: wx5b23ba7a5589ecbb # 测试号(自己的)
secret: 2a7b3b20c537e52e74afd395eb85f61f
# app-id: wxa69ab825b163be19 # 测试号Kongdy 提供的)
# secret: bd4f9fab889591b62aeac0d7b8d8b4a0
# 存储配置,解决 AccessToken 的跨节点的共享
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wx # Redis Key 的前缀
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
appid: wx63c280fe3248a3e7
secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的)
# secret: 333ae72f41552af1e998fe1f54e1584a
appid: wx879300e525dc9847
secret: 4b61f8af2eba7c71b0be50fa46855377
#appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
#secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wxc4598c446f8a9cb3 # 测试号Kongdy 提供的)
# secret: 4a1a04e07f6a4a0751b39c3064a92c8b
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wa # Redis Key 的前缀
@ -164,15 +221,23 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
captcha:
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
security:
mock-enable: true
xss:
enable: false
exclude-urls: # 如下两个 url仅仅是为了演示去掉配置也没关系
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
pay:
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
demo: true # 开启演示模式
order-notify-url: https://zd.huamar.com/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: https://zd.huamar.com/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
access-log: # 访问日志的配置项
enable: false
error-code: # 错误码相关配置项
enable: false
demo: false # 关闭演示模式
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
justauth:
@ -187,7 +252,6 @@ justauth:
client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
agent-id: 1000004
ignore-check-redirect-uri: true
# noinspection SpringBootApplicationYaml
WECHAT_MINI_APP: # 微信小程序
client-id: ${wx.miniapp.appid}
client-secret: ${wx.miniapp.secret}
@ -200,4 +264,10 @@ justauth:
cache:
type: REDIS
prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
io:
port: 7070
host: 127.0.0.1
xor:
massage: 151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515
type: mobile

@ -48,14 +48,14 @@ spring:
datasource:
master:
name: zdsc
url: jdbc:mysql://192.168.10.14:23306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://47.94.137.109:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
# url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
username: root
password: hmkj@2023
password: flAYMJUPCCXDMGFM4KX6
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# username: SYSDBA # DM 连接的示例
@ -63,22 +63,27 @@ spring:
slave: # 模拟从库,可根据自己需要修改
name: zdsc
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://192.168.10.14:23306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://47.94.137.109:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
username: root
password: hmkj@2023
password: flAYMJUPCCXDMGFM4KX6
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
# redis:
# host: 127.0.0.1 # 地址
# port: 6379 # 端口
# database: 0 # 数据库索引
# password: # 密码,建议生产环境开启
redis:
host: 192.168.10.14 # 地址
port: 26379 # 端口
host: 47.94.137.109 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
password: hmkj@2023 # 密码,建议生产环境开启
# password: a8EYUSoT8wHbuRkX # 密码,建议生产环境开启
--- #################### 定时任务相关配置 ####################
@ -201,8 +206,8 @@ wx:
miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
# appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的)
# secret: 333ae72f41552af1e998fe1f54e1584a
appid: wx53b5ac263b9cdb5b
secret: baa127ca662344cf9b0b8e52b26bac9d
appid: wx879300e525dc9847
secret: 4b61f8af2eba7c71b0be50fa46855377
#appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
#secret: 6f270509224a7ae1296bbf1c8cb97aed
# appid: wxc4598c446f8a9cb3 # 测试号Kongdy 提供的)
@ -226,8 +231,8 @@ yudao:
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
pay:
order-notify-url: https://yanghaodong.51vip.biz/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: https://yanghaodong.51vip.biz/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
order-notify-url: https://zd.huamar.com/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: https://zd.huamar.com/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
access-log: # 访问日志的配置项
enable: false
error-code: # 错误码相关配置项
@ -265,4 +270,4 @@ io:
host: 127.0.0.1
xor:
massage: 151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515
type: mobile
type: mobile

@ -3,6 +3,7 @@ spring:
name: yudao-server
profiles:
# active: dev
active: local
main:
@ -272,4 +273,4 @@ debug: false
# 积木报表配置
jeecg:
jmreport:
saas-mode: tenant
saas-mode: tenant

@ -0,0 +1,21 @@
# 版本号
SHOPRO_VERSION = v1.8.3
# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development
SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development
# SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080// 测试环境变量问题
### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc
# 后端接口前缀(一般不建议调整)
SHOPRO_API_PATH = /app-api
# 开发环境运行端口
SHOPRO_DEV_PORT = 3000
# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
SHOPRO_STATIC_URL = https://file.sheepjs.com
# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
SHOPRO_MPLIVE_ON = 0

@ -5,7 +5,6 @@ deploy.sh
.hbuilderx/
.vscode/
**/.DS_Store
.env
yarn.lock
package-lock.json
*.keystore

@ -4,12 +4,18 @@ NODE_ENV=production
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://zd.huamar.com'
VITE_BASE_URL='http://localhost:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://zd.huamar.com/admin-api/infra/file/upload'
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
# VITE_BASE_URL='http://zd.huamar.com'
# # 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
# VITE_UPLOAD_TYPE=server
# # 上传路径
# VITE_UPLOAD_URL='http://zd.huamar.com/admin-api/infra/file/upload'
# 接口地址
VITE_API_URL=/admin-api

@ -68,10 +68,10 @@ const toDocument = () => {
<Icon icon="ep:tools" />
<div @click="toProfile">{{ t('common.profile') }}</div>
</ElDropdownItem>
<ElDropdownItem>
<!-- <ElDropdownItem>
<Icon icon="ep:menu" />
<div @click="toDocument">{{ t('common.document') }}</div>
</ElDropdownItem>
</ElDropdownItem> -->
<ElDropdownItem divided>
<Icon icon="ep:lock" />
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>

@ -7,7 +7,7 @@ export default {
login: '登录',
required: '该项为必填项',
loginOut: '退出系统',
document: '项目文档',
// document: '项目文档',
profile: '个人中心',
reminder: '温馨提示',
loginOutMessage: '是否退出本系统?',
@ -152,7 +152,7 @@ export default {
router: {
login: '登录',
socialLogin: '社交登录',
home: '首页',
home: '商城首页',
analysis: '分析页',
workplace: '工作台'
},

@ -1,391 +1,113 @@
<template>
<div>
<el-card shadow="never">
<el-skeleton :loading="loading" animated>
<el-row :gutter="16" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<el-avatar :src="avatar" :size="70" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" />
</el-avatar>
<div>
<div class="text-20px">
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div>
</div>
</div>
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.project"
:duration="2600"
/>
</div>
<el-divider direction="vertical" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.todo"
:duration="2600"
/>
</div>
<el-divider direction="vertical" border-style="dashed" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.access"
:duration="2600"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</div>
<el-row class="mt-8px" :gutter="8" justify="space-between">
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.project') }}</span>
<el-link
type="primary"
:underline="false"
href="https://github.com/yudaocode"
target="_blank"
>
{{ t('action.more') }}
</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col
v-for="(item, index) in projects"
:key="`card-${index}`"
:xl="8"
:lg="8"
:md="8"
:sm="24"
:xs="24"
>
<el-card shadow="hover" class="mr-5px mt-5px">
<div class="flex items-center">
<Icon :icon="item.icon" :size="25" class="mr-8px" />
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-12px text-9px text-gray-400">{{ t(item.message) }}</div>
<div class="mt-12px flex justify-between text-12px text-gray-400">
<span>{{ item.personal }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<doc-alert title="商城手册(功能开启)" url="https://doc.iocoder.cn/mall/build/" />
<el-card shadow="never" class="mt-8px">
<el-skeleton :loading="loading" animated>
<el-row :gutter="20" justify="space-between">
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="pieOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="barOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.shortcutOperation') }}</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
<div class="flex items-center">
<Icon :icon="item.icon" class="mr-8px" />
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
{{ item.name }}
</el-link>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-8px">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.notice') }}</span>
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
<div class="flex items-center">
<el-avatar :src="avatar" :size="35" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" />
</el-avatar>
<div>
<div class="text-14px">
<Highlight :keys="item.keys.map((v) => t(v))">
{{ item.type }} : {{ item.title }}
</Highlight>
</div>
<div class="mt-16px text-12px text-gray-400">
{{ formatTime(item.date, 'yyyy-MM-dd') }}
</div>
</div>
</div>
<el-divider />
</div>
</el-skeleton>
</el-card>
</el-col>
</el-row>
<div class="flex flex-col">
<!-- 数据对照 -->
<el-row :gutter="16" class="row">
<el-col :md="6" :sm="12" :xs="24" :loading="loading">
<ComparisonCard
tag="今日"
title="销售额"
prefix="¥"
:decimals="2"
:value="fenToYuan(orderComparison?.value?.orderPayPrice || 0)"
:reference="fenToYuan(orderComparison?.reference?.orderPayPrice || 0)"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24" :loading="loading">
<ComparisonCard
tag="今日"
title="用户访问量"
:value="userComparison?.value?.visitUserCount || 0"
:reference="userComparison?.reference?.visitUserCount || 0"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24" :loading="loading">
<ComparisonCard
tag="今日"
title="订单量"
:value="orderComparison?.value?.orderPayCount || 0"
:reference="orderComparison?.reference?.orderPayCount || 0"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24" :loading="loading">
<ComparisonCard
tag="今日"
title="新增用户"
:value="userComparison?.value?.registerUserCount || 0"
:reference="userComparison?.reference?.registerUserCount || 0"
/>
</el-col>
</el-row>
<el-row :gutter="16" class="row">
<el-col :md="12">
<!-- 快捷入口 -->
<ShortcutCard />
</el-col>
<el-col :md="12">
<!-- 运营数据 -->
<OperationDataCard />
</el-col>
</el-row>
<el-row :gutter="16" class="mb-4">
<el-col :md="18" :sm="24">
<!-- 会员概览 -->
<MemberFunnelCard />
</el-col>
<el-col :md="6" :sm="24">
<!-- 会员终端 -->
<MemberTerminalCard />
</el-col>
</el-row>
<!-- 交易量趋势 -->
<TradeTrendCard class="mb-4" />
<!-- 会员统计 -->
<MemberStatisticsCard />
</div>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { formatTime } from '@/utils'
import { useUserStore } from '@/store/modules/user'
import { useWatermark } from '@/hooks/web/useWatermark'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
import { pieOptions, barOptions } from './echarts-data'
defineOptions({ name: 'Home' })
const { t } = useI18n()
const userStore = useUserStore()
const { setWatermark } = useWatermark()
const loading = ref(true)
const avatar = userStore.getUser.avatar
const username = userStore.getUser.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
//
let totalSate = reactive<WorkplaceTotal>({
project: 0,
access: 0,
todo: 0
})
const getCount = async () => {
const data = {
project: 40,
access: 2340,
todo: 10
}
totalSate = Object.assign(totalSate, data)
}
import * as TradeStatisticsApi from '@/api/mall/statistics/trade'
import * as MemberStatisticsApi from '@/api/mall/statistics/member'
import { DataComparisonRespVO } from '@/api/mall/statistics/common'
import { TradeOrderSummaryRespVO } from '@/api/mall/statistics/trade'
import { MemberCountRespVO } from '@/api/mall/statistics/member'
import { fenToYuan } from '@/utils'
import ComparisonCard from './components/ComparisonCard.vue'
import MemberStatisticsCard from './components/MemberStatisticsCard.vue'
import OperationDataCard from './components/OperationDataCard.vue'
import ShortcutCard from './components/ShortcutCard.vue'
import TradeTrendCard from './components/TradeTrendCard.vue'
import MemberTerminalCard from '@/views/mall/statistics/member/components/MemberTerminalCard.vue'
import MemberFunnelCard from '@/views/mall/statistics/member/components/MemberFunnelCard.vue'
//
let projects = reactive<Project[]>([])
const getProject = async () => {
const data = [
{
name: 'ruoyi-vue-pro',
icon: 'akar-icons:github-fill',
message: 'https://github.com/YunaiV/ruoyi-vue-pro',
personal: 'Spring Boot 单体架构',
time: new Date()
},
{
name: 'yudao-ui-admin-vue3',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vue3',
personal: 'Vue3 + element-plus',
time: new Date()
},
{
name: 'yudao-ui-admin-vben',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vben',
personal: 'Vue3 + vben(antd)',
time: new Date()
},
{
name: 'yudao-cloud',
icon: 'akar-icons:github',
message: 'https://github.com/YunaiV/yudao-cloud',
personal: 'Spring Cloud 微服务架构',
time: new Date()
},
{
name: 'yudao-ui-mall-uniapp',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-uniapp',
personal: 'Vue3 + uniapp',
time: new Date()
},
{
name: 'yudao-ui-admin-vue2',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vue2',
personal: 'Vue2 + element-ui',
time: new Date()
}
]
projects = Object.assign(projects, data)
}
//
let notice = reactive<Notice[]>([])
const getNotice = async () => {
const data = [
{
title: '系统支持 JDK 8/17/21Vue 2/3',
type: '通知',
keys: ['通知', '8', '17', '21', '2', '3'],
date: new Date()
},
{
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
type: '公告',
keys: ['公告', 'Boot', 'Cloud'],
date: new Date()
},
{
title: '全部开源,个人与企业可 100% 直接使用,无需授权',
type: '通知',
keys: ['通知', '无需授权'],
date: new Date()
},
{
title: '国内使用最广泛的快速开发平台,超 300+ 人贡献',
type: '公告',
keys: ['公告', '最广泛'],
date: new Date()
}
]
notice = Object.assign(notice, data)
}
/** 商城首页 */
defineOptions({ name: 'MallHome' })
//
let shortcut = reactive<Shortcut[]>([])
const getShortcut = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
url: 'github.io'
},
{
name: 'Vue',
icon: 'logos:vue',
url: 'vuejs.org'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
url: 'https://vitejs.dev/'
},
{
name: 'Angular',
icon: 'logos:angular-icon',
url: 'github.io'
},
{
name: 'React',
icon: 'logos:react',
url: 'github.io'
},
{
name: 'Webpack',
icon: 'logos:webpack',
url: 'github.io'
}
]
shortcut = Object.assign(shortcut, data)
}
const loading = ref(true) //
const orderComparison = ref<DataComparisonRespVO<TradeOrderSummaryRespVO>>() //
const userComparison = ref<DataComparisonRespVO<MemberCountRespVO>>() //
//
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
pieOptionsData!.series![0].data = data.map((v) => {
return {
name: t(v.name),
value: v.value
}
})
/** 查询交易对照卡片数据 */
const getOrderComparison = async () => {
orderComparison.value = await TradeStatisticsApi.getOrderComparison()
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
//
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(barOptionsData, 'series', [
{
name: t('analysis.activeQuantity'),
data: data.map((v) => v.value),
type: 'bar'
}
])
/** 查询会员用户数量对照卡片数据 */
const getUserCountComparison = async () => {
userComparison.value = await MemberStatisticsApi.getUserCountComparison()
}
const getAllApi = async () => {
await Promise.all([
getCount(),
getProject(),
getNotice(),
getShortcut(),
getUserAccessSource(),
getWeeklyUserActivity()
])
/** 初始化 **/
onMounted(async () => {
loading.value = true
await Promise.all([getOrderComparison(), getUserCountComparison()])
loading.value = false
}
getAllApi()
})
</script>
<style lang="scss" scoped>
.row {
.el-col {
margin-bottom: 1rem;
}
}
</style>

@ -58,9 +58,9 @@
{{ t('login.remember') }}
</el-checkbox>
</el-col>
<el-col :offset="6" :span="12">
<!-- <el-col :offset="6" :span="12">
<el-link style="float: right" type="primary">{{ t('login.forgetPassword') }}</el-link>
</el-col>
</el-col> -->
</el-row>
</el-form-item>
</el-col>
@ -82,7 +82,7 @@
mode="pop"
@success="handleLogin"
/>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<!-- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<el-row :gutter="5" justify="space-between" style="width: 100%">
<el-col :span="8">
@ -108,9 +108,9 @@
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
</el-col> -->
<!-- <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider> -->
<!-- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="w-[100%] flex justify-between">
<Icon
@ -124,9 +124,9 @@
/>
</div>
</el-form-item>
</el-col>
<el-divider content-position="center">萌新必读</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
</el-col> -->
<!-- <el-divider content-position="center">萌新必读</el-divider> -->
<!-- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
<el-form-item>
<div class="w-[100%] flex justify-between">
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
@ -139,7 +139,7 @@
</el-link>
</div>
</el-form-item>
</el-col>
</el-col> -->
</el-row>
</el-form>
</template>

@ -19,6 +19,12 @@
<el-radio label="1">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="允许购买人等级" prop="buyerLevelId">
<MemberLevelSelect v-model="formData.buyerLevelId" />
</el-form-item>
<el-form-item label="限购次数" prop="purchaseNum">
<el-input-number v-model="formData.purchaseNum" />
</el-form-item>
</el-form>
<!-- 子表的表单 -->
<el-tabs v-model="subTabsName">
@ -52,6 +58,8 @@ const formData = ref({
productId: undefined,
status: undefined,
levelId: undefined,
buyerLevelId: undefined,
purchaseNum: undefined
})
const formRules = reactive({
})
@ -119,6 +127,8 @@ const resetForm = () => {
productId: undefined,
status: undefined,
levelId: undefined,
buyerLevelId: undefined,
purchaseNum: undefined
}
formRef.value?.resetFields()
}

@ -23,6 +23,13 @@
</el-form-item>
</template>
</el-table-column>
<el-table-column label="同一人返佣金次数" min-width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.brokerage`" :rules="formRules.repeatStatus" class="mb-0px!">
<el-input-number v-model="row.repeatStatus" placeholder="请输入同一人返佣金次数" />
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template #default="{ $index }">
<el-button @click="handleDelete($index)" link></el-button>

Loading…
Cancel
Save