xiongzhu 4 lat temu
rodzic
commit
5d1cdd4bf3
52 zmienionych plików z 2865 dodań i 21 usunięć
  1. 1 1
      src/main/java/com/izouma/nineth/config/Constants.java
  2. 30 6
      src/main/java/com/izouma/nineth/domain/Asset.java
  3. 8 2
      src/main/java/com/izouma/nineth/domain/Collection.java
  4. 22 0
      src/main/java/com/izouma/nineth/domain/Follow.java
  5. 25 0
      src/main/java/com/izouma/nineth/domain/Like.java
  6. 55 5
      src/main/java/com/izouma/nineth/domain/Order.java
  7. 9 0
      src/main/java/com/izouma/nineth/domain/User.java
  8. 2 1
      src/main/java/com/izouma/nineth/enums/AssetStatus.java
  9. 1 1
      src/main/java/com/izouma/nineth/enums/AuthorityName.java
  10. 17 0
      src/main/java/com/izouma/nineth/enums/CollectionSource.java
  11. 5 0
      src/main/java/com/izouma/nineth/enums/LikeType.java
  12. 1 0
      src/main/java/com/izouma/nineth/enums/OrderStatus.java
  13. 16 0
      src/main/java/com/izouma/nineth/enums/PayMethod.java
  14. 16 0
      src/main/java/com/izouma/nineth/repo/AssetRepo.java
  15. 16 0
      src/main/java/com/izouma/nineth/repo/CollectionRepo.java
  16. 16 0
      src/main/java/com/izouma/nineth/repo/FollowRepo.java
  17. 16 0
      src/main/java/com/izouma/nineth/repo/LikeRepo.java
  18. 16 0
      src/main/java/com/izouma/nineth/repo/OrderRepo.java
  19. 20 0
      src/main/java/com/izouma/nineth/service/AssetService.java
  20. 20 0
      src/main/java/com/izouma/nineth/service/CollectionService.java
  21. 20 0
      src/main/java/com/izouma/nineth/service/FollowService.java
  22. 20 0
      src/main/java/com/izouma/nineth/service/LikeService.java
  23. 20 0
      src/main/java/com/izouma/nineth/service/OrderService.java
  24. 16 1
      src/main/java/com/izouma/nineth/service/UserService.java
  25. 60 0
      src/main/java/com/izouma/nineth/web/AssetController.java
  26. 60 0
      src/main/java/com/izouma/nineth/web/CollectionController.java
  27. 1 1
      src/main/java/com/izouma/nineth/web/FileUploadController.java
  28. 60 0
      src/main/java/com/izouma/nineth/web/FollowController.java
  29. 60 0
      src/main/java/com/izouma/nineth/web/LikeController.java
  30. 60 0
      src/main/java/com/izouma/nineth/web/OrderController.java
  31. 2 2
      src/main/resources/application.yaml
  32. 0 0
      src/main/resources/genjson/Asset.json
  33. 0 0
      src/main/resources/genjson/Collection.json
  34. 1 0
      src/main/resources/genjson/Follow.json
  35. 1 0
      src/main/resources/genjson/Like.json
  36. 0 0
      src/main/resources/genjson/Order.json
  37. 1 0
      src/main/vue/package.json
  38. 96 0
      src/main/vue/src/router.js
  39. 1 1
      src/main/vue/src/views/Admin.vue
  40. 150 0
      src/main/vue/src/views/AssetEdit.vue
  41. 214 0
      src/main/vue/src/views/AssetList.vue
  42. 149 0
      src/main/vue/src/views/CollectionEdit.vue
  43. 207 0
      src/main/vue/src/views/CollectionList.vue
  44. 103 0
      src/main/vue/src/views/FollowEdit.vue
  45. 166 0
      src/main/vue/src/views/FollowList.vue
  46. 103 0
      src/main/vue/src/views/LikeEdit.vue
  47. 166 0
      src/main/vue/src/views/LikeList.vue
  48. 231 0
      src/main/vue/src/views/MinterEdit.vue
  49. 172 0
      src/main/vue/src/views/MinterList.vue
  50. 167 0
      src/main/vue/src/views/OrderEdit.vue
  51. 228 0
      src/main/vue/src/views/OrderList.vue
  52. 18 0
      src/main/vue/yarn.lock

+ 1 - 1
src/main/java/com/izouma/nineth/config/Constants.java

@@ -9,7 +9,7 @@ public interface Constants {
         String ID_NO    = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}[0-9xX]$";
     }
 
-    String DEFAULT_AVATAR = "https://zhumj.oss-cn-hangzhou.aliyuncs.com/image/user.jpg";
+    String DEFAULT_AVATAR = "https://awesomeadmin.oss-cn-hangzhou.aliyuncs.com/image/avatar_male.png";
 
     String SMS_SIGN_NAME = "走马信息";
 

+ 30 - 6
src/main/java/com/izouma/nineth/domain/Asset.java

@@ -1,16 +1,18 @@
 package com.izouma.nineth.domain;
 
+import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.StringArrayConverter;
 import com.izouma.nineth.enums.AssetStatus;
+import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import javax.persistence.Entity;
-import javax.persistence.EnumType;
-import javax.persistence.Enumerated;
+import javax.persistence.*;
 import java.math.BigDecimal;
+import java.util.List;
 
 
 @Data
