Parcourir la source

Merge branch 'master' of http://git.izouma.com/xiongzhu/raex_back into dev-meta

 Conflicts:
	src/main/java/com/izouma/nineth/repo/AssetRepo.java
sunkean il y a 3 ans
Parent
commit
c259f3c1ee
53 fichiers modifiés avec 3156 ajouts et 98 suppressions
  1. 2 0
      src/main/java/com/izouma/nineth/config/RedisKeys.java
  2. 30 0
      src/main/java/com/izouma/nineth/domain/Asset.java
  3. 32 0
      src/main/java/com/izouma/nineth/domain/netease/NeteaseMessage.java
  4. 36 0
      src/main/java/com/izouma/nineth/domain/netease/NeteaseUser.java
  5. 60 0
      src/main/java/com/izouma/nineth/domain/netease/Team.java
  6. 15 0
      src/main/java/com/izouma/nineth/dto/netease/TeamMember.java
  7. 17 0
      src/main/java/com/izouma/nineth/enums/netease/TeamType.java
  8. 3 0
      src/main/java/com/izouma/nineth/repo/AssetRepo.java
  9. 19 0
      src/main/java/com/izouma/nineth/repo/NeteaseUserRepo.java
  10. 2 0
      src/main/java/com/izouma/nineth/repo/TradeAuctionOrderRepo.java
  11. 27 0
      src/main/java/com/izouma/nineth/repo/netease/NeteaseMessageRepo.java
  12. 16 0
      src/main/java/com/izouma/nineth/repo/netease/TeamRepo.java
  13. 37 0
      src/main/java/com/izouma/nineth/service/AssetService.java
  14. 9 3
      src/main/java/com/izouma/nineth/service/ContentAuditService.java
  15. 30 6
      src/main/java/com/izouma/nineth/service/GiftOrderService.java
  16. 24 0
      src/main/java/com/izouma/nineth/service/TradeAuctionOrderService.java
  17. 11 1
      src/main/java/com/izouma/nineth/service/UserService.java
  18. 59 0
      src/main/java/com/izouma/nineth/service/netease/NeteaseMessageService.java
  19. 98 0
      src/main/java/com/izouma/nineth/service/netease/NeteaseUserService.java
  20. 324 0
      src/main/java/com/izouma/nineth/service/netease/TeamService.java
  21. 42 0
      src/main/java/com/izouma/nineth/utils/netease/CheckSumBuilder.java
  22. 6 0
      src/main/java/com/izouma/nineth/web/MintOrderController.java
  23. 71 0
      src/main/java/com/izouma/nineth/web/NeteaseUserController.java
  24. 9 2
      src/main/java/com/izouma/nineth/web/OrderController.java
  25. 1 1
      src/main/java/com/izouma/nineth/web/UserController.java
  26. 75 0
      src/main/java/com/izouma/nineth/web/netease/NeteaseMessageController.java
  27. 127 0
      src/main/java/com/izouma/nineth/web/netease/TeamController.java
  28. 1 0
      src/main/vue/package.json
  29. BIN
      src/main/vue/src/assets/home_icon_redian.png
  30. BIN
      src/main/vue/src/assets/home_icon_redian2.png
  31. BIN
      src/main/vue/src/assets/icon-sosuo.png
  32. BIN
      src/main/vue/src/assets/icon_bar.png
  33. 3 0
      src/main/vue/src/assets/icons/index.js
  34. 20 0
      src/main/vue/src/assets/icons/svg/redian.svg
  35. 13 0
      src/main/vue/src/assets/icons/svg/selected.svg
  36. 42 0
      src/main/vue/src/components/SvgIcon.vue
  37. 171 30
      src/main/vue/src/components/phone/Home.vue
  38. 81 0
      src/main/vue/src/components/phone/bannerSmall.vue
  39. 1 0
      src/main/vue/src/components/phone/module.vue
  40. 19 2
      src/main/vue/src/components/phone/productLarge.vue
  41. 194 32
      src/main/vue/src/components/phone/productSmall.vue
  42. 3 0
      src/main/vue/src/main.js
  43. 40 0
      src/main/vue/src/router.js
  44. 61 0
      src/main/vue/src/styles/theme.less
  45. 112 0
      src/main/vue/src/views/NeteaseMessageEdit.vue
  46. 175 0
      src/main/vue/src/views/NeteaseMessageList.vue
  47. 192 0
      src/main/vue/src/views/NeteaseUserList.vue
  48. 139 0
      src/main/vue/src/views/TeamEdit.vue
  49. 383 0
      src/main/vue/src/views/TeamList.vue
  50. 24 4
      src/main/vue/src/views/company/CompanyTheme.vue
  51. 16 0
      src/main/vue/vue.config.js
  52. 237 15
      src/main/vue/yarn.lock
  53. 47 2
      src/test/java/com/izouma/nineth/service/UserServiceTest.java

+ 2 - 0
src/main/java/com/izouma/nineth/config/RedisKeys.java

@@ -54,4 +54,6 @@ public class RedisKeys {
     public static final String DRAW_BLIND_BOX = "drawBlindBox::";
 
     public static final String COLLECTION_NUMBER = "collectionNumber::";
+
+    public static final String USER_CHECKOUT_TIME = "tIdAccId::";
 }

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

@@ -366,4 +366,34 @@ public class Asset extends CollectionBaseEntity {
                 .chainFlag(2)
                 .build();
     }
+
+    public static Asset create(TradeAuction tradeAuction, User user) {
+        return Asset.builder()
+                .userId(user.getId())
+                .collectionId(tradeAuction.getId())
+                .minter(tradeAuction.getMinter())
+                .minterId(Long.valueOf(tradeAuction.getMinterId()))
+                .minterAvatar(tradeAuction.getMinterAvatar())
+                .name(tradeAuction.getName())
+                .pic(tradeAuction.getPic())
+                .model3d(null)
+                .category("易拍")
+                .canResale(true)
+                .privileges(tradeAuction.getPrivileges())
+                .properties(tradeAuction.getProperties())
+                .royalties(tradeAuction.getRoyalties())
+                .serviceCharge(0)
+                .price(tradeAuction.getRecommendPrice())
+                .status(AssetStatus.NORMAL)
+                .owner(user.getNickname())
+                .ownerId(user.getId())
+                .ownerAvatar(user.getAvatar())
+                .type(CollectionType.DEFAULT)
+                .holdDays(null)
+                .oldHoldDays(null)
+                .source(AssetSource.OFFICIAL)
+                .companyId(1L)
+                .chainFlag(2)
+                .build();
+    }
 }

+ 32 - 0
src/main/java/com/izouma/nineth/domain/netease/NeteaseMessage.java

@@ -0,0 +1,32 @@
+package com.izouma.nineth.domain.netease;
+
+import com.izouma.nineth.domain.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import java.sql.Timestamp;
+
+@Entity
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class NeteaseMessage extends BaseEntity {
+    private Long    msgId;
+    private String  fromId;
+    private String  fromAvatar;
+    private String  fromNickName;
+    private String  toId;
+    private String  toAvatar;
+    private String  toNickName;
+    //ope: 0 单聊消息, 1 群消息
+    private Integer ope;
+    //type: 0 文本消息, 1 图片消息,3 视频消息
+    private Integer type;
+    private String  body;
+    private String  msgInfo;
+    private Long    timetag;
+}

+ 36 - 0
src/main/java/com/izouma/nineth/domain/netease/NeteaseUser.java

@@ -0,0 +1,36 @@
+package com.izouma.nineth.domain.netease;
+
+import com.izouma.nineth.domain.BaseEntityNoID;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class NeteaseUser extends BaseEntityNoID {
+    @Id
+    private Long userId;
+
+    private String accId;
+
+    private String token;
+
+    private String name;
+
+    private String icon;
+
+    private String sign;
+
+    private String email;
+
+    private String mobile;
+
+    private String ex;
+}

+ 60 - 0
src/main/java/com/izouma/nineth/domain/netease/Team.java

@@ -0,0 +1,60 @@
+package com.izouma.nineth.domain.netease;
+
+import com.izouma.nineth.converter.StringArrayConverter;
+import com.izouma.nineth.domain.BaseEntityNoID;
+import com.izouma.nineth.enums.netease.TeamType;
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("系列活动")
+public class Team extends BaseEntityNoID {
+    @Id
+    private Long         tid;
+    private String       name;
+    private String       ownerid;
+    @Column(columnDefinition = "TEXT")
+    @Convert(converter = StringArrayConverter.class)
+    private List<String> members;
+    @Column(columnDefinition = "TEXT")
+    @Convert(converter = StringArrayConverter.class)
+    private List<String> admins;
+    @Column(columnDefinition = "TEXT")
+    @Convert(converter = StringArrayConverter.class)
+    private List<String> banned;
+    @Column(columnDefinition = "TEXT")
+    @Convert(converter = StringArrayConverter.class)
+    private List<String> kicked;
+    private String       announcement;
+    private String       intro;
+    private String       msg;
+    @Enumerated(EnumType.STRING)
+    private TeamType     custom;
+    private String       icon;
+
+    private Long   collectionId;
+    private String collection;
+    private String password;
+
+    @Transient
+    private boolean        inTeam;
+    @Transient
+    private Long           unread;
+    @Transient
+    private NeteaseMessage lastMsg;
+    @Transient
+    private boolean        ban;
+
+    private boolean mute;
+
+}

+ 15 - 0
src/main/java/com/izouma/nineth/dto/netease/TeamMember.java

@@ -0,0 +1,15 @@
+package com.izouma.nineth.dto.netease;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TeamMember {
+    private String  nickName;
+    private String  accid;
+    private String  avatar;
+    private boolean banned;
+}

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

