Explorar o código

Merge branch 'master' of dq/kmall-pt into master

黄亚琴 %!s(int64=6) %!d(string=hai) anos
pai
achega
76439fbfe3
Modificáronse 54 ficheiros con 2726 adicións e 309 borrados
  1. 4 5
      kmall-admin/src/main/java/com/kmall/admin/controller/OrderController.java
  2. 1 1
      kmall-admin/src/main/java/com/kmall/admin/dao/GoodsGalleryDao.java
  3. 3 1
      kmall-admin/src/main/java/com/kmall/admin/dao/GoodsSpecificationDao.java
  4. 0 2
      kmall-admin/src/main/java/com/kmall/admin/entity/GoodsEntity.java
  5. 49 0
      kmall-admin/src/main/java/com/kmall/admin/entity/OrderRefundEntity.java
  6. 13 0
      kmall-admin/src/main/java/com/kmall/admin/entity/SysPrinterEntity.java
  7. 1 0
      kmall-admin/src/main/java/com/kmall/admin/service/GoodsService.java
  8. 2 1
      kmall-admin/src/main/java/com/kmall/admin/service/OrderService.java
  9. 193 171
      kmall-admin/src/main/java/com/kmall/admin/service/impl/GoodsServiceImpl.java
  10. 65 3
      kmall-admin/src/main/java/com/kmall/admin/service/impl/OrderServiceImpl.java
  11. 16 0
      kmall-admin/src/main/resources/conf/print-ticket.properties
  12. 4 3
      kmall-admin/src/main/resources/mybatis/mapper/GoodsDao.xml
  13. 6 0
      kmall-admin/src/main/resources/mybatis/mapper/GoodsSpecificationDao.xml
  14. 1 2
      kmall-admin/src/main/resources/mybatis/mapper/OrderRefundDao.xml
  15. 6 10
      kmall-admin/src/main/resources/mybatis/mapper/SysPrinterDao.xml
  16. 38 10
      kmall-admin/src/main/webapp/WEB-INF/page/shop/goods.html
  17. 3 1
      kmall-admin/src/main/webapp/WEB-INF/page/shop/orderPrint.html
  18. 2 15
      kmall-admin/src/main/webapp/WEB-INF/page/shop/orderrefund.html
  19. 2 0
      kmall-admin/src/main/webapp/WEB-INF/page/sys/header.html
  20. 6 1
      kmall-admin/src/main/webapp/WEB-INF/page/sys/printer.html
  21. 1 0
      kmall-admin/src/main/webapp/WEB-INF/web.xml
  22. 35 47
      kmall-admin/src/main/webapp/js/shop/goods.js
  23. 8 6
      kmall-admin/src/main/webapp/js/shop/order.js
  24. 61 16
      kmall-admin/src/main/webapp/js/shop/orderrefund.js
  25. 2 2
      kmall-admin/src/main/webapp/js/sys/macro.js
  26. 17 3
      kmall-admin/src/main/webapp/js/sys/printer.js
  27. 1 1
      kmall-admin/src/main/webapp/statics/css/login.css
  28. BIN=BIN
      kmall-admin/src/main/webapp/statics/img/logo.jpg
  29. 237 0
      kmall-admin/src/main/webapp/statics/jquery.print.js
  30. 4 4
      kmall-api/kmall-api.iml
  31. 73 0
      kmall-common/src/main/java/com/kmall/common/service/print/ticket/PrintTicketProperties.java
  32. 40 0
      kmall-common/src/main/java/com/kmall/common/service/print/ticket/PrintTicketPropertiesBuilder.java
  33. 23 0
      kmall-common/src/main/java/com/kmall/common/service/print/ticket/PrintTicketPropertiesConfiguration.java
  34. 222 0
      kmall-common/src/main/java/com/kmall/common/utils/MapBeanUtil.java
  35. 61 0
      kmall-common/src/main/java/com/kmall/common/utils/ValidatorUtil.java
  36. 111 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/Test.java
  37. 407 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/TicketBuilder.java
  38. 48 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/TicketPrintUtil.java
  39. 99 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/TicketPrinter.java
  40. 65 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/PrintAttributeBuilder.java
  41. 60 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/PrintPageableBuilder.java
  42. 50 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/ThermalMediaSize.java
  43. 43 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/ThermalMediaSizeName.java
  44. 109 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/CashInfo.java
  45. 87 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/CusListing.java
  46. 63 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/Goods.java
  47. 119 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/Ticket.java
  48. 74 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/TicketFoot.java
  49. 91 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/TicketHead.java
  50. 11 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketCommonFont.java
  51. 57 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketFont.java
  52. 11 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketGoodsTitleFont.java
  53. 11 0
      kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketHeadFont.java
  54. 10 4
      pom.xml

+ 4 - 5
kmall-admin/src/main/java/com/kmall/admin/controller/OrderController.java

@@ -1,6 +1,5 @@
 package com.kmall.admin.controller;
 
-import com.alibaba.fastjson.JSONObject;
 import com.kmall.admin.entity.OrderEntity;
 import com.kmall.admin.entity.OrderExceptionRecordEntity;
 import com.kmall.admin.entity.OrderRefundEntity;
@@ -10,6 +9,7 @@ import com.kmall.admin.service.OrderExceptionRecordService;
 import com.kmall.admin.service.OrderProcessRecordService;
 import com.kmall.admin.service.OrderService;
 import com.kmall.api.contants.Dict;
+import com.kmall.common.utils.print.ticket.item.Ticket;
 import com.kmall.common.utils.PageUtils;
 import com.kmall.common.utils.Query;
 import com.kmall.common.utils.R;
@@ -187,16 +187,15 @@ public class OrderController {
     }
 
     /**
-     * 确定收货
+     * 打印小票
      *
      * @param id
      * @return
      */
     @RequestMapping("/printMsg")
     public R printMsg(@RequestBody Long id) {
-        String json = orderService.printMsg(id);
-        JSONObject jsStr = JSONObject.parseObject(json);
-        return R.ok().put("msg", jsStr.get("msg"));
+        Ticket ticket = orderService.printMsg(id);
+        return R.ok().put("ticket", ticket);
     }
 
     /**

+ 1 - 1
kmall-admin/src/main/java/com/kmall/admin/dao/GoodsGalleryDao.java

@@ -13,5 +13,5 @@ import java.util.Map;
  * @date 2017-08-23 14:41:43
  */
 public interface GoodsGalleryDao extends BaseDao<GoodsGalleryEntity> {
-    int deleteByGoodsId(Map<String, Long> map);
+    int deleteByGoodsId(Long goodsId);
 }

+ 3 - 1
kmall-admin/src/main/java/com/kmall/admin/dao/GoodsSpecificationDao.java

@@ -17,5 +17,7 @@ public interface GoodsSpecificationDao extends BaseDao<GoodsSpecificationEntity>
      * @param goodsId
      * @return
      */
-    int deleteByGoodsId(int goodsId);
+    int deleteByGoodsId(Long goodsId);
+
+    GoodsSpecificationEntity queryByGoodsId(Long goodsId);
 }

+ 0 - 2
kmall-admin/src/main/java/com/kmall/admin/entity/GoodsEntity.java

@@ -113,8 +113,6 @@ public class GoodsEntity implements Serializable {
     //品牌
     private String brandName;
 
-
-
     private String sku;
 
     private String goodsBizType;

+ 49 - 0
kmall-admin/src/main/java/com/kmall/admin/entity/OrderRefundEntity.java

@@ -97,6 +97,23 @@ public class OrderRefundEntity implements Serializable {
     private Date tstm;
 
     /**
+     * 冗余
+     */
+    //订单序列号
+    private String orderSn;
+    //订单状态
+    //订单相关状态字段设计,采用单个字段表示全部的订单状态
+    //1xx 表示订单取消和删除等状态 0订单创建成功等待付款, 101订单已取消, 102订单已删除
+    //2xx 表示订单支付状态 201订单已付款,等待发货
+    //3xx 表示订单物流相关状态 300订单已发货, 301用户确认收货
+    //4xx 表示订单退换货相关的状态 401 没有发货,退款 402 已收货,退款退货
+    private Integer orderStatus;
+    //实际支付
+    private BigDecimal actualPrice;
+
+    private String userName;
+
+    /**
      * 设置:主键
      */
     public void setId(Integer id) {
@@ -356,4 +373,36 @@ public class OrderRefundEntity implements Serializable {
     public Date getTstm() {
         return tstm;
     }
+
+    public String getOrderSn() {
+        return orderSn;
+    }
+
+    public void setOrderSn(String orderSn) {
+        this.orderSn = orderSn;
+    }
+
+    public Integer getOrderStatus() {
+        return orderStatus;
+    }
+
+    public void setOrderStatus(Integer orderStatus) {
+        this.orderStatus = orderStatus;
+    }
+
+    public BigDecimal getActualPrice() {
+        return actualPrice;
+    }
+
+    public void setActualPrice(BigDecimal actualPrice) {
+        this.actualPrice = actualPrice;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
 }

+ 13 - 0
kmall-admin/src/main/java/com/kmall/admin/entity/SysPrinterEntity.java

@@ -31,6 +31,11 @@ public class SysPrinterEntity implements Serializable {
     private Integer storeId;
 
     /**
+     * 门店名称
+     */
+    private String storeName;
+
+    /**
      * 设置:主键
      */
     public void setId(Integer id) {
@@ -85,4 +90,12 @@ public class SysPrinterEntity implements Serializable {
     public Integer getStoreId() {
         return storeId;
     }
+
+    public String getStoreName() {
+        return storeName;
+    }
+
+    public void setStoreName(String storeName) {
+        this.storeName = storeName;
+    }
 }

+ 1 - 0
kmall-admin/src/main/java/com/kmall/admin/service/GoodsService.java

@@ -1,6 +1,7 @@
 package com.kmall.admin.service;
 
 import com.kmall.admin.entity.GoodsEntity;
+import com.kmall.common.utils.R;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;

+ 2 - 1
kmall-admin/src/main/java/com/kmall/admin/service/OrderService.java

@@ -2,6 +2,7 @@ package com.kmall.admin.service;
 
 import com.kmall.admin.entity.OrderEntity;
 import com.kmall.admin.entity.OrderRefundEntity;
+import com.kmall.common.utils.print.ticket.item.Ticket;
 import com.kmall.common.utils.wechat.WechatRefundApiResult;
 
 import java.math.BigDecimal;
@@ -45,7 +46,7 @@ public interface OrderService {
      * @param id 订单ID
      * @return
      */
-    String printMsg(Long id);
+    Ticket printMsg(Long id);
 
     /**
      * 退款

+ 193 - 171
kmall-admin/src/main/java/com/kmall/admin/service/impl/GoodsServiceImpl.java

@@ -1,13 +1,12 @@
 package com.kmall.admin.service.impl;
 
-import com.kmall.admin.annotation.DataFilter;
+import com.google.common.collect.ImmutableBiMap;
 import com.kmall.admin.dao.*;
 import com.kmall.admin.entity.*;
 import com.kmall.admin.service.GoodsService;
+import com.kmall.api.contants.Dict;
 import com.kmall.common.entity.SysUserEntity;
-import com.kmall.common.utils.RRException;
-import com.kmall.common.utils.ShiroUtils;
-import com.kmall.common.utils.StringUtils;
+import com.kmall.common.utils.*;
 import com.kmall.common.utils.excel.ExcelImport;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -34,6 +33,8 @@ public class GoodsServiceImpl implements GoodsService {
     @Autowired
     private GoodsAttributeDao goodsAttributeDao;
     @Autowired
+    private AttributeCategoryDao attributeCategoryDao;
+    @Autowired
     private ProductDao productDao;
     @Autowired
     private GoodsGalleryDao goodsGalleryDao;
@@ -45,6 +46,8 @@ public class GoodsServiceImpl implements GoodsService {
     private StoreDao storeDao;
     @Autowired
     private GoodsGroupDao goodsGroupDao;
+    @Autowired
+    private CategoryDao categoryDao;
 
     @Override
     public GoodsEntity queryObject(Integer id) {
@@ -55,6 +58,7 @@ public class GoodsServiceImpl implements GoodsService {
         GoodsEntity entity = goodsDao.queryObject(id);
         entity.setAttributeEntityList(attributeEntities);
         entity.setProductEntityList(productEntityList);
+        entity.setAttributeCategory(categoryDao.queryObject(entity.getCategoryId()).getParentId());
         return entity;
     }
 
@@ -71,206 +75,224 @@ public class GoodsServiceImpl implements GoodsService {
     @Override
     @Transactional
     public int save(GoodsEntity goods) {
+        Map<String, Object> valideDate = MapBeanUtil.fromObject(goods);
+        ImmutableBiMap.Builder builder = new ImmutableBiMap.Builder();
+        builder.put("attributeCategory", "商品分类");
+        builder.put("categoryId", "商品二级分类");
+        builder.put("goodsSn", "商品编码");
+        builder.put("goodsBizType", "货品业务类型");
+        builder.put("name", "商品名称");
+        builder.put("brandId", "品牌");
+        builder.put("freightId", "运费模版");
+        builder.put("goodsDesc", "商品描述");
+        builder.put("isOnSale", "上架");
+        builder.put("goodsUnit", "商品单位");
+        builder.put("primaryPicUrl", "商品主图");
+        builder.put("listPicUrl", "商品列表图");
+        builder.put("goodsRate", "商品税率");
+        builder.put("retailPrice", "零售价格");
+        builder.put("isHot", "热销");
+
+        if (!Dict.orderBizType.item_11.equals(goods.getGoodsBizType())) {
+            // 海关信息,普通货物可不添加
+            builder.put("sku", "SKU");
+            builder.put("prodBarcode", "产品编码");
+            builder.put("brand", "产品品牌");
+            builder.put("unitCode", "计量单位代码");
+            builder.put("cusGoodsCode", "海关商品编码");
+            builder.put("ciqProdModel", "国检规格型号");
+            builder.put("oriCntCode", "原产国代码");
+            builder.put("cusDeclEle", "海关申报要素");
+            builder.put("cusRecCode", "海关备案编号");
+        }
+        R r = ValidatorUtil.isEmpty(builder.build(), valideDate);
+        if (Integer.valueOf(r.get("code").toString()) != 0) {
+            throw new RRException(r.get("msg").toString());
+        }
+
+        // 商品轮播图
+        List<GoodsGalleryEntity> galleryEntityList = goods.getGoodsImgList();
+        if (galleryEntityList == null || galleryEntityList.size() <= 0) {
+            throw new RRException("至少添加一张商品轮播图!");
+        }
+
         SysUserEntity user = ShiroUtils.getUserEntity();
         Map<String, Object> map = new HashMap<>();
-        map.put("name", goods.getName());
+        map.put("isSame", "true");
+        map.put("sku", goods.getSku());
+        map.put("goodsSn", goods.getGoodsSn());
+        map.put("goodsBizType", goods.getGoodsBizType());
         List<GoodsEntity> list = queryList(map);
-        if (null != list && list.size() != 0) {
-            throw new RRException("商品名称已存在!");
+        if (list != null && list.size() != 0) {
+            throw new RRException("已存在该商品编码或该货品业务类型下已存在此SKU!");
         }
-        Long id = goodsDao.queryMaxId() + 1;
-        goods.setId(id);
 
+        // 添加商品
+        goods.setAttributeCategory(categoryDao.queryObject(goods.getCategoryId()).getParentId());
         goods.setAddTime(new Date());
+        goods.setIsDelete(0);
+        goods.setIsNew(0);
+        goods.setCreateUserId(user.getUserId());
+        goods.setUpdateUserId(user.getUserId());
+        goods.setUpdateTime(new Date());
+        goods.setModTime(new Date());
+        goods.setCreateTime(new Date());
 
-        //保存商品详情页面显示的属性
-        List<GoodsAttributeEntity> attributeEntityList = goods.getAttributeEntityList();
-        if (null != attributeEntityList && attributeEntityList.size() > 0) {
-            for (GoodsAttributeEntity item : attributeEntityList) {
-                if (item.getGoodsId() == null) {
-                    item.setGoodsId(id);
-                    goodsAttributeDao.save(item);
-                }
-            }
+        // 新增商品
+        goodsDao.save(goods);
+        Long id = goods.getId();
+
+        // 添加商品轮播图
+        for (GoodsGalleryEntity galleryEntity : galleryEntityList) {
+            galleryEntity.setGoodsId(id);
+            goodsGalleryDao.save(galleryEntity);
         }
 
-        // 产品更新
-        List<ProductEntity> productEntityList = goods.getProductEntityList();
-        if (null != productEntityList && productEntityList.size() > 0) {
-            for (ProductEntity productEntity : productEntityList) {
-                if (1 == productEntity.getIsDelete()) {
-                    productDao.delete(productEntity);
-                    if (null != productEntity.getGoodsSpecificationIds()) {
-                        goodsSpecificationDao.delete(productEntity.getGoodsSpecificationIds());
+        // 添加商品参数
+        List<GoodsAttributeEntity> attributeEntityList = goods.getAttributeEntityList();
+        if (attributeEntityList != null && attributeEntityList.size() > 0) {
+            for (GoodsAttributeEntity item : attributeEntityList) {
+                if (item.getIsDelete() == 0) {
+                    if (item.getAttributeId() != null && StringUtils.isNotEmpty(item.getValue())) {
+                        item.setGoodsId(id);
+                        goodsAttributeDao.save(item);
+                    } else if (item.getAttributeId() != null && StringUtils.isNullOrEmpty(item.getValue())) {
+                        throw new RRException("商品属性【" + attributeCategoryDao.queryObject(item.getAttributeId()).getName() + "】值不能为空!");
+                    } else if (item.getAttributeId() == null) {
+                        continue;
                     }
                 }
-                if (StringUtils.isNullOrEmpty(productEntity.getGoodsSn())) {
-                    throw new RRException("产品编码不能为空!");
-                }
-                // 判断是否goods_sn重复
-                ProductEntity productDb = productDao.queryObjectBySn(productEntity.getGoodsSn());
-                if (null != productDb && !productDb.getId().equals(productEntity.getId())) {
-                    throw new RRException("产品编码重复!");  // 重复不操作
-                }
-                goods.setPrimaryProductId(productEntity.getId());
-                GoodsSpecificationEntity specificationEntity = new GoodsSpecificationEntity();
-                // 规格
-                if (StringUtils.isNullOrEmpty(productEntity.getGoodsSpecificationIds())
-                        && StringUtils.isNotEmpty(productEntity.getGoodsSpecificationNameValue())) {
-                    specificationEntity.setGoodsId(goods.getId());
-                    specificationEntity.setSpecificationId(1); // 规格写死
-                    specificationEntity.setValue(productEntity.getGoodsSpecificationNameValue());
-                    goodsSpecificationDao.save(specificationEntity);
-                    productEntity.setGoodsSpecificationNameValue(specificationEntity.getId() + "");
-                } else if (StringUtils.isNotEmpty(productEntity.getGoodsSpecificationIds())) {
-                    specificationEntity = goodsSpecificationDao.queryObject(productEntity.getGoodsSpecificationIds());
-                    specificationEntity.setValue(productEntity.getGoodsSpecificationNameValue());
-                    goodsSpecificationDao.update(specificationEntity);
-                }
-                if (null != productEntity.getId()) {
-                    productDao.save(productEntity);
-                } else {
-                    productDao.update(productEntity);
-                }
-                // default
-                if (null == goods.getPrimaryProductId()) {
-                    goods.setPrimaryProductId(productEntity.getId());
-                } else if (productEntity.getGoodsDefault() == 1) {
-                    goods.setPrimaryProductId(productEntity.getId());
-                }
-            }
-            // default
-            for (ProductEntity productEntity : productEntityList) {
-                if (null == goods.getPrimaryProductId() && 1 != productEntity.getIsDelete()) {
-                    goods.setPrimaryProductId(productEntity.getId());
-                    break;
-                }
             }
         }
 
-        //商品轮播图
-        List<GoodsGalleryEntity> galleryEntityList = goods.getGoodsImgList();
-        if (null != galleryEntityList && galleryEntityList.size() > 0) {
-            for (GoodsGalleryEntity galleryEntity : galleryEntityList) {
-                galleryEntity.setGoodsId(id);
-                goodsGalleryDao.save(galleryEntity);
-            }
+        // 添加产品
+        ProductEntity product = new ProductEntity();
+        product.setGoodsId(id);
+        product.setGoodsSn(goods.getGoodsSn());
+        // 保税商品,普通货物暂不添加商品规格
+        if (!Dict.orderBizType.item_11.equals(goods.getGoodsBizType())) {
+            // 添加商品规格
+            GoodsSpecificationEntity goodsSpecification = new GoodsSpecificationEntity();
+            goodsSpecification.setGoodsId(id);
+            goodsSpecification.setValue(goods.getCiqProdModel());
+            goodsSpecification.setSpecificationId(1);
+            goodsSpecificationDao.save(goodsSpecification);
+
+            product.setGoodsSpecificationIds(goodsSpecification.getId().toString());
+            product.setGoodsSpecificationNameValue(goodsSpecification.getValue());
         }
 
-        goods.setIsDelete(0);
-        goods.setCreateUserId(user.getUserId());
-        goods.setUpdateUserId(user.getUserId());
-        goods.setUpdateTime(new Date());
-        goods.setModTime(new Date());
-        goods.setCreateTime(new Date());
-        return goodsDao.save(goods);
+        return productDao.save(product);
     }
 
     @Override
     @Transactional
     public int update(GoodsEntity goods) {
-        Map<String, Long> map = new HashMap<String, Long>();
-        map.put("goodsId", goods.getId());
+        Map<String, Object> valideDate = MapBeanUtil.fromObject(goods);
+        ImmutableBiMap.Builder builder = new ImmutableBiMap.Builder();
+        builder.put("attributeCategory", "商品分类");
+        builder.put("categoryId", "商品二级分类");
+        builder.put("goodsSn", "商品编码");
+        builder.put("goodsBizType", "货品业务类型");
+        builder.put("name", "商品名称");
+        builder.put("brandId", "品牌");
+        builder.put("freightId", "运费模版");
+        builder.put("goodsDesc", "商品描述");
+        builder.put("isOnSale", "上架");
+        builder.put("goodsUnit", "商品单位");
+        builder.put("primaryPicUrl", "商品主图");
+        builder.put("listPicUrl", "商品列表图");
+        builder.put("goodsRate", "商品税率");
+        builder.put("retailPrice", "零售价格");
+        builder.put("isHot", "热销");
+
+        if (!Dict.orderBizType.item_11.equals(goods.getGoodsBizType())) {
+            // 海关信息,普通货物可不添加
+            builder.put("sku", "SKU");
+            builder.put("prodBarcode", "产品编码");
+            builder.put("brand", "产品品牌");
+            builder.put("unitCode", "计量单位代码");
+            builder.put("cusGoodsCode", "海关商品编码");
+            builder.put("ciqProdModel", "国检规格型号");
+            builder.put("oriCntCode", "原产国代码");
+            builder.put("cusDeclEle", "海关申报要素");
+            builder.put("cusRecCode", "海关备案编号");
+        }
+        R r = ValidatorUtil.isEmpty(builder.build(), valideDate);
+        if (Integer.valueOf(r.get("code").toString()) != 0) {
+            throw new RRException(r.get("msg").toString());
+        }
+
+        // 商品轮播图
+        List<GoodsGalleryEntity> galleryEntityList = goods.getGoodsImgList();
+        if (galleryEntityList == null || galleryEntityList.size() <= 0) {
+            throw new RRException("至少保留一张商品轮播图!");
+        }
 
         SysUserEntity user = ShiroUtils.getUserEntity();
-        List<GoodsAttributeEntity> attributeEntityList = goods.getAttributeEntityList();
+        Map<String, Object> map = new HashMap<>();
+        map.put("isSame", "true");
+        map.put("sku", goods.getSku());
+        map.put("goodsSn", goods.getGoodsSn());
+        map.put("goodsBizType", goods.getGoodsBizType());
+        List<GoodsEntity> list = queryList(map);
+        if (list != null && list.size() != 0) {
+            throw new RRException("已存在该商品编码或该货品业务类型下已存在此SKU!");
+        }
 
-        //商品参数
-        if (null != attributeEntityList && attributeEntityList.size() > 0) {
-            for (GoodsAttributeEntity goodsAttributeEntity : attributeEntityList) {
-                if (null == goodsAttributeEntity.getId() && StringUtils.isNullOrEmpty(goodsAttributeEntity.getAttributeId())) {
-                    continue;
-                }
-                if (goodsAttributeEntity.getIsDelete() == 1) {
-                    goodsAttributeDao.delete(goodsAttributeEntity);
-                } else if (null != goodsAttributeEntity.getId()) {
-                    goodsAttributeEntity.setGoodsId(goods.getId());
-                    goodsAttributeDao.update(goodsAttributeEntity);
-                } else {
-                    goodsAttributeEntity.setGoodsId(goods.getId());
-                    goodsAttributeDao.save(goodsAttributeEntity);
-                }
-            }
+
+        // 修改商品
+        goods.setAttributeCategory(categoryDao.queryObject(goods.getCategoryId()).getParentId());
+        goods.setIsDelete(0);
+        goods.setIsNew(0);
+        goods.setUpdateUserId(user.getUserId());
+        goods.setUpdateTime(new Date());
+        goods.setModTime(new Date());
+
+        // 修改商品
+        goodsDao.update(goods);
+
+        // 修改商品轮播图
+        goodsGalleryDao.deleteByGoodsId(goods.getId());
+        for (GoodsGalleryEntity galleryEntity : galleryEntityList) {
+            galleryEntity.setGoodsId(goods.getId());
+            goodsGalleryDao.save(galleryEntity);
         }
 
-        // 产品更新
-        List<ProductEntity> productEntityList = goods.getProductEntityList();
-        if (null != productEntityList && productEntityList.size() > 0) {
-            for (ProductEntity productEntity : productEntityList) {
-                if (1 == productEntity.getIsDelete()) {
-                    if (null == productEntity.getId()) {
+        // 修改商品参数
+        List<GoodsAttributeEntity> attributeEntityList = goods.getAttributeEntityList();
+        if (attributeEntityList != null && attributeEntityList.size() > 0) {
+            for (GoodsAttributeEntity item : attributeEntityList) {
+                if (item.getIsDelete() == 0) {
+                    if (item.getAttributeId() != null && StringUtils.isNotEmpty(item.getValue())) {
+                        item.setGoodsId(goods.getId());
+                        goodsAttributeDao.save(item);
+                    } else if (item.getAttributeId() != null && StringUtils.isNullOrEmpty(item.getValue())) {
+                        throw new RRException("商品属性【" + attributeCategoryDao.queryObject(item.getAttributeId()).getName() + "】值不能为空!");
+                    } else if (item.getId() != null) {
+                        goodsAttributeDao.update(item);
+                    } else if (item.getAttributeId() == null) {
                         continue;
                     }
-                    productDao.delete(productEntity);
-                    if (null != productEntity.getGoodsSpecificationIds()) {
-                        goodsSpecificationDao.delete(productEntity.getGoodsSpecificationIds());
-                    }
-                    continue;
-                }
-                if (StringUtils.isNullOrEmpty(productEntity.getGoodsSn())) {
-                    throw new RRException("产品编码不能为空!");
-                }
-                // 判断是否goods_sn重复
-                ProductEntity productDb = productDao.queryObjectBySn(productEntity.getGoodsSn());
-                if (null != productDb && !productDb.getId().equals(productEntity.getId())) {
-                    throw new RRException("产品编码重复!");  // 重复不操作
-                }
-                GoodsSpecificationEntity specificationEntity = new GoodsSpecificationEntity();
-                // 规格
-                if (StringUtils.isNullOrEmpty(productEntity.getGoodsSpecificationIds())
-                        && StringUtils.isNotEmpty(productEntity.getGoodsSpecificationNameValue())) {
-                    specificationEntity.setGoodsId(goods.getId());
-                    specificationEntity.setSpecificationId(1); // 规格写死
-                    specificationEntity.setValue(productEntity.getGoodsSpecificationNameValue());
-                    goodsSpecificationDao.save(specificationEntity);
-                    productEntity.setGoodsSpecificationIds(specificationEntity.getId() + "");
-                } else if (StringUtils.isNotEmpty(productEntity.getGoodsSpecificationIds())) {
-                    specificationEntity = goodsSpecificationDao.queryObject(productEntity.getGoodsSpecificationIds());
-                    specificationEntity.setValue(productEntity.getGoodsSpecificationNameValue());
-                    goodsSpecificationDao.update(specificationEntity);
-                }
-                if (null == productEntity.getId()) {
-                    productEntity.setGoodsId(goods.getId());
-                    productDao.save(productEntity);
-                } else {
-                    productDao.update(productEntity);
-                }
-                // default
-                if (null == goods.getPrimaryProductId()) {
-                    goods.setPrimaryProductId(productEntity.getId());
-                } else if (productEntity.getGoodsDefault() == 1) {
-                    goods.setPrimaryProductId(productEntity.getId());
-                }
-            }
-            // default
-            for (ProductEntity productEntity : productEntityList) {
-                if (null == goods.getPrimaryProductId() && 1 != productEntity.getIsDelete()) {
-                    goods.setPrimaryProductId(productEntity.getId());
-                    break;
+                } else if (item.getIsDelete() == 1) {
+                    goodsAttributeDao.delete(item.getId());
                 }
             }
         }
 
-        //商品轮播图
-        List<GoodsGalleryEntity> galleryEntityList = goods.getGoodsImgList();
-        if (null != galleryEntityList && galleryEntityList.size() > 0)
-
-        {
-            for (GoodsGalleryEntity galleryEntity : galleryEntityList) {
-                if (galleryEntity.getIsDelete() == 1) {
-                    goodsGalleryDao.delete(galleryEntity);
-                } else if (null != galleryEntity.getId()) {
-                    galleryEntity.setGoodsId(goods.getId());
-                    goodsGalleryDao.update(galleryEntity);
-                } else {
-                    galleryEntity.setGoodsId(goods.getId());
-                    goodsGalleryDao.save(galleryEntity);
-                }
-            }
+        // 修改产品
+        ProductEntity product = productDao.queryObjectBySn(goods.getGoodsSn());
+        // 保税商品,普通货物暂不添加商品规格
+        if (!Dict.orderBizType.item_11.equals(goods.getGoodsBizType())) {
+            // 添加商品规格
+            GoodsSpecificationEntity goodsSpecification = goodsSpecificationDao.queryByGoodsId(goods.getId());
+            goodsSpecification.setValue(goods.getCiqProdModel());
+            goodsSpecificationDao.update(goodsSpecification);
+
+            product.setGoodsSpecificationNameValue(goodsSpecification.getValue());
         }
-        goods.setUpdateUserId(user.getUserId());
-        goods.setUpdateTime(new Date());
-        return goodsDao.update(goods);
+
+        return productDao.update(product);
     }
 
     @Override

+ 65 - 3
kmall-admin/src/main/java/com/kmall/admin/service/impl/OrderServiceImpl.java

@@ -5,12 +5,14 @@ import com.kmall.admin.dao.*;
 import com.kmall.admin.entity.*;
 import com.kmall.admin.service.OrderService;
 import com.kmall.api.contants.Dict;
-import com.kmall.common.utils.DateUtils;
+import com.kmall.api.service.merch.OmsMerchPropertiesBuilder;
+import com.kmall.common.service.print.ticket.PrintTicketPropertiesBuilder;
+import com.kmall.common.utils.print.ticket.TicketPrintUtil;
+import com.kmall.common.utils.print.ticket.item.*;
 import com.kmall.common.utils.RRException;
 import com.kmall.common.utils.ShiroUtils;
 import com.kmall.common.utils.StringUtils;
 import com.kmall.common.utils.express.kdn.KdniaoUtil;
-import com.kmall.common.utils.printer.FeiGeUtils;
 import com.kmall.common.utils.wechat.WechatRefundApiResult;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -41,6 +43,8 @@ public class OrderServiceImpl implements OrderService {
     private OrderProcessRecordDao orderProcessRecordDao;
     @Autowired
     private OrderRefundDao orderRefundDao;
+    @Autowired
+    private StoreDao storeDao;
 
     @Override
     public OrderEntity queryObject(Long id) {
@@ -176,6 +180,64 @@ public class OrderServiceImpl implements OrderService {
     }
 
     @Override
+    public Ticket printMsg(Long id) {
+        OrderEntity orderEntity = queryInfos(id);
+        List<OrderGoodsEntity> orderGoodsEntityList = orderEntity.getOrderGoodsEntityList();
+        // 获取门店
+        StoreEntity storeEntity = storeDao.queryObject(orderEntity.getStoreId());
+        // 获取清关信息
+        OrderProcessRecordEntity orderProcessRecordEntity = orderProcessRecordDao.queryObjectByOrderSn(orderEntity.getOrderSn());
+
+        // 小票头
+        TicketHead head = new TicketHead();
+        head.setTitle(OmsMerchPropertiesBuilder.instance().getMerchName() + storeEntity.getStoreName());
+        head.setMemberId(orderEntity.getUserName().toString());
+        head.setOrderId(orderEntity.getOrderSn());
+        head.setTradeTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE").format(new Date()));
+
+        // 商品信息
+        Integer goodsTotal = 0; // 商品总个数
+        BigDecimal total = new BigDecimal(0); // 商品总计
+        List<Goods> goodsList = new ArrayList<>();
+        for (OrderGoodsEntity orderGoods : orderGoodsEntityList) {
+            goodsTotal += orderGoods.getNumber();
+            total = total.add(orderGoods.getRetailPrice().subtract(new BigDecimal(orderGoods.getNumber()))).setScale(2, BigDecimal.ROUND_HALF_UP);
+            Goods goods = new Goods(orderGoods.getGoodsName(), orderGoods.getRetailPrice().toString(), orderGoods.getNumber().toString(), orderGoods.getRetailPrice().multiply(new BigDecimal(orderGoods.getNumber()).setScale(2, BigDecimal.ROUND_HALF_UP)).toString());
+            goodsList.add(goods);
+        }
+
+        // 收银信息
+        CashInfo cashInfo = new CashInfo();
+        cashInfo.setGoodsTotal(goodsTotal.toString());
+        cashInfo.setTotal(total.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
+        cashInfo.setReceipts(orderEntity.getActualPrice().setScale(2, BigDecimal.ROUND_HALF_UP).toString());
+        cashInfo.setOddChange("0.00");
+        cashInfo.setCoupon(orderEntity.getCouponPrice().setScale(2, BigDecimal.ROUND_HALF_UP).toString());
+        cashInfo.setFreight(new BigDecimal(orderEntity.getFreightPrice()).setScale(2, BigDecimal.ROUND_HALF_UP).toString());
+        cashInfo.setPaymentMode("微信支付");
+
+        // 海关清单
+        CusListing cusListing = new CusListing();
+        cusListing.setOrderId(orderEntity.getOrderSn());
+        cusListing.setWaybillId(orderProcessRecordEntity.getLogisticsNo());
+        cusListing.setInvtNo(orderProcessRecordEntity.getInvtNo());
+        cusListing.setConsignee(orderEntity.getConsignee());
+        cusListing.setConsigneeTel(orderEntity.getMobile());
+        cusListing.setOriginAddress(PrintTicketPropertiesBuilder.instance().getAddress());
+        cusListing.setDeliveryAddress(storeEntity.getStoreAddress());
+
+        // 小票脚
+        TicketFoot foot = new TicketFoot();
+        foot.setSummary(PrintTicketPropertiesBuilder.instance().getSummary());
+        foot.setServiceTel(PrintTicketPropertiesBuilder.instance().getServiceTel());
+        foot.setUrl1(PrintTicketPropertiesBuilder.instance().getUrl1());
+
+        return TicketPrintUtil.print(head, goodsList, cashInfo, cusListing, foot);
+    }
+    /**
+     * 飞鸽打印
+     */
+    /*@Override
     public String printMsg(Long id) {
         OrderEntity orderEntity = queryObject(id);
         Map<String, Object> map = new HashMap<String, Object>();
@@ -219,7 +281,7 @@ public class OrderServiceImpl implements OrderService {
             sn = printerEntities.get(0).getSn();
         }
         return FeiGeUtils.printMsg(sn, content, "1");
-    }
+    }*/
 
     /**
      * 部分退款

+ 16 - 0
kmall-admin/src/main/resources/conf/print-ticket.properties

@@ -0,0 +1,16 @@
+########## 小票打印配置 ##########
+
+#保税仓地址
+ticket.address=深圳前海保税仓
+
+#小票脚信息
+#概要信息
+ticket.summary=======双11活动火爆销售大促======
+#客服电话
+ticket.serviceTel=0750-7755897
+#网址1
+ticket.url1=http://www.ds-bay.com
+#网址2
+ticket.url2=
+#网址3
+ticket.url3=

+ 4 - 3
kmall-admin/src/main/resources/mybatis/mapper/GoodsDao.xml

@@ -81,13 +81,11 @@
         select
         mall_goods.*,
         mall_category.name category_name,
-        mall_attribute_category.name attribute_category_name,
         mall_brand.name brand_name,
         mall_freight.name freight_name,
         case when mall_goods_group.id > 0 then 2 else 0 end as goodsType
         from mall_goods
         LEFT JOIN mall_category ON mall_goods.category_id = mall_category.id
-        LEFT JOIN mall_attribute_category ON mall_goods.attribute_category = mall_attribute_category.id
         LEFT JOIN mall_brand ON mall_brand.id = mall_goods.brand_id
         left join mall_goods_group on mall_goods_group.goods_id = mall_goods.id and mall_goods_group.open_status != 3
         left join mall_freight on mall_goods.freight_id = mall_freight.id
@@ -115,6 +113,9 @@
         <if test="isDelete != null and isDelete != ''">
             AND mall_goods.is_Delete = #{isDelete}
         </if>
+        <if test="isSame != null and isSame != ''">
+            AND (mall_goods.sku = #{sku} AND mall_goods.goods_biz_type = #{goodsBizType}) OR mall_goods.goods_sn = #{goodsSn}
+        </if>
         <choose>
             <when test="sidx != null and sidx.trim() != ''">
                 order by ${sidx} ${order}
@@ -144,7 +145,7 @@
         AND mall_goods.is_Delete = #{isDelete}
     </select>
 
-    <insert id="save" parameterType="com.kmall.admin.entity.GoodsEntity">
+    <insert id="save" parameterType="com.kmall.admin.entity.GoodsEntity" useGeneratedKeys="true" keyProperty="id">
         insert into mall_goods(
 			`category_id`,
 			`goods_sn`,

+ 6 - 0
kmall-admin/src/main/resources/mybatis/mapper/GoodsSpecificationDao.xml

@@ -118,4 +118,10 @@
         </foreach>
     </delete>
 
+    <select id="queryByGoodsId" resultMap="com.kmall.admin.entity.GoodsSpecificationEntity">
+        select *
+		from mall_goods_specification
+		where goods_id = #{goodsId}
+    </select>
+
 </mapper>

+ 1 - 2
kmall-admin/src/main/resources/mybatis/mapper/OrderRefundDao.xml

@@ -16,10 +16,9 @@
         <result property="approver" column="approver"/>
         <result property="approvalTime" column="approval_time"/>
         <result property="approvalRemark" column="approval_remark"/>
+        <result property="orderSn" column="order_sn"/>
         <result property="orderStatus" column="order_status"/>
         <result property="actualPrice" column="actual_price"/>
-        <result property="addTime" column="add_time"/>
-        <result property="orderType" column="order_type"/>
         <result property="userName" column="username"/>
     </resultMap>
 

+ 6 - 10
kmall-admin/src/main/resources/mybatis/mapper/SysPrinterDao.xml

@@ -8,29 +8,25 @@
         <result property="name" column="name"/>
         <result property="sn" column="sn"/>
         <result property="storeId" column="store_id"/>
+        <result property="storeName" column="store_name"/>
     </resultMap>
 
 	<select id="queryObject" resultType="com.kmall.admin.entity.SysPrinterEntity">
-		select
-			`id`,
-			`name`,
-			`sn`,
-			`store_id`
+		select sys_printer.*, mall_store.store_name
 		from sys_printer
+		left join mall_store on sys_printer.store_id = mall_store.id
 		where id = #{id}
 	</select>
 
 	<select id="queryList" resultType="com.kmall.admin.entity.SysPrinterEntity">
-		select
-    		`id`,
-    		`name`,
-    		`sn`,
-    		`store_id`
+		select sys_printer.*, mall_store.store_name
 		from sys_printer
+		left join mall_store on sys_printer.store_id = mall_store.id
 		WHERE 1=1
 		<if test="storeId != null">
 			AND store_id = #{storeId}
 		</if>
+
         <choose>
             <when test="sidx != null and sidx.trim() != ''">
                 order by ${sidx} ${order}

+ 38 - 10
kmall-admin/src/main/webapp/WEB-INF/page/shop/goods.html

@@ -106,7 +106,7 @@
                 <!--<i-form ref="formValidate" :model="goods" :rules="ruleValidate" :label-width="80">-->
                     <Form-item label="商品类型" prop="categoryId">
                         <!--<i-input v-model="goods.categoryName" @on-click="categoryTree" icon="eye" readonly="readonly" placeholder="商品类型"/>-->
-                        <i-select v-model="goods.category" placeholder="商品分类" filterable @on-change="changeCategories"
+                        <i-select v-model="goods.attributeCategory" placeholder="商品分类" filterable @on-change="changeCategories"
                                   label-in-value style="width: 268px;">
                             <i-option v-for="category in categories" :value="category.id"
                                       :key="category.id">{{category.name}}
@@ -156,13 +156,13 @@
                         </i-select>
                     </Form-item>
                     <Form-item label="市场价" prop="marketPrice">
-                        <Input-number :min="1" :step="1" v-model="goods.marketPrice" placeholder="市场价" style="width: 268px;"/>
+                        <Input-number :min="0.01" :step="0.01" v-model="goods.marketPrice" placeholder="市场价" style="width: 268px;"/>
                     </Form-item>
                     <Form-item label="零售价" prop="retailPrice">
-                        <Input-number :min="1" :step="1" v-model="goods.retailPrice" placeholder="零售价" style="width: 268px;"/>
+                        <Input-number :min="0.01" :step="0.01" v-model="goods.retailPrice" placeholder="零售价" style="width: 268px;"/>
                     </Form-item>
                     <Form-item label="商品税率(0.00)" prop="goodsRate">
-                        <Input-number :min="0" :step="0.01" v-model="goods.goodsRate" placeholder="商品税率" style="width: 268px;"/>
+                        <Input-number :min="0.001" :step="0.001" v-model="goods.goodsRate" placeholder="商品税率" style="width: 268px;"/>
                     </Form-item>
                     <Row>
                         <i-col span="16">
@@ -240,13 +240,13 @@
                     </Form-item>
                 <!--</i-form>-->
             </Tab-Pane>
-            <Tab-Pane label="规格" name="name3">
-                <!--<i-form ref="formValidate" :model="goods" :rules="ruleValidate" :label-width="80">-->
+            <!--<Tab-Pane label="规格" name="name3">
+                &lt;!&ndash;<i-form ref="formValidate" :model="goods" :rules="ruleValidate" :label-width="80">&ndash;&gt;
                     <table class="table table-bordered">
                         <tr>
                             <td style="text-align: center; width: 200px">编码</td>
                             <td style="text-align: center; width: 100px">商品默认</td>
-                            <!--<td style="text-align: center; width: 100px">规格</td>-->
+                            &lt;!&ndash;<td style="text-align: center; width: 100px">规格</td>&ndash;&gt;
                             <td style="text-align: center;">规格说明</td>
                             <td style="text-align: center; width: 90px">操作</td>
                         </tr>
@@ -274,8 +274,8 @@
                             </td>
                         </tr>
                     </table>
-                <!--</i-form>-->
-            </Tab-Pane>
+                &lt;!&ndash;</i-form>&ndash;&gt;
+            </Tab-Pane>-->
             <Tab-Pane label="详细描述" name="name4">
                 <template>
                     <div class="upload-list" v-for="item in uploadList">
@@ -347,7 +347,7 @@
             <Tab-Pane label="其他信息" name="name6">
                 <!--<i-form ref="formValidate" :model="goods" :rules="ruleValidate" :label-width="80">-->
                     <Form-item label="排序" prop="sortOrder">
-                        <Input-number :min="0" :step="1" v-model="goods.sortOrder" placeholder="排序" style="width: 188px;"/>
+                        <Input-number :min="1" :step="1" v-model="goods.sortOrder" placeholder="排序" style="width: 188px;"/>
                     </Form-item>
                     <Form-item label="上架" prop="isOnSale">
                         <Radio-group v-model="goods.isOnSale">
@@ -396,6 +396,34 @@
     <div id="qrcodeCanvas" style="margin: 53px 0 0 93px"></div>
 </div>
 
+
+<div id="qrcImg" style="display:none">
+    <img class="qrcImg align-center" src="">
+    <img class="upLogo align-center-middle" src="" >
+</div>
+
 <script src="${rc.contextPath}/js/shop/goods.js?_${date.systemTime}"></script>
 </body>
+
+<style>
+    .align-center {
+        position:absolute;
+        margin: auto;
+        top:0px;
+        bottom: 0px;
+        right: 0px;
+        left: 0px;
+    }
+
+    .align-center-middle {
+        position:absolute;
+        height: 30px;
+        width: 30px;
+        margin: auto;
+        top:0px;
+        bottom: 0px;
+        right: 0px;
+        left: 0px;
+    }
+</style>
 </html>

+ 3 - 1
kmall-admin/src/main/webapp/WEB-INF/page/shop/orderPrint.html

@@ -96,7 +96,9 @@
                         contentType: "application/json",
                         data: JSON.stringify(orderId),
                         success: function (r) {
-                            alert(r.msg);
+                            if (r.ticket != null) {
+                                alert('打印小票完成');
+                            }
                         }
                     });
                 })

+ 2 - 15
kmall-admin/src/main/webapp/WEB-INF/page/shop/orderrefund.html

@@ -76,25 +76,12 @@
             <Form-item label="审核备注" prop="approvalRemark">
                 <i-input v-model="orderRefund.approvalRemark" placeholder="审核备注"/>
             </Form-item>
-            <Form-item label="创建人编号" prop="createSn">
-                <i-input v-model="orderRefund.createSn" placeholder="创建人编号"/>
-            </Form-item>
             <Form-item label="创建时间,yyyy-MM-dd HH:mm:ss" prop="createTime">
                 <i-input v-model="orderRefund.createTime" placeholder="创建时间,yyyy-MM-dd HH:mm:ss"/>
             </Form-item>
-            <Form-item label="修改人编号" prop="moderSn">
-                <i-input v-model="orderRefund.moderSn" placeholder="修改人编号"/>
-            </Form-item>
-            <Form-item label="修改时间,yyyy-MM-dd HH:mm:ss" prop="modTime">
-                <i-input v-model="orderRefund.modTime" placeholder="修改时间,yyyy-MM-dd HH:mm:ss"/>
-            </Form-item>
-            <Form-item label="时间戳" prop="tstm">
-                <i-input v-model="orderRefund.tstm" placeholder="时间戳"/>
-            </Form-item>
-            <Form-item>
-                <i-button type="primary" @click="handleSubmit('formValidate')">提交</i-button>
+                <!--<i-button type="primary" @click="handleSubmit('formValidate')">提交</i-button>-->
                 <i-button type="warning" @click="reload" style="margin-left: 8px"/>返回</i-button>
-                <i-button type="ghost" @click="handleReset('formValidate')" style="margin-left: 8px">重置</i-button>
+                <!--<i-button type="ghost" @click="handleReset('formValidate')" style="margin-left: 8px">重置</i-button>-->
             </Form-item>
         </i-form>
 	</Card>

+ 2 - 0
kmall-admin/src/main/webapp/WEB-INF/page/sys/header.html

@@ -28,6 +28,8 @@
 <!--qrcode-->
 <script src="${rc.contextPath}/statics/qrcode/jquery.qrcode.min.js"></script>
 <script src="${rc.contextPath}/statics/qrcode/utf.js"></script>
+<!-- print -->
+<script src="${rc.contextPath}/statics/jquery.print.js"></script>
 <!--jqgrid-->
 <script src="${rc.contextPath}/statics/plugins/jqgrid/grid.locale-cn.js"></script>
 <script src="${rc.contextPath}/statics/plugins/jqgrid/jquery.jqGrid.min.js"></script>

+ 6 - 1
kmall-admin/src/main/webapp/WEB-INF/page/sys/printer.html

@@ -41,7 +41,12 @@
                 <i-input v-model="sysPrinter.sn" placeholder="打印机编号"/>
             </Form-item>
             <Form-item label="所属门店" prop="storeId">
-                <i-input v-model="sysPrinter.storeId" placeholder="所属门店"/>
+                <!--<i-input v-model="sysPrinter.storeId" placeholder="所属门店"/>-->
+                <i-select v-model="sysPrinter.storeId" filterable placeholder="所属门店"
+                          label-in-value>
+                    <i-option v-for="store in storeList" :value="store.id" :key="store.id">{{store.storeName}}
+                    </i-option>
+                </i-select>
             </Form-item>
             <Form-item>
                 <i-button type="primary" @click="handleSubmit('formValidate')">提交</i-button>

+ 1 - 0
kmall-admin/src/main/webapp/WEB-INF/web.xml

@@ -14,6 +14,7 @@
             classpath:spring/spring-wx-pay.xml,
             classpath:spring/spring-jdbc.xml,,
             classpath:spring/spring-oms-merch.xml,
+            classpath:spring/spring-print-ticket.xml,
             classpath:spring/spring-common.xml,
             classpath*:kmall-*.xml
         </param-value>

+ 35 - 47
kmall-admin/src/main/webapp/js/shop/goods.js

@@ -124,41 +124,11 @@ var vm = new Vue({
         uploadList: [],
         imgName: '',
         visible: false,
-        goods: {primaryPicUrl: '', listPicUrl: '', categoryId: '', isOnSale: 1, isAppExclusive: 0, isLimited: 0, isHot: 0, categoryName: ''},
+        goods: {primaryPicUrl: '', listPicUrl: '', categoryId: '', isOnSale: 1, isAppExclusive: 0, isLimited: 0, isHot: 0, categoryName: '', retailPrice: '', marketPrice: '', goodsRate: '', sortOrder: '' },
         ruleValidate: {
-            name: [
+            /*name: [
                 {required: true, message: '名称不能为空', trigger: 'blur'}
-            ],
-            sku: [
-                {required: true, message: 'SKU不能为空', trigger: 'blur'}
-            ],
-            goodsBizType: [
-                {required: true, message: '货品业务类型不能为空', trigger: 'blur'}
-            ],
-            brand: [
-                {required: true, message: '产品品牌不能为空', trigger: 'blur'}
-            ],
-            prodBarcode: [
-                {required: true, message: '产品条码不能为空', trigger: 'blur'}
-            ],
-            cusRecCode: [
-                {required: true, message: '海关备案编号不能为空', trigger: 'blur'}
-            ],
-            unitCode: [
-                {required: true, message: '计量单位代码不能为空', trigger: 'blur'}
-            ],
-            cusGoodsCode: [
-                {required: true, message: '海关商品编码不能为空', trigger: 'blur'}
-            ],
-            ciqProdMdel: [
-                {required: true, message: '国检规格型号不能为空', trigger: 'blur'}
-            ],
-            oriCntCode: [
-                {required: true, message: '原产国代码不能为空', trigger: 'blur'}
-            ],
-            cusDeclEle: [
-                {required: true, message: '海关申报要素不能为空', trigger: 'blur'}
-            ]
+            ]*/
         },
         q: {name: '', goodsSn: '', category: '', categoryTwo: ''},
         attributes: [],
@@ -212,13 +182,13 @@ var vm = new Vue({
             }
         },
         query: function () {
-            vm.reload();
+            vm.reload(1);
         },
         add: function () {
             vm.showList = false;
             vm.title = "新增";
             vm.uploadList = [];
-            vm.goods = {primaryPicUrl: '', listPicUrl: '', categoryId: '', isOnSale: 0, isAppExclusive: 0, isLimited: 0, isHot: 0, categoryName: ''};
+            vm.goods = {primaryPicUrl: '', listPicUrl: '', categoryId: '', isOnSale: 1, isAppExclusive: 0, isLimited: 0, isHot: 0, categoryName: '', retailPrice: '', marketPrice: '', goodsRate: '', sortOrder: '' };
             $('#goodsDesc').editable('setHTML', '');
             vm.getCategory();
             vm.macros = [];
@@ -397,7 +367,9 @@ var vm = new Vue({
         getInfo: function (id) {
             $.get("../goods/info/" + id, function (r) {
                 vm.goods = r.goods;
-                vm.getCategory();
+                // vm.getCategory();
+                vm.goods.category = r.goods.attributeCategory;
+                vm.changeCategories(vm.goods.category);
                 if (r.goods.attributeEntityList.length > 0) {
                     vm.attributeEntityList = r.goods.attributeEntityList;
                 } else {
@@ -413,7 +385,10 @@ var vm = new Vue({
         },
         reload: function (event) {
             vm.showList = true;
-            var page = $("#jqGrid").jqGrid('getGridParam', 'page');
+            let page = event;
+            if (event != 1) {
+                page = $("#jqGrid").jqGrid('getGridParam', 'page');
+            }
             $("#jqGrid").jqGrid('setGridParam', {
                 postData: {
                     'name': vm.q.name,
@@ -427,7 +402,7 @@ var vm = new Vue({
         },
         getCategory: function () {
             //加载分类树
-            $.get("../category/queryAll", function (r) {
+            $.get("../category/query", function (r) {
                 ztree = $.fn.zTree.init($("#categoryTree"), setting, r.list);
                 var node = ztree.getNodeByParam("id", vm.goods.categoryId);
                 if (node) {
@@ -448,6 +423,10 @@ var vm = new Vue({
                 btn: ['确定', '取消'],
                 btn1: function (index) {
                     var node = ztree.getSelectedNodes();
+                    if (node[0].isParent) {
+                        alert("只能选择");
+                        return;
+                    }
                     //选择上级菜单
                     vm.goods.categoryId = node[0].id;
                     vm.goods.categoryName = node[0].name;
@@ -475,8 +454,8 @@ var vm = new Vue({
         },
         handleRemove(file) {
             // 从 upload 实例删除数据
-            const fileList = this.uploadList;
-            this.uploadList.splice(fileList.indexOf(file), 1);
+            const fileList = vm.uploadList;
+            vm.uploadList.splice(fileList.indexOf(file), 1);
         },
         handleSuccess(res, file) {
             // 因为上传过程为实例,这里模拟添加 url
@@ -485,7 +464,7 @@ var vm = new Vue({
             vm.uploadList.add(file);
         },
         handleBeforeUpload() {
-            const check = this.uploadList.length < 5;
+            const check = vm.uploadList.length < 5;
             if (!check) {
                 this.$Notice.warning({
                     title: '最多只能上传 5 张图片。'
@@ -494,9 +473,9 @@ var vm = new Vue({
             return check;
         },
         handleSubmit: function (name) {
-            handleSubmitValidate(this, name, function () {
+            // handleSubmitValidate(this, name, function () {
                 vm.saveOrUpdate()
-            });
+            // });
         },
         handleFormatError: function (file) {
             this.$Notice.warning({
@@ -535,26 +514,35 @@ var vm = new Vue({
             $("#qrcodeCanvas").qrcode({
                 render : "canvas",    //设置渲染方式,有table和canvas,使用canvas方式渲染性能相对来说比较好
                 text : "emato../goods/goods?id=" + id,    //扫描二维码后显示的内容,可以直接填一个网址,扫描二维码后自动跳向该链接
-                width : "200",               //二维码的宽度
-                height : "200",              //二维码的高度
+                width : "100",               //二维码的宽度
+                height : "100",              //二维码的高度
                 background : "#ffffff",       //二维码的后景色
                 foreground : "#000000",        //二维码的前景色
-                src: ''             //二维码中间的图片
+                src: '../../statics/img/logo.jpg'             //二维码中间的图片
             });
 
+            var qrcSrc = $("canvas")[0].toDataURL();
+            $("#qrcImg .qrcImg").attr("src", qrcSrc);
+            var upLogSrc = "../../statics/img/logo.jpg";
+            $("#qrcImg .upLogo").attr("src", upLogSrc);
+            $("#qrcImg").show();//隐藏canvas部分
+
             openWindow({
                 title: "二维码",
                 area: ['400px', '400px'],
                 content: jQuery("#qrcode"),
                 btn: ["打印"],
                 btn1: function (index) {
+                    $("#qrcImg").print({
+
+                    });
                     layer.close(index);
                 }
             });
         }
     },
     mounted() {
-        this.uploadList = this.$refs.upload.fileList;
+        // this.uploadList = this.$refs.upload.fileList;
         $.get("../category/getCategorySelect", function (r) {
             vm.queryCategories = r.list;
             vm.categories = r.list;

+ 8 - 6
kmall-admin/src/main/webapp/js/shop/order.js

@@ -21,7 +21,7 @@ $(function () {
         datatype: "json",
         colModel: [
             {label: 'id', name: 'id', index: 'id', key: true, hidden: true},
-            {label: '订单号', name: 'orderSn', index: 'order_sn', width: 160},
+            {label: '订单号', name: 'orderSn', index: 'order_sn', width: 100},
             {label: '会员', name: 'userName', index: 'user_name', width: 80},
             /*{
                 label: '订单类型', name: 'orderType', index: 'order_type', width: 80,
@@ -102,16 +102,18 @@ $(function () {
                 }
             },
             {
-                label: '操作', width: 210, align: 'center', sortable: false,
+                label: '操作', width: 180, sortable: false,
                 formatter: function (value, col, row) {
-                    let htmlStr = '';
+                    let htmlStr = '<button class="btn btn-outline btn-info" onclick="vm.lookDetail(' + row.id + ')"><i class="fa fa-info-circle"></i>详情</button>&nbsp;';
+                    //订单状态: 0订单创建成功等待付款, 101订单已取消, 102订单已删除,201订单已付款,等待发货,300订单已发货, 301用户确认收货,401 没有发货,退款 402 已收货,退款退货
                     if (hasPermission('order:refund')) {
+                        if (row.orderStatus == 201) {
+                            htmlStr += '<button class="btn btn-outline btn-primary" onclick="vm.printDetail(' + row.id + ')"><i class="fa fa-print"></i>打印</button>&nbsp;';
+                        }
                         if (row.orderStatus == 201 || row.orderStatus == 301 || row.orderStatus == 300) {
-                            htmlStr = '<button class="btn btn-outline btn-danger" onclick="vm.refundUpdate(' + row.id + ')"><i class="fa fa-times-circle-o"></i>&nbsp;取消</button>&nbsp;';
+                            htmlStr += '<button class="btn btn-outline btn-danger" onclick="vm.refundUpdate(' + row.id + ')"><i class="fa fa-times-circle-o"></i>&nbsp;取消</button>&nbsp;';
                         }
                     }
-                    htmlStr += '<button class="btn btn-outline btn-info" onclick="vm.lookDetail(' + row.id + ')"><i class="fa fa-info-circle"></i>详情</button>&nbsp;' +
-                        '<button class="btn btn-outline btn-primary" onclick="vm.printDetail(' + row.id + ')"><i class="fa fa-print"></i>打印</button>';
                     return htmlStr;
                 }
             }

+ 61 - 16
kmall-admin/src/main/webapp/js/shop/orderrefund.js

@@ -4,34 +4,72 @@ $(function () {
         datatype: "json",
         colModel: [
 			{label: 'id', name: 'id', index: 'id', key: true, hidden: true},
-			{label: '订单Id', name: 'orderId', index: 'order_id', width: 80},
-			{label: '用户Id', name: 'userId', index: 'user_id', width: 80},
+			{label: '订单编号', name: 'orderSn', index: 'order_sn', width: 80},
+			{label: '用户名称', name: 'userName', index: 'username', width: 80},
 			{label: '商户退款单号', name: 'outRefundNo', index: 'out_refund_no', width: 80},
-			{label: '微信退款单号', name: 'refundId', index: 'refund_id', width: 80},
-			{label: '退款类型', name: 'refundType', index: 'refund_type', width: 80},
-			{label: '退款时间', name: 'refundTime', index: 'refund_time', width: 80},
-			{label: '退款金额', name: 'refundMoney', index: 'refund_money', width: 80},
-			{label: '退款状态', name: 'refundStatus', index: 'refund_status', width: 80},
-			{label: '退款原因', name: 'refundReason', index: 'refund_reason', width: 80},
-			{label: '退款入账账户', name: 'refundRecvAccout', index: 'refund_recv_accout', width: 80},
-			{label: '审核人', name: 'approver', index: 'approver', width: 80},
-			{label: '审核时间', name: 'approvalTime', index: 'approval_time', width: 80},
+            {
+                label: '申请时间', name: 'createTime', index: 'create_time', width: 100,
+                formatter: function (value) {
+                    return transDate(value, 'yyyy-MM-dd hh:mm:ss');
+                }
+            },
+			{label: '微信退款单号', name: 'refundId', index: 'refund_id', width: 80, hidden: true},
+			{
+			    label: '退款类型', name: 'refundType', index: 'refund_type', width: 80,
+                formatter: function (value) {
+                    if (value == '1') {
+                        return '用户全额退款';
+                    } else if (value == '2') {
+                        return '系统部分退款';
+                    }
+                    return value;
+                }
+            },
+			{
+			    label: '退款时间', name: 'refundTime', index: 'refund_time', width: 100,
+                formatter: function (value) {
+                    return transDate(value, 'yyyy-MM-dd hh:mm:ss');
+                }
+            },
+			{label: '退款金额', name: 'refundMoney', index: 'refund_money', width: 80, align: 'right'},
+			{
+			    label: '退款状态', name: 'refundStatus', index: 'refund_status', width: 80,
+                formatter: function (value) {
+                    if (value == '1') {
+                        return '申请中';
+                    } else if (value == '2') {
+                        return '退款成功';
+                    } else if (value == '3') {
+                        return '已拒绝';
+                    }
+                    return value;
+                }
+            },
+			{label: '退款原因', name: 'refundReason', index: 'refund_reason', width: 200},
+			{label: '退款入账账户', name: 'refundRecvAccout', index: 'refund_recv_accout', width: 80, hidden: true},
+			{label: '审核人', name: 'approver', index: 'approver', width: 80, hidden: true},
+			{
+			    label: '审核时间', name: 'approvalTime', index: 'approval_time', width: 100,
+                formatter: function (value) {
+                    return transDate(value, 'yyyy-MM-dd hh:mm:ss');
+                }
+            },
 			{label: '审核备注', name: 'approvalRemark', index: 'approval_remark', width: 80},
             {
-                label: '操作', width: 210, align: 'center', sortable: false,
+                label: '操作', width: 180, sortable: false,
                 formatter: function (value, col, row) {
-                    let htmlStr = '';
+                    let htmlStr = '<button class="btn btn-outline btn-info" onclick="vm.lookDetail(' + row.id + ')"><i class="fa fa-info-circle"></i>详情</button>&nbsp;';
                     if (hasPermission('order:refund')) {
                         if (row.refundStatus == 1) {
-                            htmlStr = '<button class="btn btn-outline btn-danger" onclick="vm.refundUpdate(' + row.orderId + ')"><i class="fa fa-times-circle-o"></i>&nbsp;确认退款</button>&nbsp;';
+                            htmlStr += '<button class="btn btn-outline btn-primary" onclick="vm.refundUpdate(' + row.orderId + ')"><i class="fa fa-check-circle"></i>&nbsp;退款</button>&nbsp;' +
+                                '<button class="btn btn-outline btn-danger" onclick="vm.printDetail(' + row.id + ')"><i class="fa fa-times-circle-o"></i>审核</button>&nbsp;';
                         }
                     }
-                    htmlStr += '<button class="btn btn-outline btn-info" onclick="vm.lookDetail(' + row.id + ')"><i class="fa fa-info-circle"></i>详情</button>';
                     return htmlStr;
                 }
             }],
 		viewrecords: true,
-        height: 385,
+        height: 645,
         rowNum: 10,
         rowList: [10, 30, 50],
         rownumbers: true,
@@ -178,6 +216,13 @@ let vm = new Vue({
                     }
                 }
             });
+        },
+        lookDetail: function (rowId) { //第三步:定义编辑操作
+            vm.showList = false;
+            vm.title = "维权订单详情";
+            $.get("../orderrefund/info/" + rowId, function (r) {
+                vm.orderRefund = r.orderRefund;
+            });
         }
 	}
 });

+ 2 - 2
kmall-admin/src/main/webapp/js/sys/macro.js

@@ -12,7 +12,7 @@ function initialPage() {
 function getGrid() {
     var colunms = TreeGrid.initColumn();
     var table = new TreeTable(TreeGrid.id, '../sys/macro/queryAll', colunms);
-    table.setExpandColumn(2);
+    table.setExpandColumn(1);
     table.setIdField("id");
     table.setCodeField("id");
     table.setParentCodeField("parentId");
@@ -34,7 +34,7 @@ var TreeGrid = {
 TreeGrid.initColumn = function () {
     var columns = [
         {field: 'selectItem', radio: true},
-        {title: 'id', field: 'id', align: 'center', valign: 'middle', width: '50px'},
+        // {title: 'id', field: 'id', align: 'center', valign: 'middle', width: '50px', hidden: true},
         {title: '名称', field: 'name', align: 'center', valign: 'middle', width: '50px'},
         {title: '值', field: 'value', align: 'center', valign: 'middle', width: '50px'},
         {

+ 17 - 3
kmall-admin/src/main/webapp/js/sys/printer.js

@@ -6,7 +6,7 @@ $(function () {
             {label: 'id', name: 'id', index: 'id', key: true, hidden: true},
             {label: '打印机名称', name: 'name', index: 'name', width: 80},
             {label: '打印机编号', name: 'sn', index: 'sn', width: 80},
-            {label: '所属门店', name: 'storeId', index: 'store_id', width: 80}],
+            {label: '所属门店', name: 'storeName', index: 'store_name', width: 80}],
         viewrecords: true,
         height: 385,
         rowNum: 10,
@@ -41,14 +41,26 @@ let vm = new Vue({
         sysPrinter: {},
         ruleValidate: {
             name: [
-                {required: true, message: '名称不能为空', trigger: 'blur'}
+                {required: true, message: '打印机名称不能为空', trigger: 'blur'}
+            ],
+            sn: [
+                {required: true, message: '打印机编号不能为空', trigger: 'blur'}
+            ],
+            storeId: [
+                {required: true, message: '所属门店不能为空', trigger: 'blur'}
             ]
         },
         q: {
             name: ''
-        }
+        },
+        storeList: []
     },
     methods: {
+        getStoreList: function() {
+            $.get("../store/queryAll", function (r) {
+                vm.storeList = r.list;
+            });
+        },
         query: function () {
             vm.reload();
         },
@@ -56,6 +68,8 @@ let vm = new Vue({
             vm.showList = false;
             vm.title = "新增";
             vm.sysPrinter = {};
+            vm.storeList = [];
+            vm.getStoreList();
         },
         update: function (event) {
             let id = getSelectedRow();

+ 1 - 1
kmall-admin/src/main/webapp/statics/css/login.css

@@ -2,7 +2,7 @@ html{height: 100%;}
 body.signin {
     background: #18c8f6;
     height: auto;
-    background:url("../img/login.jpeg") no-repeat center fixed;
+    background:url("../img/login.jpg") no-repeat center fixed;
     -webkit-background-size: cover;
     -moz-background-size: cover;
     -o-background-size: cover;

BIN=BIN
kmall-admin/src/main/webapp/statics/img/logo.jpg


+ 237 - 0
kmall-admin/src/main/webapp/statics/jquery.print.js

@@ -0,0 +1,237 @@
+/* @license
+ * jQuery.print, version 1.3.2
+ *  (c) Sathvik Ponangi, Doers' Guild
+ * Licence: CC-By (http://creativecommons.org/licenses/by/3.0/)
+ *--------------------------------------------------------------------------*/
+(function ($) {
+    "use strict";
+    // A nice closure for our definitions
+    function getjQueryObject(string) {
+        // Make string a vaild jQuery thing
+        var jqObj = $("");
+        try {
+            jqObj = $(string)
+                .clone();
+        } catch (e) {
+            jqObj = $("<span />")
+                .html(string);
+        }
+        return jqObj;
+    }
+
+    function printFrame(frameWindow) {
+        // Print the selected window/iframe
+        var def = $.Deferred();
+        try {
+            setTimeout(function () {
+                // Fix for IE : Allow it to render the iframe
+                frameWindow.focus();
+                try {
+                    // Fix for IE11 - printng the whole page instead of the iframe content
+                    if (!frameWindow.document.execCommand('print', false, null)) {
+                        // document.execCommand returns false if it failed -http://stackoverflow.com/a/21336448/937891
+                        frameWindow.print();
+                    }
+                } catch (e) {
+                    frameWindow.print();
+                }
+                frameWindow.close();
+                def.resolve();
+            }, 250);
+        } catch (err) {
+            def.reject(err);
+        }
+        return def;
+    }
+
+    function printContentInNewWindow(content) {
+        // Open a new window and print selected content
+        var w = window.open();
+        w.document.write(content);
+        w.document.close();
+        return printFrame(w);
+    }
+
+    function isNode(o) {
+        /* http://stackoverflow.com/a/384380/937891 */
+        return !!(typeof Node === "object" ? o instanceof Node : o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string");
+    }
+    $.print = $.fn.print = function () {
+        // Print a given set of elements
+        var options, $this, self = this;
+        console.log("Printing", this, arguments);
+        if (self instanceof $) {
+            // Get the node if it is a jQuery object
+            self = self.get(0);
+        }
+        if (isNode(self)) {
+            // If `this` is a HTML element, i.e. for
+            // $(selector).print()
+            $this = $(self);
+            if (arguments.length > 0) {
+                options = arguments[0];
+            }
+        } else {
+            if (arguments.length > 0) {
+                // $.print(selector,options)
+                $this = $(arguments[0]);
+                if (isNode($this[0])) {
+                    if (arguments.length > 1) {
+                        options = arguments[1];
+                    }
+                } else {
+                    // $.print(options)
+                    options = arguments[0];
+                    $this = $("html");
+                }
+            } else {
+                // $.print()
+                $this = $("html");
+            }
+        }
+        // Default options
+        var defaults = {
+            globalStyles: true,
+            mediaPrint: false,
+            stylesheet: null,
+            noPrintSelector: ".no-print",
+            iframe: true,
+            append: null,
+            prepend: null,
+            manuallyCopyFormValues: true,
+            deferred: $.Deferred()
+        };
+        // Merge with user-options
+        options = $.extend({}, defaults, (options || {}));
+        var $styles = $("");
+        if (options.globalStyles) {
+            // Apply the stlyes from the current sheet to the printed page
+            // $styles = $("style, link, meta, title");
+            $styles = $("style, link, meta");
+        } else if (options.mediaPrint) {
+            // Apply the media-print stylesheet
+            $styles = $("link[media=print]");
+        }
+        if (options.stylesheet) {
+            // Add a custom stylesheet if given
+            $styles = $.merge($styles, $('<link rel="stylesheet" href="' + options.stylesheet + '">'));
+        }
+        // Create a copy of the element to print
+        var copy = $this.clone();
+        // Wrap it in a span to get the HTML markup string
+        copy = $("<span/>")
+            .append(copy);
+        // Remove unwanted elements
+        copy.find(options.noPrintSelector)
+            .remove();
+        // Add in the styles
+        copy.append($styles.clone());
+        // Appedned content
+        copy.append(getjQueryObject(options.append));
+        // Prepended content
+        copy.prepend(getjQueryObject(options.prepend));
+        if (options.manuallyCopyFormValues) {
+            // Manually copy form values into the HTML for printing user-modified input fields
+            // http://stackoverflow.com/a/26707753
+            copy.find("input")
+                .each(function () {
+                    var $field = $(this);
+                    if ($field.is("[type='radio']") || $field.is("[type='checkbox']")) {
+                        if ($field.prop("checked")) {
+                            $field.attr("checked", "checked");
+                        }
+                    } else {
+                        $field.attr("value", $field.val());
+                    }
+                });
+            copy.find("select").each(function () {
+                var $field = $(this);
+                $field.find(":selected").attr("selected", "selected");
+            });
+            copy.find("textarea").each(function () {
+                // Fix for https://github.com/DoersGuild/jQuery.print/issues/18#issuecomment-96451589
+                var $field = $(this);
+                $field.text($field.val());
+            });
+        }
+        // Get the HTML markup string
+        var content = copy.html();
+        // Notify with generated markup & cloned elements - useful for logging, etc
+        try {
+            options.deferred.notify('generated_markup', content, copy);
+        } catch (err) {
+            console.warn('Error notifying deferred', err);
+        }
+        // Destroy the copy
+        copy.remove();
+        if (options.iframe) {
+            // Use an iframe for printing
+            try {
+                var $iframe = $(options.iframe + "");
+                var iframeCount = $iframe.length;
+                if (iframeCount === 0) {
+                    // Create a new iFrame if none is given
+                    $iframe = $('<iframe height="0" width="0" border="0" wmode="Opaque"/>')
+                        .prependTo('body')
+                        .css({
+                            "position": "absolute",
+                            "top": -999,
+                            "left": -999
+                        });
+                }
+                var w, wdoc;
+                w = $iframe.get(0);
+                w = w.contentWindow || w.contentDocument || w;
+                wdoc = w.document || w.contentDocument || w;
+                wdoc.open();
+                wdoc.write(content);
+                wdoc.close();
+                printFrame(w)
+                    .done(function () {
+                        // Success
+                        setTimeout(function () {
+                            // Wait for IE
+                            if (iframeCount === 0) {
+                                // Destroy the iframe if created here
+                                $iframe.remove();
+                            }
+                        }, 100);
+                    })
+                    .fail(function (err) {
+                        // Use the pop-up method if iframe fails for some reason
+                        console.error("Failed to print from iframe", err);
+                        printContentInNewWindow(content);
+                    })
+                    .always(function () {
+                        try {
+                            options.deferred.resolve();
+                        } catch (err) {
+                            console.warn('Error notifying deferred', err);
+                        }
+                    });
+            } catch (e) {
+                // Use the pop-up method if iframe fails for some reason
+                console.error("Failed to print from iframe", e.stack, e.message);
+                printContentInNewWindow(content)
+                    .always(function () {
+                        try {
+                            options.deferred.resolve();
+                        } catch (err) {
+                            console.warn('Error notifying deferred', err);
+                        }
+                    });
+            }
+        } else {
+            // Use a new window for printing
+            printContentInNewWindow(content)
+                .always(function () {
+                    try {
+                        options.deferred.resolve();
+                    } catch (err) {
+                        console.warn('Error notifying deferred', err);
+                    }
+                });
+        }
+        return this;
+    };
+})(jQuery);

+ 4 - 4
kmall-api/kmall-api.iml

@@ -92,7 +92,7 @@
     <orderEntry type="module-library">
       <library name="Maven: com.alibaba:jconsole:1.8.0">
         <CLASSES>
-          <root url="jar://D:/Program Files/Java/jdk1.8.0_131/lib/jconsole.jar!/" />
+          <root url="jar://C:/Program Files/Java/jdk1.8.0_144/lib/jconsole.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES />
@@ -101,7 +101,7 @@
     <orderEntry type="module-library">
       <library name="Maven: com.alibaba:tools:1.8.0">
         <CLASSES>
-          <root url="jar://D:/Program Files/Java/jdk1.8.0_131/lib/tools.jar!/" />
+          <root url="jar://C:/Program Files/Java/jdk1.8.0_144/lib/tools.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES />
@@ -114,14 +114,14 @@
     <orderEntry type="library" name="Maven: commons-codec:commons-codec:1.10" level="project" />
     <orderEntry type="library" name="Maven: commons-configuration:commons-configuration:1.10" level="project" />
     <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.1.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.9.3" level="project" />
+    <orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
     <orderEntry type="library" name="Maven: org.apache.shiro:shiro-core:1.3.2" level="project" />
-    <orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.8.3" level="project" />
     <orderEntry type="library" name="Maven: org.apache.shiro:shiro-spring:1.3.2" level="project" />
     <orderEntry type="library" name="Maven: org.apache.shiro:shiro-web:1.3.2" level="project" />
     <orderEntry type="library" name="Maven: com.github.axet:kaptcha:0.0.9" level="project" />
     <orderEntry type="library" name="Maven: com.jhlabs:filters:2.0.235" level="project" />
     <orderEntry type="library" name="Maven: org.apache.velocity:velocity:1.7" level="project" />
-    <orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.1" level="project" />
     <orderEntry type="library" name="Maven: org.apache.velocity:velocity-tools:2.0" level="project" />
     <orderEntry type="library" name="Maven: commons-digester:commons-digester:1.8" level="project" />
     <orderEntry type="library" name="Maven: jstl:jstl:1.2" level="project" />

+ 73 - 0
kmall-common/src/main/java/com/kmall/common/service/print/ticket/PrintTicketProperties.java

@@ -0,0 +1,73 @@
+package com.kmall.common.service.print.ticket;
+
+import java.io.Serializable;
+
+/**
+ * @author duanqu
+ * @version 1.0
+ * 2018-11-2 11:40
+ */
+public class PrintTicketProperties implements Serializable {
+
+    private static final long serialVersionUID = -6392534991404517905L;
+
+    private String address;
+
+    private String serviceTel;
+
+    private String summary;
+
+    private String url1;
+
+    private String url2;
+
+    private String url3;
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getServiceTel() {
+        return serviceTel;
+    }
+
+    public void setServiceTel(String serviceTel) {
+        this.serviceTel = serviceTel;
+    }
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+
+    public String getUrl1() {
+        return url1;
+    }
+
+    public void setUrl1(String url1) {
+        this.url1 = url1;
+    }
+
+    public String getUrl2() {
+        return url2;
+    }
+
+    public void setUrl2(String url2) {
+        this.url2 = url2;
+    }
+
+    public String getUrl3() {
+        return url3;
+    }
+
+    public void setUrl3(String url3) {
+        this.url3 = url3;
+    }
+}

+ 40 - 0
kmall-common/src/main/java/com/kmall/common/service/print/ticket/PrintTicketPropertiesBuilder.java

@@ -0,0 +1,40 @@
+package com.kmall.common.service.print.ticket;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author duanqu
+ * @since 1.0
+ * 2018-11-2
+ */
+@Component
+public class PrintTicketPropertiesBuilder {
+    private static final Logger logger = LoggerFactory.getLogger(PrintTicketPropertiesBuilder.class);
+
+    @Autowired
+    @Qualifier("printTicketProperties")
+    private PrintTicketProperties printTicketProperties;
+
+    private static PrintTicketProperties ticketProp;
+
+    /**
+     * Bean 向静态变量赋值
+     */
+    @PostConstruct
+    private void init(){
+        logger.info("PrintTicketProperties初始化开始......");
+        ticketProp = printTicketProperties;
+    }
+
+    public static PrintTicketProperties instance(){
+        logger.info("PrintTicketProperties实例返回");
+        return ticketProp;
+    }
+
+}

+ 23 - 0
kmall-common/src/main/java/com/kmall/common/service/print/ticket/PrintTicketPropertiesConfiguration.java

@@ -0,0 +1,23 @@
+package com.kmall.common.service.print.ticket;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author duanqu
+ * @since 1.0
+ * 2018-11-2
+ */
+@Component
+public class PrintTicketPropertiesConfiguration {
+
+    @Autowired
+    private PrintTicketProperties printTicketProperties;
+
+    @Bean
+    public PrintTicketProperties printTicketProperties(){
+        return printTicketProperties;
+    }
+
+}

+ 222 - 0
kmall-common/src/main/java/com/kmall/common/utils/MapBeanUtil.java

@@ -0,0 +1,222 @@
+package com.kmall.common.utils;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * map bean 相互转换
+ *
+ * @author Scott Chen
+ * @version 1.0
+ * 2017-09-19 16:58
+ */
+public class MapBeanUtil {
+    private static final Logger logger = LoggerFactory.getLogger(MapBeanUtil.class);
+
+
+    /**
+     * map 转 bean by BeanUtils
+     * @param map
+     * @param beanClass
+     * @param <T>
+     * @return
+     * @throws Exception
+     */
+    public static <T> T toObject(Map<String, Object> map, Class<T> beanClass) throws Exception {
+        T bean;
+        if (map == null) {
+            return null;
+        }
+        bean = beanClass.newInstance();
+        BeanUtils.populate(bean, map);
+
+        return bean;
+    }
+
+    /**
+     * bean 转 map   by Introspector
+     *
+     * @param bean
+     * @return
+     */
+    public static <T> Map<String, Object> fromObject(T bean) {
+        if (bean == null) {
+            return null;
+        }
+        return fromObjectByIntrospector(bean);
+    }
+
+    //----------  使用Introspector进行转换 ----------
+
+    /**
+     * map 转 bean by Introspector
+     * @param map
+     * @param beanClass
+     * @param <T>
+     * @return
+     */
+    public static <T> T toObjectByIntrospector(Map<String, Object> map, Class<T> beanClass) {
+        if (map == null) {
+            return null;
+        }
+        T obj = null;
+
+        try {
+            obj = beanClass.newInstance();
+
+            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
+            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
+            for (PropertyDescriptor property : propertyDescriptors) {
+                Method setter = property.getWriteMethod();
+                if (setter != null) {
+                    setter.invoke(obj, map.get(property.getName()));
+                }
+            }
+        } catch (InstantiationException e) {
+            String info = "[toObjectByIntrospector] InstantiationException::Map转换Bean失败";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            String info = "[toObjectByIntrospector] IllegalAccessException::Map转换Bean失败";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (IntrospectionException e) {
+            String info = "[toObjectByIntrospector] IntrospectionException::Map转换Bean失败";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            String info = "[toObjectByIntrospector] InvocationTargetException::Map转换Bean失败";
+            logger.error(info);
+            e.printStackTrace();
+        }
+        return obj;
+    }
+
+    /**
+     * bean 转 map   by Introspector
+     * @param obj
+     * @param <T>
+     * @return
+     */
+    public static <T> Map<String, Object> fromObjectByIntrospector(T obj) {
+        if (obj == null) {
+            return null;
+        }
+
+        Map<String, Object> map = new HashMap<String, Object>();
+
+        try {
+            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
+            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
+            for (PropertyDescriptor property : propertyDescriptors) {
+                String key = property.getName();
+                if (key.compareToIgnoreCase("class") == 0) {
+                    continue;
+                }
+                Method getter = property.getReadMethod();
+                Object value = getter != null ? getter.invoke(obj) : null;
+                map.put(key, value);
+            }
+        } catch (IntrospectionException e) {
+            String info = "[fromObjectByIntrospector] IntrospectionException::Bean转换Map失败";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            String info = "[fromObjectByIntrospector] InvocationTargetException::Bean转换Map失败";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (IllegalArgumentException e) {
+            String info = "[fromObjectByIntrospector] IllegalArgumentException::Bean转换Map失败,";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            String info = "[fromObjectByIntrospector] IllegalAccessException::Bean转换Map失败";
+            logger.error(info);
+            e.printStackTrace();
+        }
+
+        return map;
+    }
+
+    //----------  使用reflect进行转换 ----------
+
+    /**
+     * map 转 bean  by Reflect
+     * @param map
+     * @param beanClass
+     * @param <T>
+     * @return
+     */
+    public static <T> T toObjectByReflect(Map<String, Object> map, Class<T> beanClass) {
+        if (map == null) {
+            return null;
+        }
+
+        T obj = null;
+        try {
+            obj = beanClass.newInstance();
+
+            Field[] fields = obj.getClass().getDeclaredFields();
+            for (Field field : fields) {
+                int mod = field.getModifiers();
+                if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
+                    continue;
+                }
+
+                field.setAccessible(true);
+                field.set(obj, map.get(field.getName()));
+            }
+        }catch (InstantiationException e) {
+            String info = "[toObjectByReflect] InstantiationException::Map转换Bean失败";
+            logger.error(info);
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            String info = "[toObjectByReflect] IllegalAccessException::Map转换Bean失败";
+            logger.error(info);
+            e.printStackTrace();
+        }
+        return obj;
+    }
+
+    /**
+     * bean 转 map   by Reflect
+     * @param obj
+     * @param <T>
+     * @return
+     */
+    public static <T> Map<String, Object> fromObjectByReflect(T obj) {
+        if (obj == null) {
+            return null;
+        }
+
+        Map<String, Object> map = new HashMap<String, Object>();
+
+        try{
+            Field[] declaredFields = obj.getClass().getDeclaredFields();
+            for (Field field : declaredFields) {
+                field.setAccessible(true);
+                map.put(field.getName(), field.get(obj));
+            }
+        } catch (IllegalAccessException e) {
+            String info = "[fromObjectByReflect] IllegalAccessException::Bean转换Map失败";
+            logger.error(info);
+            e.printStackTrace();
+        }
+
+        return map;
+    }
+
+
+}

+ 61 - 0
kmall-common/src/main/java/com/kmall/common/utils/ValidatorUtil.java

@@ -0,0 +1,61 @@
+package com.kmall.common.utils;
+
+import com.kmall.common.utils.R;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Map;
+
+public class ValidatorUtil {
+
+    /**
+     * 非空校验
+     *
+     * @param awaitValideField 要验证的字段 <code>{"字段名","错误消息"}</code>
+     * @param valideDate       验证数据
+     * @return
+     */
+    public static R isEmpty(Map<String, Object> awaitValideField, Map<String, Object> valideDate) {
+        return isEmpty(awaitValideField, valideDate, null, null);
+    }
+
+
+    /**
+     * 非空校验
+     *
+     * @param awaitValideField 要验证的字段 <code>{"字段名","错误消息"}</code>
+     * @param valideDate       验证数据
+     * @param code             验证失败消息码
+     * @param msg              验证失败消息
+     * @return
+     */
+    public static R isEmpty(Map<String, Object> awaitValideField, Map<String, Object> valideDate, String code, String msg) {
+
+        code = StringUtils.isBlank(code) ? "500" : code;
+        msg = StringUtils.isBlank(msg) ? "未知异常,请联系管理员" : msg;
+
+        /**
+         * 验证数据为空
+         */
+        if (valideDate == null) {
+            return R.ok();
+        }
+
+        /**
+         * 没有要验证的
+         */
+        if (awaitValideField == null) {
+            return R.ok();
+        }
+
+        for (String key : awaitValideField.keySet()) {
+            //验证数据没有要验证的字段, 或有要验证的字段,但字段为空
+            if (!valideDate.containsKey(key) ||
+                    (valideDate.containsKey(key) && StringUtils.isBlank(String.valueOf(valideDate.get(key)))) ||
+                    String.valueOf(valideDate.get(key)).equals("null")) {
+                return R.error(awaitValideField.get(key) + "不能为空!");
+            }
+        }
+        return R.ok();
+    }
+
+}

+ 111 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/Test.java

@@ -0,0 +1,111 @@
+package com.kmall.common.utils.print.ticket;
+
+import com.kmall.common.utils.print.ticket.item.*;
+import com.kmall.common.utils.print.ticket.item.font.TicketCommonFont;
+import com.kmall.common.utils.print.ticket.item.font.TicketGoodsTitleFont;
+import com.kmall.common.utils.print.ticket.item.font.TicketHeadFont;
+
+import java.awt.*;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-25
+ */
+public class Test {
+
+    public static void main(String[] args) {
+
+        //---------- 小票字体 ----------
+
+        TicketHeadFont headFont = new TicketHeadFont();
+        headFont.setFamily("宋体");
+        headFont.setWeight(Font.BOLD);
+        headFont.setSize(12);
+
+        TicketGoodsTitleFont goodsTitleFont = new TicketGoodsTitleFont();
+        goodsTitleFont.setFamily("宋体");
+        goodsTitleFont.setWeight(Font.BOLD);
+        goodsTitleFont.setSize(8);
+
+        TicketCommonFont commonFont = new TicketCommonFont();
+        commonFont.setFamily("宋体");
+        commonFont.setWeight(Font.PLAIN);
+        commonFont.setSize(8);
+
+
+        //---------- 小票信息 ----------
+
+        // 小票头
+        TicketHead head = new TicketHead();
+        head.setTitle("三如生活馆");
+        head.setCashierId("10000");
+        head.setPosId("1001");
+        head.setMemberId("900800");
+        head.setIntegral("120");
+        head.setOrderId("201810301000");
+        head.setTradeTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE").format(new Date()));
+
+        // 商品信息
+        List<Goods> goods = new ArrayList<Goods>();
+        goods.add(new Goods("奥地利 进口 Loacker/莱家 威化饼干巧克力味250g/袋", "59.90", "1", "59.9"));
+        goods.add(new Goods("比利时进口零食品 LOTUS和情焦糖饼干312.5g咖啡伴侣小吃", "16.80", "1", "16.80 "));
+        goods.add(new Goods("博智900g高钙燕麦植物奶", "238.00", "1", "238.00"));
+        goods.add(new Goods("韩国进口 海太蜂蜜黄油薯片60g*5袋土豆片休闲网红零食大礼包批发", "100.00", "1", "100.00"));
+        goods.add(new Goods("新西兰进口纽瑞滋脱脂奶粉1kg成人青少年中老年奶粉", "100.00", "1", "100.00"));
+
+        // 收银信息
+        CashInfo cashInfo = new CashInfo();
+        cashInfo.setTotal("517.70");
+        cashInfo.setGoodsTotal("5");
+        cashInfo.setReceipts("517.70");
+        cashInfo.setOddChange("0.00");
+        cashInfo.setCoupon("0.00");
+        cashInfo.setIntegral("0");
+        cashInfo.setFreight("0.00");
+        cashInfo.setPaymentMode("微信支付");
+
+        // 海关清单
+        CusListing cusListing = new CusListing();
+        cusListing.setOrderId("201810301000");
+        cusListing.setWaybillId("920187494");
+        cusListing.setInvtNo("5011235610990");
+        cusListing.setConsignee("张三");
+        cusListing.setConsigneeTel("13923908230");
+        cusListing.setOriginAddress("深圳前海保税仓");
+        cusListing.setDeliveryAddress("临海大道59号海运中心主塔楼1701室");
+
+        // 小票脚信息
+        TicketFoot foot = new TicketFoot();
+        foot.setSummary("======双11活动火爆销售大促======");
+        foot.setServiceTel("0750-7755897");
+        foot.setUrl1("http://www.ds-bay.com");
+
+
+        Ticket ticket = new Ticket();
+        ticket.setTicketHead(head);
+        ticket.setGoodsAlign(Ticket.BOTTOM);
+        ticket.setGoods(goods);
+        ticket.setCashInfo(cashInfo);
+        ticket.setCusListing(cusListing);
+        ticket.setTicketFoot(foot);
+
+        ticket.setTicketHeadFont(headFont);
+        ticket.setTicketGoodsTitleFont(goodsTitleFont);
+        ticket.setTicketCommonFont(commonFont);
+
+        // 打印
+        TicketPrinter.print2(ticket);
+
+
+        //---------------------------------------------
+
+    }
+
+
+
+}

+ 407 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/TicketBuilder.java

@@ -0,0 +1,407 @@
+package com.kmall.common.utils.print.ticket;
+
+import com.kmall.common.utils.print.ticket.doc.PrintPageableBuilder;
+import com.kmall.common.utils.print.ticket.item.Goods;
+import com.kmall.common.utils.print.ticket.item.Ticket;
+import org.apache.commons.lang3.StringUtils;
+
+import java.awt.*;
+import java.awt.print.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * 票据构建类
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-25
+ */
+public class TicketBuilder implements Printable {
+
+    // 小票打印要素
+    private Ticket ticket;
+
+    /**
+     * 向外提供打印文档Pageable
+     * @return
+     */
+    public Pageable getPage() {
+        return new PrintPageableBuilder().buildPage(this);
+    }
+
+
+    @Override
+    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
+        //此 Graphics2D 类扩展 Graphics 类,以提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制。
+        //它是用于在 Java(tm) 平台上呈现二维形状、文本和图像的基础类。
+        Graphics2D g2 = (Graphics2D) graphics;
+        //设置打印颜色为黑色
+        g2.setColor(Color.black);
+
+        //打印起点坐标
+        //返回与此 PageFormat相关的 Paper对象的可成像区域左上方点的 x坐标, 根据实际调整加减
+        double x = pageFormat.getImageableX() + 3;
+        //返回与此 PageFormat相关的 Paper对象的可成像区域左上方点的 y坐标, 根据实际调整加减
+        double y = pageFormat.getImageableY();
+
+        float xIdx = (float) x;
+        float yIdx = (float) y;
+
+
+
+        //-------------------- 小票标题 --------------------
+
+        //Font.PLAIN: 普通样式常量 Font.ITALIC 斜体样式常量 Font.BOLD 粗体样式常量
+        //根据指定名称、样式和磅值大小,创建一个新 Font。
+        Font fontTit = new Font(
+                ticket.getTicketHeadFont().getFamily(),
+                ticket.getTicketHeadFont().getWeight(),
+                ticket.getTicketHeadFont().getSize()
+        );
+        //设置标题打印字体
+        g2.setFont(fontTit);
+        //获取字体的高度
+        float fontHeigthTit = fontTit.getSize2D();
+
+        yIdx += (y + fontHeigthTit);
+        //yIdx += y;
+        g2.drawString(ticket.getTicketHead().getTitle(), xIdx + 50, yIdx);
+
+
+        //-------------------- 小票订单信息 --------------------
+
+        //设置正文字体
+        Font font = new Font(
+                ticket.getTicketCommonFont().getFamily(),
+                ticket.getTicketCommonFont().getWeight(),
+                ticket.getTicketCommonFont().getSize()
+        );
+        g2.setFont(font);
+        float fontHeigth = font.getSize2D();
+        float line = fontHeigth + 2;
+
+        xIdx = (float)x;
+        yIdx += line + 2;
+        //设置操作员
+//        g2.drawString("收银员:" + ticket.getTicketHead().getCashierId(), xIdx, yIdx);
+
+        //if (ticketItem.getCardNumber() != null && !"".equals(ticketItem.getCardNumber())) {
+        yIdx += line;
+        g2.drawString("会员:" + ticket.getTicketHead().getMemberId(), xIdx, yIdx);
+//        yIdx += line;
+//        g2.drawString("积分:" + ticket.getTicketHead().getIntegral(), xIdx, yIdx);
+        //}
+
+        yIdx += line;
+        //设置订单号
+        g2.drawString("订单号:" + ticket.getTicketHead().getOrderId(), xIdx, yIdx);
+        yIdx += line;
+        //设置订单号
+        g2.drawString("交易时间:" + ticket.getTicketHead().getTradeTime(), xIdx, yIdx);
+
+
+        yIdx += line - 2;
+
+        /**
+         * 虚线设置
+         * width 虚线单点宽
+         * cap <code>BasicStroke</code>末尾的装饰
+         * join 当两条<code>BasicStroke</code>连接时, 连接处的形状
+         * miterlimit 当两条<code>BasicStroke</code>连接时, 修剪斜接连接的限制, 大于或等于1.0f
+         * dash <code>BasicStroke</code>连接时, 线与缺口各自的单元长度
+         * dash_phase 与dash相关, 启动划线模式的偏移量
+         *
+         */
+        g2.setStroke(new BasicStroke(0.4f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4.0f, new float[]{4.0f, 2.0f, 1.0f, 2.0f}, 0.0f));
+        //在此图形上下文的坐标系中使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线。 即绘制虚线
+        g2.drawLine((int) x, (int) yIdx, (int) (xIdx + 200), (int) yIdx);
+
+
+        //-------------------- 小票商品标题 --------------------
+
+        // 商品名称缩进长
+        int gnInd = 4;
+
+        // 商品标题字体
+        Font fontGoodsTit = new Font(
+                ticket.getTicketGoodsTitleFont().getFamily(),
+                ticket.getTicketGoodsTitleFont().getWeight(),
+                ticket.getTicketGoodsTitleFont().getSize()
+        );
+        g2.setFont(fontGoodsTit);
+        float fontHeigthGoodsTit = fontGoodsTit.getSize2D();
+        float lineGoodsTit = fontHeigthGoodsTit + 2;
+
+        yIdx += lineGoodsTit + 2;
+        //设置标题
+        g2.drawString("商品名称", xIdx, yIdx);
+        g2.drawString("单价", xIdx + 100 + gnInd, yIdx);
+        g2.drawString("数量", xIdx + 130 + gnInd, yIdx);
+        g2.drawString("小计", xIdx + 160 + gnInd, yIdx);
+
+        //-------------------- 小票商品明细 --------------------
+
+        // 还原回普通字体
+        g2.setFont(font);
+
+        //虚线设置
+        yIdx += line - 3;
+        g2.drawLine((int) x, (int) yIdx, (int) (xIdx + 200), (int) yIdx);
+
+        //设置商品清单
+        List<Goods> goodsList = ticket.getGoods();
+
+
+        if (goodsList != null && goodsList.size() > 0) {
+            int i = 0;
+            for (Goods gdf : goodsList) {
+                i++;
+
+                //---------- 商品名称 ----------
+                // 商品名称过长,字符串截分,换行打印
+                List<String> gnameL = detachString(gdf.getGname(), 12);
+                int gname = gnameL.size();
+
+                if (gname > 0) {
+                    for (int j = 0; j < gname; j++) {
+                        // 顶部对齐
+                        if (ticket.getGoodsAlign().equalsIgnoreCase(Ticket.TOP)) {
+                            if (j == 0) {
+                                yIdx += line + 3;
+                                g2.drawString("#" + gnameL.get(j), xIdx, yIdx);
+
+                                // 只有第一行打印单价, 数量, 小计
+                                g2.drawString(gdf.getUprice(), xIdx + 100 + gnInd, yIdx);
+                                g2.drawString(gdf.getNum(), xIdx + 130 + gnInd, yIdx);
+                                g2.drawString(gdf.getSubtotal() + "元", xIdx + 160 + gnInd, yIdx);
+                            } else {
+                                yIdx += line;
+                                // 第二行只打印剩余的商品名称
+                                g2.drawString(String.valueOf(gnameL.get(j)), xIdx + gnInd, yIdx);
+                            }
+                        } else {
+                            // 底部对齐
+                            if (j < gname - 1) {
+                                if (j == 0) {
+                                    yIdx += line + 5;
+                                    // 第一行, 只打印商品名称
+                                    g2.drawString("#" + gnameL.get(j), xIdx, yIdx);
+                                } else {
+                                    yIdx += line;
+                                    // 除第一行和最后一行外, 只打印商品名称
+                                    g2.drawString(String.valueOf(gnameL.get(j)), xIdx + gnInd, yIdx);
+                                }
+
+
+                            } else {
+                                yIdx += line;
+                                // 最后一行打印剩余的商品名称, 打印单价, 数量, 小计
+                                g2.drawString(String.valueOf(gnameL.get(j)), xIdx + gnInd, yIdx);
+                                g2.drawString(gdf.getUprice(), xIdx + 100 + gnInd, yIdx);
+                                g2.drawString(gdf.getNum(), xIdx + 130 + gnInd, yIdx);
+                                g2.drawString(gdf.getSubtotal() + "元", xIdx + 160 + gnInd, yIdx);
+                            }
+                        }
+
+                    }// end for
+                }
+
+            }
+        }
+
+        /*if (goodsList != null && goodsList.size() > 0) {
+            int i = 0;
+            for (Goods gdf : goodsList) {
+                i++;
+                 yIdx += line;
+                // 商品名称过长,用星号代替
+                g2.drawString(replaceStar(gdf.getGname(), 12, 0, 0), xIdx, yIdx);
+                g2.drawString(gdf.getUprice(), xIdx + 100, yIdx);
+                g2.drawString(gdf.getNum(), xIdx + 130, yIdx);
+                g2.drawString(gdf.getSubtotal() + "元", xIdx + 160, yIdx);
+
+            }
+        }*/
+
+        yIdx += line;
+        g2.drawLine((int) x, (int) yIdx, (int) (xIdx + 200), (int) yIdx);
+
+
+        //-------------------- 小票收银信息 --------------------
+
+        yIdx += line + 2;
+        g2.drawString("商品合计:", xIdx, yIdx);
+        g2.drawString(ticket.getCashInfo().getGoodsTotal() + "件", xIdx + 130 + gnInd, yIdx);
+        g2.drawString(ticket.getCashInfo().getTotal() + "元", xIdx + 160 + gnInd, yIdx);
+
+        yIdx += line;
+        g2.drawString("实收:", xIdx, yIdx);
+        g2.drawString(ticket.getCashInfo().getReceipts() + "元", xIdx + 160 + gnInd, yIdx);
+        yIdx += line;
+        g2.drawString("找零:", xIdx, yIdx);
+        g2.drawString(ticket.getCashInfo().getOddChange() + "元", xIdx + 160 + gnInd, yIdx);
+
+        yIdx += line;
+        g2.drawString("优惠券:", xIdx, yIdx);
+        g2.drawString(ticket.getCashInfo().getCoupon() + "元", xIdx + 160 + gnInd, yIdx);
+        yIdx += line;
+        g2.drawString("运费:", xIdx, yIdx);
+        g2.drawString(ticket.getCashInfo().getFreight() + "元", xIdx + 160 + gnInd, yIdx);
+        yIdx += line;
+        g2.drawString("支付渠道:", xIdx, yIdx);
+        g2.drawString(ticket.getCashInfo().getPaymentMode(), xIdx + 160 + gnInd, yIdx);
+
+        yIdx += line - 1;
+        g2.drawLine((int) x, (int) yIdx, (int) (xIdx + 200), (int) yIdx);
+
+        //-------------------- 小票清单信息 --------------------
+
+        yIdx += line + 2;
+        g2.drawString("订单号:" + ticket.getCusListing().getOrderId(), xIdx, yIdx);
+        yIdx += line;
+        g2.drawString("运单编号:" + ticket.getCusListing().getWaybillId(), xIdx, yIdx);
+        yIdx += line;
+        g2.drawString("海关清单号:" + ticket.getCusListing().getInvtNo(), xIdx, yIdx);
+
+        yIdx += line;
+        g2.drawString("收货人:" + ticket.getCusListing().getConsignee(), xIdx, yIdx);
+        yIdx += line;
+        g2.drawString("收货人电话:" + ticket.getCusListing().getConsigneeTel(), xIdx, yIdx);
+        yIdx += line;
+        g2.drawString("始发地:" + ticket.getCusListing().getOriginAddress(), xIdx, yIdx);
+        yIdx += line;
+        List<String> addressList = detachString(ticket.getCusListing().getDeliveryAddress(), 20);
+        for (int i = 0; i < addressList.size(); i++) {
+            if (i == 0) {
+                g2.drawString("交货地:" + addressList.get(i), xIdx, yIdx);
+            } else {
+                yIdx += line;
+                g2.drawString(addressList.get(i), xIdx + 30, yIdx);
+            }
+        }
+
+        //-------------------- 小票其它信息 --------------------
+
+        yIdx += line - 1;
+        g2.drawLine((int) x, (int) yIdx, (int) (xIdx + 200), (int) yIdx);
+
+        if (!StringUtils.isBlank(ticket.getTicketFoot().getSummary())) {
+            yIdx += line + 1;
+            g2.drawString(ticket.getTicketFoot().getSummary(), xIdx, yIdx);
+        }
+        if (!StringUtils.isBlank(ticket.getTicketFoot().getServiceTel())) {
+            yIdx += line;
+            g2.drawString("客服电话:" + ticket.getTicketFoot().getServiceTel(), xIdx, yIdx);
+        }
+        if (!StringUtils.isBlank(ticket.getTicketFoot().getUrl1())) {
+            yIdx += line;
+            g2.drawString("网址:" + ticket.getTicketFoot().getUrl1(), xIdx, yIdx);
+        }
+        if (!StringUtils.isBlank(ticket.getTicketFoot().getUrl2())) {
+            yIdx += line;
+            g2.drawString("网址:" + ticket.getTicketFoot().getUrl2(), xIdx, yIdx);
+        }
+        if (!StringUtils.isBlank(ticket.getTicketFoot().getUrl3())) {
+            yIdx += line;
+            g2.drawString("网址:" + ticket.getTicketFoot().getUrl3(), xIdx, yIdx);
+        }
+        yIdx += line;
+        g2.drawString(ticket.getTicketFoot().getWelcome(), xIdx, yIdx);
+
+        switch (pageIndex) {
+            case 0:
+                return PAGE_EXISTS; //0
+            default:
+                return NO_SUCH_PAGE;  //1
+        }
+    }
+
+    /**
+     * 分拆长字符串
+     *
+     * @param str 原字符
+     * @param max 分拆最大长度
+     * @return
+     */
+    public List<String> detachString(String str, int max) {
+        if (StringUtils.isBlank(str)) {
+            return Collections.EMPTY_LIST;
+        }
+        int len = str.length();
+
+        List<String> list = new ArrayList<>();
+
+        if (len <= max) {
+            list.add(str);
+            return list;
+        }
+
+        String tmp;
+
+        while (true) {
+            if (len > max) {
+                tmp = str.substring(0, max);
+                list.add(tmp);
+
+                // 截去tmp字符串
+                str = str.substring(tmp.length(), len);
+                len = str.length();
+            } else {
+                list.add(str);
+                break;
+            }
+        }
+        return list;
+    }
+
+
+    /**
+     * 名称过长, 星号替换
+     * 替换为 ABCD****XYZ的样式
+     *
+     * @param str 原字符串
+     * @param max 最长数目
+     * @param beg 保留开始数目
+     * @param end 保留结束数目
+     * @return
+     */
+    public String replaceStar(String str, int max, int beg, int end) {
+        if (StringUtils.isBlank(str)) {
+            return str;
+        }
+        int len = str.length();
+
+        if (beg == 0 || beg < 0) {
+            beg = 10;
+        }
+        if (end == 0 || end < 0) {
+            end = 8;
+        }
+
+        if (len <= max) {
+            return str;
+        } else {
+            int ibeg = beg;
+            int istar;
+            int iend = end;
+            do{
+                ibeg--;
+                iend--;
+                istar = max - ibeg - iend;
+                // 至少保证4个星号
+            }while (istar < 4);
+
+            String asterisk = "";
+            for (int i = 0; i < istar; i++) {
+                asterisk += "*";
+            }
+            return str.substring(0, ibeg) + asterisk + str.substring(len - iend, len);
+        }
+    }
+
+    public void setTicket(Ticket ticket) {
+        this.ticket = ticket;
+    }
+
+}

+ 48 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/TicketPrintUtil.java

@@ -0,0 +1,48 @@
+package com.kmall.common.utils.print.ticket;
+
+import com.kmall.common.utils.print.ticket.item.*;
+import com.kmall.common.utils.print.ticket.item.font.TicketCommonFont;
+import com.kmall.common.utils.print.ticket.item.font.TicketGoodsTitleFont;
+import com.kmall.common.utils.print.ticket.item.font.TicketHeadFont;
+
+import java.awt.*;
+import java.util.List;
+
+public class TicketPrintUtil {
+
+    public static Ticket print(TicketHead head, List<Goods> goods, CashInfo cashInfo, CusListing cusListing, TicketFoot foot) {
+        //---------- 小票字体 ----------
+        TicketHeadFont headFont = new TicketHeadFont();
+        headFont.setFamily("宋体");
+        headFont.setWeight(Font.BOLD);
+        headFont.setSize(12);
+
+        TicketGoodsTitleFont goodsTitleFont = new TicketGoodsTitleFont();
+        goodsTitleFont.setFamily("宋体");
+        goodsTitleFont.setWeight(Font.BOLD);
+        goodsTitleFont.setSize(8);
+
+        TicketCommonFont commonFont = new TicketCommonFont();
+        commonFont.setFamily("宋体");
+        commonFont.setWeight(Font.PLAIN);
+        commonFont.setSize(8);
+
+        //---------- 组装小票 ----------
+        Ticket ticket = new Ticket();
+        ticket.setTicketHead(head);
+        ticket.setGoodsAlign(Ticket.BOTTOM);
+        ticket.setGoods(goods);
+        ticket.setCashInfo(cashInfo);
+        ticket.setCusListing(cusListing);
+        ticket.setTicketFoot(foot);
+
+        ticket.setTicketHeadFont(headFont);
+        ticket.setTicketGoodsTitleFont(goodsTitleFont);
+        ticket.setTicketCommonFont(commonFont);
+
+        // 打印
+        TicketPrinter.print2(ticket);
+        return ticket;
+    }
+
+}

+ 99 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/TicketPrinter.java

@@ -0,0 +1,99 @@
+package com.kmall.common.utils.print.ticket;
+
+import com.kmall.common.utils.print.ticket.doc.PrintAttributeBuilder;
+import com.kmall.common.utils.print.ticket.item.Ticket;
+
+import javax.print.*;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+/**
+ * 票据打印类
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-26
+ */
+public class TicketPrinter {
+
+    /**
+     * 打印票据一
+     *
+     * @param ticket
+     */
+    public static void print(Ticket ticket) {
+        try {
+            // 创建打印作业
+            PrinterJob job = PrinterJob.getPrinterJob();
+            if (job.getPrintService() == null) {
+                throw new PrinterException("没有默认打印机");
+            }
+
+            // 打印票据构建
+            TicketBuilder ticketBuilder = new TicketBuilder();
+            if (ticket == null) {
+                throw new PrinterException("无对应小票信息");
+            }
+            ticketBuilder.setTicket(ticket);
+
+            //设置打印文档
+            job.setPageable(ticketBuilder.getPage());
+
+            job.print();
+        } catch (PrinterException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 打印票据二
+     *
+     * @param ticket
+     */
+    public static void print2(Ticket ticket) {
+
+        // 获取默认的打印服务
+        PrintService pringService = PrintServiceLookup.lookupDefaultPrintService();
+        if (pringService == null) {
+            throw new RuntimeException("没有默认打印服务");
+        }
+
+        // 创建打印作业
+        DocPrintJob job = pringService.createPrintJob();
+        if (job.getPrintService() == null) {
+            throw new RuntimeException("没有默认打印机");
+        }
+
+        // 打印属性设置(自定义)
+        PrintAttributeBuilder printAttr = new PrintAttributeBuilder();
+
+
+        // 小票打印信息
+        TicketBuilder ticketBuilder = new TicketBuilder();
+        if (ticket == null) {
+            throw new IllegalArgumentException("无对应小票信息");
+        }
+        ticketBuilder.setTicket(ticket);
+
+
+        /**
+         * 打印文档设置
+         * ticketBuilder 为实现Printable接口的类实例
+         */
+        Doc doc = new SimpleDoc(ticketBuilder,
+                printAttr.buildDocFlavor(),
+                printAttr.buildDocAttributeSet());
+
+        // 不显示打印对话框,直接进行打印工作
+        try {
+            // 打印操作
+            job.print(doc, printAttr.buildPrintRequestAttributeSet());
+
+        } catch (PrintException pe) {
+            pe.printStackTrace();
+        }
+
+    }
+
+
+}

+ 65 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/PrintAttributeBuilder.java

@@ -0,0 +1,65 @@
+package com.kmall.common.utils.print.ticket.doc;
+
+import javax.print.DocFlavor;
+import javax.print.attribute.DocAttributeSet;
+import javax.print.attribute.HashDocAttributeSet;
+import javax.print.attribute.HashPrintRequestAttributeSet;
+import javax.print.attribute.PrintRequestAttributeSet;
+import javax.print.attribute.standard.OrientationRequested;
+
+/**
+ * 打印文档构造
+ * 适用于{@link javax.print}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-29
+ */
+public class PrintAttributeBuilder {
+
+    /**
+     * 设置打印属性
+     * 应用于当前打印任务全部文档
+     *
+     * @return
+     */
+    public PrintRequestAttributeSet buildPrintRequestAttributeSet() {
+        // 设置打印属性
+        PrintRequestAttributeSet printAttr = new HashPrintRequestAttributeSet();
+        // 纵向打印, LANDSCAPE:横向,PORTRAIT:纵向
+        printAttr.add(OrientationRequested.PORTRAIT);
+
+        // 设置纸张大小,也可以新建MediaSizeName类来自定义大小
+        printAttr.add(ThermalMediaSizeName.TP_80);
+
+        return printAttr;
+    }
+
+
+    /**
+     * 指定应用到{@link javax.print.DocPrintJob}打印数据的格式,
+     * 支持文档,图片(jpg,gif等),url
+     * 只应用于当前文档
+     *
+     * @return
+     */
+    public DocFlavor buildDocFlavor() {
+        // 指定打印输出格式
+        DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
+        return flavor;
+    }
+
+    /**
+     * 设置打印文档属性
+     * 只应用于当前文档
+     *
+     * @return
+     */
+    public DocAttributeSet buildDocAttributeSet() {
+        // 打印文档属性
+        DocAttributeSet docAttr = new HashDocAttributeSet();
+        return docAttr;
+    }
+
+
+}

+ 60 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/PrintPageableBuilder.java

@@ -0,0 +1,60 @@
+package com.kmall.common.utils.print.ticket.doc;
+
+import java.awt.print.*;
+
+/**
+ * 打印文档构造
+ * 适用于{@link java.awt.print}
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-25
+ */
+public class PrintPageableBuilder {
+
+    /**
+     * Book 类提供文档的表示形式,该文档的页面可以使用不同的页面格式和页面
+     *
+     * @return
+     */
+    public Pageable buildPage(Printable printable) {
+        //要打印的文档
+        Book book = new Book();
+        book.append(printable, buildPageFormat());
+        return book;
+    }
+
+
+    /**
+     * PageFormat类描述要打印的页面大小和方向
+     *
+     * @return
+     */
+    public PageFormat buildPageFormat() {
+        //初始化一个页面打印对象
+        PageFormat pf = new PageFormat();
+        //设置页面打印方向,从上往下,从左往右
+        pf.setOrientation(PageFormat.PORTRAIT);
+
+        pf.setPaper(buildPaper());
+        return pf;
+    }
+
+
+    /**
+     * 设置打印文件区域
+     *
+     * @return
+     */
+    public Paper buildPaper() {
+        //通过Paper设置页面的空白边距和可打印区域。必须与实际打印纸张大小相符。
+        Paper paper = new Paper();
+        // 纸张大小, 80mm热敏纸
+        paper.setSize(226, 30000);
+        // 纸左右各留10mm
+        paper.setImageableArea(0, 0, 226-10-10, 30000);
+        return paper;
+    }
+
+
+}

+ 50 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/ThermalMediaSize.java

@@ -0,0 +1,50 @@
+package com.kmall.common.utils.print.ticket.doc;
+
+import javax.print.attribute.Size2DSyntax;
+import javax.print.attribute.standard.MediaSize;
+import javax.print.attribute.standard.MediaSizeName;
+
+/**
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-29
+ */
+public class ThermalMediaSize extends MediaSize {
+
+    private static final long serialVersionUID = 7004676280394045459L;
+
+    public ThermalMediaSize(int x, int y, int units, MediaSizeName media) {
+        super(x, y, units, media);
+    }
+
+
+    public final static class TP{
+
+        /**
+         * 57mm
+         * 80 X 297
+         */
+        public static final ThermalMediaSize T57
+                = new ThermalMediaSize(57, 297, Size2DSyntax.MM,
+                ThermalMediaSizeName.TP_57);
+
+        /**
+         * 80mm
+         * 80 X 297
+         */
+        public static final ThermalMediaSize T80
+                = new ThermalMediaSize(80, 297, Size2DSyntax.MM,
+                ThermalMediaSizeName.TP_80);
+
+        private TP(){}
+
+    }
+
+
+    static {
+        ThermalMediaSize TP57 = TP.T57;
+        ThermalMediaSize TP80 = TP.T80;
+    }
+
+}
+

+ 43 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/doc/ThermalMediaSizeName.java

@@ -0,0 +1,43 @@
+package com.kmall.common.utils.print.ticket.doc;
+
+import javax.print.attribute.standard.MediaSizeName;
+
+/**
+ * 热敏纸 thermal paper
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-29
+ */
+public class ThermalMediaSizeName extends MediaSizeName {
+
+    private static final long serialVersionUID = 2625316120464977704L;
+
+    /**
+     * 57mm热敏纸
+     */
+    public static final ThermalMediaSizeName
+            TP_57 = new ThermalMediaSizeName(900);
+
+    /**
+     * 80mm热敏纸
+     */
+    public static final ThermalMediaSizeName
+            TP_80 = new ThermalMediaSizeName(901);
+
+    protected ThermalMediaSizeName(int value) {
+        super (value);
+    }
+
+    private static final String[] myStringTable = {
+            "tp_57",
+            "tp_80"
+    };
+
+    private static final MediaSizeName[] myEnumValueTable = {
+            TP_57,
+            TP_80
+    };
+
+
+}

+ 109 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/CashInfo.java

@@ -0,0 +1,109 @@
+package com.kmall.common.utils.print.ticket.item;
+
+import java.io.Serializable;
+
+/**
+ * 收银信息
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class CashInfo implements Serializable {
+
+    private static final long serialVersionUID = 5573772493011494943L;
+
+    // 商品总额
+    private String total;
+    // 商品总数
+    private String goodsTotal;
+
+    // 实收款
+    private String receipts;
+    // 找零
+    private String oddChange;
+    // 优惠券
+    private String coupon;
+    // 积分
+    private String integral;
+    // 运费
+    private String freight;
+
+    // 支付方式
+    private String paymentType;
+    // 支付方式名称
+    private String paymentMode;
+
+
+    public String getTotal() {
+        return total;
+    }
+
+    public void setTotal(String total) {
+        this.total = total;
+    }
+
+    public String getGoodsTotal() {
+        return goodsTotal;
+    }
+
+    public void setGoodsTotal(String goodsTotal) {
+        this.goodsTotal = goodsTotal;
+    }
+
+    public String getReceipts() {
+        return receipts;
+    }
+
+    public void setReceipts(String receipts) {
+        this.receipts = receipts;
+    }
+
+    public String getOddChange() {
+        return oddChange;
+    }
+
+    public void setOddChange(String oddChange) {
+        this.oddChange = oddChange;
+    }
+
+    public String getCoupon() {
+        return coupon;
+    }
+
+    public void setCoupon(String coupon) {
+        this.coupon = coupon;
+    }
+
+    public String getIntegral() {
+        return integral;
+    }
+
+    public void setIntegral(String integral) {
+        this.integral = integral;
+    }
+
+    public String getFreight() {
+        return freight;
+    }
+
+    public void setFreight(String freight) {
+        this.freight = freight;
+    }
+
+    public String getPaymentType() {
+        return paymentType;
+    }
+
+    public void setPaymentType(String paymentType) {
+        this.paymentType = paymentType;
+    }
+
+    public String getPaymentMode() {
+        return paymentMode;
+    }
+
+    public void setPaymentMode(String paymentMode) {
+        this.paymentMode = paymentMode;
+    }
+}

+ 87 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/CusListing.java

@@ -0,0 +1,87 @@
+package com.kmall.common.utils.print.ticket.item;
+
+import java.io.Serializable;
+
+/**
+ * 海关清单信息
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class CusListing implements Serializable {
+
+    private static final long serialVersionUID = 1859828989099474346L;
+
+    // 订单编号
+    private String orderId;
+    // 运单编号
+    private String waybillId;
+    // 海关清单号
+    private String invtNo;
+
+    // 收货人
+    private String consignee;
+    // 收货人电话
+    private String consigneeTel;
+    // 始发地
+    private String originAddress;
+    // 交货地
+    private String deliveryAddress;
+
+
+    public String getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(String orderId) {
+        this.orderId = orderId;
+    }
+
+    public String getWaybillId() {
+        return waybillId;
+    }
+
+    public void setWaybillId(String waybillId) {
+        this.waybillId = waybillId;
+    }
+
+    public String getInvtNo() {
+        return invtNo;
+    }
+
+    public void setInvtNo(String invtNo) {
+        this.invtNo = invtNo;
+    }
+
+    public String getConsignee() {
+        return consignee;
+    }
+
+    public void setConsignee(String consignee) {
+        this.consignee = consignee;
+    }
+
+    public String getConsigneeTel() {
+        return consigneeTel;
+    }
+
+    public void setConsigneeTel(String consigneeTel) {
+        this.consigneeTel = consigneeTel;
+    }
+
+    public String getOriginAddress() {
+        return originAddress;
+    }
+
+    public void setOriginAddress(String originAddress) {
+        this.originAddress = originAddress;
+    }
+
+    public String getDeliveryAddress() {
+        return deliveryAddress;
+    }
+
+    public void setDeliveryAddress(String deliveryAddress) {
+        this.deliveryAddress = deliveryAddress;
+    }
+}

+ 63 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/Goods.java

@@ -0,0 +1,63 @@
+package com.kmall.common.utils.print.ticket.item;
+
+import java.io.Serializable;
+
+/**
+ * 商品明细
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-25
+ */
+public class Goods implements Serializable {
+
+    private static final long serialVersionUID = -8645662031529882791L;
+
+    // 商品名称
+    private String gname;
+    // 商品单价
+    private String uprice;
+    // 商品数量
+    private String num;
+    // 小计
+    private String subtotal;
+
+    public Goods(String gname, String uprice, String num, String subtotal) {
+        this.gname = gname;
+        this.uprice = uprice;
+        this.num = num;
+        this.subtotal = subtotal;
+    }
+
+    public String getGname() {
+        return gname;
+    }
+
+    public void setGname(String gname) {
+        this.gname = gname;
+    }
+
+    public String getUprice() {
+        return uprice;
+    }
+
+    public void setUprice(String uprice) {
+        this.uprice = uprice;
+    }
+
+    public String getNum() {
+        return num;
+    }
+
+    public void setNum(String num) {
+        this.num = num;
+    }
+
+    public String getSubtotal() {
+        return subtotal;
+    }
+
+    public void setSubtotal(String subtotal) {
+        this.subtotal = subtotal;
+    }
+}

+ 119 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/Ticket.java

@@ -0,0 +1,119 @@
+package com.kmall.common.utils.print.ticket.item;
+
+import com.kmall.common.utils.print.ticket.item.font.TicketCommonFont;
+import com.kmall.common.utils.print.ticket.item.font.TicketGoodsTitleFont;
+import com.kmall.common.utils.print.ticket.item.font.TicketHeadFont;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 小票要素
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class Ticket implements Serializable {
+
+    public static final String TOP = "top";
+    public static final String BOTTOM = "bottom";
+
+
+    // 小票头信息
+    private TicketHead ticketHead;
+    // 商品信息
+    private List<Goods> goods;
+    // 收银信息
+    private CashInfo cashInfo;
+    // 海关清单信息
+    private CusListing cusListing;
+    // 小票页脚
+    private TicketFoot ticketFoot;
+
+    //---------- 商品要素对齐方式 ----------
+    private String goodsAlign = BOTTOM;
+
+    //---------- 字体设置 ----------
+
+    // 小票头字体
+    private TicketHeadFont ticketHeadFont;
+    // 小票商品标题字体
+    private TicketGoodsTitleFont ticketGoodsTitleFont;
+    // 小票字体
+    private TicketCommonFont ticketCommonFont;
+
+
+
+    public TicketHead getTicketHead() {
+        return ticketHead;
+    }
+
+    public void setTicketHead(TicketHead ticketHead) {
+        this.ticketHead = ticketHead;
+    }
+
+    public List<Goods> getGoods() {
+        return goods;
+    }
+
+    public void setGoods(List<Goods> goods) {
+        this.goods = goods;
+    }
+
+    public CashInfo getCashInfo() {
+        return cashInfo;
+    }
+
+    public void setCashInfo(CashInfo cashInfo) {
+        this.cashInfo = cashInfo;
+    }
+
+    public CusListing getCusListing() {
+        return cusListing;
+    }
+
+    public void setCusListing(CusListing cusListing) {
+        this.cusListing = cusListing;
+    }
+
+    public String getGoodsAlign() {
+        return goodsAlign;
+    }
+
+    public void setGoodsAlign(String goodsAlign) {
+        this.goodsAlign = goodsAlign;
+    }
+
+    public TicketFoot getTicketFoot() {
+        return ticketFoot;
+    }
+
+    public void setTicketFoot(TicketFoot ticketFoot) {
+        this.ticketFoot = ticketFoot;
+    }
+
+    public TicketHeadFont getTicketHeadFont() {
+        return ticketHeadFont;
+    }
+
+    public void setTicketHeadFont(TicketHeadFont ticketHeadFont) {
+        this.ticketHeadFont = ticketHeadFont;
+    }
+
+    public TicketGoodsTitleFont getTicketGoodsTitleFont() {
+        return ticketGoodsTitleFont;
+    }
+
+    public void setTicketGoodsTitleFont(TicketGoodsTitleFont ticketGoodsTitleFont) {
+        this.ticketGoodsTitleFont = ticketGoodsTitleFont;
+    }
+
+    public TicketCommonFont getTicketCommonFont() {
+        return ticketCommonFont;
+    }
+
+    public void setTicketCommonFont(TicketCommonFont ticketCommonFont) {
+        this.ticketCommonFont = ticketCommonFont;
+    }
+}

+ 74 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/TicketFoot.java

@@ -0,0 +1,74 @@
+package com.kmall.common.utils.print.ticket.item;
+
+import java.io.Serializable;
+
+/**
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class TicketFoot implements Serializable {
+
+    private static final long serialVersionUID = -6484890772614752569L;
+
+    // 概要信息
+    private String summary;
+    // 客服电话
+    private String serviceTel;
+    // 网址1
+    private String url1;
+    // 网址2
+    private String url2;
+    // 网址3
+    private String url3;
+    //
+    private String welcome = "欢迎再次惠顾";
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+
+    public String getServiceTel() {
+        return serviceTel;
+    }
+
+    public void setServiceTel(String serviceTel) {
+        this.serviceTel = serviceTel;
+    }
+
+    public String getUrl1() {
+        return url1;
+    }
+
+    public void setUrl1(String url1) {
+        this.url1 = url1;
+    }
+
+    public String getUrl2() {
+        return url2;
+    }
+
+    public void setUrl2(String url2) {
+        this.url2 = url2;
+    }
+
+    public String getUrl3() {
+        return url3;
+    }
+
+    public void setUrl3(String url3) {
+        this.url3 = url3;
+    }
+
+    public String getWelcome() {
+        return welcome;
+    }
+
+    public void setWelcome(String welcome) {
+        this.welcome = welcome;
+    }
+}

+ 91 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/TicketHead.java

@@ -0,0 +1,91 @@
+package com.kmall.common.utils.print.ticket.item;
+
+import java.io.Serializable;
+
+/**
+ * 小票头信息
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class TicketHead implements Serializable {
+
+    private static final long serialVersionUID = -4364410394045486767L;
+
+    // 小票标题
+    private String title;
+
+    // 收银员ID
+    private String cashierId;
+    // 收银机
+    private String posId;
+
+    // 会员ID
+    private String memberId;
+    // 会员积分
+    private String integral;
+
+    // 订单编号
+    private String orderId;
+    // 交易时间
+    private String tradeTime;
+
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getCashierId() {
+        return cashierId;
+    }
+
+    public void setCashierId(String cashierId) {
+        this.cashierId = cashierId;
+    }
+
+    public String getPosId() {
+        return posId;
+    }
+
+    public void setPosId(String posId) {
+        this.posId = posId;
+    }
+
+    public String getMemberId() {
+        return memberId;
+    }
+
+    public void setMemberId(String memberId) {
+        this.memberId = memberId;
+    }
+
+    public String getIntegral() {
+        return integral;
+    }
+
+    public void setIntegral(String integral) {
+        this.integral = integral;
+    }
+
+    public String getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(String orderId) {
+        this.orderId = orderId;
+    }
+
+    public String getTradeTime() {
+        return tradeTime;
+    }
+
+    public void setTradeTime(String tradeTime) {
+        this.tradeTime = tradeTime;
+    }
+
+}

+ 11 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketCommonFont.java

@@ -0,0 +1,11 @@
+package com.kmall.common.utils.print.ticket.item.font;
+
+/**
+ * 小票字体
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class TicketCommonFont extends TicketFont {
+}

+ 57 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketFont.java

@@ -0,0 +1,57 @@
+package com.kmall.common.utils.print.ticket.item.font;
+
+import java.io.Serializable;
+
+/**
+ * 小票字体
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class TicketFont implements Serializable {
+
+    private static final long serialVersionUID = 3556863077788246698L;
+
+    // 字体系列
+    private String family;
+    // 字体风格
+    private String style;
+    // 字体大小
+    private int size;
+    // 字体粗细
+    private int weight;
+
+    public String getFamily() {
+        return family;
+    }
+
+    public void setFamily(String family) {
+        this.family = family;
+    }
+
+    public String getStyle() {
+        return style;
+    }
+
+    public void setStyle(String style) {
+        this.style = style;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public void setWeight(int weight) {
+        this.weight = weight;
+    }
+
+}

+ 11 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketGoodsTitleFont.java

@@ -0,0 +1,11 @@
+package com.kmall.common.utils.print.ticket.item.font;
+
+/**
+ * 商品标题字体
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class TicketGoodsTitleFont extends TicketFont {
+}

+ 11 - 0
kmall-common/src/main/java/com/kmall/common/utils/print/ticket/item/font/TicketHeadFont.java

@@ -0,0 +1,11 @@
+package com.kmall.common.utils.print.ticket.item.font;
+
+/**
+ * 小票头字体
+ *
+ * @author Scott Chen
+ * @since 1.0
+ * 2018-10-30
+ */
+public class TicketHeadFont extends TicketFont {
+}

+ 10 - 4
pom.xml

@@ -36,6 +36,7 @@
         <commons-io-version>2.5</commons-io-version>
         <commons-codec-version>1.10</commons-codec-version>
         <commons-configuration-version>1.10</commons-configuration-version>
+        <commons-beanutils-version>1.9.3</commons-beanutils-version>
         <slf4j-version>1.7.19</slf4j-version>
         <logback-version>1.2.3</logback-version>
         <logback-ext-spring-version>0.1.5</logback-ext-spring-version>
@@ -224,6 +225,11 @@
             <version>${commons-configuration-version}</version>
         </dependency>
         <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>${commons-beanutils-version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-core</artifactId>
             <version>${shiro-version}</version>
@@ -277,10 +283,10 @@
                     <artifactId>commons-validator</artifactId>
                     <groupId>commons-validator</groupId>
                 </exclusion>
-                <exclusion>
-                    <artifactId>commons-beanutils</artifactId>
-                    <groupId>commons-beanutils</groupId>
-                </exclusion>
+                <!--<exclusion>-->
+                    <!--<artifactId>commons-beanutils</artifactId>-->
+                    <!--<groupId>commons-beanutils</groupId>-->
+                <!--</exclusion>-->
                 <exclusion>
                     <artifactId>commons-chain</artifactId>
                     <groupId>commons-chain</groupId>