@@ -18,6 +20,7 @@ import java.math.BigDecimal;
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
+@ApiModel("资产")
 public class Asset extends BaseEntity {
 
     @ApiModelProperty("用户ID")
@@ -27,27 +30,48 @@ public class Asset extends BaseEntity {
     private Long orderId;
 
     @ApiModelProperty("名称")
+    @Searchable
     private String name;
 
     @ApiModelProperty("铸造者")
+    @Searchable
     private String minter;
 
-    private String pic;
+    @ApiModelProperty("铸造者ID")
+    private Long minterId;
 
+    @ApiModelProperty("铸造者头像")
+    private Long minterAvatar;
+
+    @ApiModelProperty("图片")
+    @Convert(converter = StringArrayConverter.class)
+    @Column(columnDefinition = "TEXT")
+    private List<String> pic;
+
+    @ApiModelProperty("tokenId")
     private String tokenId;
 
-    private String transactionId;
+    @ApiModelProperty("购买hash")
+    private String txHash;
 
+    @ApiModelProperty("购买hash")
     private String ipfsUrl;
 
+    @ApiModelProperty("购买价格")
+    @Column(precision = 10, scale = 2)
     private BigDecimal price;
 
+    @ApiModelProperty("转让价格")
+    @Column(precision = 10, scale = 2)
     private BigDecimal sellPrice;
 
-    private String outTransactionId;
+    @ApiModelProperty("转让hash")
+    private String outTxHash;
 
+    @ApiModelProperty("转让订单ID")
     private Long outOrderId;
 
+    @ApiModelProperty("状态")
     @Enumerated(EnumType.STRING)
     private AssetStatus status;
 }

+ 8 - 2
src/main/java/com/izouma/nineth/domain/Collection.java

@@ -16,6 +16,7 @@ import java.util.List;
 
 @Data
 @Entity
+@Table(name = "collection_info")
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
@@ -32,12 +33,13 @@ public class Collection extends BaseEntity {
     private List<String> pics;
 
     @ApiModelProperty("铸造者")
+    @Searchable
     private String minter;
 
     @ApiModelProperty("铸造者ID")
     private Long minterId;
 
-    @ApiModelProperty("铸造者ID")
+    @ApiModelProperty("铸造者头像")
     private Long minterAvatar;
 
     @ApiModelProperty("详情")
@@ -48,6 +50,10 @@ public class Collection extends BaseEntity {
     @Enumerated(EnumType.STRING)
     private CollectionType type;
 
+    @ApiModelProperty("来源")
+    @Enumerated(EnumType.STRING)
+    private CollectionType source;
+
     @ApiModelProperty("已售")
     private int sale;
 
@@ -55,7 +61,7 @@ public class Collection extends BaseEntity {
     private int stock;
 
     @ApiModelProperty("点赞")
-    private int like;
+    private int likes;
 
     @ApiModelProperty("上架")
     private boolean onShelf;

+ 22 - 0
src/main/java/com/izouma/nineth/domain/Follow.java

@@ -0,0 +1,22 @@
+package com.izouma.nineth.domain;
+
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("关注")
+public class Follow extends BaseEntity {
+
+    Long userId;
+
+    Long followUserId;
+}

+ 25 - 0
src/main/java/com/izouma/nineth/domain/Like.java

@@ -0,0 +1,25 @@
+package com.izouma.nineth.domain;
+
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Data
+@Entity
+@Table(name = "like_info")
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("点赞")
+public class Like extends BaseEntity {
+
+    private Long userId;
+
+    private Long collectionId;
+
+}

+ 55 - 5
src/main/java/com/izouma/nineth/domain/Order.java

@@ -1,17 +1,20 @@
 package com.izouma.nineth.domain;
 
+import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.StringArrayConverter;
 import com.izouma.nineth.enums.OrderStatus;
+import com.izouma.nineth.enums.PayMethod;
 import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import javax.persistence.Entity;
-import javax.persistence.EnumType;
-import javax.persistence.Enumerated;
-import javax.persistence.Table;
+import javax.persistence.*;
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
 
 @Data
 @Entity
@@ -22,20 +25,67 @@ import java.math.BigDecimal;
 @ApiModel("订单")
 public class Order extends BaseEntity {
 
+    @ApiModelProperty("用户ID")
+    @Searchable
     private Long userId;
 
+    @ApiModelProperty("藏品ID")
+    @Searchable
     private Long collectionId;
 
+    @ApiModelProperty("数量")
+    private int qty;
+
+    @ApiModelProperty("名称")
+    @Searchable
     private String name;
 
-    private String pic;
+    @ApiModelProperty("图片")
+    @Convert(converter = StringArrayConverter.class)
+    @Column(columnDefinition = "TEXT")
+    private List<String> pic;
 
+    @ApiModelProperty("铸造者")
+    @Searchable
     private String minter;
 
+    @ApiModelProperty("铸造者ID")
+    @Searchable
+    private Long minterId;
+
+    @ApiModelProperty("铸造者头像")
+    private Long minterAvatar;
+
+    @ApiModelProperty("价格")
+    @Column(precision = 10, scale = 2)
     private BigDecimal price;
 
+    @ApiModelProperty("gas费")
+    @Column(precision = 10, scale = 2)
+    private BigDecimal gasPrice;
+
+    @ApiModelProperty("总价")
+    @Column(precision = 10, scale = 2)
+    private BigDecimal totalPrice;
+
+    @ApiModelProperty("状态")
     @Enumerated(EnumType.STRING)
     private OrderStatus status;
 
+    @ApiModelProperty("支付方式")
+    @Enumerated(EnumType.STRING)
+    private PayMethod payMethod;
+
+    @ApiModelProperty("交易ID")
+    @Searchable
+    private String transactionId;
+
+    @ApiModelProperty("支付时间")
+    private LocalDateTime payTime;
+
+    @ApiModelProperty("链上hash")
+    private String txHash;
 
+    @ApiModelProperty("消耗gas")
+    private int gas;
 }

+ 9 - 0
src/main/java/com/izouma/nineth/domain/User.java

@@ -68,4 +68,13 @@ public class User extends BaseEntity implements Serializable {
     private String phone;
 
     private String email;
+
+    private int follows;
+
+    private int followers;
+
+    private String intro;
+
+    private String bg;
+
 }

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

@@ -1,7 +1,8 @@
 package com.izouma.nineth.enums;
 
 public enum AssetStatus {
-    VALID("正常"),
+    NORMAL("正常"),
+    ON_SALE("出售中"),
     TRANSFERRED("已转让"),;
 
     private final String description;

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

@@ -2,7 +2,7 @@ package com.izouma.nineth.enums;
 
 public enum AuthorityName {
     ROLE_USER("普通用户"),
-    ROLE_AUTHOR("铸造者"),
+    ROLE_MINTER("铸造者"),
     ROLE_DEV("开发者"),
     ROLE_ADMIN("管理员");
     private final String description;

+ 17 - 0
src/main/java/com/izouma/nineth/enums/CollectionSource.java

@@ -0,0 +1,17 @@
+package com.izouma.nineth.enums;
+
+public enum CollectionSource {
+    OFFICIAL("官方铸造"),
+    USER("用户铸造"),
+    TRANSFER("转让");
+
+    private final String description;
+
+    CollectionSource(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 5 - 0
src/main/java/com/izouma/nineth/enums/LikeType.java

@@ -0,0 +1,5 @@
+package com.izouma.nineth.enums;
+
+public enum LikeType {
+
+}

+ 1 - 0
src/main/java/com/izouma/nineth/enums/OrderStatus.java

@@ -2,6 +2,7 @@ package com.izouma.nineth.enums;
 
 public enum OrderStatus {
     NOT_PAID("未支付"),
+    PROCESSING("已支付,处理中"),
     FINISH("已完成"),
     CANCELLED("已取消");
 

+ 16 - 0
src/main/java/com/izouma/nineth/enums/PayMethod.java

@@ -0,0 +1,16 @@
+package com.izouma.nineth.enums;
+
+public enum PayMethod {
+    WEIXIN("微信"),
+    ALIPAY("支付宝");
+
+    private final String description;
+
+    PayMethod(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

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

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Asset;
+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 AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationExecutor<Asset> {
+    @Query("update Asset t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

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

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Collection;
+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 CollectionRepo extends JpaRepository<Collection, Long>, JpaSpecificationExecutor<Collection> {
+    @Query("update Collection t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

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

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Follow;
+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 FollowRepo extends JpaRepository<Follow, Long>, JpaSpecificationExecutor<Follow> {
+    @Query("update Follow t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

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

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Like;
+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 LikeRepo extends JpaRepository<Like, Long>, JpaSpecificationExecutor<Like> {
+    @Query("update Like t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

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

@@ -0,0 +1,16 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.Order;
+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 OrderRepo extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
+    @Query("update Order t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+}

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

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

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

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

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

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

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

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

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

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

+ 16 - 1
src/main/java/com/izouma/nineth/service/UserService.java

@@ -7,6 +7,7 @@ import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.dto.UserRegister;
+import com.izouma.nineth.enums.AuthorityName;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.UserRepo;
 import com.izouma.nineth.security.Authority;
@@ -25,9 +26,14 @@ import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Page;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 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 java.text.SimpleDateFormat;
 import java.util.*;
 
@@ -44,10 +50,19 @@ public class UserService {
     private CaptchaService captchaService;
 
     public Page<User> all(PageQuery pageQuery) {
-        return userRepo.findAll(JpaUtils.toSpecification(pageQuery, User.class), JpaUtils.toPageRequest(pageQuery));
+        Specification<User> specification = JpaUtils.toSpecification(pageQuery, User.class);
+        if (pageQuery.getQuery().containsKey("hasRole")) {
+            String roleName = (String) pageQuery.getQuery().get("hasRole");
+            specification = specification.and((Specification<User>) (root, criteriaQuery, criteriaBuilder) ->
+                    criteriaBuilder.isMember(Authority.get(AuthorityName.valueOf(roleName)), root.get("authorities")));
+        }
+        return userRepo.findAll(specification, JpaUtils.toPageRequest(pageQuery));
     }
 
     public User create(UserRegister userRegister) {
+        if (StringUtils.isNoneEmpty(userRegister.getPhone()) && userRepo.findByPhoneAndDelFalse(userRegister.getPhone()) != null) {
+            throw new BusinessException("该手机号已注册");
+        }
         User user = new User();
         BeanUtils.copyProperties(userRegister, user);
         if (StringUtils.isNotBlank(userRegister.getPassword())) {

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

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.Asset;
+import com.izouma.nineth.service.AssetService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.AssetRepo;
+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("/asset")
+@AllArgsConstructor
+public class AssetController extends BaseController {
+    private AssetService assetService;
+    private AssetRepo assetRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Asset save(@RequestBody Asset record) {
+        if (record.getId() != null) {
+            Asset orig = assetRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return assetRepo.save(orig);
+        }
+        return assetRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Asset> all(@RequestBody PageQuery pageQuery) {
+        return assetService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Asset get(@PathVariable Long id) {
+        return assetRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        assetRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Asset> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

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

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.Collection;
+import com.izouma.nineth.service.CollectionService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.CollectionRepo;
+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("/collection")
+@AllArgsConstructor
+public class CollectionController extends BaseController {
+    private CollectionService collectionService;
+    private CollectionRepo collectionRepo;
+
+    //@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);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Collection> all(@RequestBody PageQuery pageQuery) {
+        return collectionService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Collection get(@PathVariable Long id) {
+        return collectionRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        collectionRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Collection> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 1 - 1
src/main/java/com/izouma/nineth/web/FileUploadController.java

@@ -31,7 +31,7 @@ public class FileUploadController {
 
     @PostMapping("/file")
     public String uploadFile(@RequestParam("file") MultipartFile file,
-                             @RequestParam(value = "   ", required = false) String path) {
+                             @RequestParam(value = "path", required = false) String path) {
         if (path == null) {
             String basePath = "application";
             try {

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

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.Follow;
+import com.izouma.nineth.service.FollowService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.FollowRepo;
+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("/follow")
+@AllArgsConstructor
+public class FollowController extends BaseController {
+    private FollowService followService;
+    private FollowRepo followRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Follow save(@RequestBody Follow record) {
+        if (record.getId() != null) {
+            Follow orig = followRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return followRepo.save(orig);
+        }
+        return followRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Follow> all(@RequestBody PageQuery pageQuery) {
+        return followService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Follow get(@PathVariable Long id) {
+        return followRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        followRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Follow> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

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

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.Like;
+import com.izouma.nineth.service.LikeService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.LikeRepo;
+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("/like")
+@AllArgsConstructor
+public class LikeController extends BaseController {
+    private LikeService likeService;
+    private LikeRepo likeRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Like save(@RequestBody Like record) {
+        if (record.getId() != null) {
+            Like orig = likeRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return likeRepo.save(orig);
+        }
+        return likeRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Like> all(@RequestBody PageQuery pageQuery) {
+        return likeService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Like get(@PathVariable Long id) {
+        return likeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        likeRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Like> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

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

@@ -0,0 +1,60 @@
+package com.izouma.nineth.web;
+import com.izouma.nineth.domain.Order;
+import com.izouma.nineth.service.OrderService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.OrderRepo;
+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("/order")
+@AllArgsConstructor
+public class OrderController extends BaseController {
+    private OrderService orderService;
+    private OrderRepo orderRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Order save(@RequestBody Order record) {
+        if (record.getId() != null) {
+            Order orig = orderRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return orderRepo.save(orig);
+        }
+        return orderRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Order> all(@RequestBody PageQuery pageQuery) {
+        return orderService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Order get(@PathVariable Long id) {
+        return orderRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        orderRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Order> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 2 - 2
src/main/resources/application.yaml

@@ -78,8 +78,8 @@ aliyun:
   access-key-id: PXzJyah5rZfWHIIH
   access-key-secret: e1MS6j0wypXJrw8CM0hObZu8qKbfah
   oss-end-point: oss-cn-hangzhou.aliyuncs.com
-  oss-bucket-name: ticket-exchange
-  oss-domain: https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com
+  oss-bucket-name: awesomeadmin
+  oss-domain: https://awesomeadmin.oss-cn-hangzhou.aliyuncs.com
 general:
   host: http://art.izouma.com
 mychain:

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


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


+ 1 - 0
src/main/resources/genjson/Follow.json

@@ -0,0 +1 @@
+{"tableName":"Follow","className":"Follow","remark":"关注","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/drew/Projects/Java/9th/src/main/java/com/izouma/nineth","viewPath":"/Users/drew/Projects/Java/9th/src/main/vue/src/views","routerPath":"/Users/drew/Projects/Java/9th/src/main/vue/src","resourcesPath":"/Users/drew/Projects/Java/9th/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"userId","modelName":"userId","remark":"userId","showInList":true,"showInForm":true,"formType":"number"},{"name":"followUserId","modelName":"followUserId","remark":"followUserId","showInList":true,"showInForm":true,"formType":"number"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.Follow"}

+ 1 - 0
src/main/resources/genjson/Like.json

@@ -0,0 +1 @@
+{"tableName":"Like","className":"Like","remark":"点赞","genTable":true,"genClass":true,"genList":true,"genForm":true,"genRouter":true,"javaPath":"/Users/drew/Projects/Java/9th/src/main/java/com/izouma/nineth","viewPath":"/Users/drew/Projects/Java/9th/src/main/vue/src/views","routerPath":"/Users/drew/Projects/Java/9th/src/main/vue/src","resourcesPath":"/Users/drew/Projects/Java/9th/src/main/resources","dataBaseType":"Mysql","fields":[{"name":"userId","modelName":"userId","remark":"userId","showInList":true,"showInForm":true,"formType":"number"},{"name":"collectionId","modelName":"collectionId","remark":"collectionId","showInList":true,"showInForm":true,"formType":"number"}],"readTable":false,"dataSourceCode":"dataSource","genJson":"","subtables":[],"update":false,"basePackage":"com.izouma.nineth","tablePackage":"com.izouma.nineth.domain.Like"}

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


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

@@ -24,6 +24,7 @@
     "jdenticon": "^3.1.0",
     "normalize.css": "^8.0.1",
     "qs": "^6.9.0",
+    "randomstring": "^1.2.1",
     "resolve-url": "^0.2.1",
     "tinymce": "^5.2.2",
     "vue": "^2.6.11",

+ 96 - 0
src/main/vue/src/router.js

@@ -134,6 +134,102 @@ const router = new Router({
                     meta: {
                         title: '异常日志'
                     }
+                },
+                {
+                    path: '/collectionEdit',
+                    name: 'CollectionEdit',
+                    component: () => import(/* webpackChunkName: "collectionEdit" */ '@/views/CollectionEdit.vue'),
+                    meta: {
+                        title: '藏品管理编辑'
+                    }
+                },
+                {
+                    path: '/collectionList',
+                    name: 'CollectionList',
+                    component: () => import(/* webpackChunkName: "collectionList" */ '@/views/CollectionList.vue'),
+                    meta: {
+                        title: '藏品管理'
+                    }
+                },
+                {
+                    path: '/orderEdit',
+                    name: 'OrderEdit',
+                    component: () => import(/* webpackChunkName: "orderEdit" */ '@/views/OrderEdit.vue'),
+                    meta: {
+                        title: '订单编辑'
+                    }
+                },
+                {
+                    path: '/orderList',
+                    name: 'OrderList',
+                    component: () => import(/* webpackChunkName: "orderList" */ '@/views/OrderList.vue'),
+                    meta: {
+                        title: '订单'
+                    }
+                },
+                {
+                    path: '/assetEdit',
+                    name: 'AssetEdit',
+                    component: () => import(/* webpackChunkName: "assetEdit" */ '@/views/AssetEdit.vue'),
+                    meta: {
+                        title: '资产编辑'
+                    }
+                },
+                {
+                    path: '/assetList',
+                    name: 'AssetList',
+                    component: () => import(/* webpackChunkName: "assetList" */ '@/views/AssetList.vue'),
+                    meta: {
+                        title: '资产'
+                    }
+                },
+                {
+                    path: '/likeEdit',
+                    name: 'LikeEdit',
+                    component: () => import(/* webpackChunkName: "likeEdit" */ '@/views/LikeEdit.vue'),
+                    meta: {
+                        title: '点赞编辑'
+                    }
+                },
+                {
+                    path: '/likeList',
+                    name: 'LikeList',
+                    component: () => import(/* webpackChunkName: "likeList" */ '@/views/LikeList.vue'),
+                    meta: {
+                        title: '点赞'
+                    }
+                },
+                {
+                    path: '/followEdit',
+                    name: 'FollowEdit',
+                    component: () => import(/* webpackChunkName: "followEdit" */ '@/views/FollowEdit.vue'),
+                    meta: {
+                        title: '关注编辑'
+                    }
+                },
+                {
+                    path: '/followList',
+                    name: 'FollowList',
+                    component: () => import(/* webpackChunkName: "followList" */ '@/views/FollowList.vue'),
+                    meta: {
+                        title: '关注'
+                    }
+                },
+                {
+                    path: '/minterEdit',
+                    name: 'MinterEdit',
+                    component: () => import(/* webpackChunkName: "minterEdit" */ '@/views/MinterEdit.vue'),
+                    meta: {
+                        title: '铸造者编辑'
+                    }
+                },
+                {
+                    path: '/minterList',
+                    name: 'MinterList',
+                    component: () => import(/* webpackChunkName: "minterList" */ '@/views/MinterList.vue'),
+                    meta: {
+                        title: '铸造者列表'
+                    }
                 }
                 /**INSERT_LOCATION**/
             ]

+ 1 - 1
src/main/vue/src/views/Admin.vue

@@ -4,7 +4,7 @@
             <div v-if="collapse" class="logo-wrapper collapse" @click="$router.push('/dashboard')">
                 Thoma<br /><b>Admin</b>
             </div>
-            <div v-else class="logo-wrapper" @click="$router.push('/dashboard')">Thoma&nbsp;<b>Admin</b></div>
+            <div v-else class="logo-wrapper" @click="$router.push('/dashboard')">9th</div>
             <el-menu
                 :collapse="collapse"
                 :background-color="$theme['menu-bg']"

+ 150 - 0
src/main/vue/src/views/AssetEdit.vue

@@ -0,0 +1,150 @@
+<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="userId" label="用户ID">
+                                    <el-input-number type="number" v-model="formData.userId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="orderId" label="订单ID">
+                                    <el-input-number type="number" v-model="formData.orderId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="name" label="名称">
+                                    <el-input v-model="formData.name"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="minter" label="铸造者">
+                                    <el-input v-model="formData.minter"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="minterId" label="铸造者ID">
+                                    <el-input-number type="number" v-model="formData.minterId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="minterAvatar" label="铸造者头像">
+                                    <el-input-number type="number" v-model="formData.minterAvatar"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="pic" label="图片">
+                                    <el-input v-model="formData.pic"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="tokenId" label="tokenId">
+                                    <el-input v-model="formData.tokenId"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="txHash" label="购买hash">
+                                    <el-input v-model="formData.txHash"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="ipfsUrl" label="购买hash">
+                                    <el-input v-model="formData.ipfsUrl"></el-input>
+                        </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="sellPrice" label="转让价格">
+                                    <el-input-number type="number" v-model="formData.sellPrice"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="outTxHash" label="转让hash">
+                                    <el-input v-model="formData.outTxHash"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="outOrderId" label="转让订单ID">
+                                    <el-input-number type="number" v-model="formData.outOrderId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="status" label="状态">
+                                    <el-select v-model="formData.status" clearable filterable placeholder="请选择">
+                                        <el-option
+                                                v-for="item in statusOptions"
+                                                :key="item.value"
+                                                :label="item.label"
+                                                :value="item.value">
+                                        </el-option>
+                                    </el-select>
+                        </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>
+    </div>
+</template>
+<script>
+    export default {
+        name: 'AssetEdit',
+        created() {
+            if (this.$route.query.id) {
+                this.$http
+                    .get('asset/get/' + this.$route.query.id)
+                    .then(res => {
+                        this.formData = res;
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        data() {
+            return {
+                saving: false,
+                formData: {
+                },
+                rules: {
+                },
+                statusOptions: [{"label":"正常","value":"NORMAL"},{"label":"出售中","value":"ON_SALE"},{"label":"已转让","value":"TRANSFERRED"}],
+            }
+        },
+        methods: {
+            onSave() {
+                this.$refs.form.validate((valid) => {
+                    if (valid) {
+                        this.submit();
+                    } else {
+                        return false;
+                    }
+                });
+            },
+            submit() {
+                let data = {...this.formData};
+
+                this.saving = true;
+                this.$http
+                    .post('/asset/save', 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(`/asset/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 || '删除失败');
+                    }
+                })
+            },
+        }
+    }
+</script>
+<style lang="less" scoped></style>

+ 214 - 0
src/main/vue/src/views/AssetList.vue

@@ -0,0 +1,214 @@
+<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="userId" label="用户ID"
+>
+                    </el-table-column>
+                    <el-table-column prop="orderId" label="订单ID"
+>
+                    </el-table-column>
+                    <el-table-column prop="name" label="名称"
+>
+                    </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="minterAvatar" label="铸造者头像"
+>
+                    </el-table-column>
+                    <el-table-column prop="pic" label="图片"
+>
+                    </el-table-column>
+                    <el-table-column prop="tokenId" label="tokenId"
+>
+                    </el-table-column>
+                    <el-table-column prop="txHash" label="购买hash"
+>
+                    </el-table-column>
+                    <el-table-column prop="ipfsUrl" label="购买hash"
+>
+                    </el-table-column>
+                    <el-table-column prop="price" label="购买价格"
+>
+                    </el-table-column>
+                    <el-table-column prop="sellPrice" label="转让价格"
+>
+                    </el-table-column>
+                    <el-table-column prop="outTxHash" label="转让hash"
+>
+                    </el-table-column>
+                    <el-table-column prop="outOrderId" label="转让订单ID"
+>
+                    </el-table-column>
+                    <el-table-column prop="status" label="状态"
+                            :formatter="statusFormatter"
+                        >
+                    </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: 'AssetList',
+        mixins: [pageableTable],
+        data() {
+            return {
+                multipleMode: false,
+                search: "",
+                url: "/asset/all",
+                downloading: false,
+                        statusOptions:[{"label":"正常","value":"NORMAL"},{"label":"出售中","value":"ON_SALE"},{"label":"已转让","value":"TRANSFERRED"}],
+            }
+        },
+        computed: {
+            selection() {
+                return this.$refs.table.selection.map(i => i.id);
+            }
+        },
+        methods: {
+                    statusFormatter(row, column, cellValue, index) {
+                        let selectedOption = this.statusOptions.find(i => i.value === cellValue);
+                        if (selectedOption) {
+                            return selectedOption.label;
+                        }
+                        return '';
+                    },
+            beforeGetData() {
+                return { search: this.search, query: { del: false } };
+            },
+            toggleMultipleMode(multipleMode) {
+                this.multipleMode = multipleMode;
+                if (!multipleMode) {
+                    this.$refs.table.clearSelection();
+                }
+            },
+            addRow() {
+                this.$router.push({
+                    path: "/assetEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/assetEdit",
+                    query: {
+                    id: row.id
+                    }
+                });
+            },
+            download() {
+                this.downloading = true;
+                this.$axios
+                    .get("/asset/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(`/asset/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>

+ 149 - 0
src/main/vue/src/views/CollectionEdit.vue

@@ -0,0 +1,149 @@
+<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="minter" label="铸造者">
+                                    <el-input v-model="formData.minter"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="minterId" label="铸造者ID">
+                                    <el-input-number type="number" v-model="formData.minterId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="minterAvatar" label="铸造者头像">
+                        </el-form-item>
+                        <el-form-item prop="detail" label="详情">
+                                    <el-input v-model="formData.detail"></el-input>
+                        </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="sale" label="已售">
+                        </el-form-item>
+                        <el-form-item prop="stock" label="库存">
+                                    <el-input v-model="formData.stock"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="likes" label="点赞">
+                        </el-form-item>
+                        <el-form-item prop="onShelf" label="上架">
+                                    <el-input v-model="formData.onShelf"></el-input>
+                        </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 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>
+    </div>
+</template>
+<script>
+    export default {
+        name: 'CollectionEdit',
+        created() {
+            if (this.$route.query.id) {
+                this.$http
+                    .get('collection/get/' + this.$route.query.id)
+                    .then(res => {
+                        this.formData = res;
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        data() {
+            return {
+                saving: false,
+                formData: {
+                },
+                rules: {
+                },
+                typeOptions: [{"label":"默认","value":"DEFAULT"},{"label":"盲盒","value":"BLIND_BOX"},{"label":"拍卖","value":"AUCTION"}],
+                sourceOptions: [{"label":"默认","value":"DEFAULT"},{"label":"盲盒","value":"BLIND_BOX"},{"label":"拍卖","value":"AUCTION"}],
+            }
+        },
+        methods: {
+            onSave() {
+                this.$refs.form.validate((valid) => {
+                    if (valid) {
+                        this.submit();
+                    } else {
+                        return false;
+                    }
+                });
+            },
+            submit() {
+                let data = {...this.formData};
+
+                this.saving = true;
+                this.$http
+                    .post('/collection/save', 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 || '删除失败');
+                    }
+                })
+            },
+        }
+    }
+</script>
+<style lang="less" scoped></style>

+ 207 - 0
src/main/vue/src/views/CollectionList.vue

@@ -0,0 +1,207 @@
+<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="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>
+                    <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":"DEFAULT"},{"label":"盲盒","value":"BLIND_BOX"},{"label":"拍卖","value":"AUCTION"}],
+            }
+        },
+        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 } };
+            },
+            toggleMultipleMode(multipleMode) {
+                this.multipleMode = multipleMode;
+                if (!multipleMode) {
+                    this.$refs.table.clearSelection();
+                }
+            },
+            addRow() {
+                this.$router.push({
+                    path: "/collectionEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/collectionEdit",
+                    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>

+ 103 - 0
src/main/vue/src/views/FollowEdit.vue

@@ -0,0 +1,103 @@
+<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="107px" label-position="right"
+                         size="small"
+                         style="max-width: 500px;">
+                        <el-form-item prop="userId" label="userId">
+                                    <el-input-number type="number" v-model="formData.userId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="followUserId" label="followUserId">
+                                    <el-input-number type="number" v-model="formData.followUserId"></el-input-number>
+                        </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>
+    </div>
+</template>
+<script>
+    export default {
+        name: 'FollowEdit',
+        created() {
+            if (this.$route.query.id) {
+                this.$http
+                    .get('follow/get/' + this.$route.query.id)
+                    .then(res => {
+                        this.formData = res;
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        data() {
+            return {
+                saving: false,
+                formData: {
+                },
+                rules: {
+                },
+            }
+        },
+        methods: {
+            onSave() {
+                this.$refs.form.validate((valid) => {
+                    if (valid) {
+                        this.submit();
+                    } else {
+                        return false;
+                    }
+                });
+            },
+            submit() {
+                let data = {...this.formData};
+
+                this.saving = true;
+                this.$http
+                    .post('/follow/save', 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(`/follow/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 || '删除失败');
+                    }
+                })
+            },
+        }
+    }
+</script>
+<style lang="less" scoped></style>

+ 166 - 0
src/main/vue/src/views/FollowList.vue

@@ -0,0 +1,166 @@
+<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="userId" label="userId"
+>
+                    </el-table-column>
+                    <el-table-column prop="followUserId" label="followUserId"
+>
+                    </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: 'FollowList',
+        mixins: [pageableTable],
+        data() {
+            return {
+                multipleMode: false,
+                search: "",
+                url: "/follow/all",
+                downloading: false,
+            }
+        },
+        computed: {
+            selection() {
+                return this.$refs.table.selection.map(i => i.id);
+            }
+        },
+        methods: {
+            beforeGetData() {
+                return { search: this.search, query: { del: false } };
+            },
+            toggleMultipleMode(multipleMode) {
+                this.multipleMode = multipleMode;
+                if (!multipleMode) {
+                    this.$refs.table.clearSelection();
+                }
+            },
+            addRow() {
+                this.$router.push({
+                    path: "/followEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/followEdit",
+                    query: {
+                    id: row.id
+                    }
+                });
+            },
+            download() {
+                this.downloading = true;
+                this.$axios
+                    .get("/follow/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(`/follow/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>

+ 103 - 0
src/main/vue/src/views/LikeEdit.vue

@@ -0,0 +1,103 @@
+<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="101px" label-position="right"
+                         size="small"
+                         style="max-width: 500px;">
+                        <el-form-item prop="userId" label="userId">
+                                    <el-input-number type="number" v-model="formData.userId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="collectionId" label="collectionId">
+                                    <el-input-number type="number" v-model="formData.collectionId"></el-input-number>
+                        </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>
+    </div>
+</template>
+<script>
+    export default {
+        name: 'LikeEdit',
+        created() {
+            if (this.$route.query.id) {
+                this.$http
+                    .get('like/get/' + this.$route.query.id)
+                    .then(res => {
+                        this.formData = res;
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        data() {
+            return {
+                saving: false,
+                formData: {
+                },
+                rules: {
+                },
+            }
+        },
+        methods: {
+            onSave() {
+                this.$refs.form.validate((valid) => {
+                    if (valid) {
+                        this.submit();
+                    } else {
+                        return false;
+                    }
+                });
+            },
+            submit() {
+                let data = {...this.formData};
+
+                this.saving = true;
+                this.$http
+                    .post('/like/save', 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(`/like/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 || '删除失败');
+                    }
+                })
+            },
+        }
+    }
+</script>
+<style lang="less" scoped></style>

+ 166 - 0
src/main/vue/src/views/LikeList.vue

@@ -0,0 +1,166 @@
+<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="userId" label="userId"
+>
+                    </el-table-column>
+                    <el-table-column prop="collectionId" label="collectionId"
+>
+                    </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: 'LikeList',
+        mixins: [pageableTable],
+        data() {
+            return {
+                multipleMode: false,
+                search: "",
+                url: "/like/all",
+                downloading: false,
+            }
+        },
+        computed: {
+            selection() {
+                return this.$refs.table.selection.map(i => i.id);
+            }
+        },
+        methods: {
+            beforeGetData() {
+                return { search: this.search, query: { del: false } };
+            },
+            toggleMultipleMode(multipleMode) {
+                this.multipleMode = multipleMode;
+                if (!multipleMode) {
+                    this.$refs.table.clearSelection();
+                }
+            },
+            addRow() {
+                this.$router.push({
+                    path: "/likeEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/likeEdit",
+                    query: {
+                    id: row.id
+                    }
+                });
+            },
+            download() {
+                this.downloading = true;
+                this.$axios
+                    .get("/like/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(`/like/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>

+ 231 - 0
src/main/vue/src/views/MinterEdit.vue

@@ -0,0 +1,231 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)">取消</el-button>
+            <el-button
+                @click="del"
+                :loading="$store.state.fetchingData"
+                type="danger"
+                v-if="formData.id && formData.id !== 1"
+                >删除
+            </el-button>
+            <el-button @click="onSave" :loading="$store.state.fetchingData" 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="80px"
+                    label-position="right"
+                    style="max-width: 500px"
+                >
+                    <el-form-item prop="avatar" label="头像">
+                        <crop-upload v-model="formData.avatar"></crop-upload>
+                    </el-form-item>
+                    <el-form-item prop="username" label="用户名">
+                        <el-input v-model="formData.username"></el-input>
+                        <div class="gen" @dblclick="gen"></div>
+                    </el-form-item>
+                    <el-form-item prop="nickname" label="昵称">
+                        <el-input v-model="formData.nickname"></el-input>
+                    </el-form-item>
+                    <el-form-item v-if="formData.id" label="密码">
+                        <el-button type="primary" plain @click="resetPassword" size="mini">重置 </el-button>
+                    </el-form-item>
+                    <el-form-item v-else prop="password" label="密码">
+                        <el-input v-model="formData.password"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="phone" label="手机">
+                        <el-input v-model="formData.phone"></el-input>
+                    </el-form-item>
+                    <!-- <el-form-item prop="authorities" label="角色">
+                        <el-select
+                            v-model="formData.authorities"
+                            multiple
+                            placeholder="请选择"
+                            value-key="name"
+                            style="width: 100%"
+                        >
+                            <el-option
+                                v-for="item in authorities"
+                                :key="item.name"
+                                :label="item.description"
+                                :value="item"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item> -->
+                    <el-form-item>
+                        <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+                        <el-button @click="del" :disabled="saving" type="danger" v-if="formData.id && formData.id !== 1"
+                            >删除
+                        </el-button>
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import { toSvg } from 'jdenticon';
+import { createIcon } from '@download/blockies';
+import randomstring from 'randomstring';
+import faker from 'faker';
+faker.locale = 'zh_CN';
+console.log(faker);
+export default {
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get(`/user/get/${this.$route.query.id}`)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+        this.$http
+            .get('/authority/all')
+            .then(res => {
+                this.authorities = res;
+            })
+            .catch(e => {
+                console.log(e);
+            });
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {
+                username:
+                    '9th_' +
+                    randomstring.generate({
+                        length: 6,
+                        charset: 'alphabetic',
+                        capitalization: 'lowercase'
+                    }),
+                avatar:
+                    parseInt(Math.random() * 10) % 2 === 1
+                        ? 'https://awesomeadmin.oss-cn-hangzhou.aliyuncs.com/image/avatar_male.png'
+                        : 'https://awesomeadmin.oss-cn-hangzhou.aliyuncs.com/image/avatar_female.png'
+            },
+            rules: {
+                avatar: [{ required: true, message: '请上传头像', trigger: 'blur' }],
+                username: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
+                nickname: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
+                password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+                phone: [
+                    // { required: true, message: '请输入密码', trigger: 'blur' },
+                    {
+                        pattern: /^1[3-9]\d{9}$/,
+                        message: '请输入正确的手机号',
+                        trigger: 'blur'
+                    }
+                ],
+                authorities: [{ required: true, message: '请选择角色', trigger: 'blur' }],
+                saving: false
+            },
+            authorities: [{ name: 'ROLE_MINTER', description: '铸造者' }]
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            this.saving = true;
+            this.$http
+                .post(this.formData.id ? '/user/save' : '/user/create', this.formData, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.formData = res;
+                    this.$router.replace({
+                        query: {
+                            id: res.id
+                        }
+                    });
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        del() {
+            this.$confirm('确认删除吗?', '提示', { type: 'warning' })
+                .then(() => {
+                    return this.$http.post(`/user/del/${this.formData.id}`);
+                })
+                .then(res => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if ('cancel' !== e) {
+                        this.$message.error(e.error || '删除失败');
+                    }
+                });
+        },
+        resetPassword() {
+            this.$prompt('请输入新密码', '重置密码', { inputType: 'password' })
+                .then(res => {
+                    console.log(res);
+                    if (res.value) {
+                        this.$alert('确定重置密码?', '提示', {
+                            showCancelButton: true
+                        })
+                            .then(() => {
+                                return this.$http.post('/user/setPasswordAdmin', {
+                                    userId: this.formData.id,
+                                    password: res.value
+                                });
+                            })
+                            .then(res => {
+                                this.$message.success('密码重置成功');
+                            })
+                            .catch(() => {
+                                this.$message.error(res.error || '重置密码失败');
+                            });
+                    }
+                })
+                .catch(() => {});
+        },
+        gen() {
+            const icon = createIcon({
+                size: 10,
+                scale: 20
+            });
+            this.$http.post('/upload/base64', { base64: icon.toDataURL() }).then(res => {
+                this.formData.avatar = res;
+            });
+            const card = faker.helpers.createCard();
+            this.formData.username = card.username;
+            this.formData.nickname = card.name;
+            this.formData.phone = card.phone;
+            this.$message('ok');
+            console.log(card);
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.gen {
+    position: absolute;
+    top: 0;
+    right: -50px;
+    width: 50px;
+    height: 32px;
+}
+</style>

+ 172 - 0
src/main/vue/src/views/MinterList.vue

@@ -0,0 +1,172 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" :loading="downloading" class="filter-item">
+                新增
+            </el-button>
+            <el-button @click="download" icon="el-icon-upload2" :loading="downloading" class="filter-item">
+                导出
+            </el-button>
+        </page-title>
+        <div class="filters-container">
+            <el-input placeholder="搜索..." v-model="search" clearable class="filter-item search">
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            height="tableHeight"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+        >
+            <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="username" label="用户名" min-width="300"> </el-table-column>
+            <el-table-column prop="nickname" label="昵称" min-width="300"> </el-table-column>
+            <el-table-column label="头像" min-width="300">
+                <template slot-scope="{ row }">
+                    <el-image
+                        style="width: 30px; height: 30px"
+                        :src="row.avatar"
+                        fit="cover"
+                        :preview-src-list="[row.avatar]"
+                    ></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right">
+                <template slot-scope="{ row }">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <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';
+import ClipboardJS from 'clipboard';
+const clickData = {};
+export default {
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/user/all',
+            downloading: false
+        };
+    },
+    computed: {
+        ...mapState([]),
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        beforeGetData() {
+            return { search: this.search, query: { hasRole: 'ROLE_MINTER' } };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/minterEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/minterEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/user/excel', { responseType: 'blob' })
+                .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');
+        },
+        clickId(row) {
+            if (row.id !== clickData.id) {
+                clickData.id = row.id;
+                clickData.c = 0;
+            }
+            clickData.c = (clickData.c || 0) + 1;
+            if (clickData.i) {
+                clearInterval(clickData.i);
+            }
+            clickData.i = setTimeout(_ => {
+                clickData.c = 0;
+            }, 200);
+            if (clickData.c === 5) {
+                this.$http
+                    .get(`/user/getToken/${row.id}`)
+                    .then(res => {
+                        let el = document.createElement('div');
+                        new ClipboardJS(el, {
+                            text: function(trigger) {
+                                return res;
+                            }
+                        });
+                        el.click();
+                        this.$message.success('已复制Token');
+                        clickData.c = 0;
+                    })
+                    .catch(e => {
+                        this.$message.error(e.error);
+                    });
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 167 - 0
src/main/vue/src/views/OrderEdit.vue

@@ -0,0 +1,167 @@
+<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="userId" label="用户ID">
+                                    <el-input-number type="number" v-model="formData.userId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="collectionId" label="藏品ID">
+                                    <el-input-number type="number" v-model="formData.collectionId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="qty" label="数量">
+                                    <el-input v-model="formData.qty"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="name" label="名称">
+                                    <el-input v-model="formData.name"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="pic" label="图片">
+                                    <multi-upload v-model="formData.pic"></multi-upload>
+                        </el-form-item>
+                        <el-form-item prop="minter" label="铸造者">
+                        </el-form-item>
+                        <el-form-item prop="minterId" label="铸造者ID">
+                                    <el-input-number type="number" v-model="formData.minterId"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="minterAvatar" label="铸造者头像">
+                        </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="gasPrice" label="gas费">
+                                    <el-input-number type="number" v-model="formData.gasPrice"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="totalPrice" label="总价">
+                                    <el-input-number type="number" v-model="formData.totalPrice"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="status" label="状态">
+                                    <el-select v-model="formData.status" clearable filterable placeholder="请选择">
+                                        <el-option
+                                                v-for="item in statusOptions"
+                                                :key="item.value"
+                                                :label="item.label"
+                                                :value="item.value">
+                                        </el-option>
+                                    </el-select>
+                        </el-form-item>
+                        <el-form-item prop="payMethod" label="支付方式">
+                                    <el-select v-model="formData.payMethod" clearable filterable placeholder="请选择">
+                                        <el-option
+                                                v-for="item in payMethodOptions"
+                                                :key="item.value"
+                                                :label="item.label"
+                                                :value="item.value">
+                                        </el-option>
+                                    </el-select>
+                        </el-form-item>
+                        <el-form-item prop="transactionId" label="交易ID">
+                                    <el-input v-model="formData.transactionId"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="payTime" label="支付时间">
+                                    <el-date-picker
+                                            v-model="formData.payTime"
+                                            type="datetime"
+                                            value-format="yyyy-MM-dd HH:mm:ss"
+                                            placeholder="选择日期时间">
+                                    </el-date-picker>
+                        </el-form-item>
+                        <el-form-item prop="txHash" label="链上hash">
+                                    <el-input v-model="formData.txHash"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="gas" label="消耗gas">
+                                    <el-input v-model="formData.gas"></el-input>
+                        </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>
+    </div>
+</template>
+<script>
+    export default {
+        name: 'OrderEdit',
+        created() {
+            if (this.$route.query.id) {
+                this.$http
+                    .get('order/get/' + this.$route.query.id)
+                    .then(res => {
+                        this.formData = res;
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        data() {
+            return {
+                saving: false,
+                formData: {
+                },
+                rules: {
+                },
+                statusOptions: [{"label":"未支付","value":"NOT_PAID"},{"label":"已支付,处理中","value":"PROCESSING"},{"label":"已完成","value":"FINISH"},{"label":"已取消","value":"CANCELLED"}],
+                payMethodOptions: [{"label":"微信","value":"WEIXIN"},{"label":"支付宝","value":"ALIPAY"}],
+            }
+        },
+        methods: {
+            onSave() {
+                this.$refs.form.validate((valid) => {
+                    if (valid) {
+                        this.submit();
+                    } else {
+                        return false;
+                    }
+                });
+            },
+            submit() {
+                let data = {...this.formData};
+
+                this.saving = true;
+                this.$http
+                    .post('/order/save', 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(`/order/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 || '删除失败');
+                    }
+                })
+            },
+        }
+    }
+</script>
+<style lang="less" scoped></style>

+ 228 - 0
src/main/vue/src/views/OrderList.vue

@@ -0,0 +1,228 @@
+<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="userId" label="用户ID"
+>
+                    </el-table-column>
+                    <el-table-column prop="collectionId" label="藏品ID"
+>
+                    </el-table-column>
+                    <el-table-column prop="qty" label="数量"
+>
+                    </el-table-column>
+                    <el-table-column prop="name" label="名称"
+>
+                    </el-table-column>
+                    <el-table-column prop="pic" label="图片"
+>
+                            <template slot-scope="{row}">
+                                <el-image style="width: 30px; height: 30px"
+                                          :src="row.pic[0]" fit="cover"
+                                          :preview-src-list="row.pic"></el-image>
+                            </template>
+                    </el-table-column>
+                    <el-table-column prop="minter" label="铸造者"
+>
+                    </el-table-column>
+                    <el-table-column prop="price" label="价格"
+>
+                    </el-table-column>
+                    <el-table-column prop="gasPrice" label="gas费"
+>
+                    </el-table-column>
+                    <el-table-column prop="totalPrice" label="总价"
+>
+                    </el-table-column>
+                    <el-table-column prop="status" label="状态"
+                            :formatter="statusFormatter"
+                        >
+                    </el-table-column>
+                    <el-table-column prop="payMethod" label="支付方式"
+                            :formatter="payMethodFormatter"
+                        >
+                    </el-table-column>
+                    <el-table-column prop="transactionId" label="交易ID"
+>
+                    </el-table-column>
+                    <el-table-column prop="payTime" label="支付时间"
+>
+                    </el-table-column>
+                    <el-table-column prop="txHash" label="链上hash"
+>
+                    </el-table-column>
+                    <el-table-column prop="gas" label="消耗gas"
+>
+                    </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: 'OrderList',
+        mixins: [pageableTable],
+        data() {
+            return {
+                multipleMode: false,
+                search: "",
+                url: "/order/all",
+                downloading: false,
+                        statusOptions:[{"label":"未支付","value":"NOT_PAID"},{"label":"已支付,处理中","value":"PROCESSING"},{"label":"已完成","value":"FINISH"},{"label":"已取消","value":"CANCELLED"}],
+                        payMethodOptions:[{"label":"微信","value":"WEIXIN"},{"label":"支付宝","value":"ALIPAY"}],
+            }
+        },
+        computed: {
+            selection() {
+                return this.$refs.table.selection.map(i => i.id);
+            }
+        },
+        methods: {
+                    statusFormatter(row, column, cellValue, index) {
+                        let selectedOption = this.statusOptions.find(i => i.value === cellValue);
+                        if (selectedOption) {
+                            return selectedOption.label;
+                        }
+                        return '';
+                    },
+                    payMethodFormatter(row, column, cellValue, index) {
+                        let selectedOption = this.payMethodOptions.find(i => i.value === cellValue);
+                        if (selectedOption) {
+                            return selectedOption.label;
+                        }
+                        return '';
+                    },
+            beforeGetData() {
+                return { search: this.search, query: { del: false } };
+            },
+            toggleMultipleMode(multipleMode) {
+                this.multipleMode = multipleMode;
+                if (!multipleMode) {
+                    this.$refs.table.clearSelection();
+                }
+            },
+            addRow() {
+                this.$router.push({
+                    path: "/orderEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/orderEdit",
+                    query: {
+                    id: row.id
+                    }
+                });
+            },
+            download() {
+                this.downloading = true;
+                this.$axios
+                    .get("/order/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(`/order/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>

+ 18 - 0
src/main/vue/yarn.lock

@@ -1632,6 +1632,11 @@ array-union@^1.0.1, array-union@^1.0.2:
   dependencies:
     array-uniq "^1.0.1"
 
+array-uniq@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d"
+  integrity sha1-X8w3OSB3VyPP1k1lxkvvU7+eum0=
+
 array-uniq@^1.0.1, array-uniq@^1.0.2:
   version "1.0.3"
   resolved "https://registry.npm.taobao.org/array-uniq/download/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@@ -7829,6 +7834,11 @@ querystringify@^2.1.1:
   resolved "https://registry.npm.taobao.org/querystringify/download/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
   integrity sha1-YOWl/WSn+L+k0qsu1v30yFutFU4=
 
+randombytes@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
+  integrity sha1-Z0yZdgkBw8QRJ3GjHlIdw0nMCew=
+
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -7844,6 +7854,14 @@ randomfill@^1.0.3:
     randombytes "^2.0.5"
     safe-buffer "^5.1.0"
 
+randomstring@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.2.1.tgz#71cd3cda24ad1b7e0b65286b3aa5c10853019349"
+  integrity sha512-eMnfell9XuU3jfCx3f4xCaFAt0YMFPZhx9R3PSStmLarDKg5j5vivqKhf/8pvG+VX/YkxsckHK/VPUrKa5V07A==
+  dependencies:
+    array-uniq "1.0.2"
+    randombytes "2.0.3"
+
 range-parser@^1.2.1, range-parser@~1.2.1:
   version "1.2.1"
   resolved "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"

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