@@ -0,0 +1,17 @@
+package com.izouma.nineth.enums.netease;
+
+public enum TeamType {
+    PUBLIC("公共群"),
+    COLLECTION("藏品群"),
+    PRIVATE("私密群");
+
+    private final String description;
+
+    TeamType(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

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

@@ -137,6 +137,9 @@ public interface AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationE
     @Query("select count(id) from Asset where name like ?1 and status = 'NORMAL' and ownerId <> 1435297")
     Long countNameLikeNotDestroyed(String name);
 
+    @Query(value = "SELECT count(a.id) from asset a where a.name LIKE ?1 and status = 'NORMAL' and owner_id = ?2", nativeQuery = true)
+    Long countNameLikeNotDestroyedAndOwner(String name, Long ownerId);
+
     @Query(value = "select a from Asset a where a.userId = ?6 and a.name like ?1 and a.name like ?4 and a.name not like ?5 and a.status in ?2 and a.id not in ?3 and a.del = false")
     List<Asset> findAllBoats(String name, List<AssetStatus> status, List<Long> boatIds, String like, String unLike, Long userId);
 }

+ 19 - 0
src/main/java/com/izouma/nineth/repo/NeteaseUserRepo.java

@@ -0,0 +1,19 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.netease.NeteaseUser;
+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;
+import java.util.List;
+
+public interface NeteaseUserRepo extends JpaRepository<NeteaseUser, Long>, JpaSpecificationExecutor<NeteaseUser> {
+    @Query("update NeteaseUser t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    List<NeteaseUser> findAllByAccIdIn(List<String> accids);
+}

+ 2 - 0
src/main/java/com/izouma/nineth/repo/TradeAuctionOrderRepo.java

@@ -21,5 +21,7 @@ public interface TradeAuctionOrderRepo extends JpaRepository<TradeAuctionOrder,
 
     List<TradeAuctionOrder> findByStatusAndCreatedAtBeforeAndDelFalse(AuctionOrderStatus status, LocalDateTime time);
 
+    List<TradeAuctionOrder> findByStatusAndDelFalse(AuctionOrderStatus status);
+
     List<TradeAuctionOrder> findByPaybackStatus(PaybackStatus status);
 }

+ 27 - 0
src/main/java/com/izouma/nineth/repo/netease/NeteaseMessageRepo.java

@@ -0,0 +1,27 @@
+package com.izouma.nineth.repo.netease;
+
+import com.izouma.nineth.domain.netease.NeteaseMessage;
+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;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface NeteaseMessageRepo extends JpaRepository<NeteaseMessage, Long>, JpaSpecificationExecutor<NeteaseMessage> {
+    @Query("update NeteaseMessage t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    @Query(value = "select * from netease_message n where n.to_id = ?1 and n.ope = ?2 order by n.created_at desc limit 100", nativeQuery = true)
+    List<NeteaseMessage> findAllByToIdAndOpe(String toId, Integer ope);
+
+    Long countAllByToIdAndOpeAndCreatedAtAfter(String toId, Integer ope, LocalDateTime createdAt);
+
+    Long countAllByToIdAndOpe(String toId, Integer ope);
+
+    NeteaseMessage findFirstByToIdAndOpeOrderByCreatedAtDesc(String toId, Integer ope);
+}

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

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

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

@@ -292,6 +292,43 @@ public class AssetService {
         return asset;
     }
 
+    public Asset createAsset(TradeAuction tradeAuction, User user, Long orderId, BigDecimal price, String type,
+                             Integer number, boolean safeFlag) {
+        Asset asset = Asset.create(tradeAuction, user);
+        asset.setTokenId(TokenUtils.genTokenId());
+        asset.setNumber(number);
+        asset.setOrderId(orderId);
+        asset.setPrice(price);
+        asset.setPrefixName("易拍");
+        asset.setTags(new HashSet<>());
+        User fakeUser = null;
+        if (safeFlag) {
+            fakeUser = createFakeUser();
+            asset.setOwner(fakeUser.getNickname());
+            asset.setOwnerId(fakeUser.getId());
+            asset.setOwnerAvatar(fakeUser.getAvatar());
+        }
+        assetRepo.saveAndFlush(asset);
+        tokenHistoryRepo.save(TokenHistory.builder()
+                .tokenId(asset.getTokenId())
+                .fromUser(tradeAuction.getMinter())
+                .fromUserId(Long.valueOf(tradeAuction.getMinterId()))
+                .fromAvatar(tradeAuction.getMinterAvatar())
+                .toUser((safeFlag ? fakeUser : user).getNickname())
+                .toUserId((safeFlag ? fakeUser : user).getId())
+                .toAvatar((safeFlag ? fakeUser : user).getAvatar())
+                .operation(type)
+                .price(null)
+                .companyId(asset.getCompanyId())
+                .build());
+
+        //绿洲石
+//        rockRecordService.addRock(user.getId(), price, "购买");
+
+        rocketMQTemplate.syncSend(generalProperties.getMintTopic(), asset.getId());
+        return asset;
+    }
+
     public synchronized int getMessedNumber(Long collectionId, int number, int total) {
         NumberSeq numberSeq = numberSeqRepo.findById(collectionId).orElse(null);
         if (numberSeq == null || numberSeq.getTotal() != total) {

+ 9 - 3
src/main/java/com/izouma/nineth/service/ContentAuditService.java

@@ -118,7 +118,12 @@ public class ContentAuditService {
          * 例如,检测2张图片,场景传递porn和terrorism,计费会按照2张图片鉴黄,2张图片暴恐检测计算。
          * porn:表示鉴黄场景。
          */
-        httpBody.put("scenes", Collections.singletonList("porn"));
+        List<String> scenes = new ArrayList<>();
+        scenes.add("porn");
+        scenes.add("terrorism");
+        scenes.add("ad");
+        scenes.add("live");
+        httpBody.put("scenes", scenes);
 
         /**
          * 设置待检测图片。一张图片对应一个task。
@@ -169,14 +174,15 @@ public class ContentAuditService {
                         for (Object sceneResult : sceneResults) {
                             String scene = ((JSONObject) sceneResult).getString("scene");
                             String suggestion = ((JSONObject) sceneResult).getString("suggestion");
-                            if (StringUtils.equals(suggestion, "pass")) {
-                                return true;
+                            if (StringUtils.equals(suggestion, "block")) {
+                                return false;
                             }
                             // 根据scene和suggestion做相关处理。
                             // 根据不同的suggestion结果做业务上的不同处理。例如,将违规数据删除等。
                             System.out.println("scene = [" + scene + "]");
                             System.out.println("suggestion = [" + suggestion + "]");
                         }
+                        return true;
                     } else {
                         // 单张图片处理失败, 原因视具体的情况详细分析。
                         System.out.println("task process fail. task response:" + JSON.toJSONString(taskResult));

+ 30 - 6
src/main/java/com/izouma/nineth/service/GiftOrderService.java

@@ -91,10 +91,22 @@ public class GiftOrderService {
         }
 
         int holdDays;
-        if (ObjectUtils.isEmpty(asset.getHoldDays())) {
-            holdDays = sysConfigService.getInt("hold_days");
+        if (asset.getSource() == AssetSource.GIFT) {
+            LocalDateTime localDateTime = asset.getCreatedAt();
+            LocalDateTime gift_change_time = LocalDateTime
+                    .parse(sysConfigService.getString("gift_change_time"), DateTimeFormatter
+                            .ofPattern("yyyy-MM-dd HH:mm:ss"));
+            if (localDateTime.isBefore(gift_change_time)) {
+                holdDays = 20;
+            } else {
+                holdDays = sysConfigService.getInt("gift_days");
+            }
         } else {
-            holdDays = asset.getHoldDays();
+            if (ObjectUtils.isEmpty(asset.getHoldDays())) {
+                holdDays = sysConfigService.getInt("hold_days");
+            } else {
+                holdDays = asset.getHoldDays();
+            }
         }
 
         if (holdDays == 0 && AssetSource.OFFICIAL.equals(asset.getSource())) {
@@ -169,10 +181,22 @@ public class GiftOrderService {
         }
 
         int holdDays;
-        if (ObjectUtils.isEmpty(asset.getHoldDays())) {
-            holdDays = sysConfigService.getInt("hold_days");
+        if (asset.getSource() == AssetSource.GIFT) {
+            LocalDateTime localDateTime = asset.getCreatedAt();
+            LocalDateTime gift_change_time = LocalDateTime
+                    .parse(sysConfigService.getString("gift_change_time"), DateTimeFormatter
+                            .ofPattern("yyyy-MM-dd HH:mm:ss"));
+            if (localDateTime.isBefore(gift_change_time)) {
+                holdDays = 20;
+            } else {
+                holdDays = sysConfigService.getInt("gift_days");
+            }
         } else {
-            holdDays = asset.getHoldDays();
+            if (ObjectUtils.isEmpty(asset.getHoldDays())) {
+                holdDays = sysConfigService.getInt("hold_days");
+            } else {
+                holdDays = asset.getHoldDays();
+            }
         }
 
         if (holdDays == 0 && AssetSource.OFFICIAL.equals(asset.getSource())) {

+ 24 - 0
src/main/java/com/izouma/nineth/service/TradeAuctionOrderService.java

@@ -41,6 +41,7 @@ public class TradeAuctionOrderService {
     private SysConfigService              sysConfigService;
     private CommissionRecordRepo          commissionRecordRepo;
     private RedisTemplate<String, Object> redisTemplate;
+    private AssetService                  assetService;
 
     private static int orderCancelInterval = 210;
 
@@ -155,6 +156,7 @@ public class TradeAuctionOrderService {
             tradeAuction.setCurrentOwner(tradeAuctionOrder.getNickname());
             tradeAuction.setCurrentOwnerId(tradeAuctionOrder.getUserId());
             tradeAuction.setStatus(TradeAuctionStatus.FIXED_PRICE_PURCHASED);
+            tradeAuctionOrder.setStatus(AuctionOrderStatus.AIR_DROP);
 
         } else if (tradeAuctionOrder.getPaymentType().equals(AuctionPaymentType.DEPOSIT)) {
             if (tradeAuction.getSale() == 29) {
@@ -259,6 +261,28 @@ public class TradeAuctionOrderService {
 //        releaseOrderLock(order.getId());
     }
 
+    @Scheduled(fixedRate = 30000)
+    public void batchAirdrop() {
+        List<TradeAuctionOrder> tradeAuctionOrders = tradeAuctionOrderRepo
+                .findByStatusAndDelFalse(AuctionOrderStatus.AIR_DROP);
+        tradeAuctionOrders.parallelStream().forEach(o -> {
+            try {
+                TradeAuctionOrder order = tradeAuctionOrderRepo.findById(o.getId())
+                        .orElseThrow(new BusinessException("订单不存在"));
+                TradeAuction tradeAuction = tradeAuctionRepo.findById(order.getTradeAuctionId())
+                        .orElseThrow(new BusinessException("未找到易拍活动"));
+                User owner = userRepo.findById(order.getUserId())
+                        .orElseThrow(new BusinessException("暂无用户"));
+                assetService.createAsset(tradeAuction, owner, order.getId(), tradeAuction
+                        .getCurrentPrice(), "空投", 1, false);
+                order.setStatus(AuctionOrderStatus.FINISH);
+                tradeAuctionOrderRepo.save(order);
+            } catch (Exception e) {
+                log.error("取消易拍订单错误 " + o.getId(), e);
+            }
+        });
+    }
+
     @Scheduled(fixedRate = 30000)
     public void batchPayEarning() {
         List<TradeAuctionOrder> tradeAuctionOrders = tradeAuctionOrderRepo.findByPaybackStatus(PaybackStatus.PASSED);

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

@@ -112,7 +112,7 @@ public class UserService {
     private RockRecordRepo                rockRecordRepo;
 
     public User update(User user) {
-        if (!SecurityUtils.hasRole(AuthorityName.ROLE_ADMIN)) {
+        if (!SecurityUtils.hasRole(AuthorityName.ROLE_ADMIN) & !SecurityUtils.hasRole(AuthorityName.ROLE_SAAS)) {
             if (!SecurityUtils.getAuthenticatedUser().getId().equals(user.getId())) {
                 throw new BusinessException("无权限");
             }
@@ -170,12 +170,22 @@ public class UserService {
             user.setNickname(nickname);
         }
         if (StringUtils.isNotBlank(avatar)) {
+            if (!avatar.equals(user.getAvatar())) {
+                if (!contentAuditService.auditImage(avatar)) {
+                    throw new BusinessException("头像包含敏感信息");
+                }
+            }
             user.setAvatar(avatar);
         }
         if (StringUtils.isNotBlank(sex)) {
             user.setSex(sex);
         }
         if (StringUtils.isNotBlank(bg)) {
+            if (!bg.equals(user.getBg())) {
+                if (!contentAuditService.auditImage(bg)) {
+                    throw new BusinessException("背景包含敏感信息");
+                }
+            }
             user.setBg(bg);
         }
         if (StringUtils.isNotBlank(intro)) {

+ 59 - 0
src/main/java/com/izouma/nineth/service/netease/NeteaseMessageService.java

@@ -0,0 +1,59 @@
+package com.izouma.nineth.service.netease;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.domain.User;
+import com.izouma.nineth.domain.netease.NeteaseMessage;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.netease.NeteaseMessageRepo;
+import com.izouma.nineth.repo.UserRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@AllArgsConstructor
+public class NeteaseMessageService {
+
+    private NeteaseMessageRepo neteaseMessageRepo;
+    private NeteaseUserService neteaseUserService;
+    private UserRepo           userRepo;
+
+    public Page<NeteaseMessage> all(PageQuery pageQuery) {
+        return neteaseMessageRepo
+                .findAll(JpaUtils.toSpecification(pageQuery, NeteaseMessage.class), JpaUtils.toPageRequest(pageQuery));
+    }
+
+    public NeteaseMessage sendMessage(NeteaseMessage msg) {
+        User from = userRepo.findById(Long.valueOf(msg.getFromId())).orElseThrow(new BusinessException("未找到用户"));
+//        User to = userRepo.findById(Long.valueOf(msg.getToId())).orElseThrow(new BusinessException("未找到用户"));
+        msg.setFromAvatar(from.getAvatar());
+        msg.setFromNickName(from.getNickname());
+//        msg.setToAvatar(to.getAvatar());
+//        msg.setToNickName(to.getNickname());
+        String url = "msg/sendMsg.action";
+        String contentType = "application/x-www-form-urlencoded;charset=utf-8";
+        Map<String, Object> params = new HashMap<>();
+        params.put("from", msg.getFromId());
+        params.put("to", msg.getToId());
+        params.put("ope", msg.getOpe());
+        params.put("type", msg.getType());
+        params.put("body", msg.getBody());
+        params.put("markRead", 1);
+        String result = neteaseUserService.httpPost(url, contentType, params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("发信出错,请检查后重新发送");
+        }
+        JSONObject data = jsonObject.getJSONObject("data");
+        msg.setMsgId(data.getLong("msgid"));
+        msg.setTimetag(data.getLong("timetag"));
+        return neteaseMessageRepo.save(msg);
+    }
+}

+ 98 - 0
src/main/java/com/izouma/nineth/service/netease/NeteaseUserService.java

@@ -0,0 +1,98 @@
+package com.izouma.nineth.service.netease;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.github.kevinsawicki.http.HttpRequest;
+import com.izouma.nineth.domain.netease.NeteaseUser;
+import com.izouma.nineth.domain.User;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.NeteaseUserRepo;
+import com.izouma.nineth.repo.UserRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import com.izouma.nineth.utils.netease.CheckSumBuilder;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang.RandomStringUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@AllArgsConstructor
+public class NeteaseUserService {
+
+    private NeteaseUserRepo neteaseUserRepo;
+    private UserRepo        userRepo;
+
+    public Page<NeteaseUser> all(PageQuery pageQuery) {
+        return neteaseUserRepo
+                .findAll(JpaUtils.toSpecification(pageQuery, NeteaseUser.class), JpaUtils.toPageRequest(pageQuery));
+    }
+
+    public String httpPost(String url, String contentType, Map<String, Object> params) {
+        Map<String, String> headers = new HashMap<>();
+        String appKey = "872dd9d0a0f8eda25b579654745db459";
+        String nonce = RandomStringUtils.randomAlphabetic(32);
+        Timestamp timestamp = new Timestamp(LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC)
+                .getEpochSecond());
+        String curTime = String.valueOf(timestamp.getTime());
+        String appSecret = "24e63777e4bb";
+        headers.put("AppKey", appKey);
+        headers.put("Nonce", nonce);
+        headers.put("CurTime", curTime);
+        headers.put("CheckSum", CheckSumBuilder.getCheckSum(appSecret, nonce, curTime));
+        return HttpRequest.post("https://api.netease.im/nimserver/" + url)
+                .contentType(contentType)
+                .headers(headers)
+                .form(params)
+                .body();
+    }
+
+    public NeteaseUser create(Long userId) {
+        User user = userRepo.findById(userId).orElseThrow(new BusinessException("暂无用户信息"));
+        String url = "user/create.action";
+        String contentType = "application/x-www-form-urlencoded;charset=utf-8";
+        Map<String, Object> params = new HashMap<>();
+        params.put("accid", String.valueOf(user.getId()));
+        params.put("name", user.getNickname());
+        params.put("mobile", "+86-" + user.getPhone());
+        params.put("icon", user.getAvatar());
+        String result = httpPost(url, contentType, params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("注册出错,请核查后重新注册");
+        }
+        JSONObject info = jsonObject.getJSONObject("info");
+        String accId = info.getString("accid");
+        String name = info.getString("name");
+        String token = info.getString("token");
+        String icon = info.getString("icon");
+        NeteaseUser neteaseUser = NeteaseUser.builder().accId(accId).userId(userId).name(name).token(token).icon(icon)
+                .build();
+        return neteaseUserRepo.save(neteaseUser);
+    }
+
+    public NeteaseUser updateToken(Long userId) {
+        NeteaseUser record = neteaseUserRepo.findById(userId).orElseThrow(new BusinessException("该用户暂无注册信息"));
+        String url = "user/update.refreshToken";
+        String contentType = "application/x-www-form-urlencoded;charset=utf-8";
+        Map<String, Object> params = new HashMap<>();
+        params.put("accid", String.valueOf(userId));
+        String result = httpPost(url, contentType, params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("注册出错,请核查后重新注册");
+        }
+        JSONObject info = jsonObject.getJSONObject("info");
+        String token = info.getString("token");
+        record.setToken(token);
+        return neteaseUserRepo.save(record);
+    }
+}

+ 324 - 0
src/main/java/com/izouma/nineth/service/netease/TeamService.java

@@ -0,0 +1,324 @@
+package com.izouma.nineth.service.netease;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.nineth.config.RedisKeys;
+import com.izouma.nineth.domain.netease.NeteaseMessage;
+import com.izouma.nineth.domain.netease.NeteaseUser;
+import com.izouma.nineth.domain.netease.Team;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.netease.TeamMember;
+import com.izouma.nineth.enums.netease.TeamType;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.AssetRepo;
+import com.izouma.nineth.repo.NeteaseUserRepo;
+import com.izouma.nineth.repo.netease.NeteaseMessageRepo;
+import com.izouma.nineth.repo.netease.TeamRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import com.izouma.nineth.utils.SecurityUtils;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.util.*;
+
+@Service
+@AllArgsConstructor
+public class TeamService {
+
+    private TeamRepo                      teamRepo;
+    private NeteaseUserService            neteaseUserService;
+    private NeteaseUserRepo               neteaseUserRepo;
+    private RedisTemplate<String, Object> redisTemplate;
+    private NeteaseMessageRepo            neteaseMessageRepo;
+    private AssetRepo                     assetRepo;
+
+    public Page<Team> all(PageQuery pageQuery) {
+        Long userId = SecurityUtils.getAuthenticatedUser().getId();
+        Page<Team> teams = teamRepo
+                .findAll(JpaUtils.toSpecification(pageQuery, Team.class), JpaUtils.toPageRequest(pageQuery));
+        List<Team> content = teams.getContent();
+        List<Team> newContent = new ArrayList<>();
+        content.forEach(team -> {
+            List<String> members = new ArrayList<>(team.getMembers());
+            boolean inTeam = false;
+            if (StringUtils.equals(team.getOwnerid(), userId.toString())) {
+                inTeam = true;
+            } else {
+                for (String member : members) {
+                    String accid = userId.toString();
+                    if (StringUtils.equals(accid, member)) {
+                        inTeam = true;
+                    }
+                }
+            }
+            team.setBan(team.getBanned().stream().anyMatch(member -> StringUtils.equals(member, userId.toString())));
+            team.setLastMsg(getTeamLastMessage(team.getTid()));
+            team.setUnread(getUnreadCount(userId.toString(), team.getTid().toString()));
+            team.setInTeam(inTeam);
+            newContent.add(team);
+        });
+        return new PageImpl<>(newContent, teams.getPageable(), teams.getTotalElements());
+    }
+
+    public Team create(Team team) {
+        NeteaseUser neteaseUser = neteaseUserRepo.findById(Long.valueOf(team.getOwnerid())).orElse(null);
+        if (neteaseUser == null) {
+            neteaseUserService.create(Long.valueOf(team.getOwnerid()));
+        }
+        Map<String, Object> params = new HashMap<>();
+        params.put("tname", team.getName());
+        params.put("owner", team.getOwnerid());
+        params.put("members", JSONObject.toJSONString(team.getMembers()));
+        params.put("joinmode", "0");
+        team.setCustom(TeamType.PUBLIC);
+        params.put("custom", team.getCustom().toString());
+        params.put("msg", "欢迎加入本群!");
+        String result = neteaseUserService
+                .httpPost("team/create.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("注册出错,请核查后重新注册");
+        }
+        Long id = jsonObject.getLong("tid");
+        team.setTid(id);
+        return teamRepo.save(team);
+    }
+
+    public Team update(Team team) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("tid", team.getTid());
+        params.put("tname", team.getName());
+        params.put("owner", team.getOwnerid());
+        params.put("announcement", team.getAnnouncement());
+        params.put("intro", team.getIntro());
+        params.put("icon", "0");
+        String result = neteaseUserService
+                .httpPost("team/update.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("注册出错,请核查后重新注册");
+        }
+        return teamRepo.save(team);
+    }
+
+    public void mute(Long tid) {
+        Map<String, Object> params = new HashMap<>();
+        Team team = teamRepo.findById(tid).orElseThrow(new BusinessException("暂无"));
+        params.put("tid", tid);
+        params.put("owner", team.getOwnerid());
+        params.put("mute", true);
+        String result = neteaseUserService
+                .httpPost("team/muteTlistAll.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("禁言操作出错");
+        }
+        team.setMute(true);
+        teamRepo.save(team);
+    }
+
+    public void cancelMute(Long tid) {
+        Map<String, Object> params = new HashMap<>();
+        Team team = teamRepo.findById(tid).orElseThrow(new BusinessException("暂无"));
+        params.put("tid", tid);
+        params.put("owner", team.getOwnerid());
+        params.put("mute", false);
+        String result = neteaseUserService
+                .httpPost("team/muteTlistAll.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("禁言操作出错");
+        }
+        team.setMute(false);
+        teamRepo.save(team);
+    }
+
+    public void muteMember(Long tid, String accid) {
+        Map<String, Object> params = new HashMap<>();
+        Team team = teamRepo.findById(tid).orElseThrow(new BusinessException("暂无"));
+        params.put("tid", tid);
+        params.put("owner", team.getOwnerid());
+        params.put("accid", accid);
+        params.put("mute", 1);
+        String result = neteaseUserService
+                .httpPost("team/muteTlist.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("禁言操作出错");
+        }
+        List<String> banned = new ArrayList<>(team.getBanned());
+        banned.add(accid);
+        team.setBanned(banned);
+        teamRepo.save(team);
+    }
+
+    public void cancelMuteMember(Long tid, String accid) {
+        Map<String, Object> params = new HashMap<>();
+        Team team = teamRepo.findById(tid).orElseThrow(new BusinessException("暂无"));
+        params.put("tid", tid);
+        params.put("owner", team.getOwnerid());
+        params.put("accid", accid);
+        params.put("mute", 0);
+        String result = neteaseUserService
+                .httpPost("team/muteTlist.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("禁言操作出错");
+        }
+        List<String> banned = new ArrayList<>(team.getBanned());
+        banned.removeIf(id -> StringUtils.equals(id, accid));
+        team.setBanned(banned);
+        teamRepo.save(team);
+    }
+
+    public Team invite(String id, String tid) {
+        List<String> ids = new ArrayList<>();
+        ids.add(id);
+        Team team = teamRepo.findById(Long.valueOf(tid)).orElseThrow(new BusinessException("未找到群聊"));
+        List<String> kicked = team.getKicked();
+        if (kicked.stream().anyMatch(member -> StringUtils.equals(member, id))) {
+            throw new BusinessException("用户被踢出,无法加入");
+        }
+        Map<String, Object> params = new HashMap<>();
+        params.put("tid", tid);
+        params.put("owner", team.getOwnerid());
+        params.put("members", JSONObject.toJSONString(ids));
+        params.put("magree", "0");
+        params.put("custom", team.getCustom().toString());
+        params.put("msg", "欢迎加入大厅测试群~~");
+        String result = neteaseUserService
+                .httpPost("team/add.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("注册出错,请核查后重新注册");
+        }
+        List<String> members = new ArrayList<>(team.getMembers());
+        members.add(id);
+        team.setMembers(members);
+        return teamRepo.save(team);
+    }
+
+    public void leave(String id, String tid) {
+        Team team = teamRepo.findById(Long.valueOf(tid)).orElseThrow(new BusinessException("未找到群聊"));
+        Map<String, Object> params = new HashMap<>();
+        params.put("tid", tid);
+        params.put("accid", id);
+        String result = neteaseUserService
+                .httpPost("team/leave.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("退群出错");
+        }
+        List<String> members = new ArrayList<>(team.getMembers());
+        members.removeIf(member -> StringUtils.equals(member, id));
+        team.setMembers(members);
+        teamRepo.save(team);
+    }
+
+    public void kick(String id, String tid) {
+        Team team = teamRepo.findById(Long.valueOf(tid)).orElseThrow(new BusinessException("未找到群聊"));
+        Map<String, Object> params = new HashMap<>();
+        params.put("tid", tid);
+        params.put("accid", id);
+        String result = neteaseUserService
+                .httpPost("team/leave.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        Integer code = jsonObject.getInteger("code");
+        if (code != 200) {
+            throw new BusinessException("退群出错");
+        }
+        List<String> members = new ArrayList<>(team.getMembers());
+        members.removeIf(member -> StringUtils.equals(member, id));
+        team.setMembers(members);
+        List<String> kicked = new ArrayList<>(team.getKicked());
+        kicked.add(id);
+        team.setKicked(kicked);
+        teamRepo.save(team);
+    }
+
+    public void checkout(String accid, String tid) {
+        Long ts = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
+        redisTemplate.opsForValue().set(RedisKeys.USER_CHECKOUT_TIME + accid + ":" + tid, ts);
+    }
+
+    public Long getUnreadCount(String accid, String tid) {
+        Object object = redisTemplate.opsForValue().get(RedisKeys.USER_CHECKOUT_TIME + accid + ":" + tid);
+        Long unreadCount;
+        if (object != null) {
+            Long ts = Long.valueOf(object.toString());
+            Instant instant = Instant.ofEpochMilli(ts);
+            LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+            unreadCount = neteaseMessageRepo.countAllByToIdAndOpeAndCreatedAtAfter(tid, 1, localDateTime);
+        } else {
+            unreadCount = neteaseMessageRepo.countAllByToIdAndOpe(tid, 1);
+        }
+        return unreadCount;
+    }
+
+    public NeteaseMessage getTeamLastMessage(Long tid) {
+        return neteaseMessageRepo.findFirstByToIdAndOpeOrderByCreatedAtDesc(tid.toString(), 1);
+    }
+
+    public void getTeamInfo(Long tid) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("tid", tid);
+        String result = neteaseUserService
+                .httpPost("team/queryDetail.action", "application/x-www-form-urlencoded;charset=utf-8", params);
+        JSONObject jsonObject = JSON.parseObject(result);
+        System.out.println(jsonObject);
+    }
+
+    public boolean checkTeamIn(Long tid, String accid) {
+        Team team = teamRepo.findById(tid).orElseThrow(new BusinessException("暂无群信息"));
+        Long ownerId = Long.valueOf(accid);
+        String collectionString = team.getCollection();
+        if (StringUtils.isNotBlank(collectionString)) {
+            String[] collectionKeywords = collectionString.split(",");
+            boolean canJoin = false;
+            for (String keyword : collectionKeywords) {
+                if (StringUtils.isNotBlank(keyword)) {
+                    Long count = assetRepo.countNameLikeNotDestroyedAndOwner("%" + keyword + "%", ownerId);
+                    if (count > 0) {
+                        canJoin = true;
+                    }
+                }
+            }
+            return canJoin;
+        }
+        return true;
+    }
+
+    public List<TeamMember> getTeamMembers(Long tid) {
+        Team team = teamRepo.findById(tid).orElseThrow(new BusinessException("未找到群组"));
+        List<String> strings = team.getMembers();
+        List<NeteaseUser> neteaseUsers = neteaseUserRepo.findAllByAccIdIn(strings);
+        List<TeamMember> result = new ArrayList<>();
+        List<String> banned = new ArrayList<>(team.getBanned());
+        neteaseUsers.forEach(member -> {
+            TeamMember teamMember = new TeamMember();
+            teamMember.setAccid(member.getAccId());
+            teamMember.setAvatar(member.getIcon());
+            teamMember.setNickName(member.getName());
+            teamMember.setBanned(banned.stream()
+                    .anyMatch(bannedOne -> StringUtils.equals(bannedOne, member.getAccId())));
+            result.add(teamMember);
+        });
+        return result;
+    }
+}

+ 42 - 0
src/main/java/com/izouma/nineth/utils/netease/CheckSumBuilder.java

@@ -0,0 +1,42 @@
+package com.izouma.nineth.utils.netease;
+
+import java.security.MessageDigest;
+
+public class CheckSumBuilder {
+    // 计算并获取CheckSum
+    public static String getCheckSum(String appSecret, String nonce, String curTime) {
+        return encode("sha1", appSecret + nonce + curTime);
+    }
+
+    // 计算并获取md5值
+    public static String getMD5(String requestBody) {
+        return encode("md5", requestBody);
+    }
+
+    private static String encode(String algorithm, String value) {
+        if (value == null) {
+            return null;
+        }
+        try {
+            MessageDigest messageDigest
+                    = MessageDigest.getInstance(algorithm);
+            messageDigest.update(value.getBytes());
+            return getFormattedText(messageDigest.digest());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static String getFormattedText(byte[] bytes) {
+        int len = bytes.length;
+        StringBuilder buf = new StringBuilder(len * 2);
+        for (int j = 0; j < len; j++) {
+            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
+            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
+        }
+        return buf.toString();
+    }
+
+    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
+            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+}

+ 6 - 0
src/main/java/com/izouma/nineth/web/MintOrderController.java

@@ -109,6 +109,12 @@ public class MintOrderController extends BaseController {
     @ApiOperation("导出")
     @PostMapping("/excelPhone")
     public void excelPhone(HttpServletResponse response, @RequestBody PageQuery pageQuery) throws IOException {
+        Long companyId = SecurityUtils.getAuthenticatedUser().getCompanyId();
+        if (companyId != null) {
+            Map<String, Object> query = pageQuery.getQuery();
+            query.put("companyId", companyId);
+            pageQuery.setQuery(query);
+        }
         List<MintOrderDTO> data = all(pageQuery).map(MintOrderDTO::new).getContent();
         ExcelUtils.export(response, data);
     }

+ 71 - 0
src/main/java/com/izouma/nineth/web/NeteaseUserController.java

@@ -0,0 +1,71 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.netease.NeteaseUser;
+import com.izouma.nineth.service.netease.NeteaseUserService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.NeteaseUserRepo;
+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.data.domain.Pageable;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/neteaseUser")
+@AllArgsConstructor
+public class NeteaseUserController extends BaseController {
+    private NeteaseUserService neteaseUserService;
+    private NeteaseUserRepo    neteaseUserRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public NeteaseUser save(@RequestBody NeteaseUser record) {
+        if (record.getUserId() != null) {
+            NeteaseUser orig = neteaseUserRepo.findById(record.getUserId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return neteaseUserRepo.save(orig);
+        }
+        return neteaseUserRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<NeteaseUser> all(@RequestBody PageQuery pageQuery) {
+        return neteaseUserService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public NeteaseUser get(@PathVariable Long id) {
+        return neteaseUserRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        neteaseUserRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<NeteaseUser> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+
+    @PostMapping("/create")
+    public NeteaseUser create(@RequestParam Long userId) {
+        return neteaseUserService.create(userId);
+    }
+
+    @PostMapping("/refresh")
+    public NeteaseUser refresh(@RequestParam Long userId) {
+        return neteaseUserService.updateToken(userId);
+    }
+}
+

+ 9 - 2
src/main/java/com/izouma/nineth/web/OrderController.java

@@ -92,7 +92,13 @@ public class OrderController extends BaseController {
     @PostMapping("/excel")
     @ResponseBody
     public void excel(HttpServletResponse response, @RequestBody PageQuery pageQuery) throws IOException {
-        List<Order> data = all(pageQuery).getContent();
+        Long companyId = SecurityUtils.getAuthenticatedUser().getCompanyId();
+        if (companyId != null) {
+            Map<String, Object> query = pageQuery.getQuery();
+            query.put("companyId", companyId);
+            pageQuery.setQuery(query);
+        }
+        List<Order> data = backAll(pageQuery).getContent();
         ExcelUtils.export(response, data.stream().map(order -> {
             OrderDTO dto = new OrderDTO();
             BeanUtils.copyProperties(order, dto);
@@ -193,7 +199,8 @@ public class OrderController extends BaseController {
         try {
             response.setContentType("application/vnd.ms-excel");
             response.setCharacterEncoding("utf-8");
-            response.setHeader("Content-Disposition", "attachment;filename=" + DateTimeUtils.format(LocalDateTime.now(), "MMdd") + ".xlsx");
+            response.setHeader("Content-Disposition", "attachment;filename=" + DateTimeUtils
+                    .format(LocalDateTime.now(), "MMdd") + ".xlsx");
             orderService.calcSettle(start, end, response.getOutputStream());
         } catch (IOException e) {
             e.printStackTrace();

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

@@ -71,7 +71,7 @@ public class UserController extends BaseController {
         return userService.create(userRegister);
     }
 
-    @PreAuthorize("hasAnyRole('ADMIN', 'SHOWROOM')")
+    @PreAuthorize("hasAnyRole('ADMIN', 'SHOWROOM','ROLE_SAAS')")
     @PostMapping("/save")
     public User save(@RequestBody User user) {
         if (user.getId() != null) {

+ 75 - 0
src/main/java/com/izouma/nineth/web/netease/NeteaseMessageController.java

@@ -0,0 +1,75 @@
+package com.izouma.nineth.web.netease;
+
+import com.izouma.nineth.domain.netease.NeteaseUser;
+import com.izouma.nineth.web.BaseController;
+import com.izouma.nineth.domain.netease.NeteaseMessage;
+import com.izouma.nineth.service.netease.NeteaseMessageService;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.netease.NeteaseMessageRepo;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.validator.Msg;
+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("/neteaseMessage")
+@AllArgsConstructor
+public class NeteaseMessageController extends BaseController {
+    private NeteaseMessageService neteaseMessageService;
+    private NeteaseMessageRepo    neteaseMessageRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public NeteaseMessage save(@RequestBody NeteaseMessage record) {
+        if (record.getId() != null) {
+            NeteaseMessage orig = neteaseMessageRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return neteaseMessageRepo.save(orig);
+        }
+        return neteaseMessageRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<NeteaseMessage> all(@RequestBody PageQuery pageQuery) {
+        return neteaseMessageService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public NeteaseMessage get(@PathVariable Long id) {
+        return neteaseMessageRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/record")
+    public List<NeteaseMessage> record(String toId, Integer ope) {
+        return neteaseMessageRepo.findAllByToIdAndOpe(toId, ope);
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        neteaseMessageRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<NeteaseMessage> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+
+    @PostMapping("/sendMsg")
+    public NeteaseMessage sendMsg(@RequestBody NeteaseMessage msg) {
+        return neteaseMessageService.sendMessage(msg);
+    }
+}
+

+ 127 - 0
src/main/java/com/izouma/nineth/web/netease/TeamController.java

@@ -0,0 +1,127 @@
+package com.izouma.nineth.web.netease;
+
+import com.izouma.nineth.dto.netease.TeamMember;
+import com.izouma.nineth.service.netease.TeamService;
+import com.izouma.nineth.utils.SecurityUtils;
+import com.izouma.nineth.web.BaseController;
+import com.izouma.nineth.domain.netease.Team;
+
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.netease.TeamRepo;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+
+import freemarker.template.utility.RichObjectWrapper;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.bytedeco.tesseract.StringGenericVector;
+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("/team")
+@AllArgsConstructor
+public class TeamController extends BaseController {
+    private TeamService teamService;
+    private TeamRepo    teamRepo;
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Team save(@RequestBody Team record) {
+        if (record.getTid() != null) {
+            return teamService.update(record);
+        }
+        return teamService.create(record);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/mute/{id}")
+    public void mute(@PathVariable Long id) {
+        teamService.mute(id);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/muteSolo")
+    public void muteSolo(Long tid, String accid) {
+        teamService.muteMember(tid, accid);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/cancelMuteSolo")
+    public void cancelMuteSolo(Long tid, String accid) {
+        teamService.cancelMuteMember(tid, accid);
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/cancelMute/{id}")
+    public void cancelMute(@PathVariable Long id) {
+        teamService.cancelMute(id);
+    }
+
+    @PostMapping("/create")
+    public Team create(@RequestParam Team team) {
+        return teamService.create(team);
+    }
+
+    @PostMapping("/invite")
+    public Team inviteSolo(@RequestParam String userId, @RequestParam String tid) {
+        return teamService.invite(userId, tid);
+    }
+
+    @PostMapping("/leave")
+    public void leave(@RequestParam String userId, @RequestParam String tid) {
+        teamService.leave(userId, tid);
+    }
+
+    @PostMapping("/kick")
+    public void kick(@RequestParam String userId, @RequestParam String tid) {
+        teamService.kick(userId, tid);
+    }
+
+    @PostMapping("/memberInfos")
+    public List<TeamMember> memberInfos(Long tid) {
+        return teamService.getTeamMembers(tid);
+    }
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<Team> all(@RequestBody PageQuery pageQuery) {
+        return teamService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public Team get(@PathVariable Long id) {
+        Team team = teamRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+        String userId = SecurityUtils.getAuthenticatedUser().getId().toString();
+        team.setBan(team.getBanned().stream().anyMatch(member -> StringUtils.equals(member, userId)));
+        return team;
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        teamRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Team> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+
+    @PostMapping("/checkout")
+    public void checkout(String userId, String tid) {
+        teamService.checkout(userId, tid);
+    }
+
+    @PostMapping("/checkCanjoin")
+    public boolean checkJoin(Long tid, String accid) {
+        return teamService.checkTeamIn(tid, accid);
+    }
+}
+

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

@@ -53,6 +53,7 @@
     "less-vars-loader": "^1.1.0",
     "prettier": "1.19.1",
     "style-resources-loader": "^1.3.3",
+    "svg-sprite-loader": "^6.0.11",
     "vue-cli-plugin-style-resources-loader": "^0.1.4",
     "vue-template-compiler": "2.6.11"
   }

BIN
src/main/vue/src/assets/home_icon_redian.png


BIN
src/main/vue/src/assets/home_icon_redian2.png


BIN
src/main/vue/src/assets/icon-sosuo.png


BIN
src/main/vue/src/assets/icon_bar.png


+ 3 - 0
src/main/vue/src/assets/icons/index.js

@@ -0,0 +1,3 @@
+const requireAll = requireContext => requireContext.keys().map(requireContext);
+const req = require.context('./svg', false, /\.svg$/);
+requireAll(req);

+ 20 - 0
src/main/vue/src/assets/icons/svg/redian.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="55px" height="16px" viewBox="0 0 55 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>home_icon_redian</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill-rule="evenodd">
+        <g id="首页-黑色" transform="translate(-26.000000, -386.000000)" fill-rule="nonzero">
+            <g id="编组" transform="translate(16.000000, 376.000000)">
+                <g id="编组-8" transform="translate(10.000000, 10.000000)">
+                    <g id="公告" transform="translate(28.280000, 2.920000)" fill="#FFFFFF">
+                        <path d="M9.506,0 L12.278,4.424 L10.164,4.424 L7.392,0 L9.506,0 Z M2.114,4.424 L0,4.424 L2.604,0 L4.718,0 L2.114,4.424 Z M10.052,5.488 L12.306,10.066 L10.108,10.066 L9.996,9.828 L0.896,9.828 L0.042,9.828 L3.64,3.724 L5.922,3.724 L2.982,8.47 L9.324,8.47 L7.84,5.488 L10.052,5.488 Z" id="形状"></path>
+                        <path d="M24.906,8.554 C24.906,8.76866667 24.8663333,8.967 24.787,9.149 C24.7076667,9.331 24.598,9.492 24.458,9.632 C24.318,9.772 24.1546667,9.88166667 23.968,9.961 C23.7813333,10.0403333 23.5806667,10.08 23.366,10.08 L13.496,10.08 L13.496,6.174 L24.906,6.174 L24.906,8.554 Z M23.142,7.378 L15.26,7.378 L15.26,8.89 L22.694,8.89 C22.8246667,8.89 22.932,8.848 23.016,8.764 C23.1,8.68 23.142,8.57733333 23.142,8.456 L23.142,7.378 Z M20.062,3.906 L25.186,3.906 L25.186,5.292 L12.894,5.292 L12.894,3.906 L18.116,3.906 L18.116,2.632 L14.742,2.632 L13.286,2.632 L14,0 L16.002,0 L15.666,1.232 L18.116,1.232 L18.116,0 L20.062,0 L20.062,1.232 L24.892,1.232 L24.892,2.632 L20.062,2.632 L20.062,3.906 Z" id="形状"></path>
+                    </g>
+                    <g id="新闻" transform="translate(2.294000, 2.878000)" >
+                        <path d="M0.014,0.448 L1.75,0.448 L1.568,0.028 L3.85,0.028 L4.032,0.448 L6.216,0.448 L6.216,1.834 L0.014,1.834 L0.014,0.448 Z M2.17,5.628 L2.17,4.774 L0.014,4.774 L0.014,3.388 L0.742,3.388 L0.238,2.198 L2.044,2.198 L2.548,3.388 L3.696,3.388 L4.186,2.198 L5.992,2.198 L5.488,3.388 L6.216,3.388 L6.216,4.774 L4.074,4.774 L4.074,5.628 L6.216,5.628 L6.216,7.014 L4.074,7.014 L4.074,8.806 C4.074,8.98333333 4.039,9.15133333 3.969,9.31 C3.899,9.46866667 3.80566667,9.60866667 3.689,9.73 C3.57233333,9.85133333 3.43233333,9.947 3.269,10.017 C3.10566667,10.087 2.93533333,10.122 2.758,10.122 L1.526,10.122 L1.89,9.156 L1.946,9.156 C2.09533333,9.156 2.17,9.08133333 2.17,8.932 L2.17,7.014 L0.014,7.014 L0.014,5.628 L2.17,5.628 Z M0,10.122 L0.294,7.546 L1.778,7.546 L1.484,10.122 L0,10.122 Z M4.452,7.546 L5.922,7.546 L6.216,10.122 L4.746,10.122 L4.452,7.546 Z M8.764,1.428 L8.764,2.8 L12.292,2.8 L12.292,3.99 L11.816,3.99 L11.816,10.108 L9.926,10.108 L9.926,3.99 L8.764,3.99 L8.764,8.932 L8.4,10.122 L6.51,10.122 L6.874,8.932 L6.874,1.54 L6.874,0.868 L6.874,0.35 L12.292,0.042 L12.292,1.232 L8.764,1.428 Z" id="形状"></path>
+                        <path d="M19.782,9.38 L19.782,8.428 L15.47,8.624 L15.47,7.616 L15.904,7.602 L15.904,3.682 L15.47,3.682 L15.47,2.674 L15.904,2.674 L17.724,2.674 L19.782,2.674 L21.602,2.674 L22.12,2.674 L22.12,3.682 L21.602,3.682 L21.602,7.336 L22.12,7.308 L22.12,8.316 L21.602,8.344 L21.602,9.38 L19.782,9.38 Z M17.724,5.866 L19.782,5.866 L19.782,5.278 L17.724,5.278 L17.724,5.866 Z M17.724,3.682 L17.724,4.27 L19.782,4.27 L19.782,3.682 L17.724,3.682 Z M19.782,7.42 L19.782,6.874 L17.724,6.874 L17.724,7.518 L19.782,7.42 Z M14.938,0 L17.234,0 L18.116,2.016 L15.834,2.016 L14.938,0 Z M25.06,8.61 C25.06,8.82466667 25.0203333,9.02533333 24.941,9.212 C24.8616667,9.39866667 24.752,9.562 24.612,9.702 C24.472,9.842 24.3086667,9.95166667 24.122,10.031 C23.9353333,10.1103333 23.7346667,10.15 23.52,10.15 L21.7,10.15 L22.232,8.946 L22.68,8.946 C22.8013333,8.946 22.9063333,8.904 22.995,8.82 C23.0836667,8.736 23.128,8.62866667 23.128,8.498 L23.128,1.61 L18.452,1.61 L18.452,0.238 L25.06,0.238 L25.06,8.61 Z M12.978,1.176 L14.924,1.176 L14.924,10.15 L12.978,10.15 L12.978,1.176 Z" id="形状"></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 13 - 0
src/main/vue/src/assets/icons/svg/selected.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_gouxuan_pre</title>
+    <g id="暗色-新" stroke="none" stroke-width="1"  fill-rule="evenodd">
+        <g id="确认订单" transform="translate(-335.000000, -359.000000)">
+            <g id="编组-2" transform="translate(0.000000, 347.000000)">
+                <g id="icon/gouxuan_pre" transform="translate(335.000000, 12.000000)">
+                    <path d="M12,3 C16.9705627,3 21,7.02943725 21,12 C21,16.9705627 16.9705627,21 12,21 C7.02943725,21 3,16.9705627 3,12 C3,7.02943725 7.02943725,3 12,3 Z M16.1555861,9.48335676 C15.861615,9.25961888 15.4402153,9.28199267 15.1717299,9.55047812 L10.7519559,13.9691923 L8.80776884,12.0253519 L8.7309649,11.9582305 C8.43699375,11.7344926 8.01559412,11.7568664 7.74710866,12.0253519 C7.45421545,12.3182451 7.45421545,12.7931188 7.74710866,13.086012 L10.2219824,15.5608858 L10.2987863,15.6280071 C10.5927575,15.851745 11.0141571,15.8293712 11.2826426,15.5608858 L16.23239,10.6111383 L16.2995114,10.5343344 C16.5232493,10.2403632 16.5008755,9.81896357 16.23239,9.55047812 Z" id="形状结合"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 42 - 0
src/main/vue/src/components/SvgIcon.vue

@@ -0,0 +1,42 @@
+<template>
+    <svg :class="svgClass" aria-hidden="true">
+        <use :xlink:href="iconName"></use>
+    </svg>
+</template>
+
+<script>
+export default {
+    name: 'svg-icon',
+    props: {
+        iconClass: {
+            type: String,
+            required: true
+        },
+        className: {
+            type: String
+        }
+    },
+    computed: {
+        iconName() {
+            return `#icon-${this.iconClass}`;
+        },
+        svgClass() {
+            if (this.className) {
+                return 'svg-icon ' + this.className;
+            } else {
+                return 'svg-icon';
+            }
+        }
+    }
+};
+</script>
+
+<style scoped>
+.svg-icon {
+    width: 1em;
+    height: 1em;
+    vertical-align: -0.15em;
+    fill: currentColor;
+    overflow: hidden;
+}
+</style>

+ 171 - 30
src/main/vue/src/components/phone/Home.vue

@@ -1,6 +1,7 @@
 <template>
     <van-pull-refresh
         class="home"
+        :class="[`home-${theme}`]"
         success-text="加载成功"
         success-duration="500"
         v-model="isLoading"
@@ -12,6 +13,18 @@
         <img src="../../assets/theme3-bg.png" alt="" v-if="theme === 'theme3'" class="theme3-bg" />
         <div class="filter-bg" :style="{ backgroundImage: `url(${bgImg})` }"></div>
 
+        <van-sticky ref="top" v-if="theme === 'theme6'">
+            <div class="padding-safe-top">
+                <div class="top">
+                    <div class="search" @click="$router.push('/' + $route.params.companyId + '/productSearch')">
+                        <img src="../../assets/icon-sosuo.png" alt="" />
+                        <span>搜索你要的精彩</span>
+                    </div>
+                </div>
+            </div>
+        </van-sticky>
+
+        <div class="home-title" v-if="theme === 'theme6'">数字艺术品</div>
         <div class="welcom3" v-if="theme === 'theme3'">
             <van-image
                 width="38"
@@ -37,7 +50,7 @@
             <div class="mySwiper" :class="[`swiper-${theme}`]" ref="mySwiper">
                 <div class="swiper-wrapper">
                     <div class="swiper-slide" v-for="item in banners" :key="item.id">
-                        <product-small :info="item"></product-small>
+                        <banner-small :info="item"></banner-small>
                     </div>
                 </div>
                 <!-- 如果需要分页器 -->
@@ -52,35 +65,64 @@
                 fit="cover"
                 :src="companyInfo.logo || require('../../assets/img_default_photo.png')"
             ></van-image>
+            <div class="company-descript" v-if="theme === 'theme7'">
+                <div class="text1">{{ companyInfo.description }}</div>
+                <div class="text2">{{ companyInfo.name }}欢迎您</div>
+            </div>
         </div>
 
-        <div class="home-title">
-            <span>新闻公告</span>
+        <div class="news-notice" v-if="theme === 'theme6' || theme === 'theme7'">
+            <van-notice-bar :text="newsInfo.title">
+                <template #left-icon>
+                    <img src="../../assets/home_icon_redian.png" v-if="theme === 'theme6'" alt="" class="bar-icon" />
+                    <img src="../../assets/home_icon_redian2.png" v-else="theme === 'theme7'" alt="" class="bar-icon" />
+                </template>
+
+                <template #right-icon>
+                    <img src="../../assets/icon_bar.png" class="right-bar" alt="" />
+                </template>
+            </van-notice-bar>
+        </div>
+
+        <template v-else>
+            <div class="home-title">
+                <span>新闻公告</span>
 
-            <div class="title-more">
-                <span>更多</span>
-                <van-icon name="icon-icon_inter" class-prefix="font_family" />
+                <div class="title-more">
+                    <span>更多</span>
+                    <van-icon name="icon-icon_inter" class-prefix="font_family" />
+                </div>
             </div>
-        </div>
 
-        <div class="newsList">
-            <news-large v-for="(item, index) in news" :theme="theme" :key="index" :info="item.obj"></news-large>
-        </div>
+            <div class="newsList">
+                <news-large v-for="(item, index) in news" :theme="theme" :key="index" :info="item.obj"></news-large>
+            </div>
+        </template>
 
-        <div class="home-title">数字艺术品</div>
+        <div class="home-title" v-if="theme !== 'theme6'">数字艺术品</div>
 
         <!-- <div class="tabs" v-if="!empty">
             <div class="tab" :class="{ active: active === 0 }" @click="active = 0">数字藏品</div>
             <div class="tab" :class="{ active: active === 1 }" @click="active = 1">产品新闻端</div>
         </div> -->
         <div class="listSwiper">
-            <product-large
-                v-for="(item, index) in products"
-                :theme="theme"
-                :key="index"
-                :info="item.obj"
-                :type="item.type"
-            ></product-large>
+            <template v-for="(item, index) in products">
+                <template v-if="theme === 'theme6'">
+                    <product-small
+                        :key="`small_${index}`"
+                        :info="products[index].obj"
+                        :type="item.type"
+                    ></product-small>
+                </template>
+
+                <product-large
+                    v-if="theme !== 'theme6'"
+                    :theme="theme"
+                    :key="index"
+                    :info="item.obj"
+                    :type="item.type"
+                ></product-large>
+            </template>
         </div>
     </van-pull-refresh>
 </template>
@@ -95,6 +137,7 @@ import product from '../../mixins/product';
 import ProductLarge from './productLarge.vue';
 import NewsLarge from './NewsLarge.vue';
 import ProductSmall from './productSmall.vue';
+import BannerSmall from './bannerSmall.vue';
 import { mapState } from 'vuex';
 
 export default {
@@ -115,7 +158,8 @@ export default {
     components: {
         ProductLarge,
         ProductSmall,
-        NewsLarge
+        NewsLarge,
+        BannerSmall
     },
     computed: {
         ...mapState(['userInfo']),
@@ -142,6 +186,12 @@ export default {
                 }
             });
             return flag;
+        },
+        newsInfo() {
+            if (this.news.length > 0) {
+                return [...this.news][0].obj;
+            }
+            return {};
         }
     },
     data() {
@@ -373,17 +423,6 @@ export default {
     height: auto;
     z-index: -1;
 }
-.top {
-    display: flex;
-    padding: 9px 16px;
-    background-color: var(--bg);
-    display: flex;
-    align-items: center;
-    .logo {
-        width: 74px;
-        height: 26px;
-    }
-}
 
 .home {
     width: 100%;
@@ -395,6 +434,7 @@ export default {
     overflow-y: auto;
     max-height: 720px;
     overflow-x: hidden;
+    min-height: 800px;
     &::-webkit-scrollbar {
         display: none; /* Chrome Safari */
     }
@@ -464,6 +504,48 @@ export default {
     height: 160px;
     overflow: hidden;
 }
+
+.home-theme6 {
+    .large-logo {
+        padding: 4px 16px 16px;
+        border-radius: 0px;
+    }
+}
+
+.home-theme7 {
+    .large-logo {
+        padding: 0 0 48px;
+        height: 200px;
+        position: relative;
+    }
+
+    .company-descript {
+        background: var(--bg2);
+        position: absolute;
+        z-index: 2;
+        top: 150px;
+        color: var(--text1);
+        .flex-col();
+        align-items: center;
+        left: 16px;
+        right: 16px;
+        padding: 9px 0 22px;
+        box-shadow: 0px 2px 8px 0px rgba(255, 255, 255, 0.1);
+        .text1 {
+            font-size: 20px;
+            font-weight: bold;
+            line-height: 28px;
+            text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5);
+        }
+
+        .text2 {
+            font-size: 12px;
+            line-height: 17px;
+            text-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5);
+            margin-top: 2px;
+        }
+    }
+}
 // .swiper-slide {
 //     width: 255px;
 // }
@@ -671,4 +753,63 @@ export default {
 .newsList {
     padding-bottom: 8px;
 }
+
+/deep/.news-notice {
+    padding: 0 16px 16px;
+    .van-notice-bar {
+        background-color: var(--bglight);
+        color: var(--info);
+        padding: 0 10px;
+        position: relative;
+        .bar-icon {
+            width: 55px;
+            height: 16px;
+            display: block;
+            margin-right: 17px;
+            color: var(--prim);
+        }
+
+        &::before {
+            content: '';
+            position: absolute;
+            width: 1px;
+            height: 10px;
+            background: rgba(255, 255, 255, 0.2);
+            left: 73px;
+            top: 50%;
+            transform: translateY(-50%);
+        }
+
+        .right-bar {
+            width: 18px;
+            height: 18px;
+            margin-left: 10px;
+        }
+    }
+}
+
+.top {
+    display: flex;
+    padding: 9px 16px;
+    background-color: var(--bg2);
+    display: flex;
+    align-items: center;
+
+    .search {
+        flex-grow: 1;
+        background-color: var(--bglight);
+        display: flex;
+        align-items: center;
+        padding: 4px 20px;
+        border-radius: 21px;
+        img {
+            width: 16px;
+            height: 16px;
+            margin-right: 10px;
+        }
+        font-size: 14px;
+        color: #939599;
+        line-height: 24px;
+    }
+}
 </style>

+ 81 - 0
src/main/vue/src/components/phone/bannerSmall.vue

@@ -0,0 +1,81 @@
+<template>
+    <div class="product">
+        <van-image width="100%" height="100%" :radius="30" :src="getImg(info.pic)" fit="cover" />
+
+        <!-- <div class="content">
+            <div class="name van-ellipsis">
+                {{ info.name }}
+            </div>
+            <div class="text van-ellipsis">
+                {{ info.remark }}
+            </div>
+        </div> -->
+    </div>
+</template>
+
+<script>
+export default {
+    props: {
+        info: {
+            type: Object,
+            default: () => {
+                return {};
+            }
+        }
+    },
+    methods: {
+        getImg(imgs = '', type = '', size = 800) {
+            if (!imgs) {
+                imgs = '';
+            }
+            if (!(imgs instanceof Array)) {
+                imgs = imgs.split(',');
+            }
+
+            imgs = imgs.filter(item => {
+                return !!item;
+            });
+            if (imgs.length > 0) {
+                let img = type ? imgs[0][type] : imgs[0];
+                // console.log(img);
+                return img + (/\.gif$/i.test(img) ? '' : `?x-oss-process=image/resize,h_${size},m_lfit`);
+            } else {
+                return '';
+            }
+        }
+    }
+};
+</script>
+
+<style lang="less" scoped>
+.product {
+    // width: 255px;
+    // margin: 8px;
+    width: 100%;
+    background-color: #fff;
+    display: inline-block;
+    border-radius: 30px;
+    overflow: hidden;
+    height: 100%;
+
+    .van-image {
+        overflow: hidden;
+        display: block;
+    }
+    .content {
+        padding: 6px 16px 10px;
+
+        .name {
+            font-size: 14px;
+            color: #ffffff;
+            line-height: 24px;
+        }
+
+        .text {
+            font-size: 12px;
+            color: #939599;
+            line-height: 20px;
+        }
+    }
+}
+</style>

+ 1 - 0
src/main/vue/src/components/phone/module.vue

@@ -60,6 +60,7 @@ export default {
         border-radius: 30px;
         overflow: hidden;
         box-sizing: border-box;
+        min-height: 100px;
     }
 }
 

+ 19 - 2
src/main/vue/src/components/phone/productLarge.vue

@@ -1,14 +1,13 @@
 <template>
     <div class="product" :class="[`product-${theme}`]">
         <van-image
-            :radius="30"
             width="100%"
             height="calc(354px - 32px)"
             v-if="type === 'collection'"
             :src="getImg(changeImgs(info.pic))"
             fit="cover"
         />
-        <van-image :radius="30" width="100%" height="calc(100% - 32px)" v-else :src="getImg(info.pic)" fit="cover" />
+        <van-image width="100%" height="calc(100% - 32px)" v-else :src="getImg(info.pic)" fit="cover" />
 
         <template v-if="type === 'collection'">
             <div class="content">
@@ -139,6 +138,9 @@ export default {
 </script>
 
 <style lang="less" scoped>
+.product > .van-image {
+    border-radius: var(--product-radius);
+}
 .product {
     width: calc(100% - 32px);
     margin: 8px 16px;
@@ -147,6 +149,7 @@ export default {
     display: inline-block;
     border-radius: 30px;
     overflow: hidden;
+    border-radius: var(--product-radius);
 
     .bg {
         position: absolute;
@@ -303,4 +306,18 @@ export default {
         color: var(--danger);
     }
 }
+
+.product-theme7 {
+    position: relative;
+    .content {
+        position: absolute;
+        bottom: 10px;
+        left: 10px;
+        right: 10px;
+        z-index: 2;
+        background-color: var(--bglight);
+        border-radius: var(--product-radius);
+        backdrop-filter: blur(3px);
+    }
+}
 </style>

+ 194 - 32
src/main/vue/src/components/phone/productSmall.vue

@@ -1,46 +1,112 @@
 <template>
     <div class="product">
-        <van-image width="100%" height="100%" :radius="30" :src="getImg(info.pic)" fit="cover" />
+        <van-image width="120" height="120" :radius="2" :src="getImg(changeImgs(info.pic))" fit="cover" />
 
-        <!-- <div class="content">
-            <div class="name van-ellipsis">
-                {{ info.name }}
+        <div class="content">
+            <div class="name van-ellipsis">{{ info.name }}</div>
+            <div class="sales-list">
+                <div class="sales" v-if="info.category">
+                    <span>{{ info.category }}</span>
+                </div>
+                <div class="sales">
+                    <span class="sales-fir">限量</span>
+                    <span>{{ info.total }}份</span>
+                </div>
             </div>
-            <div class="text van-ellipsis">
-                {{ info.remark }}
+            <div class="flex1"></div>
+
+            <div class="bottom">
+                <div class="miner">
+                    <van-image width="18" height="18" radius="18" :src="getImg(info.minterAvatar)" fit="cover" />
+                    <span class="van-ellipsis">{{ info.minter }}</span>
+                </div>
+                <div class="price" v-if="info.salable">
+                    <van-icon name="icon-icon_jiage" class-prefix="font_family" />
+                    <span> {{ info.price }}</span>
+                </div>
+                <div v-else class="status">仅展示</div>
             </div>
-        </div> -->
+        </div>
+
+        <div class="sold xianliang" v-if="time && info.scheduleSale">
+            <van-icon name="icon-clock" class-prefix="font_family" />
+            <span>开售:{{ startTime || time }}</span>
+        </div>
+        <div class="sold" v-else-if="isSolded">
+            <van-icon name="icon-jinzhi" class-prefix="font_family" />
+            <span>已售罄</span>
+        </div>
+        <div class="sold" v-else-if="isSold">
+            <van-icon name="icon-info_icon_qianggouzhong" class-prefix="font_family" />
+            <span>即将售罄</span>
+        </div>
+        <div class="sold hot" v-else>
+            <van-icon name="icon-info_icon_qianggouzhong" class-prefix="font_family" />
+            <span>火爆抢购中…</span>
+        </div>
     </div>
 </template>
 
 <script>
+import { mapState } from 'vuex';
+import product from '../../mixins/product';
 export default {
+    mixins: [product],
     props: {
         info: {
             type: Object,
             default: () => {
                 return {};
             }
+        },
+        type: {
+            type: String,
+            default: 'collection'
         }
     },
-    methods: {
-        getImg(imgs = '', type = '', size = 800) {
-            if (!imgs) {
-                imgs = '';
-            }
-            if (!(imgs instanceof Array)) {
-                imgs = imgs.split(',');
+    computed: {
+        ...mapState(['theme']),
+        time() {
+            if (this.info.startTime) {
+                if (this.dayjs().isSameOrBefore(this.info.startTime, 'YYYY-MM-DD HH:mm:ss')) {
+                    return this.dayjs(this.info.startTime).format('MM月DD日');
+                }
             }
 
-            imgs = imgs.filter(item => {
-                return !!item;
-            });
-            if (imgs.length > 0) {
-                let img = type ? imgs[0][type] : imgs[0];
-                // console.log(img);
-                return img + (/\.gif$/i.test(img) ? '' : `?x-oss-process=image/resize,h_${size},m_lfit`);
+            return '';
+        }
+    },
+    mounted() {
+        if (this.info.startTime) {
+            var x = this.dayjs(this.info.startTime, 'YYYY-MM-DD HH:mm:ss');
+            var y = this.dayjs();
+            let d = this.dayjs.duration(x.diff(y));
+            let day = parseInt(d.asDays());
+            if (day <= 0) {
+                this.getTime(this.info.startTime);
+            }
+        }
+    },
+    methods: {
+        likeProduct() {
+            if (!this.info.liked) {
+                this.$http.get(`/collection/${this.info.id}/like`).then(() => {
+                    this.$emit('update:info', {
+                        ...this.info,
+                        liked: true,
+                        likes: this.info.likes + 1
+                    });
+                    this.$toast.success('收藏成功');
+                });
             } else {
-                return '';
+                this.$http.get(`/collection/${this.info.id}/unlike`).then(() => {
+                    this.$emit('update:info', {
+                        ...this.info,
+                        liked: false,
+                        likes: this.info.likes - 1
+                    });
+                    this.$toast.success('取消收藏');
+                });
             }
         }
     }
@@ -51,31 +117,127 @@ export default {
 .product {
     // width: 255px;
     // margin: 8px;
-    width: 100%;
-    background-color: #fff;
-    display: inline-block;
-    border-radius: 30px;
+    .flex();
+    align-items: stretch;
+
+    background-color: var(--bglight);
+    border-radius: 2px;
     overflow: hidden;
-    height: 100%;
+    margin: 8px 16px;
+    position: relative;
 
     .van-image {
         overflow: hidden;
         display: block;
+        flex-shrink: 0;
     }
     .content {
-        padding: 6px 16px 10px;
+        padding: 12px 12px 3px;
+        flex-grow: 1;
+        .flex-col();
+        overflow: hidden;
 
         .name {
             font-size: 14px;
-            color: #ffffff;
+            font-weight: bold;
+            color: var(--text1);
             line-height: 24px;
         }
 
-        .text {
+        .sales-list {
+            margin-top: 8px;
+            .flex();
+        }
+
+        .sales {
+            overflow: hidden;
             font-size: 12px;
-            color: #939599;
-            line-height: 20px;
+            border-radius: 2px;
+
+            span {
+                padding: 0 10px;
+                line-height: 20px;
+                height: 20px;
+                display: inline-block;
+                &.sales-fir {
+                    background: var(--prim);
+                    color: var(--text2);
+                }
+                background-color: var(--fadePrim);
+                color: var(--prim);
+            }
+        }
+        .sales + .sales {
+            margin-left: 14px;
         }
+
+        .bottom {
+            display: flex;
+            margin-top: 14px;
+            align-items: center;
+            overflow: hidden;
+            width: 100%;
+
+            .miner {
+                display: flex;
+                align-items: center;
+                margin-right: 6px;
+                overflow: hidden;
+                flex-grow: 1;
+
+                span {
+                    color: var(--info);
+                    font-size: 12px;
+                    margin-left: 6px;
+                }
+            }
+
+            .price {
+                flex-shrink: 0;
+                font-size: 28px;
+                color: var(--text1);
+                line-height: 20px;
+                font-family: OSP;
+                .flex();
+                .font_family {
+                    font-size: 8px;
+                    line-height: 16px;
+                    margin-top: 8px;
+                }
+            }
+        }
+    }
+}
+
+.status {
+    font-size: 14px;
+    color: var(--text1);
+}
+.sold {
+    background-color: var(--bglight);
+    font-size: 12px;
+    color: var(--info);
+    padding: 0 6px;
+    line-height: 24px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 3;
+    .flex();
+    .font_family {
+        margin-right: 2px;
+    }
+
+    &.preSold {
+        color: var(--danger);
+    }
+
+    &.xianliang {
+        color: var(--primlight);
+    }
+
+    &.hot {
+        color: var(--danger);
     }
 }
 </style>

+ 3 - 0
src/main/vue/src/main.js

@@ -21,6 +21,8 @@ import ObjectUpload from '@/components/ObjectUpload';
 import CollectionSearch from '@/components/CollectionSearch';
 import CollectionCoupon from '@/components/CollectionCoupon';
 import CreatedAtPicker from '@/components/CreatedAtPicker';
+import SvgIcon from '@/components/SvgIcon.vue';
+import '@/assets/icons';
 import { mapGetters } from 'vuex';
 import Vant from 'vant';
 import queryString from 'query-string';
@@ -65,6 +67,7 @@ Vue.component('file-upload', FileUpload);
 Vue.component('video-upload', VideoUpload);
 Vue.component('rich-text', RichText);
 Vue.component('crop-upload', CropUpload);
+Vue.component('svg-icon', SvgIcon)
 Vue.component('district-choose', DistrictChoose);
 Vue.component('page-title', PageTitle);
 Vue.component('minter-select', MinterSelect);

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

@@ -1476,6 +1476,46 @@ const router = new Router({
                     meta: {
                        title: '游客管理',
                     },
+                },
+                {
+                    path: '/neteaseUserList',
+                    name: 'NeteaseUserList',
+                    component: () => import(/* webpackChunkName: "neteaseUserList" */ '@/views/NeteaseUserList.vue'),
+                    meta: {
+                       title: '云信用户表',
+                    },
+               },
+                {
+                    path: '/teamEdit',
+                    name: 'TeamEdit',
+                    component: () => import(/* webpackChunkName: "teamEdit" */ '@/views/TeamEdit.vue'),
+                    meta: {
+                       title: '群组编辑',
+                    },
+                },
+                {
+                    path: '/teamList',
+                    name: 'TeamList',
+                    component: () => import(/* webpackChunkName: "teamList" */ '@/views/TeamList.vue'),
+                    meta: {
+                       title: '群组',
+                    },
+               },
+                {
+                    path: '/neteaseMessageEdit',
+                    name: 'NeteaseMessageEdit',
+                    component: () => import(/* webpackChunkName: "neteaseMessageEdit" */ '@/views/NeteaseMessageEdit.vue'),
+                    meta: {
+                       title: '信息编辑',
+                    },
+                },
+                {
+                    path: '/neteaseMessageList',
+                    name: 'NeteaseMessageList',
+                    component: () => import(/* webpackChunkName: "neteaseMessageList" */ '@/views/NeteaseMessageList.vue'),
+                    meta: {
+                       title: '信息',
+                    },
                }
                 /**INSERT_LOCATION**/
             ]

+ 61 - 0
src/main/vue/src/styles/theme.less

@@ -12,6 +12,7 @@
     --bg3:#272b2e;
     --text1:#fff;
     --text2:#000;
+    --product-radius:30px;
 }
 
 .theme2 {
@@ -28,6 +29,7 @@
     --bg3:#272b2e;
     --text1:#fff;
     --text2:#000;
+    --product-radius:30px;
     --van-primary-color: var(--prim);
     --van-tabbar-item-active-color: var(--prim);
     --van-tabs-default-color: var(--prim);
@@ -54,6 +56,7 @@
     --bg3:#272b2e;
     --text1:#fff;
     --text2:#000;
+    --product-radius:30px;
     --van-primary-color: var(--prim);
     --van-tabbar-item-active-color: var(--prim);
     --van-tabs-default-color: var(--prim);
@@ -81,6 +84,7 @@
     --bg3:#fff;
     --text1:#000;
     --text2:#fff;
+    --product-radius:30px;
     --van-primary-color: var(--prim);
     --van-tabbar-item-active-color: var(--prim);
     --van-tabs-default-color: var(--prim);
@@ -108,6 +112,63 @@
     --bg3:#1C1C1C;
     --text1:#fff;
     --text2:#000;
+    --product-radius:30px;
+    --van-primary-color: var(--prim);
+    --van-tabbar-item-active-color: var(--prim);
+    --van-tabs-default-color: var(--prim);
+    --van-tabs-bottom-bar-color: var(--prim);
+    --van-tab-active-text-color: var(--prim);
+    --van-field-error-message-color: var(--prim);
+    --van-button-primary-background-color: var(--prim);
+    --van-button-primary-border-color: var(--prim);
+    --van-number-keyboard-button-background-color: var(--prim);
+    --van-button-primary-background-color:var(--prim);
+}
+
+
+.theme6 {
+    --prim: #FFE196;
+    --fadePrim: #2D2B25;
+    --darkPrim: #FFE196;
+    --primlight: #FFB600;
+    --btnText: #000;
+    --bglight:#1C1C1C;
+    --danger:#FF4F50;
+    --info:#60616D;
+    --bg:#181818;
+    --bg2:#000000;
+    --bg3:#272b2e;
+    --text1:#fff;
+    --text2:#000;
+    --product-radius:2px;
+    --van-primary-color: var(--prim);
+    --van-tabbar-item-active-color: var(--prim);
+    --van-tabs-default-color: var(--prim);
+    --van-tabs-bottom-bar-color: var(--prim);
+    --van-tab-active-text-color: var(--prim);
+    --van-field-error-message-color: var(--prim);
+    --van-button-primary-background-color: var(--prim);
+    --van-button-primary-border-color: var(--prim);
+    --van-number-keyboard-button-background-color: var(--prim);
+    --van-button-primary-background-color:var(--prim);
+}
+
+
+.theme7 {
+    --prim: #D3FA51;
+    --fadePrim: #2D2B25;
+    --darkPrim: #D3FA51;
+    --primlight: #7CD400;
+    --btnText: #000;
+    --bglight:#1C1C1C;
+    --danger:#FF4F50;
+    --info:#60616D;
+    --bg:#181818;
+    --bg2:#000000;
+    --bg3:#272b2e;
+    --text1:#fff;
+    --text2:#000;
+    --product-radius:2px;
     --van-primary-color: var(--prim);
     --van-tabbar-item-active-color: var(--prim);
     --van-tabs-default-color: var(--prim);

+ 112 - 0
src/main/vue/src/views/NeteaseMessageEdit.vue

@@ -0,0 +1,112 @@
+<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="80px" label-position="right"
+                         size="small"
+                         style="max-width: 500px;">
+                        <el-form-item prop="from" label="发送用户">
+                                    <el-input v-model="formData.from"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="to" label="收信用户">
+                                    <el-input v-model="formData.to"></el-input>
+                        </el-form-item>
+                        <el-form-item prop="ope" label="发信类型">
+                                    <el-input-number type="number" v-model="formData.ope"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="type" label="信息类型">
+                                    <el-input-number type="number" v-model="formData.type"></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="body" label="信息主题">
+                                    <el-input v-model="formData.body"></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: 'NeteaseMessageEdit',
+        created() {
+            if (this.$route.query.id) {
+                this.$http
+                    .get('neteaseMessage/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('/neteaseMessage/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(`/neteaseMessage/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>

+ 175 - 0
src/main/vue/src/views/NeteaseMessageList.vue

@@ -0,0 +1,175 @@
+<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="from" label="发送用户"
+>
+                    </el-table-column>
+                    <el-table-column prop="to" label="收信用户"
+>
+                    </el-table-column>
+                    <el-table-column prop="ope" label="发信类型"
+>
+                    </el-table-column>
+                    <el-table-column prop="type" label="信息类型"
+>
+                    </el-table-column>
+                    <el-table-column prop="body" 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: 'NeteaseMessageList',
+        mixins: [pageableTable],
+        data() {
+            return {
+                multipleMode: false,
+                search: "",
+                url: "/neteaseMessage/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: "/neteaseMessageEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/neteaseMessageEdit",
+                    query: {
+                    id: row.id
+                    }
+                });
+            },
+            download() {
+                this.downloading = true;
+                this.$axios
+                    .get("/neteaseMessage/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(`/neteaseMessage/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>

+ 192 - 0
src/main/vue/src/views/NeteaseUserList.vue

@@ -0,0 +1,192 @@
+<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="accId" label="云信账号id"
+>
+                    </el-table-column>
+                    <el-table-column prop="token" label="token"
+>
+                    </el-table-column>
+                    <el-table-column prop="name" label="昵称"
+>
+                    </el-table-column>
+                    <el-table-column prop="icon" label="头像"
+>
+                            <template slot-scope="{row}">
+                                <el-image style="width: 30px; height: 30px"
+                                          :src="row.icon" fit="cover"
+                                          :preview-src-list="[row.icon]"></el-image>
+                            </template>
+                    </el-table-column>
+                    <el-table-column prop="sign" label="签名"
+>
+                    </el-table-column>
+                    <el-table-column prop="email" label="邮箱"
+>
+                    </el-table-column>
+                    <el-table-column prop="mobile" label="手机"
+>
+                    </el-table-column>
+                    <el-table-column prop="ex" 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: 'NeteaseUserList',
+        mixins: [pageableTable],
+        data() {
+            return {
+                multipleMode: false,
+                search: "",
+                url: "/neteaseUser/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: "/neteaseUserEdit",
+                    query: {
+                        ...this.$route.query
+                    }
+                });
+            },
+            editRow(row) {
+                this.$router.push({
+                    path: "/neteaseUserEdit",
+                    query: {
+                    id: row.id
+                    }
+                });
+            },
+            download() {
+                this.downloading = true;
+                this.$axios
+                    .get("/neteaseUser/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(`/neteaseUser/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>

+ 139 - 0
src/main/vue/src/views/TeamEdit.vue

@@ -0,0 +1,139 @@
+<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="tid" label="tid">
+                        <el-input-number type="number" v-model="formData.tid" disabled></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="ownerid" label="群主id">
+                        <el-input v-model="formData.ownerid" :disabled="!canEdit"></el-input>
+                    </el-form-item>
+                    <!--                        <el-form-item prop="members" label="群成员列表">-->
+                    <!--                                    <el-input v-model="formData.members"></el-input>-->
+                    <!--                        </el-form-item>-->
+                    <!--                        <el-form-item prop="admins" label="管理员列表">-->
+                    <!--                                    <el-input v-model="formData.admins"></el-input>-->
+                    <!--                        </el-form-item>-->
+                    <el-form-item prop="announcement" label="公告">
+                        <el-input type="textarea" v-model="formData.announcement"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="intro" label="介绍">
+                        <el-input type="textarea" v-model="formData.intro"></el-input>
+                    </el-form-item>
+                    <!--                    <el-form-item prop="msg" label="邀请信息">-->
+                    <!--                        <el-input type="textarea" v-model="formData.msg" :disabled="!canEdit"></el-input>-->
+                    <!--                    </el-form-item>-->
+                    <el-form-item prop="collection" label="指定持仓">
+                        <el-input v-model="formData.collection"></el-input>
+                    </el-form-item>
+                    <!--                        <el-form-item prop="custom" label="群组分类">-->
+                    <!--                                    <el-input v-model="formData.custom"></el-input>-->
+                    <!--                        </el-form-item>-->
+                    <el-form-item prop="icon" label="图标">
+                        <single-upload v-model="formData.icon"></single-upload>
+                    </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="password" label="密码">-->
+                    <!--                                    <el-input v-model="formData.password"></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: 'TeamEdit',
+    created() {
+        if (this.$route.query.id) {
+            this.$http
+                .get('team/get/' + this.$route.query.id)
+                .then(res => {
+                    this.formData = res;
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        }
+    },
+    computed: {
+        canEdit() {
+            return !!!this.$route.query.id;
+        }
+    },
+    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('/team/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(`/team/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>

+ 383 - 0
src/main/vue/src/views/TeamList.vue

@@ -0,0 +1,383 @@
+<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="tid" label="tid"
+            >
+            </el-table-column>
+            <el-table-column prop="name" label="群名" width="150"
+            >
+            </el-table-column>
+            <el-table-column prop="ownerid" label="群主id"
+            >
+            </el-table-column>
+            <el-table-column prop="members" label="群成员列表"
+            >
+            </el-table-column>
+            <el-table-column prop="announcement" label="公告"
+                             show-tooltip-when-overflow
+            >
+            </el-table-column>
+            <el-table-column prop="intro" label="介绍"
+                             show-tooltip-when-overflow
+            >
+            </el-table-column>
+            <!--            <el-table-column prop="msg" label="邀请信息"-->
+            <!--                             show-tooltip-when-overflow-->
+            <!--            >-->
+            <!--            </el-table-column>-->
+            <el-table-column prop="icon" label="图标"
+            >
+                <template slot-scope="{row}">
+                    <el-image style="width: 30px; height: 30px"
+                              :src="row.icon" fit="cover"
+                              :preview-src-list="[row.icon]"></el-image>
+                </template>
+            </el-table-column>
+            <!--            <el-table-column prop="collectionId" label="藏品id"-->
+            <!--            >-->
+            <!--            </el-table-column>-->
+            <el-table-column
+                label="操作"
+                align="center"
+                fixed="right"
+                width="300">
+                <template slot-scope="{row}">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                    <el-button @click="mute(row)" v-if="row.mute === false" type="danger" size="mini" plain>群禁言
+                    </el-button>
+                    <el-button @click="cancelMute(row)" v-if="row.mute === true" size="mini" plain>解除禁言</el-button>
+                    <el-button @click="showMemberInfoDialog(row)" 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>
+
+        <el-dialog :visible.sync="showInviteInfo" width="600px">
+            <el-form
+                ref="privilegeForm"
+                :model="inviteForm"
+                label-position="right"
+                label-width="80px"
+            >
+                <el-form-item
+                    prop="userId"
+                    label="邀请用户id"
+                >
+                    <el-input v-model="inviteForm.userId"></el-input>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button @click="showInviteInfo = false">取消</el-button>
+                <el-button type="primary" @click="inviteSolo">保存</el-button>
+            </div>
+        </el-dialog>
+        <el-dialog :visible.sync="showMemberInfo" width="600px">
+            <el-table :data="memberInfos" height="60vh">
+                <el-table-column prop="nickName" label="昵称"></el-table-column>
+                <el-table-column prop="accid" label="用户id"></el-table-column>
+                <!--                <el-table-column prop="icon" label="图标"-->
+                <!--                >-->
+                <!--                    <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"
+                    width="300">
+                    <template slot-scope="{row}">
+                        <el-button @click="kick(row)" type="danger" size="mini" plain>踢出</el-button>
+                        <el-button @click="muteMember(row)" v-if="row.banned === false" type="danger" size="mini" plain>
+                            群禁言
+                        </el-button>
+                        <el-button @click="cancelMuteMember(row)" v-if="row.banned === true" type="danger" size="mini"
+                                   plain>
+                            解除禁言
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <div slot="footer">
+                <el-button @click="showMemberInfo = false">关闭</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import {mapState} from "vuex";
+import pageableTable from "@/mixins/pageableTable";
+
+export default {
+    name: 'TeamList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: "",
+            url: "/team/all",
+            downloading: false,
+            showInviteInfo: false,
+            selectedTid: null,
+            showMemberInfo: false,
+            inviteForm: {
+                userId: '',
+                tid: ''
+            },
+            memberInfos: []
+        }
+    },
+    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: "/teamEdit",
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        invite(row) {
+            this.inviteForm.tid = row.tid;
+            this.showInviteInfo = true;
+        },
+        inviteSolo() {
+            this.$http.post(`/team/invite`, this.inviteForm)
+                .then(() => {
+                    this.$message.success('要求请成功');
+                    this.showInviteInfo = false;
+                    this.getData();
+                }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        showMemberInfoDialog(row) {
+            this.selectedTid = row.tid;
+            let data = {
+                tid: row.tid
+            }
+            this.$http.post(`/team/memberInfos`, data)
+                .then((res) => {
+                    this.memberInfos = res;
+                    this.showMemberInfo = true;
+                }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        kick(row) {
+            let data = {
+                tid: this.selectedTid,
+                userId: row.accid
+            }
+            this.$alert('踢出后用户无法再进入本群,确定要踢出群聊?', '警告', {type: 'error'}).then(() => {
+                return this.$http.post(`/team/kick`, data)
+            }).then(() => {
+                this.$message.success('踢出成功');
+                this.$http.post(`/team/memberInfos`, {tid: this.selectedTid})
+                    .then((res) => {
+                        this.memberInfos = res;
+                    }).catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                })
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        muteMember(row) {
+            let data = {
+                tid: this.selectedTid,
+                accid: row.accid
+            }
+            this.$alert('禁言后用户无法发言,确认要禁言么?', '警告', {type: 'error'}).then(() => {
+                return this.$http.post(`/team/muteSolo`, data)
+            }).then(() => {
+                this.$message.success('禁言成功');
+                this.$http.post(`/team/memberInfos`, {tid: this.selectedTid})
+                    .then((res) => {
+                        this.memberInfos = res;
+                    }).catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                })
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        cancelMuteMember(row) {
+            let data = {
+                tid: this.selectedTid,
+                accid: row.accid
+            }
+            this.$alert('解禁后用户可正常发言,确定解禁吗?', '警告', {type: 'error'}).then(() => {
+                return this.$http.post(`/team/cancelMuteSolo`, data)
+            }).then(() => {
+                this.$message.success('禁言成功');
+                this.$http.post(`/team/memberInfos`, {tid: this.selectedTid})
+                    .then((res) => {
+                        this.memberInfos = res;
+                    }).catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                })
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        mute(row) {
+            this.$alert('禁言后用户无法发言,确认要禁言么?', '警告', {type: 'error'}).then(() => {
+                return this.$http.post(`/team/mute/${row.tid}`)
+            }).then(() => {
+                this.$message.success('禁言成功');
+                this.getData();
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        cancelMute(row) {
+            this.$alert('解除禁言所有非封禁用户可以正常发言?', '警告', {type: 'error'}).then(() => {
+                return this.$http.post(`/team/cancelMute/${row.tid}`)
+            }).then(() => {
+                this.$message.success('解禁成功');
+                this.getData();
+            }).catch(e => {
+                if (e !== 'cancel') {
+                    this.$message.error(e.error);
+                }
+            })
+        },
+        editRow(row) {
+            this.$router.push({
+                path: "/teamEdit",
+                query: {
+                    id: row.tid
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get("/team/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(`/team/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>

+ 24 - 4
src/main/vue/src/views/company/CompanyTheme.vue

@@ -21,12 +21,27 @@
                             <el-form-item prop="logo" label="LOGO">
                                 <single-upload v-model="formData.logo"></single-upload>
                             </el-form-item>
+
+                            <el-form-item prop="description" label="描述">
+                                <el-input type="textarea" v-model="formData.description"></el-input>
+                            </el-form-item>
+
                             <el-form-item label="选择主题">
-                                <el-radio-group v-model="formData.theme">
+                                <!-- <el-radio-group v-model="formData.theme">
                                     <el-radio :label="item.value" v-for="(item, index) in themeOptions" :key="index">
                                         {{ item.label }}
                                     </el-radio>
-                                </el-radio-group>
+                                </el-radio-group> -->
+
+                                <el-select v-model="formData.theme" placeholder="请选择">
+                                    <el-option
+                                        v-for="item in themeOptions"
+                                        :key="item.value"
+                                        :label="item.label"
+                                        :value="item.value"
+                                    >
+                                    </el-option>
+                                </el-select>
                             </el-form-item>
 
                             <el-form-item class="form-submit">
@@ -120,6 +135,7 @@ export default {
                 this.formData.logo = res.logo;
                 this.formData.bgImg = res.bgImg;
                 this.formData.bgColor = res.bgColor;
+                this.formData.description = res.description;
             }
         });
     },
@@ -128,6 +144,7 @@ export default {
             saving: false,
             formData: {
                 theme: 'theme1',
+                description: '',
                 logo: '',
                 bgImg: '',
                 bgColor: ''
@@ -138,7 +155,9 @@ export default {
                 { label: '主题2', value: 'theme2' },
                 { label: '主题3', value: 'theme3' },
                 { label: '主题4', value: 'theme4' },
-                { label: '主题5', value: 'theme5' }
+                { label: '主题5', value: 'theme5' },
+                { label: '主题6', value: 'theme6' },
+                { label: '主题7', value: 'theme7' }
             ],
             settings: [],
             collections: [],
@@ -163,7 +182,8 @@ export default {
                         theme: this.formData.theme,
                         logo: this.formData.logo,
                         bgImg: this.formData.bgImg,
-                        bgColor: this.formData.bgColor
+                        bgColor: this.formData.bgColor,
+                        description: this.formData.description
                     },
                     { body: 'json' }
                 )

+ 16 - 0
src/main/vue/vue.config.js

@@ -14,5 +14,21 @@ module.exports = {
         if (process.env.NODE_ENV === 'development') {
             config.output.filename('[name].[hash].js').end();
         }
+
+        const dir = path.resolve(__dirname, './src/assets/icons');
+
+        config.module
+            .rule('svg-sprite')
+            .test(/\.svg$/)
+            .include.add(dir)
+            .end()
+            .use('svg-sprite-loader')
+            .loader('svg-sprite-loader')
+            .options({ extract: false })
+            .end();
+
+        config.plugin('svg-sprite').use(require('svg-sprite-loader/plugin'), [{ plainSprite: true }]);
+
+        config.module.rule('svg').exclude.add(dir);
     }
 };

+ 237 - 15
src/main/vue/yarn.lock

@@ -1955,6 +1955,11 @@ ansi-regex@^5.0.1:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
   integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
 
+ansi-styles@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+  integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
+
 ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -2304,7 +2309,7 @@ bindings@^1.5.0:
   dependencies:
     file-uri-to-path "1.0.0"
 
-bluebird@^3.1.1, bluebird@^3.5.5:
+bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.5:
   version "3.7.2"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@@ -2362,7 +2367,7 @@ brace-expansion@^1.1.7:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
-braces@^2.3.1, braces@^2.3.2:
+braces@^2.2.2, braces@^2.3.1, braces@^2.3.2:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
   integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
@@ -2648,6 +2653,17 @@ cfb@^1.1.4:
     adler-32 "~1.3.0"
     crc-32 "~1.2.0"
 
+chalk@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+  integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
+  dependencies:
+    ansi-styles "^2.2.1"
+    escape-string-regexp "^1.0.2"
+    has-ansi "^2.0.0"
+    strip-ansi "^3.0.0"
+    supports-color "^2.0.0"
+
 chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -3426,6 +3442,11 @@ deep-equal@^1.0.1:
     object-keys "^1.1.1"
     regexp.prototype.flags "^1.2.0"
 
+deepmerge@1.3.2:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.3.2.tgz#1663691629d4dbfe364fa12a2a4f0aa86aa3a050"
+  integrity sha512-qjMjTrk+RKv/sp4RPDpV5CnKhxjFI9p+GkLBOls5A8EEElldYWCWA9zceAkmfd0xIo2aU1nxiaLFoiya2sb6Cg==
+
 deepmerge@^1.2.0, deepmerge@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
@@ -3611,7 +3632,7 @@ domain-browser@^1.1.1:
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
   integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
 
-domelementtype@1:
+domelementtype@1, domelementtype@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
   integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
@@ -3621,6 +3642,13 @@ domelementtype@^2.0.1, domelementtype@^2.2.0:
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
   integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
 
+domhandler@^2.3.0:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
+  integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
+  dependencies:
+    domelementtype "1"
+
 domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
@@ -3628,7 +3656,12 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
   dependencies:
     domelementtype "^2.2.0"
 
-domutils@^1.7.0:
+domready@1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/domready/-/domready-1.0.8.tgz#91f252e597b65af77e745ae24dd0185d5e26d58c"
+  integrity sha512-uIzsOJUNk+AdGE9a6VDeessoMCzF8RrZvJCX/W8QtyfgdR6Uofn/MvRonih3OtCO79b2VDzDOymuiABrQ4z3XA==
+
+domutils@^1.5.1, domutils@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
   integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
@@ -3788,6 +3821,11 @@ enhanced-resolve@^4.5.0:
     memory-fs "^0.5.0"
     tapable "^1.0.0"
 
+entities@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
+  integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
+
 entities@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@@ -3867,7 +3905,7 @@ escape-html@~1.0.3:
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
-escape-string-regexp@^1.0.5:
+escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
@@ -4051,7 +4089,7 @@ extend@~3.0.2:
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
 
-extglob@^2.0.4:
+extglob@^2.0.2, extglob@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
   integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==
@@ -4501,11 +4539,23 @@ har-validator@~5.1.3:
     ajv "^6.12.3"
     har-schema "^2.0.0"
 
+has-ansi@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+  integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
+  dependencies:
+    ansi-regex "^2.0.0"
+
 has-bigints@^1.0.1, has-bigints@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
   integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
 
+has-flag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+  integrity sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==
+
 has-flag@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -4600,7 +4650,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
-he@1.2.x, he@^1.1.0:
+he@1.2.x, he@^1.1.0, he@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -4695,6 +4745,18 @@ html-webpack-plugin@^3.2.0:
     toposort "^1.0.0"
     util.promisify "1.0.0"
 
+htmlparser2@^3.8.3:
+  version "3.10.1"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
+  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
+  dependencies:
+    domelementtype "^1.3.1"
+    domhandler "^2.3.0"
+    domutils "^1.5.1"
+    entities "^1.1.1"
+    inherits "^2.0.1"
+    readable-stream "^3.1.1"
+
 htmlparser2@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
@@ -4819,7 +4881,7 @@ ignore@^4.0.3:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
 
-image-size@~0.5.0:
+image-size@^0.5.1, image-size@~0.5.0:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
   integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
@@ -5162,7 +5224,7 @@ is-path-inside@^2.1.0:
   dependencies:
     path-is-inside "^1.0.2"
 
-is-plain-obj@^1.0.0:
+is-plain-obj@^1.0.0, is-plain-obj@^1.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
@@ -5267,7 +5329,7 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
 
-isobject@^2.0.0:
+isobject@^2.0.0, isobject@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
   integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==
@@ -5296,6 +5358,11 @@ jdenticon@^3.1.0:
   dependencies:
     canvas-renderer "~2.2.0"
 
+js-base64@^2.1.9:
+  version "2.6.4"
+  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
+  integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
+
 js-message@1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
@@ -5407,7 +5474,7 @@ kind-of@^4.0.0:
   dependencies:
     is-buffer "^1.1.5"
 
-kind-of@^5.0.0:
+kind-of@^5.0.0, kind-of@^5.0.2:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
   integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
@@ -5671,6 +5738,13 @@ merge-descriptors@1.0.1:
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
   integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
 
+merge-options@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-1.0.1.tgz#2a64b24457becd4e4dc608283247e94ce589aa32"
+  integrity sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==
+  dependencies:
+    is-plain-obj "^1.1"
+
 merge-source-map@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
@@ -5693,6 +5767,25 @@ methods@~1.1.2:
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
 
+micromatch@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.0.tgz#5102d4eaf20b6997d6008e3acfe1c44a3fa815e2"
+  integrity sha512-3StSelAE+hnRvMs8IdVW7Uhk8CVed5tp+kLLGlBP6WiRAXS21GPGu/Nat4WNPXj2Eoc24B02SaeoyozPMfj0/g==
+  dependencies:
+    arr-diff "^4.0.0"
+    array-unique "^0.3.2"
+    braces "^2.2.2"
+    define-property "^1.0.0"
+    extend-shallow "^2.0.1"
+    extglob "^2.0.2"
+    fragment-cache "^0.2.1"
+    kind-of "^5.0.2"
+    nanomatch "^1.2.1"
+    object.pick "^1.3.0"
+    regex-not "^1.0.0"
+    snapdragon "^0.8.1"
+    to-regex "^3.0.1"
+
 micromatch@^3.1.10, micromatch@^3.1.4:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -5815,6 +5908,11 @@ mississippi@^3.0.0:
     stream-each "^1.1.0"
     through2 "^2.0.0"
 
+mitt@1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.1.2.tgz#380e61480d6a615b660f07abb60d51e0a4e4bed6"
+  integrity sha512-3btxP0O9iGADGWAkteQ8mzDtEspZqu4I32y4GZYCV5BrwtzdcRpF4dQgNdJadCrbBx7Lu6Sq9AVrerMHR0Hkmw==
+
 mixin-deep@^1.2.0:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -5889,7 +5987,7 @@ nan@^2.12.1:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
   integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
 
-nanomatch@^1.2.9:
+nanomatch@^1.2.1, nanomatch@^1.2.9:
   version "1.2.13"
   resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
   integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
@@ -6759,6 +6857,11 @@ postcss-ordered-values@^4.1.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-prefix-selector@^1.6.0:
+  version "1.16.0"
+  resolved "https://registry.yarnpkg.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.0.tgz#ad5b56f9a73a2c090ca7161049632c9d89bcb404"
+  integrity sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q==
+
 postcss-reduce-initial@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df"
@@ -6824,6 +6927,16 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
 
+postcss@^5.2.17:
+  version "5.2.18"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
+  integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==
+  dependencies:
+    chalk "^1.1.3"
+    js-base64 "^2.1.9"
+    source-map "^0.5.6"
+    supports-color "^3.2.3"
+
 postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6:
   version "7.0.39"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309"
@@ -6832,6 +6945,44 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.3
     picocolors "^0.2.1"
     source-map "^0.6.1"
 
+posthtml-parser@^0.2.0, posthtml-parser@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.2.1.tgz#35d530de386740c2ba24ff2eb2faf39ccdf271dd"
+  integrity sha512-nPC53YMqJnc/+1x4fRYFfm81KV2V+G9NZY+hTohpYg64Ay7NemWWcV4UWuy/SgMupqQ3kJ88M/iRfZmSnxT+pw==
+  dependencies:
+    htmlparser2 "^3.8.3"
+    isobject "^2.1.0"
+
+posthtml-rename-id@^1.0:
+  version "1.0.12"
+  resolved "https://registry.yarnpkg.com/posthtml-rename-id/-/posthtml-rename-id-1.0.12.tgz#cf7f6eb37146bf1afac31e68f18c6cc19ae61433"
+  integrity sha512-UKXf9OF/no8WZo9edRzvuMenb6AD5hDLzIepJW+a4oJT+T/Lx7vfMYWT4aWlGNQh0WMhnUx1ipN9OkZ9q+ddEw==
+  dependencies:
+    escape-string-regexp "1.0.5"
+
+posthtml-render@^1.0.5, posthtml-render@^1.0.6:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-1.4.0.tgz#40114070c45881cacb93347dae3eff53afbcff13"
+  integrity sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw==
+
+posthtml-svg-mode@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/posthtml-svg-mode/-/posthtml-svg-mode-1.0.3.tgz#abd554face81223cab0cb367e18e4efd2a4e74b0"
+  integrity sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==
+  dependencies:
+    merge-options "1.0.1"
+    posthtml "^0.9.2"
+    posthtml-parser "^0.2.1"
+    posthtml-render "^1.0.6"
+
+posthtml@^0.9.2:
+  version "0.9.2"
+  resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.9.2.tgz#f4c06db9f67b61fd17c4e256e7e3d9515bf726fd"
+  integrity sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==
+  dependencies:
+    posthtml-parser "^0.2.0"
+    posthtml-render "^1.0.5"
+
 prepend-http@^1.0.0:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
@@ -6984,7 +7135,7 @@ qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
   integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
 
-query-string@^4.1.0:
+query-string@^4.1.0, query-string@^4.3.2:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
   integrity sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==
@@ -7083,7 +7234,7 @@ read-pkg@^5.1.1:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@^3.0.6, readable-stream@^3.6.0:
+readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -7888,7 +8039,7 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-strip-ansi@^3.0.1:
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
   integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
@@ -7943,6 +8094,18 @@ stylehacks@^4.0.0:
     postcss "^7.0.0"
     postcss-selector-parser "^3.0.0"
 
+supports-color@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+  integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
+
+supports-color@^3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+  integrity sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==
+  dependencies:
+    has-flag "^1.0.0"
+
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -7969,6 +8132,48 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
+svg-baker-runtime@^1.4.7:
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/svg-baker-runtime/-/svg-baker-runtime-1.4.7.tgz#f4720637f5b6202eef6378d81f1fead0815f8a4e"
+  integrity sha512-Zorfwwj5+lWjk/oxwSMsRdS2sPQQdTmmsvaSpzU+i9ZWi3zugHLt6VckWfnswphQP0LmOel3nggpF5nETbt6xw==
+  dependencies:
+    deepmerge "1.3.2"
+    mitt "1.1.2"
+    svg-baker "^1.7.0"
+
+svg-baker@^1.5.0, svg-baker@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/svg-baker/-/svg-baker-1.7.0.tgz#8367f78d875550c52fe4756f7303d5c5d7c2e9a7"
+  integrity sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==
+  dependencies:
+    bluebird "^3.5.0"
+    clone "^2.1.1"
+    he "^1.1.1"
+    image-size "^0.5.1"
+    loader-utils "^1.1.0"
+    merge-options "1.0.1"
+    micromatch "3.1.0"
+    postcss "^5.2.17"
+    postcss-prefix-selector "^1.6.0"
+    posthtml-rename-id "^1.0"
+    posthtml-svg-mode "^1.0.3"
+    query-string "^4.3.2"
+    traverse "^0.6.6"
+
+svg-sprite-loader@^6.0.11:
+  version "6.0.11"
+  resolved "https://registry.yarnpkg.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz#a4d60cee3d74232a2c17d31c73a2008295f61220"
+  integrity sha512-TedsTf8wsHH6HgdwKjUveDZRC6q5gPloYV8A8/zZaRWP929J7x6TzQ6MvZFl+YYDJuJ0Akyuu/vNVJ+fbPuYXg==
+  dependencies:
+    bluebird "^3.5.0"
+    deepmerge "1.3.2"
+    domready "1.0.8"
+    escape-string-regexp "1.0.5"
+    loader-utils "^1.1.0"
+    svg-baker "^1.5.0"
+    svg-baker-runtime "^1.4.7"
+    url-slug "2.0.0"
+
 svg-tags@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@@ -8153,6 +8358,11 @@ tough-cookie@~2.5.0:
     psl "^1.1.28"
     punycode "^2.1.1"
 
+traverse@^0.6.6:
+  version "0.6.7"
+  resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.7.tgz#46961cd2d57dd8706c36664acde06a248f1173fe"
+  integrity sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==
+
 tryer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
@@ -8249,6 +8459,11 @@ unicode-property-aliases-ecmascript@^2.0.0:
   resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8"
   integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==
 
+unidecode@0.1.8:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/unidecode/-/unidecode-0.1.8.tgz#efbb301538bc45246a9ac8c559d72f015305053e"
+  integrity sha512-SdoZNxCWpN2tXTCrGkPF/0rL2HEq+i2gwRG1ReBvx8/0yTzC3enHfugOf8A9JBShVwwrRIkLX0YcDUGbzjbVCA==
+
 union-value@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -8353,6 +8568,13 @@ url-parse@^1.5.10:
     querystringify "^2.1.1"
     requires-port "^1.0.0"
 
+url-slug@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/url-slug/-/url-slug-2.0.0.tgz#a789d5aed4995c0d95af33377ad1d5c68d4d7027"
+  integrity sha512-aiNmSsVgrjCiJ2+KWPferjT46YFKoE8i0YX04BlMVDue022Xwhg/zYlnZ6V9/mP3p8Wj7LEp0myiTkC/p6sxew==
+  dependencies:
+    unidecode "0.1.8"
+
 url@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"

+ 47 - 2
src/test/java/com/izouma/nineth/service/UserServiceTest.java

@@ -1,10 +1,13 @@
 package com.izouma.nineth.service;
 
+import com.alibaba.fastjson.JSONObject;
 import com.alipay.api.AlipayApiException;
 import com.huifu.adapay.core.exception.BaseAdaPayException;
 import com.izouma.nineth.ApplicationTests;
 import com.izouma.nineth.config.Constants;
 import com.izouma.nineth.domain.IdentityAuth;
+import com.izouma.nineth.domain.netease.NeteaseMessage;
+import com.izouma.nineth.domain.netease.Team;
 import com.izouma.nineth.domain.User;
 import com.izouma.nineth.dto.BankValidate;
 import com.izouma.nineth.dto.PageQuery;
@@ -12,10 +15,13 @@ import com.izouma.nineth.dto.UserBankCard;
 import com.izouma.nineth.dto.UserRegister;
 import com.izouma.nineth.enums.AuthStatus;
 import com.izouma.nineth.enums.InviteType;
+import com.izouma.nineth.enums.netease.TeamType;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.IdentityAuthRepo;
 import com.izouma.nineth.repo.UserBankCardRepo;
 import com.izouma.nineth.repo.UserRepo;
+import com.izouma.nineth.service.netease.NeteaseMessageService;
+import com.izouma.nineth.service.netease.TeamService;
 import com.izouma.nineth.service.storage.StorageService;
 import com.izouma.nineth.utils.BankUtils;
 import lombok.extern.slf4j.Slf4j;
@@ -24,6 +30,8 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -49,6 +57,10 @@ public class UserServiceTest extends ApplicationTests {
     private AdapayMerchantService adapayMerchantService;
     @Autowired
     private IdentityAuthRepo      identityAuthRepo;
+    @Autowired
+    private TeamService           teamService;
+    @Autowired
+    private NeteaseMessageService neteaseMessageService;
 
     @Test
     public void findByUsernameAndDelFalse1() {
@@ -139,7 +151,8 @@ public class UserServiceTest extends ApplicationTests {
         String bankNo = "6222024301070380165";
         String phone = "15077886171";
         User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
-        IdentityAuth identityAuth = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
+        IdentityAuth identityAuth = identityAuthRepo
+                .findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS)
                 .orElseThrow(new BusinessException("用户未认证"));
         if (identityAuth.isOrg()) {
             //throw new BusinessException("企业认证用户请绑定对公账户");
@@ -152,7 +165,9 @@ public class UserServiceTest extends ApplicationTests {
             throw new BusinessException("暂不支持此卡");
         }
 
-        adapayMerchantService.createMemberForAll(userId.toString(), user.getPhone(), identityAuth.getRealName(), identityAuth.getIdNo());
+        adapayMerchantService
+                .createMemberForAll(userId.toString(), user.getPhone(), identityAuth.getRealName(), identityAuth
+                        .getIdNo());
         user.setMemberId(user.getId().toString());
         userRepo.save(user);
 
@@ -217,4 +232,34 @@ public class UserServiceTest extends ApplicationTests {
     public void checkauth() throws AlipayApiException {
         userService.checkFaceAuth("7160e6a875cb67648bc7a3cce6e5397e");
     }
+
+    @Test
+    public void teamTest() {
+        Team team = new Team();
+        team.setName("测试大厅2");
+        team.setOwnerid("9859");
+        List<String> members = new ArrayList<>();
+        members.add("7956591");
+        team.setMembers(members);
+        team.setCustom(TeamType.PUBLIC);
+        teamService.create(team);
+    }
+
+    @Test
+    public void sendMessage() {
+        NeteaseMessage neteaseMessage = new NeteaseMessage();
+        neteaseMessage.setFromId("9859");
+        neteaseMessage.setToId("7807550605");
+        neteaseMessage.setOpe(1);
+        neteaseMessage.setType(0);
+        Map<String, Object> body = new HashMap<>();
+        body.put("msg", "这是一条测试消息");
+        neteaseMessage.setBody(JSONObject.toJSONString(body));
+        neteaseMessageService.sendMessage(neteaseMessage);
+    }
+
+    @Test
+    public void tTe() {
+        teamService.getUnreadCount("9850","7807550605");
+    }
 }