xiongzhu 4 lat temu
rodzic
commit
5e13088ddb

+ 6 - 0
pom.xml

@@ -332,6 +332,12 @@
             <artifactId>java-ipfs-http-client</artifactId>
             <version>v1.3.3</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.javafaker</groupId>
+            <artifactId>javafaker</artifactId>
+            <version>1.0.2</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 80 - 0
src/main/java/com/izouma/nineth/domain/BlindBoxItem.java

@@ -0,0 +1,80 @@
+package com.izouma.nineth.domain;
+
+import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.StringArrayConverter;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class BlindBoxItem extends BaseEntity {
+
+    private Long blindBoxId;
+
+    private Long collectionId;
+
+    @ApiModelProperty("名称")
+    @Searchable
+    private String name;
+
+    @ApiModelProperty("图片")
+    @Column(columnDefinition = "TEXT")
+    @Convert(converter = StringArrayConverter.class)
+    private List<String> pics;
+
+    @ApiModelProperty("铸造者")
+    @Searchable
+    private String minter;
+
+    @ApiModelProperty("铸造者ID")
+    private Long minterId;
+
+    @ApiModelProperty("铸造者头像")
+    private String minterAvatar;
+
+    @ApiModelProperty("详情")
+    @Column(columnDefinition = "TEXT")
+    private String detail;
+
+    @ApiModelProperty("已售")
+    private int sale;
+
+    @ApiModelProperty("库存")
+    private int stock;
+
+    @ApiModelProperty("发行数量")
+    private int total;
+
+    @ApiModelProperty("价格")
+    @Column(precision = 10, scale = 2)
+    private BigDecimal price;
+
+    @Column(columnDefinition = "TEXT")
+    @Convert(converter = Collection.PropertyListConverter.class)
+    @ApiModelProperty("特性")
+    private List<Collection.CollectionProperty> properties;
+
+    @ApiModelProperty("是否可转售")
+    private boolean canResale;
+
+    @ApiModelProperty("版税比例")
+    private int royalties;
+
+    @ApiModelProperty("手续费比例")
+    private int serviceCharge;
+
+    @ApiModelProperty("稀有")
+    private boolean rare;
+}

+ 12 - 0
src/main/java/com/izouma/nineth/domain/Collection.java

@@ -64,6 +64,9 @@ public class Collection extends BaseEntity {
     @ApiModelProperty("库存")
     private int stock;
 
+    @ApiModelProperty("发行数量")
+    private int total;
+
     @ApiModelProperty("点赞")
     private int likes;
 
@@ -82,10 +85,19 @@ public class Collection extends BaseEntity {
     @ApiModelProperty("特性")
     private List<CollectionProperty> properties;
 
+    @ApiModelProperty("是否可转售")
     private boolean canResale;
 
+    @ApiModelProperty("版税比例")
+    private int royalties;
+
+    @ApiModelProperty("手续费比例")
+    private int serviceCharge;
+
+
     @Data
     public static class CollectionProperty {
+
         private String name;
 
         private String value;

+ 13 - 0
src/main/java/com/izouma/nineth/dto/CreateBlindBox.java

@@ -0,0 +1,13 @@
+package com.izouma.nineth.dto;
+
+import com.izouma.nineth.domain.BlindBoxItem;
+import com.izouma.nineth.domain.Collection;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class CreateBlindBox {
+    private Collection         blindBox;
+    private List<BlindBoxItem> items;
+}

+ 2 - 1
src/main/java/com/izouma/nineth/enums/AuthorityName.java

@@ -4,7 +4,8 @@ public enum AuthorityName {
     ROLE_USER("普通用户"),
     ROLE_MINTER("铸造者"),
     ROLE_DEV("开发者"),
-    ROLE_ADMIN("管理员");
+    ROLE_ADMIN("管理员"),
+    ROLE_OPERATOR("运营");
     private final String description;
 
     AuthorityName(String description) {

+ 30 - 0
src/main/java/com/izouma/nineth/event/CreateAssetEvent.java

@@ -0,0 +1,30 @@
+package com.izouma.nineth.event;
+
+import com.izouma.nineth.domain.Asset;
+import com.izouma.nineth.domain.Order;
+import org.springframework.context.ApplicationEvent;
+
+public class CreateAssetEvent extends ApplicationEvent {
+    private final boolean success;
+    private final Order   order;
+    private final Asset   asset;
+
+    public CreateAssetEvent(Object source, boolean success, Order order, Asset asset) {
+        super(source);
+        this.success = success;
+        this.order = order;
+        this.asset = asset;
+    }
+
+    public Order getOrder() {
+        return order;
+    }
+
+    public Asset getAsset() {
+        return asset;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+}

+ 16 - 0
src/main/java/com/izouma/nineth/repo/BlindBoxItemRepo.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.BlindBoxItem;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+
+public interface BlindBoxItemRepo extends JpaRepository<BlindBoxItem, Long>, JpaSpecificationExecutor<BlindBoxItem> {
+    @Query("update BlindBoxItem t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

+ 33 - 26
src/main/java/com/izouma/nineth/service/AssetService.java

@@ -8,6 +8,7 @@ import com.izouma.nineth.dto.NFT;
 import com.izouma.nineth.dto.NFTAccount;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.AssetStatus;
+import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.AssetRepo;
 import com.izouma.nineth.repo.CollectionRepo;
@@ -16,6 +17,7 @@ import com.izouma.nineth.utils.JpaUtils;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.ApplicationContext;
 import org.springframework.data.domain.Page;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -25,16 +27,17 @@ import org.springframework.stereotype.Service;
 @Slf4j
 public class AssetService {
 
-    private AssetRepo      assetRepo;
-    private UserRepo       userRepo;
-    private NFTService     nftService;
+    private AssetRepo          assetRepo;
+    private UserRepo           userRepo;
+    private NFTService         nftService;
+    private ApplicationContext applicationContext;
 
     public Page<Asset> all(PageQuery pageQuery) {
         return assetRepo.findAll(JpaUtils.toSpecification(pageQuery, Asset.class), JpaUtils.toPageRequest(pageQuery));
     }
 
     @Async
-    public Asset createAsset(Order order) throws Exception {
+    public void createAsset(Order order) {
         User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在"));
         if (StringUtils.isEmpty(user.getPublicKey())) {
             NFTAccount account = nftService.createAccount(user.getUsername());
@@ -43,28 +46,32 @@ public class AssetService {
             user.setPublicKey(account.getPublicKey());
             userRepo.save(user);
         }
-
-        NFT nft = nftService.createToken(user.getNftAccount());
-        if (nft != null) {
-            Asset asset = Asset.builder()
-                    .userId(user.getId())
-                    .orderId(order.getId())
-                    .minter(order.getMinter())
-                    .minterId(order.getMinterId())
-                    .minterAvatar(order.getMinterAvatar())
-                    .name(order.getName())
-                    .pic(order.getPic())
-                    .properties(order.getProperties())
-                    .tokenId(nft.getTokenId())
-                    .blockNumber(nft.getBlockNumber())
-                    .txHash(nft.getTxHash())
-                    .gasUsed(nft.getGasUsed())
-                    .price(order.getPrice())
-                    .status(AssetStatus.NORMAL)
-                    .build();
-            assetRepo.save(asset);
-            return asset;
+        try {
+            NFT nft = nftService.createToken(user.getNftAccount());
+            if (nft != null) {
+                Asset asset = Asset.builder()
+                        .userId(user.getId())
+                        .orderId(order.getId())
+                        .minter(order.getMinter())
+                        .minterId(order.getMinterId())
+                        .minterAvatar(order.getMinterAvatar())
+                        .name(order.getName())
+                        .pic(order.getPic())
+                        .properties(order.getProperties())
+                        .tokenId(nft.getTokenId())
+                        .blockNumber(nft.getBlockNumber())
+                        .txHash(nft.getTxHash())
+                        .gasUsed(nft.getGasUsed())
+                        .price(order.getPrice())
+                        .status(AssetStatus.NORMAL)
+                        .build();
+                assetRepo.save(asset);
+                applicationContext.publishEvent(new CreateAssetEvent(this, true, order, asset));
+                return;
+            }
+        } catch (Exception e) {
+            log.error("创建token失败", e);
         }
-        throw new BusinessException("创建asset失败");
+        applicationContext.publishEvent(new CreateAssetEvent(this, false, order, null));
     }
 }

+ 20 - 0
src/main/java/com/izouma/nineth/service/BlindBoxItemService.java

@@ -0,0 +1,20 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.domain.BlindBoxItem;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.repo.BlindBoxItemRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class BlindBoxItemService {
+
+    private BlindBoxItemRepo blindBoxItemRepo;
+
+    public Page<BlindBoxItem> all(PageQuery pageQuery) {
+        return blindBoxItemRepo.findAll(JpaUtils.toSpecification(pageQuery, BlindBoxItem.class), JpaUtils.toPageRequest(pageQuery));
+    }
+}

+ 74 - 7
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -1,23 +1,32 @@
 package com.izouma.nineth.service;
 
-import com.izouma.nineth.domain.Collection;
-import com.izouma.nineth.domain.Follow;
-import com.izouma.nineth.domain.Like;
-import com.izouma.nineth.domain.User;
+import com.izouma.nineth.domain.*;
 import com.izouma.nineth.dto.CollectionDTO;
+import com.izouma.nineth.dto.CreateBlindBox;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.UserDTO;
+import com.izouma.nineth.enums.CollectionType;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.BlindBoxItemRepo;
 import com.izouma.nineth.repo.CollectionRepo;
 import com.izouma.nineth.repo.LikeRepo;
 import com.izouma.nineth.utils.JpaUtils;
 import com.izouma.nineth.utils.SecurityUtils;
 import lombok.AllArgsConstructor;
+import org.apache.commons.collections.MapUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.transaction.Transactional;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -25,11 +34,32 @@ import java.util.stream.Collectors;
 @AllArgsConstructor
 public class CollectionService {
 
-    private CollectionRepo collectionRepo;
-    private LikeRepo       likeRepo;
+    private CollectionRepo   collectionRepo;
+    private LikeRepo         likeRepo;
+    private BlindBoxItemRepo blindBoxItemRepo;
 
     public Page<Collection> all(PageQuery pageQuery) {
-        return collectionRepo.findAll(JpaUtils.toSpecification(pageQuery, Collection.class), JpaUtils.toPageRequest(pageQuery));
+        pageQuery.getQuery().put("del", false);
+        Specification<Collection> specification = JpaUtils.toSpecification(pageQuery, Collection.class);
+
+        specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> and = new ArrayList<>();
+            if (!MapUtils.getString(pageQuery.getQuery(), "type", "").equals("BLIND_BOX")) {
+                and.add(criteriaBuilder.notEqual(root.get("type"), CollectionType.BLIND_BOX));
+            }
+            return criteriaBuilder.and(and.toArray(new Predicate[0]));
+        });
+        return collectionRepo.findAll(specification, JpaUtils.toPageRequest(pageQuery));
+    }
+
+    public Collection create(Collection record) {
+        User user = SecurityUtils.getAuthenticatedUser();
+        record.setMinter(user.getNickname());
+        record.setMinterId(user.getId());
+        record.setMinterAvatar(user.getAvatar());
+        record.setStock(record.getTotal());
+        record.setSale(0);
+        return collectionRepo.save(record);
     }
 
     public CollectionDTO toDTO(Collection collection) {
@@ -67,4 +97,41 @@ public class CollectionService {
         List<CollectionDTO> userDTOS = toDTO(collections.getContent());
         return new PageImpl<>(userDTOS, collections.getPageable(), collections.getTotalElements());
     }
+
+    @Transactional
+    public Collection createBlindBox(CreateBlindBox createBlindBox) {
+        Collection blindBox = createBlindBox.getBlindBox();
+
+        List<Collection> list = new ArrayList<>();
+        createBlindBox.getItems().stream().parallel().forEach(item -> {
+            Collection collection = collectionRepo.findById(item.getId()).orElseThrow(new BusinessException("所选藏品不存在"));
+            list.add(collection);
+            if (item.getTotal() > collection.getStock()) {
+                throw new BusinessException("所选藏品库存不足");
+            }
+        });
+
+        User user = SecurityUtils.getAuthenticatedUser();
+        blindBox.setMinter(user.getNickname());
+        blindBox.setMinterId(user.getId());
+        blindBox.setMinterAvatar(user.getAvatar());
+        collectionRepo.save(blindBox);
+        createBlindBox.getItems().stream().parallel().forEach(item -> {
+            Collection collection = list.stream().filter(i -> i.getId().equals(item.getId())).findAny().get();
+            collection.setStock(collection.getStock() - item.getTotal());
+            collectionRepo.save(collection);
+            BlindBoxItem blindBoxItem = new BlindBoxItem();
+            BeanUtils.copyProperties(collection, blindBoxItem);
+            blindBoxItem.setCollectionId(collection.getId());
+            blindBoxItem.setId(null);
+            blindBoxItem.setSale(0);
+            blindBoxItem.setTotal(item.getTotal());
+            blindBoxItem.setStock(item.getTotal());
+            blindBoxItem.setRare(item.isRare());
+            blindBoxItem.setBlindBoxId(blindBox.getId());
+            blindBoxItemRepo.save(blindBoxItem);
+        });
+
+        return blindBox;
+    }
 }

+ 25 - 20
src/main/java/com/izouma/nineth/service/OrderService.java

@@ -18,6 +18,7 @@ import com.izouma.nineth.dto.NFTAccount;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.enums.OrderStatus;
 import com.izouma.nineth.enums.PayMethod;
+import com.izouma.nineth.event.CreateAssetEvent;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.CollectionRepo;
 import com.izouma.nineth.repo.OrderRepo;
@@ -33,6 +34,7 @@ import org.apache.commons.codec.EncoderException;
 import org.apache.commons.codec.net.URLCodec;
 import org.apache.commons.collections.MapUtils;
 import org.apache.http.client.utils.URLEncodedUtils;
+import org.springframework.context.event.EventListener;
 import org.springframework.core.env.Environment;
 import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
@@ -177,7 +179,7 @@ public class OrderService {
         body.put("orderId", order.getId());
         request.setAttach(body.toJSONString());
 
-        WxPayMwebOrderResult result = wxPayService. createOrder(request);
+        WxPayMwebOrderResult result = wxPayService.createOrder(request);
 
         return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl());
     }
@@ -213,25 +215,28 @@ public class OrderService {
     public void notifyAlipay(Long orderId, Map<String, String> params) {
         Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在"));
         if (order.getStatus() == OrderStatus.NOT_PAID) {
-            Asset asset = null;
-            try {
-                asset = assetService.createAsset(order);
-                order.setStatus(OrderStatus.PROCESSING);
-                order.setPayTime(LocalDateTime.now());
-                order.setTransactionId(MapUtils.getString(params, "trade_no"));
-                order.setPayMethod(PayMethod.ALIPAY);
-                orderRepo.save(order);
-
-                if (asset != null) {
-                    order.setTxHash(asset.getTxHash());
-                    order.setGasUsed(asset.getGasUsed());
-                    order.setBlockNumber(asset.getBlockNumber());
-                    order.setStatus(OrderStatus.FINISH);
-                    orderRepo.save(order);
-                }
-            } catch (Exception e) {
-                log.error("支付宝回调出错", e);
-            }
+            order.setStatus(OrderStatus.PROCESSING);
+            order.setPayTime(LocalDateTime.now());
+            order.setTransactionId(MapUtils.getString(params, "trade_no"));
+            order.setPayMethod(PayMethod.ALIPAY);
+            orderRepo.save(order);
+            assetService.createAsset(order);
+        }
+    }
+
+    @EventListener
+    public void onCreateAsset(CreateAssetEvent event) {
+        Order order = event.getOrder();
+        Asset asset = event.getAsset();
+
+        if (event.isSuccess()) {
+            order.setTxHash(asset.getTxHash());
+            order.setGasUsed(asset.getGasUsed());
+            order.setBlockNumber(asset.getBlockNumber());
+            order.setStatus(OrderStatus.FINISH);
+            orderRepo.save(order);
+        } else {
+            log.error("创建asset失败");
         }
     }
 }

+ 60 - 0
src/main/java/com/izouma/nineth/web/BlindBoxItemController.java

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.BlindBoxItem;
+import com.izouma.nineth.service.BlindBoxItemService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.BlindBoxItemRepo;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/blindBoxItem")
+@AllArgsConstructor
+public class BlindBoxItemController extends BaseController {
+    private BlindBoxItemService blindBoxItemService;
+    private BlindBoxItemRepo blindBoxItemRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public BlindBoxItem save(@RequestBody BlindBoxItem record) {
+        if (record.getId() != null) {
+            BlindBoxItem orig = blindBoxItemRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return blindBoxItemRepo.save(orig);
+        }
+        return blindBoxItemRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<BlindBoxItem> all(@RequestBody PageQuery pageQuery) {
+        return blindBoxItemService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public BlindBoxItem get(@PathVariable Long id) {
+        return blindBoxItemRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        blindBoxItemRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<BlindBoxItem> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 14 - 6
src/main/java/com/izouma/nineth/web/CollectionController.java

@@ -2,6 +2,7 @@ package com.izouma.nineth.web;
 
 import com.izouma.nineth.domain.Collection;
 import com.izouma.nineth.dto.CollectionDTO;
+import com.izouma.nineth.dto.CreateBlindBox;
 import com.izouma.nineth.service.CollectionService;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.exception.BusinessException;
@@ -31,12 +32,14 @@ public class CollectionController extends BaseController {
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/save")
     public Collection save(@RequestBody Collection record) {
-        if (record.getId() != null) {
-            Collection orig = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
-            ObjUtils.merge(orig, record);
-            return collectionRepo.save(orig);
-        }
-        return collectionRepo.save(record);
+        Collection orig = collectionRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+        ObjUtils.merge(orig, record);
+        return collectionRepo.save(orig);
+    }
+
+    @PostMapping("/create")
+    public Collection create(@RequestBody Collection record) {
+        return collectionService.create(record);
     }
 
     //@PreAuthorize("hasRole('ADMIN')")
@@ -79,5 +82,10 @@ public class CollectionController extends BaseController {
     public List<CollectionDTO> myLikes() {
         return collectionService.toDTO(collectionRepo.userLikes(SecurityUtils.getAuthenticatedUser().getId()));
     }
+
+    @PostMapping("/createBlindBox")
+    public Collection createBlindBox(@RequestBody CreateBlindBox createBlindBox) {
+        return collectionService.createBlindBox(createBlindBox);
+    }
 }
 

Plik diff jest za duży
+ 0 - 0
src/main/resources/genjson/BlindBoxItem.json


+ 1 - 1
src/main/vue/package.json

@@ -20,7 +20,7 @@
     "core-js": "^3.6.5",
     "date-fns": "^2.14.0",
     "element-ui": "^2.15.1",
-    "faker": "^5.4.0",
+    "faker": "^5.5.3",
     "jdenticon": "^3.1.0",
     "normalize.css": "^8.0.1",
     "qs": "^6.9.0",

+ 27 - 11
src/main/vue/src/router.js

@@ -236,33 +236,49 @@ const router = new Router({
                     name: 'IdentityAuthEdit',
                     component: () => import(/* webpackChunkName: "identityAuthEdit" */ '@/views/IdentityAuthEdit.vue'),
                     meta: {
-                       title: '身份认证编辑',
-                    },
+                        title: '身份认证编辑'
+                    }
                 },
                 {
                     path: '/identityAuthList',
                     name: 'IdentityAuthList',
                     component: () => import(/* webpackChunkName: "identityAuthList" */ '@/views/IdentityAuthList.vue'),
                     meta: {
-                       title: '身份认证',
-                    },
-               },
+                        title: '身份认证'
+                    }
+                },
                 {
                     path: '/bannerEdit',
                     name: 'BannerEdit',
                     component: () => import(/* webpackChunkName: "bannerEdit" */ '@/views/BannerEdit.vue'),
                     meta: {
-                       title: 'Banner编辑',
-                    },
+                        title: 'Banner编辑'
+                    }
                 },
                 {
                     path: '/bannerList',
                     name: 'BannerList',
                     component: () => import(/* webpackChunkName: "bannerList" */ '@/views/BannerList.vue'),
                     meta: {
-                       title: 'Banner',
-                    },
-               }
+                        title: 'Banner'
+                    }
+                },
+                {
+                    path: '/blindBoxEdit',
+                    name: 'BlindBoxEdit',
+                    component: () => import(/* webpackChunkName: "blindBoxEdit" */ '@/views/BlindBoxEdit.vue'),
+                    meta: {
+                        title: '盲盒编辑'
+                    }
+                },
+                {
+                    path: '/blindBoxList',
+                    name: 'BlindBoxList',
+                    component: () => import(/* webpackChunkName: "blindBoxList" */ '@/views/BlindBoxList.vue'),
+                    meta: {
+                        title: '盲盒'
+                    }
+                }
                 /**INSERT_LOCATION**/
             ]
         },
@@ -314,4 +330,4 @@ router.beforeEach((to, from, next) => {
     }
 });
 
-export default router;
+export default router;

+ 409 - 0
src/main/vue/src/views/BlindBoxEdit.vue

@@ -0,0 +1,409 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+            <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id"> 删除 </el-button>
+            <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form
+                    :model="formData"
+                    :rules="rules"
+                    ref="form"
+                    label-width="94px"
+                    label-position="right"
+                    size="small"
+                    style="max-width: 500px"
+                >
+                    <el-form-item prop="name" label="名称">
+                        <el-input v-model="formData.name"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="pics" label="图片">
+                        <multi-upload v-model="formData.pics"></multi-upload>
+                    </el-form-item>
+                    <el-form-item prop="minterId" label="创建者">
+                        <minter-select v-model="formData.minterId" @detail="onMinterDetail"></minter-select>
+                    </el-form-item>
+                    <el-form-item prop="detail" label="详情" style="width: calc(100vw - 450px)">
+                        <rich-text v-model="formData.detail"></rich-text>
+                    </el-form-item>
+                    <el-form-item prop="properties" label="特性" style="width: calc(100vw - 450px)" size="mini">
+                        <el-table :data="formData.properties">
+                            <el-table-column prop="name" label="名称">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.name" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column prop="value" label="内容">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.value" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column width="80" align="center">
+                                <template v-slot="{ row, $index }">
+                                    <el-button type="danger" plain size="mini" @click="delProperty($index)">
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button size="mini" @click="addProperty"> 添加特性 </el-button>
+                    </el-form-item>
+                    <!-- <el-form-item prop="type" label="类型">
+                        <el-select v-model="formData.type" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in typeOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="source" label="来源">
+                        <el-select v-model="formData.source" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in sourceOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item> -->
+                    <el-form-item prop="price" label="价格">
+                        <el-input-number type="number" v-model="formData.price"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="total" label="发行数量">
+                        <el-input-number v-model="formData.total" disabled></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="collectionIds" label="包含作品" style="width: calc(100vw - 450px)">
+                        <el-table :data="blindBoxItems">
+                            <el-table-column
+                                prop="id"
+                                label="名称"
+                                :formatter="collectionNameFormatter"
+                            ></el-table-column>
+                            <el-table-column prop="total" label="数量" width="100"></el-table-column>
+                            <el-table-column prop="rare" label="稀有" width="100">
+                                <template v-slot="{ row }">
+                                    <el-tag :type="row.rare ? 'success' : 'info'">{{ row.rare ? '是' : '否' }}</el-tag>
+                                </template>
+                            </el-table-column>
+                            <el-table-column width="100">
+                                <template v-slot="{ row, $index }">
+                                    <el-button @click="removeItem($index)" type="danger" plain size="mini"
+                                        >移除</el-button
+                                    >
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button size="mini" @click="addItem">添加作品</el-button>
+                    </el-form-item>
+                    <!-- <el-form-item prop="likes" label="点赞">
+                        <el-input-number v-model="formData.likes"></el-input-number>
+                    </el-form-item> -->
+                    <el-form-item prop="onShelf" label="上架">
+                        <el-switch v-model="formData.onShelf" active-text="上架" inactive-text="下架"></el-switch>
+                    </el-form-item>
+                    <el-form-item prop="salable" label="可售">
+                        <el-switch v-model="formData.salable" active-text="可销售" inactive-text="仅展示"></el-switch>
+                    </el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
+                        <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button>
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+        <el-dialog :visible.sync="showAddItemDialog" title="添加" width="600px">
+            <el-form
+                :model="addItemForm"
+                label-position="right"
+                label-width="80px"
+                ref="addItemForm"
+                :rules="addItemFormRules"
+            >
+                <el-form-item prop="id" label="作品">
+                    <el-select v-model="addItemForm.id">
+                        <el-option
+                            v-for="item in collections"
+                            :label="item.name"
+                            :value="item.id"
+                            :key="item.id"
+                        ></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item prop="total" label="数量">
+                    <el-input-number v-model="addItemForm.total" :min="1" :max="addItemForm.max || 0"></el-input-number>
+                </el-form-item>
+                <el-form-item prop="rare" label="稀有">
+                    <el-switch v-model="addItemForm.rare" active-text="是" inactive-text="否"></el-switch>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button size="mini" @click="showAddItemDialog = false">取消</el-button>
+                <el-button size="mini" @click="onAddItem" type="primary">确定</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script>
+export default {
+    name: 'BlindBoxEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('collection/get/' + this.$route.query.id)
+                .then(res => {
+                    res.properties = res.properties || [];
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+            this.$http
+                .post(
+                    '/blindBoxItem/all',
+                    { query: { blindBoxId: this.$route.query.id }, size: 10000 },
+                    { body: 'json' }
+                )
+                .then(res => {
+                    this.blindBoxItems = res.content;
+                });
+        }
+        this.$http.post('/collection/all', { query: { del: false }, size: 10000 }, { body: 'json' }).then(res => {
+            this.collections = res.content;
+        });
+    },
+    data() {
+        return {
+            saving: false,
+            formData: { onShelf: true, salable: true, properties: [], type: 'BLIND_BOX', source: 'OFFICIAL' },
+            rules: {
+                name: [
+                    {
+                        required: true,
+                        message: '请输入名称',
+                        trigger: 'blur'
+                    }
+                ],
+                pics: [
+                    {
+                        required: true,
+                        message: '请输入图片',
+                        trigger: 'blur'
+                    }
+                ],
+                minter: [
+                    {
+                        required: true,
+                        message: '请输入创建者',
+                        trigger: 'blur'
+                    }
+                ],
+                minterId: [
+                    {
+                        required: true,
+                        message: '请输入铸造者ID',
+                        trigger: 'blur'
+                    }
+                ],
+                minterAvatar: [
+                    {
+                        required: true,
+                        message: '请输入铸造者头像',
+                        trigger: 'blur'
+                    }
+                ],
+                detail: [
+                    {
+                        required: true,
+                        message: '请输入详情',
+                        trigger: 'blur'
+                    }
+                ],
+                type: [
+                    {
+                        required: true,
+                        message: '请输入类型',
+                        trigger: 'blur'
+                    }
+                ],
+                source: [
+                    {
+                        required: true,
+                        message: '请输入来源',
+                        trigger: 'blur'
+                    }
+                ],
+                total: [
+                    {
+                        required: true,
+                        message: '请输入发行数量',
+                        trigger: 'blur'
+                    }
+                ],
+                onShelf: [
+                    {
+                        required: true,
+                        message: '请输入上架',
+                        trigger: 'blur'
+                    }
+                ],
+                price: [
+                    {
+                        required: true,
+                        message: '请输入价格',
+                        trigger: 'blur'
+                    }
+                ],
+                properties: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('properties must be array!'));
+                                    return;
+                                } else {
+                                    for (let i = 0; i < value.length; i++) {
+                                        if (value[i].name === '' || value[i].name === undefined) {
+                                            callback(new Error('请填写名称'));
+                                            return;
+                                        }
+                                        if (value[i].value === '' || value[i].value === undefined) {
+                                            callback(new Error('请填写内容'));
+                                            return;
+                                        }
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ]
+            },
+            typeOptions: [
+                { label: '默认', value: 'DEFAULT' },
+                { label: '盲盒', value: 'BLIND_BOX' },
+                { label: '拍卖', value: 'AUCTION' }
+            ],
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ],
+            collections: [],
+            showAddItemDialog: false,
+            blindBoxItems: [],
+            addItemForm: {},
+            addItemFormRules: {
+                id: [{ required: true, message: '请选择作品' }],
+                total: [{ required: true, message: '请输入数量' }]
+            }
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { blindBox: { ...this.formData }, items: this.blindBoxItems };
+
+            this.saving = true;
+            this.$http
+                .post('/collection/createBlindBox', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error((e || {}).error || '删除失败');
+                    }
+                });
+        },
+        onMinterDetail(e) {
+            console.log(e);
+            this.$set(this.formData, 'minter', e.nickname);
+            this.$set(this.formData, 'minterAvatar', e.avatar);
+        },
+        addProperty() {
+            this.formData.properties.push({
+                name: '',
+                value: ''
+            });
+        },
+        delProperty(i) {
+            this.formData.properties.splice(i, 1);
+        },
+        addItem() {
+            this.addItemForm = { rare: false };
+            this.showAddItemDialog = true;
+        },
+        onAddItem() {
+            this.$refs.addItemForm
+                .validate()
+                .then(_ => {
+                    this.blindBoxItems.push({ ...this.addItemForm });
+                    this.showAddItemDialog = false;
+                })
+                .catch(_ => {});
+        },
+        collectionNameFormatter(row, column, value, index) {
+            return (this.collections.find(i => i.id === value) || {}).name || '';
+        },
+        removeItem(i) {
+            this.blindBoxItems.splice(i, 1);
+        }
+    },
+    watch: {
+        'addItemForm.id'(val) {
+            if (val) {
+                this.$set(this.addItemForm, 'max', (this.collections.find(i => i.id === val) || {}).stock || 0);
+            }
+        },
+        blindBoxItems() {
+            this.$set(
+                this.formData,
+                'total',
+                this.blindBoxItems.map(i => i.total).reduce((a, b) => a + b, 0)
+            );
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 223 - 0
src/main/vue/src/views/BlindBoxList.vue

@@ -0,0 +1,223 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button
+                @click="addRow"
+                type="primary"
+                icon="el-icon-plus"
+                :disabled="fetchingData || downloading"
+                class="filter-item"
+            >
+                新增
+            </el-button>
+            <!-- <el-button
+                @click="download"
+                icon="el-icon-upload2"
+                :loading="downloading"
+                :disabled="fetchingData"
+                class="filter-item"
+            >
+                导出
+            </el-button> -->
+        </page-title>
+        <div class="filters-container">
+            <el-input
+                placeholder="搜索..."
+                v-model="search"
+                clearable
+                class="filter-item search"
+                @keyup.enter.native="getData"
+            >
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+            v-loading="fetchingData"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="name" label="名称"> </el-table-column>
+            <el-table-column prop="pics" label="图片">
+                <template slot-scope="{ row }">
+                    <el-image
+                        style="width: 30px; height: 30px"
+                        :src="row.pics[0]"
+                        fit="cover"
+                        :preview-src-list="row.pics"
+                    ></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="minter" label="铸造者"> </el-table-column>
+            <el-table-column prop="minterId" label="铸造者ID"> </el-table-column>
+            <el-table-column prop="type" label="类型" :formatter="typeFormatter"> </el-table-column>
+            <el-table-column prop="source" label="来源" :formatter="sourceFormatter"> </el-table-column>
+            <el-table-column prop="total" label="发行数量"> </el-table-column>
+            <el-table-column prop="stock" label="剩余库存"> </el-table-column>
+            <el-table-column prop="onShelf" label="上架">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="row.onShelf">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="price" label="价格"> </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" width="150">
+                <template slot-scope="{ row }">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                    <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+    name: 'CollectionList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/collection/all',
+            downloading: false,
+            typeOptions: [
+                { label: '默认', value: 'DEFAULT' },
+                { label: '盲盒', value: 'BLIND_BOX' },
+                { label: '拍卖', value: 'AUCTION' }
+            ],
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ]
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        typeFormatter(row, column, cellValue, index) {
+            let selectedOption = this.typeOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        sourceFormatter(row, column, cellValue, index) {
+            let selectedOption = this.sourceOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            return { search: this.search, query: { del: false, type: 'BLIND_BOX' } };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/blindBoxEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/blindBoxEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/collection/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 13 - 13
src/main/vue/src/views/CollectionEdit.vue

@@ -50,9 +50,9 @@
                         </el-table>
                     </el-form-item>
                     <el-form-item>
-                        <el-button size="mini" @click="addProperty"> 添加 </el-button>
+                        <el-button size="mini" @click="addProperty"> 添加特性 </el-button>
                     </el-form-item>
-                    <el-form-item prop="type" label="类型">
+                    <!-- <el-form-item prop="type" label="类型">
                         <el-select v-model="formData.type" clearable filterable placeholder="请选择">
                             <el-option
                                 v-for="item in typeOptions"
@@ -73,21 +73,21 @@
                             >
                             </el-option>
                         </el-select>
-                    </el-form-item>
+                    </el-form-item> -->
                     <el-form-item prop="price" label="价格">
                         <el-input-number type="number" v-model="formData.price"></el-input-number>
                     </el-form-item>
-                    <el-form-item prop="stock" label="库存">
-                        <el-input-number v-model="formData.stock"></el-input-number>
-                    </el-form-item>
-                    <el-form-item prop="sale" label="已售">
-                        <el-input-number v-model="formData.sale"></el-input-number>
+                    <el-form-item prop="total" label="发行数量">
+                        <el-input-number v-model="formData.total"></el-input-number>
                     </el-form-item>
                     <el-form-item prop="likes" label="点赞">
                         <el-input-number v-model="formData.likes"></el-input-number>
                     </el-form-item>
                     <el-form-item prop="onShelf" label="上架">
-                        <el-switch v-model="formData.onShelf"></el-switch>
+                        <el-switch v-model="formData.onShelf" active-text="上架" inactive-text="下架"></el-switch>
+                    </el-form-item>
+                    <el-form-item prop="salable" label="可售">
+                        <el-switch v-model="formData.salable" active-text="可销售" inactive-text="仅展示"></el-switch>
                     </el-form-item>
                     <el-form-item class="form-submit">
                         <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
@@ -121,7 +121,7 @@ export default {
     data() {
         return {
             saving: false,
-            formData: { onShelf: true, properties: [] },
+            formData: { onShelf: true, salable: true, properties: [], type: 'DEFAULT', source: 'OFFICIAL' },
             rules: {
                 name: [
                     {
@@ -179,10 +179,10 @@ export default {
                         trigger: 'blur'
                     }
                 ],
-                stock: [
+                total: [
                     {
                         required: true,
-                        message: '请输入库存',
+                        message: '请输入发行数量',
                         trigger: 'blur'
                     }
                 ],
@@ -253,7 +253,7 @@ export default {
 
             this.saving = true;
             this.$http
-                .post('/collection/save', data, { body: 'json' })
+                .post(this.formData.id ? '/collection/save' : '/collection/create', data, { body: 'json' })
                 .then(res => {
                     this.saving = false;
                     this.$message.success('成功');

+ 7 - 6
src/main/vue/src/views/CollectionList.vue

@@ -10,7 +10,7 @@
             >
                 新增
             </el-button>
-            <el-button
+            <!-- <el-button
                 @click="download"
                 icon="el-icon-upload2"
                 :loading="downloading"
@@ -18,7 +18,7 @@
                 class="filter-item"
             >
                 导出
-            </el-button>
+            </el-button> -->
         </page-title>
         <div class="filters-container">
             <el-input
@@ -44,8 +44,8 @@
         >
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
-            <el-table-column prop="name" label="名称"> </el-table-column>
-            <el-table-column prop="pics" label="图片">
+            <el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="pics" label="图片" width="90" align="center">
                 <template slot-scope="{ row }">
                     <el-image
                         style="width: 30px; height: 30px"
@@ -59,8 +59,9 @@
             <el-table-column prop="minterId" label="铸造者ID"> </el-table-column>
             <el-table-column prop="type" label="类型" :formatter="typeFormatter"> </el-table-column>
             <el-table-column prop="source" label="来源" :formatter="sourceFormatter"> </el-table-column>
-            <el-table-column prop="stock" label="库存"> </el-table-column>
-            <el-table-column prop="onShelf" label="上架">
+            <el-table-column prop="total" label="发行数量" width="90" align="center"> </el-table-column>
+            <el-table-column prop="stock" label="剩余库存" width="90" align="center"> </el-table-column>
+            <el-table-column prop="onShelf" label="上架" width="90" align="center">
                 <template v-slot="{ row }">
                     <el-tag type="success" v-if="row.onShelf">是</el-tag>
                     <el-tag type="info" v-else>否</el-tag>

+ 4 - 4
src/main/vue/yarn.lock

@@ -3837,10 +3837,10 @@ extsprintf@^1.2.0:
   resolved "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
   integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
 
-faker@^5.4.0:
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/faker/-/faker-5.4.0.tgz#f18e55993c6887918182b003d163df14daeb3011"
-  integrity sha512-Y9n/Ky/xZx/Bj8DePvXspUYRtHl/rGQytoIT5LaxmNwSe3wWyOeOXb3lT6Dpipq240PVpeFaGKzScz/5fvff2g==
+faker@^5.5.3:
+  version "5.5.3"
+  resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e"
+  integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==
 
 fancy-log@^1.1.0, fancy-log@^1.3.2:
   version "1.3.3"

+ 17 - 3
src/test/java/com/izouma/nineth/repo/UserRepoTest.java

@@ -1,7 +1,10 @@
 package com.izouma.nineth.repo;
 
+import com.github.javafaker.Faker;
 import com.izouma.nineth.domain.User;
+import com.izouma.nineth.enums.AuthorityName;
 import com.izouma.nineth.security.Authority;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -9,7 +12,7 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.test.context.junit4.SpringRunner;
 
-import java.util.List;
+import java.util.*;
 
 @RunWith(SpringRunner.class)
 @SpringBootTest
@@ -25,16 +28,27 @@ public class UserRepoTest {
                 .avatar("")
                 .build();
         userRepo.save(user);
+
     }
 
     @Test
     public void createUser() {
-
+        for (int i = 0; i < 30; i++) {
+            Faker faker = new Faker(new Locale("zh-CN"));
+            User user = User.builder()
+                    .username("9th_" + RandomStringUtils.randomAlphabetic(8))
+                    .nickname(faker.name().fullName())
+                    .avatar(faker.avatar().image())
+                    .authorities(new HashSet<>(Arrays.asList(Authority.get(AuthorityName.ROLE_USER), Authority.get(AuthorityName.ROLE_MINTER))))
+                    .build();
+            userRepo.save(user);
+        }
     }
 
     @Test
     public void findAllByAuthoritiesContains() {
-        List<User> list = userRepo.findAllByAuthoritiesContainsAndDelFalse(Authority.builder().name("ROLE_ADMIN").build());
+        List<User> list = userRepo.findAllByAuthoritiesContainsAndDelFalse(Authority.builder().name("ROLE_ADMIN")
+                .build());
         System.out.println(list);
     }
 }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików