xiongzhu 3 lat temu
rodzic
commit
7f2bc22c93
40 zmienionych plików z 2710 dodań i 108 usunięć
  1. 2 0
      src/main/java/com/izouma/nineth/domain/AirDrop.java
  2. 2 0
      src/main/java/com/izouma/nineth/domain/Company.java
  3. 2 0
      src/main/java/com/izouma/nineth/dto/PageQuery.java
  4. 2 0
      src/main/java/com/izouma/nineth/dto/UserRegister.java
  5. 2 0
      src/main/java/com/izouma/nineth/service/AssetMintService.java
  6. 5 4
      src/main/java/com/izouma/nineth/service/CompanyService.java
  7. 2 2
      src/main/java/com/izouma/nineth/web/CollectionController.java
  8. 15 4
      src/main/java/com/izouma/nineth/web/CompanyController.java
  9. 1 1
      src/main/java/com/izouma/nineth/web/UserBalanceController.java
  10. 5 5
      src/main/java/com/izouma/nineth/web/UserController.java
  11. 1 1
      src/main/vue/package.json
  12. 1 0
      src/main/vue/src/components/CreatedAtPicker.vue
  13. 9 0
      src/main/vue/src/main.js
  14. 19 0
      src/main/vue/src/plugins/http.js
  15. 41 17
      src/main/vue/src/router.js
  16. 9 1
      src/main/vue/src/store.js
  17. 179 4
      src/main/vue/src/views/Admin.vue
  18. 1 1
      src/main/vue/src/views/AirDropList.vue
  19. 1 1
      src/main/vue/src/views/AuctionOrderList.vue
  20. 1 1
      src/main/vue/src/views/AuctionOrderUsedList.vue
  21. 12 11
      src/main/vue/src/views/BlindBoxEdit.vue
  22. 1 1
      src/main/vue/src/views/BlindBoxList.vue
  23. 10 8
      src/main/vue/src/views/CollectionEdit.vue
  24. 2 3
      src/main/vue/src/views/CollectionList.vue
  25. 1 1
      src/main/vue/src/views/CollectionRoomList.vue
  26. 1 1
      src/main/vue/src/views/CommissionRecordList.vue
  27. 7 7
      src/main/vue/src/views/CompanyAdmin.vue
  28. 905 0
      src/main/vue/src/views/CompanyBlindBoxEdit.vue
  29. 235 0
      src/main/vue/src/views/CompanyBlindBoxList.vue
  30. 856 0
      src/main/vue/src/views/CompanyCollectionEdit.vue
  31. 272 0
      src/main/vue/src/views/CompanyCollectionList1.vue
  32. 83 0
      src/main/vue/src/views/CompanyGas.vue
  33. 1 1
      src/main/vue/src/views/MintOrderAuditList.vue
  34. 1 1
      src/main/vue/src/views/MintOrderList.vue
  35. 1 1
      src/main/vue/src/views/OrderList.vue
  36. 1 1
      src/main/vue/src/views/OrderUsedList.vue
  37. 1 1
      src/main/vue/src/views/UserBalance.vue
  38. 1 1
      src/main/vue/src/views/UserList.vue
  39. 15 19
      src/main/vue/src/views/company/CompanyTheme.vue
  40. 4 9
      src/main/vue/yarn.lock

+ 2 - 0
src/main/java/com/izouma/nineth/domain/AirDrop.java

@@ -71,4 +71,6 @@ public class AirDrop extends BaseEntity {
     private boolean auto = false;
 
     private Long oasisId;
+
+    private Long companyId;
 }

+ 2 - 0
src/main/java/com/izouma/nineth/domain/Company.java

@@ -21,4 +21,6 @@ public class Company extends BaseEntity {
     private String logo;
 
     private boolean disabled;
+
+    private String theme;
 }

+ 2 - 0
src/main/java/com/izouma/nineth/dto/PageQuery.java

@@ -1,5 +1,6 @@
 package com.izouma.nineth.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import lombok.Data;
 import lombok.ToString;
 
@@ -8,6 +9,7 @@ import java.util.Map;
 
 @Data
 @ToString
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class PageQuery {
     private int                 page  = 0;
     private int                 size  = 20;

+ 2 - 0
src/main/java/com/izouma/nineth/dto/UserRegister.java

@@ -55,4 +55,6 @@ public class UserRegister {
     private boolean minter;
 
     private boolean company;
+
+    private Long companyId;
 }

+ 2 - 0
src/main/java/com/izouma/nineth/service/AssetMintService.java

@@ -41,6 +41,7 @@ public class AssetMintService {
     @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 1000))
     public void mint(Long assetId) {
         Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("asset不存在"));
+        if (StringUtils.isNotBlank(asset.getTxHash())) return;
         if ((asset.getChainFlag() & 0b01) != 0b01) return;
         User user = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
         if (StringUtils.isEmpty(user.getNftAccount())) {
@@ -73,6 +74,7 @@ public class AssetMintService {
     @Retryable(maxAttempts = 10, backoff = @Backoff(delay = 1000))
     public void hcMint(Long assetId) {
         Asset asset = assetRepo.findById(assetId).orElseThrow(new BusinessException("asset不存在"));
+        if (StringUtils.isNotBlank(asset.getHcTxHash())) return;
         if ((asset.getChainFlag() & 0b10) != 0b10) return;
         User user = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("用户不存在"));
         if (StringUtils.isEmpty(user.getHcChainAddress())) {

+ 5 - 4
src/main/java/com/izouma/nineth/service/CompanyService.java

@@ -16,16 +16,17 @@ import org.springframework.data.domain.Page;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
 import java.util.Collections;
 
 @Service
 @AllArgsConstructor
 public class CompanyService {
 
-    private CompanyRepo     companyRepo;
-    private UserRepo        userRepo;
-    private PasswordEncoder passwordEncoder;
-    private CacheService    cacheService;
+    private CompanyRepo        companyRepo;
+    private UserRepo           userRepo;
+    private PasswordEncoder    passwordEncoder;
+    private CacheService       cacheService;
 
     public Page<Company> all(PageQuery pageQuery) {
         return companyRepo.findAll(JpaUtils.toSpecification(pageQuery, Company.class), JpaUtils.toPageRequest(pageQuery));

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

@@ -43,7 +43,7 @@ public class CollectionController extends BaseController {
     private CacheService            cacheService;
     private CollectionPrivilegeRepo collectionPrivilegeRepo;
 
-    @PreAuthorize("hasRole('ADMIN')")
+    @PreAuthorize("hasAnyRole('ADMIN','SAAS')")
     @PostMapping("/save")
     public Collection save(@RequestBody CollectionInfoDTO record) {
         Collection collection = collectionService.update(record);
@@ -61,7 +61,7 @@ public class CollectionController extends BaseController {
         return collection;
     }
 
-    @PreAuthorize("hasRole('ADMIN')")
+    @PreAuthorize("hasAnyRole('ADMIN','SAAS')")
     @PostMapping("/create")
     public Collection create(@RequestBody CollectionInfoDTO record) {
         Collection collection = new Collection();

+ 15 - 4
src/main/java/com/izouma/nineth/web/CompanyController.java

@@ -1,11 +1,14 @@
 package com.izouma.nineth.web;
 
 import com.izouma.nineth.domain.Company;
+import com.izouma.nineth.domain.UserBalance;
+import com.izouma.nineth.repo.UserBalanceRepo;
 import com.izouma.nineth.service.CompanyService;
 import com.izouma.nineth.dto.PageQuery;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.CompanyRepo;
 import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.SecurityUtils;
 import com.izouma.nineth.utils.excel.ExcelUtils;
 import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
@@ -14,16 +17,19 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.List;
+import java.util.Optional;
 
 @RestController
 @RequestMapping("/company")
 @AllArgsConstructor
 public class CompanyController extends BaseController {
-    private CompanyService companyService;
-    private CompanyRepo    companyRepo;
+    private CompanyService  companyService;
+    private CompanyRepo     companyRepo;
+    private UserBalanceRepo userBalanceRepo;
 
-    @PreAuthorize("hasRole('ADMIN')")
+    @PreAuthorize("hasAnyRole('ADMIN','SAAS')")
     @PostMapping("/save")
     public Company save(@RequestBody Company record) {
         if (record.getId() != null) {
@@ -34,7 +40,6 @@ public class CompanyController extends BaseController {
         return companyRepo.save(record);
     }
 
-
     @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/all")
     public Page<Company> all(@RequestBody PageQuery pageQuery) {
@@ -69,5 +74,11 @@ public class CompanyController extends BaseController {
     public void delAdmin(@RequestParam Long companyId, @RequestParam Long userId) {
         companyService.delAdmin(companyId, userId);
     }
+
+    @GetMapping("/balance")
+    public UserBalance balance() {
+        Long companyId = Optional.ofNullable(SecurityUtils.getAuthenticatedUser().getCompanyId()).orElseThrow(new BusinessException("非企业账号"));
+        return userBalanceRepo.findByUserId(companyId).orElse(new UserBalance(companyId));
+    }
 }
 

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

@@ -104,7 +104,7 @@ public class UserBalanceController extends BaseController {
 
     @GetMapping("/my")
     public UserBalance my() {
-        return userBalanceRepo.findById(SecurityUtils.getAuthenticatedUser().getId()).orElse(new UserBalance());
+        return userBalanceRepo.findById(SecurityUtils.getAuthenticatedUser().getId()).orElse(new UserBalance(SecurityUtils.getAuthenticatedUser().getId()));
     }
 
     @GetMapping("/my/record")

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

@@ -65,7 +65,7 @@ public class UserController extends BaseController {
         return userService.create(user);
     }
 
-    @PreAuthorize("hasAnyRole('ADMIN', 'SHOWROOM')")
+    @PreAuthorize("hasAnyRole('ADMIN', 'SHOWROOM','SAAS')")
     @PostMapping("/create")
     public User create(@RequestBody UserRegister userRegister) {
         return userService.create(userRegister);
@@ -409,17 +409,17 @@ public class UserController extends BaseController {
         //银行卡
         EncryptConverter converter = new EncryptConverter();
         List<UserBankCard> byUserId = userBankCardRepo.findByUserId(user.getId());
-        if (CollectionUtils.isNotEmpty(byUserId)){
+        if (CollectionUtils.isNotEmpty(byUserId)) {
             dto.setIsUserBankCard(true);
             String bankNo = converter.convertToEntityAttribute(byUserId.get(0).getBankNo());
             dto.setBankNo(bankNo);
-        }else {
+        } else {
             dto.setIsUserBankCard(false);
         }
         //实名
-        if (ObjectUtils.isNotEmpty(user.getAuthId())){
+        if (ObjectUtils.isNotEmpty(user.getAuthId())) {
             IdentityAuth identityAuth = identityAuthRepo.findByIdAndDelFalse(user.getAuthId()).orElse(null);
-            if (ObjectUtils.isNotEmpty(identityAuth)){
+            if (ObjectUtils.isNotEmpty(identityAuth)) {
                 String idNo = converter.convertToEntityAttribute(identityAuth.getIdNo());
                 dto.setRealName(identityAuth.getRealName());
                 dto.setIdNo(idNo);

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

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

+ 1 - 0
src/main/vue/src/components/CreatedAtPicker.vue

@@ -71,6 +71,7 @@ export default {
     methods: {
         changeSelect(val) {
             this.$emit('input', val);
+            this.$emit('change');
         }
     },
     watch: {

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

@@ -21,6 +21,7 @@ import ObjectUpload from '@/components/ObjectUpload';
 import CollectionSearch from '@/components/CollectionSearch';
 import CollectionCoupon from '@/components/CollectionCoupon';
 import CreatedAtPicker from '@/components/CreatedAtPicker';
+import { mapGetters } from 'vuex';
 import Vant from 'vant';
 import 'vant/lib/index.css';
 
@@ -70,6 +71,14 @@ Vue.component('collection-search', CollectionSearch);
 Vue.component('collection-coupon', CollectionCoupon);
 Vue.component('created-at-picker', CreatedAtPicker);
 Vue.mixin(Formatters);
+Vue.mixin({
+    computed: {
+        ...mapGetters(['companyId']),
+        isCompany() {
+            return this.companyId > 1;
+        }
+    }
+});
 Vue.prototype.$theme = theme;
 console.log(theme);
 new Vue({

+ 19 - 0
src/main/vue/src/plugins/http.js

@@ -1,6 +1,7 @@
 import axios from 'axios';
 import router from '../router';
 import qs from 'qs';
+import store from '../store';
 /* eslint-disable */
 let baseUrl = process.env.VUE_APP_BASE_URL;
 const axiosInstance = axios.create({
@@ -62,6 +63,12 @@ export default {
         _Vue.prototype.$http = {
             get(url, params) {
                 params = params || {};
+                if (url.endsWith('/all') || url.endsWith('/backAll')) {
+                    params.query = params.query || {};
+                    params.query.companyId = params.query.companyId || store.getters.companyId;
+                } else {
+                    params.companyId = params.companyId || store.state.companyId;
+                }
                 return new Promise((resolve, reject) => {
                     axiosInstance
                         .get(
@@ -82,6 +89,18 @@ export default {
             post(url, body, options) {
                 options = options || {};
                 body = body || {};
+                if (body instanceof FormData) {
+                    if (!body.get('companyId')) {
+                        body.append('companyId', store.getters.companyId);
+                    }
+                } else {
+                    if (url.endsWith('/all') || url.endsWith('/backAll')) {
+                        body.query = body.query || {};
+                        body.query.companyId = body.query.companyId || store.getters.companyId;
+                    } else {
+                        body.companyId = body.companyId || store.getters.companyId;
+                    }
+                }
                 if (!(body instanceof FormData)) {
                     if (options.body !== 'json') {
                         body = qs.stringify(body);

+ 41 - 17
src/main/vue/src/router.js

@@ -598,22 +598,6 @@ const router = new Router({
                         title: '企业藏品'
                     }
                 },
-                {
-                    path: '/companyEdit',
-                    name: 'CompanyEdit',
-                    component: () => import(/* webpackChunkName: "companyEdit" */ '@/views/CompanyEdit.vue'),
-                    meta: {
-                        title: '企业编辑'
-                    }
-                },
-                {
-                    path: '/companyList',
-                    name: 'CompanyList',
-                    component: () => import(/* webpackChunkName: "companyList" */ '@/views/CompanyList.vue'),
-                    meta: {
-                        title: '企业'
-                    }
-                },
                 /**企业 */
                 {
                     path: '/collectionPendingList',
@@ -1052,7 +1036,47 @@ const router = new Router({
                     meta: {
                         title: '企业管理员'
                     }
-                }
+                },
+                {
+                    path: '/companyCollectionEdit',
+                    name: 'CompanyCollectionEdit',
+                    component: () => import(/* webpackChunkName: "companyCollectionEdit" */ '@/views/CompanyCollectionEdit.vue'),
+                    meta: {
+                        title: '藏品管理编辑'
+                    }
+                },
+                {
+                    path: '/companyCollectionList1',
+                    name: 'CompanyCollectionList1',
+                    component: () => import(/* webpackChunkName: "companyCollectionList1" */ '@/views/CompanyCollectionList1.vue'),
+                    meta: {
+                        title: '藏品管理'
+                    }
+                },
+                {
+                    path: '/companyBlindBoxEdit',
+                    name: 'CompanyBlindBoxEdit',
+                    component: () => import(/* webpackChunkName: "companyBlindBoxEdit" */ '@/views/CompanyBlindBoxEdit.vue'),
+                    meta: {
+                        title: '盲盒编辑'
+                    }
+                },
+                {
+                    path: '/companyBlindBoxList',
+                    name: 'CompanyBlindBoxList',
+                    component: () => import(/* webpackChunkName: "companyBlindBoxList" */ '@/views/CompanyBlindBoxList.vue'),
+                    meta: {
+                        title: '盲盒'
+                    }
+                },
+                {
+                    path: '/companyGas',
+                    name: 'CompanyGas',
+                    component: () => import(/* webpackChunkName: "companyGas" */ '@/views/CompanyGas.vue'),
+                    meta: {
+                        title: '盲盒'
+                    }
+                },
                 /**INSERT_LOCATION**/
             ]
         },

+ 9 - 1
src/main/vue/src/store.js

@@ -20,5 +20,13 @@ export default new Vuex.Store({
             state.userInfo = userInfo;
         }
     },
-    actions: {}
+    actions: {},
+    getters: {
+        companyId(state) {
+            if (state.userInfo) {
+                return state.userInfo.companyId || -1;
+            }
+            return -1;
+        }
+    }
 });

+ 179 - 4
src/main/vue/src/views/Admin.vue

@@ -176,11 +176,186 @@ export default {
             findActiveMenu([], this.rawMenus);
         },
         getMenus() {
-            this.$http.get('/menu/userMenu').then(res => {
-                this.rawMenus = res;
-                this.menus = res;
+            if (this.userInfo.authorities.find(i => i.name === 'ROLE_SAAS')) {
+                let menus = [
+                    {
+                        id: '3410316',
+                        name: '藏品管理',
+                        path: '',
+                        icon: 'fas fa-gift',
+                        sort: 11,
+                        root: true,
+                        active: true,
+                        children: [
+                            {
+                                id: '3410324',
+                                name: '藏品列表',
+                                path: '/collectionList',
+                                icon: '',
+                                sort: 41,
+                                parent: '3410316',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '3410329',
+                                name: '盲盒列表',
+                                path: '/blindBoxList',
+                                icon: '',
+                                sort: 42,
+                                parent: '3410316',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '3410339',
+                                name: '空投',
+                                path: '/airDropList',
+                                icon: '',
+                                sort: 43,
+                                parent: '3410316',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '3410345',
+                                name: '兑换券',
+                                path: '/couponList',
+                                icon: '',
+                                sort: 44,
+                                parent: '3410316',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '3410399',
+                                name: '铸造者',
+                                path: '/minterList',
+                                icon: '',
+                                sort: 45,
+                                parent: '3410272',
+                                root: false,
+                                active: true
+                            }
+                        ]
+                    },
+                    {
+                        id: '17',
+                        name: '订单管理',
+                        path: '',
+                        icon: 'fas fa-bars',
+                        sort: 12,
+                        root: true,
+                        active: true,
+                        children: [
+                            {
+                                id: '1435449',
+                                name: '官方订单',
+                                path: '/orderList',
+                                icon: '',
+                                sort: 24,
+                                parent: '17',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '1435452',
+                                name: '二手订单',
+                                path: '/orderUsedList',
+                                icon: '',
+                                sort: 25,
+                                parent: '17',
+                                root: false,
+                                active: true
+                            }
+                        ]
+                    },
+                    {
+                        id: '3252226',
+                        name: '内容管理',
+                        path: '',
+                        icon: 'fas fa-clone',
+                        sort: 23,
+                        root: true,
+                        active: true,
+                        category: '',
+                        children: [
+                            {
+                                id: '3252238',
+                                name: '轮播图',
+                                path: '/bannerList',
+                                icon: '',
+                                sort: 34,
+                                parent: '3252226',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '3252239',
+                                name: '首页推荐',
+                                path: '/recommendList',
+                                icon: '',
+                                sort: 35,
+                                parent: '3252226',
+                                root: false,
+                                active: true
+                            },
+                            {
+                                id: '3252230',
+                                name: '新闻管理',
+                                path: '/newsList',
+                                icon: '',
+                                sort: 36,
+                                parent: '3252226',
+                                root: false,
+                                active: true
+                            }
+                        ]
+                    },
+                    {
+                        id: '3252226',
+                        name: '主题设置',
+                        path: '/companyTheme',
+                        icon: 'fas fa-mobile',
+                        sort: 23,
+                        root: true,
+                        active: true,
+                        category: ''
+                    },
+                    {
+                        id: '3252226',
+                        name: 'GAS管理',
+                        path: '/companyGas',
+                        icon: 'fas fa-burn',
+                        sort: 23,
+                        root: true,
+                        active: true,
+                        category: ''
+                    }
+                ];
+                function setId(parentId, menus) {
+                    menus.forEach((i, idx) => {
+                        if (parentId) {
+                            i.id = parentId * 10000 + idx + 1;
+                        } else {
+                            i.id = idx + 1;
+                        }
+                        if (i.children) {
+                            setId(i.id, i.children);
+                        }
+                    });
+                }
+                setId('', menus);
+                this.rawMenus = menus;
+                this.menus = menus;
                 this.findActiveMenu();
-            });
+            } else {
+                this.$http.get('/menu/userMenu').then(res => {
+                    this.rawMenus = res;
+                    this.menus = res;
+                    this.findActiveMenu();
+                });
+            }
         },
         toggleFullScreen() {
             this.isFullscreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;

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

@@ -15,7 +15,7 @@
             </el-button> -->
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" style="margin-right: 10px"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" style="margin-right: 10px"></created-at-picker>
             <el-select v-model="type" clearable placeholder="请选择空投类型" @change="getData">
                 <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
             </el-select>

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

@@ -35,7 +35,7 @@
                 @keyup.enter.native="getData"
             >
             </el-input>
-            <created-at-picker v-model="createdAt" @input="getData" name="下单" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="下单" class="filter-item"></created-at-picker>
         </div>
         <el-table
             :data="tableData"

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

@@ -35,7 +35,7 @@
                 @keyup.enter.native="getData"
             >
             </el-input>
-            <created-at-picker v-model="createdAt" @input="getData" name="下单" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="下单" class="filter-item"></created-at-picker>
         </div>
         <el-table
             :data="tableData"

+ 12 - 11
src/main/vue/src/views/BlindBoxEdit.vue

@@ -217,7 +217,7 @@
                         <div class="tip">持有多少天可转赠/转让。为空时,按系统设置天数</div>
                     </el-form-item>
 
-                    <el-form-item prop="minimumCharge" label="最低消费">
+                    <el-form-item prop="minimumCharge" label="最低消费" v-if="!isCompany">
                         <el-input-number type="number" :min="0" v-model="formData.minimumCharge" style="width: 180px;">
                         </el-input-number>
                     </el-form-item>
@@ -226,14 +226,14 @@
                         <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
                         <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
                     </el-form-item>
-                    <el-form-item prop="couponPayment" label="支付方式">
+                    <el-form-item prop="couponPayment" label="支付方式" v-if="!isCompany">
                         <el-radio-group v-model="formData.couponPayment">
                             <el-radio :label="true">兑换券</el-radio>
                             <el-radio :label="false">支付宝/微信</el-radio>
                         </el-radio-group>
                     </el-form-item>
                     <div class="inline-wrapper">
-                        <el-form-item prop="assignment" label="拉新任务指标">
+                        <el-form-item prop="assignment" label="拉新任务指标" v-if="!isCompany">
                             <el-input-number
                                 type="number"
                                 :min="0"
@@ -243,7 +243,7 @@
                             ></el-input-number>
                             <div class="tip">0表示无拉新任务限制</div>
                         </el-form-item>
-                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0">
+                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0 && !isCompany">
                             <el-input-number
                                 type="number"
                                 :min="0"
@@ -253,19 +253,19 @@
                             <div class="tip">多少人拉新可获得积分</div>
                         </el-form-item>
                     </div>
-                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0">
+                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0 && !isCompany">
                         <el-radio v-model="formData.openQuota" :label="true">开启</el-radio>
                         <el-radio v-model="formData.openQuota" :label="false">关闭</el-radio>
                     </el-form-item>
                     <div class="inline-wrapper">
-                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0">
+                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0 && !isCompany">
                             <el-radio v-model="formData.timeDelay" :label="true">是</el-radio>
                             <el-radio v-model="formData.timeDelay" :label="false">否</el-radio>
                         </el-form-item>
                         <el-form-item
                             prop="saleTime"
                             label="销售时间"
-                            v-if="formData.assignment > 0 && formData.timeDelay"
+                            v-if="formData.assignment > 0 && formData.timeDelay && !isCompany"
                             style="margin-left: 22px;"
                         >
                             <el-date-picker
@@ -277,13 +277,13 @@
                         </el-form-item>
                     </div>
 
-                    <el-form-item label="分享海报" v-if="formData.assignment > 0">
+                    <el-form-item label="分享海报" v-if="formData.assignment > 0 && !isCompany">
                         <single-upload v-model="formData.shareBg"></single-upload>
                     </el-form-item>
-                    <el-form-item label="注册海报" v-if="formData.assignment > 0">
+                    <el-form-item label="注册海报" v-if="formData.assignment > 0 && !isCompany">
                         <single-upload v-model="formData.registerBg"></single-upload>
                     </el-form-item>
-                    <el-form-item label="活动规则" v-if="formData.assignment > 0">
+                    <el-form-item label="活动规则" v-if="formData.assignment > 0 && !isCompany">
                         <rich-text style="width:500px" onlyText v-model="formData.rule"></rich-text>
                     </el-form-item>
                     <el-form-item class="form-submit">
@@ -476,7 +476,8 @@ export default {
                 noSoldOut: true,
                 assignment: 0,
                 couponPayment: false,
-                chainFlag: 3
+                chainFlag: 3,
+                vip: false
             },
             rules: {
                 name: [

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

@@ -21,7 +21,7 @@
             </el-button> -->
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" class="filter-item"></created-at-picker>
             <minter-filter v-model="minterId" @input="getData" class="filter-item"></minter-filter>
             <el-input
                 placeholder="搜索..."

+ 10 - 8
src/main/vue/src/views/CollectionEdit.vue

@@ -259,7 +259,7 @@
                         <div class="tip">持有多少天可转赠/转让。为空时,按系统设置天数。</div>
                     </el-form-item>
 
-                    <el-form-item prop="minimumCharge" label="最低消费">
+                    <el-form-item prop="minimumCharge" label="最低消费" v-if="!isCompany">
                         <el-input-number type="number" :min="0" v-model="formData.minimumCharge" style="width: 180px">
                         </el-input-number>
                     </el-form-item>
@@ -268,14 +268,14 @@
                         <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
                         <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
                     </el-form-item>
-                    <el-form-item prop="couponPayment" label="支付方式">
+                    <el-form-item prop="couponPayment" label="支付方式" v-if="!isCompany">
                         <el-radio-group v-model="formData.couponPayment">
                             <el-radio :label="true">兑换券</el-radio>
                             <el-radio :label="false">支付宝/微信</el-radio>
                         </el-radio-group>
                     </el-form-item>
 
-                    <el-form-item prop="vip" label="享有特权">
+                    <el-form-item prop="vip" label="享有特权" v-if="!isCompany">
                         <el-radio-group v-model="formData.vip">
                             <el-radio :label="true">白名单权利</el-radio>
                             <el-radio :label="false">无特权</el-radio>
@@ -283,7 +283,7 @@
                     </el-form-item>
 
                     <div class="inline-wrapper">
-                        <el-form-item prop="assignment" label="拉新任务指标">
+                        <el-form-item prop="assignment" label="拉新任务指标" v-if="!isCompany">
                             <el-input-number
                                 type="number"
                                 :min="0"
@@ -293,7 +293,7 @@
                             ></el-input-number>
                             <div class="tip">0表示无拉新任务限制</div>
                         </el-form-item>
-                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0">
+                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0 && !isCompany">
                             <el-input-number
                                 type="number"
                                 :min="0"
@@ -303,12 +303,12 @@
                             <div class="tip">多少人拉新可获得积分</div>
                         </el-form-item>
                     </div>
-                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0">
+                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0 && !isCompany">
                         <el-radio v-model="formData.openQuota" :label="true">开启</el-radio>
                         <el-radio v-model="formData.openQuota" :label="false">关闭</el-radio>
                     </el-form-item>
                     <div class="inline-wrapper">
-                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0">
+                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0 && !isCompany">
                             <el-radio v-model="formData.timeDelay" :label="true">是</el-radio>
                             <el-radio v-model="formData.timeDelay" :label="false">否</el-radio>
                         </el-form-item>
@@ -459,7 +459,9 @@ export default {
                 scanCode: false,
                 noSoldOut: true,
                 assignment: 0,
-                couponPayment: false
+                couponPayment: false,
+                chainFlag: 3,
+                vip: false
             },
             rules: {
                 name: [

+ 2 - 3
src/main/vue/src/views/CollectionList.vue

@@ -21,7 +21,7 @@
             </el-button> -->
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" class="filter-item"></created-at-picker>
             <minter-filter v-model="minterId" @input="getData" class="filter-item"></minter-filter>
             <el-input
                 placeholder="搜索..."
@@ -268,5 +268,4 @@ export default {
     }
 };
 </script>
-<style lang="less" scoped>
-</style>
+<style lang="less" scoped></style>

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

@@ -12,7 +12,7 @@
             </el-button>
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" class="filter-item"></created-at-picker>
             <minter-filter v-model="minterId" @input="getData" class="filter-item"></minter-filter>
             <el-input
                 placeholder="搜索..."

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

@@ -21,7 +21,7 @@
             </el-button>
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" name="获得" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="获得" class="filter-item"></created-at-picker>
             <el-input
                 placeholder="用户ID/昵称/手机号"
                 v-model="search"

+ 7 - 7
src/main/vue/src/views/CompanyAdmin.vue

@@ -106,7 +106,7 @@ export default {
             search: '',
             url: '/user/adminAll',
             downloading: false,
-            companyId: null,
+            companyId1: null,
             showDialog: false,
             form: {
                 username: null,
@@ -142,13 +142,13 @@ export default {
     methods: {
         beforeCreated() {
             if (this.$route.query.id) {
-                this.companyId = this.$route.query.id;
-            } else if (this.$store.userInfo.authorities.find(i => i.name === 'ROLE_COMPANY')) {
-                this.companyId = this.$store.userInfo.companyId;
+                this.companyId1 = this.$route.query.id;
+            } else {
+                this.companyId1 =  this.companyId;
             }
         },
         beforeGetData() {
-            return { search: this.search, query: { del: false, admin: true, companyId: this.companyId } };
+            return { search: this.search, query: { del: false, admin: true, companyId: this.companyId1 } };
         },
         toggleMultipleMode(multipleMode) {
             this.multipleMode = multipleMode;
@@ -205,7 +205,7 @@ export default {
         deleteRow(row) {
             this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
                 .then(() => {
-                    return this.$http.post(`/company/delAdmin`, { companyId: this.companyId, userId: row.id });
+                    return this.$http.post(`/company/delAdmin`, { companyId: this.companyId1, userId: row.id });
                 })
                 .then(() => {
                     this.$message.success('删除成功');
@@ -236,7 +236,7 @@ export default {
                 .validate()
                 .then(() => {
                     this.$http
-                        .post('/company/addAdmin', { companyId: this.companyId, ...this.form })
+                        .post('/company/addAdmin', { companyId: this.companyId1, ...this.form })
                         .then(res => {
                             this.$message.success('添加成功');
                             this.getData();

+ 905 - 0
src/main/vue/src/views/CompanyBlindBoxEdit.vue

@@ -0,0 +1,905 @@
+<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="100px"
+                    label-position="right"
+                    size="small"
+                    style="max-width: 500px;"
+                >
+                    <el-form-item prop="name" label="名称">
+                        <el-input v-model="formData.name" :disabled="!canEdit"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="pic" label="图片">
+                        <object-upload
+                            v-model="formData.pic[0]"
+                            :disabled="!canEdit"
+                            compress
+                            width="3000"
+                            height="3000"
+                        ></object-upload>
+                    </el-form-item>
+                    <el-form-item prop="minterId" label="创建者">
+                        <minter-select
+                            v-model="formData.minterId"
+                            @detail="onMinterDetail"
+                            :disabled="!canEdit"
+                        ></minter-select>
+                    </el-form-item>
+                    <el-form-item prop="category" label="分类">
+                        <el-select v-model="formData.category" :disabled="!canEdit">
+                            <el-option v-for="item in cateogories" :label="item" :value="item" :key="item"></el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="detail" label="详情" style="width: calc(100vw - 450px);">
+                        <rich-text v-model="formData.detail"></rich-text>
+                    </el-form-item>
+                    <el-form-item prop="properties" label="特性" style="width: calc(100vw - 450px);" size="mini">
+                        <el-table :data="formData.properties">
+                            <el-table-column prop="name" label="名称">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.name" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column prop="value" label="内容">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.value" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column width="80" align="center">
+                                <template v-slot="{ $index }">
+                                    <el-button type="danger" plain size="mini" @click="delProperty($index)">
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button size="mini" @click="addProperty"> 添加特性 </el-button>
+                    </el-form-item>
+                    <el-form-item label="特权" prop="privileges" style="width: calc(100vw - 450px);">
+                        <el-table :data="privilegeOptions">
+                            <el-table-column prop="name" label="可选特权" width="150"></el-table-column>
+                            <el-table-column prop="description"></el-table-column>
+                            <el-table-column width="155" align="right">
+                                <template v-slot="{ row, $index }">
+                                    <el-button size="mini" v-if="!row.added" @click="addPrivilege(row, $index)">
+                                        添加
+                                    </el-button>
+                                    <el-button size="mini" v-if="!!row.added" plain @click="editPrivilege(row, $index)">
+                                        编辑
+                                    </el-button>
+                                    <el-button
+                                        size="mini"
+                                        v-if="!!row.added"
+                                        type="danger"
+                                        plain
+                                        @click="delPrivilege(row, $index)"
+                                    >
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <!-- <el-form-item prop="type" label="类型">
+                        <el-select v-model="formData.type" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in typeOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="source" label="来源">
+                        <el-select v-model="formData.source" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in sourceOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item> -->
+                    <el-form-item prop="price" label="价格">
+                        <el-input-number type="number" v-model="formData.price" :disabled="!canEdit"></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="total" label="发行数量">
+                        <el-input-number v-model="formData.total" disabled></el-input-number>
+                    </el-form-item>
+                    <el-form-item prop="items" label="包含作品" v-if="!canEdit">
+                        <el-button v-if="formData.id" type="primary" size="mini" @click="getBox">查看作品</el-button>
+                    </el-form-item>
+                    <el-form-item v-if="canEdit" label="包含作品" style="width: calc(100vw - 450px);">
+                        <el-table :data="blindBoxItems" max-height="300px">
+                            <el-table-column prop="collectionId" label="ID"></el-table-column>
+                            <el-table-column prop="name" label="名称"></el-table-column>
+                            <el-table-column prop="total" label="数量" width="100"></el-table-column>
+                            <el-table-column prop="rare" label="稀有" width="100">
+                                <template v-slot="{ row }">
+                                    <el-tag :type="row.rare ? 'success' : 'info'">{{ row.rare ? '是' : '否' }}</el-tag>
+                                </template>
+                            </el-table-column>
+                            <el-table-column width="100">
+                                <template v-slot="{ $index }">
+                                    <el-button
+                                        @click="removeItem($index)"
+                                        type="danger"
+                                        plain
+                                        size="mini"
+                                        :disabled="!canEdit"
+                                    >
+                                        移除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                        <el-button size="mini" @click="addItem" :disabled="!canEdit">添加作品</el-button>
+                    </el-form-item>
+                    <el-form-item prop="prefixName" label="系列名称">
+                        <el-input
+                            v-model="formData.prefixName"
+                            style="width: 200px"
+                            placeholder="请输入系列名称"
+                        ></el-input>
+                    </el-form-item>
+                    <!-- <el-form-item prop="likes" label="点赞">
+                        <el-input-number v-model="formData.likes"></el-input-number>
+                    </el-form-item> -->
+                    <el-form-item prop="chainFlag" label="上链">
+                        <el-select v-model="formData.chainFlag" placeholder="请选择">
+                            <el-option label="蚂蚁链+华储链" :value="3" :key="3"></el-option>
+                            <el-option label="华储链" :value="2" :key="2"></el-option>
+                            <el-option label="蚂蚁链" :value="1" :key="1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="scanCode" label="仅扫码可见">
+                        <el-radio v-model="formData.scanCode" :label="true">是</el-radio>
+                        <el-radio v-model="formData.scanCode" :label="false">否</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="onShelf" label="上架" v-if="!formData.scanCode">
+                        <el-radio v-model="formData.onShelf" :label="true">是</el-radio>
+                        <el-radio v-model="formData.onShelf" :label="false">否</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="startTime" label="定时发布">
+                        <el-radio v-model="formData.scheduleSale" :label="true">是</el-radio>
+                        <el-radio v-model="formData.scheduleSale" :label="false">否</el-radio>
+                        <div style="margin-top: 10px;" v-if="formData.scheduleSale">
+                            <el-date-picker
+                                v-model="formData.startTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="发布时间"
+                            ></el-date-picker>
+                        </div>
+                    </el-form-item>
+                    <el-form-item
+                        prop="salable"
+                        label="可售"
+                        v-if="
+                            formData.onShelf === true || (formData.scanCode === true && formData.scheduleSale === false)
+                        "
+                    >
+                        <el-radio v-model="formData.salable" :label="true">是</el-radio>
+                        <el-radio v-model="formData.salable" :label="false">仅展示</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="maxCount" label="限购">
+                        <el-input-number v-model="formData.maxCount"></el-input-number>
+                        <div class="tip">0表示不限购</div>
+                    </el-form-item>
+                    <el-form-item prop="countId" label="限购识别码" v-if="formData.maxCount > 0">
+                        <el-input v-model="formData.countId"></el-input>
+                        <div class="tip">相同识别码的藏品共享限购数量</div>
+                    </el-form-item>
+
+                    <el-form-item prop="holdDays" label="持有天数">
+                        <el-input-number
+                            type="number"
+                            :min="0"
+                            :step="1"
+                            :max="2147483647"
+                            v-model="formData.holdDays"
+                            style="width: 180px;"
+                        ></el-input-number>
+                        <div class="tip">持有多少天可转赠/转让。为空时,按系统设置天数</div>
+                    </el-form-item>
+
+                    <el-form-item prop="minimumCharge" label="最低消费">
+                        <el-input-number type="number" :min="0" v-model="formData.minimumCharge" style="width: 180px;">
+                        </el-input-number>
+                    </el-form-item>
+
+                    <el-form-item prop="noSoldOut" label="售罄">
+                        <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
+                        <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="couponPayment" label="支付方式">
+                        <el-radio-group v-model="formData.couponPayment">
+                            <el-radio :label="true">兑换券</el-radio>
+                            <el-radio :label="false">支付宝/微信</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="assignment" label="拉新任务指标">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                :max="5"
+                                v-model="formData.assignment"
+                            ></el-input-number>
+                            <div class="tip">0表示无拉新任务限制</div>
+                        </el-form-item>
+                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                v-model="formData.totalQuota"
+                            ></el-input-number>
+                            <div class="tip">多少人拉新可获得积分</div>
+                        </el-form-item>
+                    </div>
+                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0">
+                        <el-radio v-model="formData.openQuota" :label="true">开启</el-radio>
+                        <el-radio v-model="formData.openQuota" :label="false">关闭</el-radio>
+                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0">
+                            <el-radio v-model="formData.timeDelay" :label="true">是</el-radio>
+                            <el-radio v-model="formData.timeDelay" :label="false">否</el-radio>
+                        </el-form-item>
+                        <el-form-item
+                            prop="saleTime"
+                            label="销售时间"
+                            v-if="formData.assignment > 0 && formData.timeDelay"
+                            style="margin-left: 22px;"
+                        >
+                            <el-date-picker
+                                v-model="formData.saleTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="销售时间"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </div>
+
+                    <el-form-item label="分享海报" v-if="formData.assignment > 0">
+                        <single-upload v-model="formData.shareBg"></single-upload>
+                    </el-form-item>
+                    <el-form-item label="注册海报" v-if="formData.assignment > 0">
+                        <single-upload v-model="formData.registerBg"></single-upload>
+                    </el-form-item>
+                    <el-form-item label="活动规则" v-if="formData.assignment > 0">
+                        <rich-text style="width:500px" onlyText v-model="formData.rule"></rich-text>
+                    </el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary" v-if="!formData.id">
+                            保存
+                        </el-button>
+                        <!-- <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button> -->
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+        <el-dialog :visible.sync="showAddItemDialog" title="添加" width="600px">
+            <el-form
+                :model="addItemForm"
+                label-position="right"
+                label-width="80px"
+                ref="addItemForm"
+                :rules="addItemFormRules"
+            >
+                <el-form-item prop="collectionId" label="作品">
+                    <collection-search v-model="addItemForm.collectionId" @select="selectItem"> </collection-search>
+                </el-form-item>
+                <el-form-item prop="total" label="数量">
+                    <el-input-number v-model="addItemForm.total" :min="1" :max="addItemForm.max || 0"></el-input-number>
+                </el-form-item>
+                <el-form-item prop="rare" label="稀有">
+                    <el-switch v-model="addItemForm.rare" active-text="是" inactive-text="否"></el-switch>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button size="mini" @click="showAddItemDialog = false">取消</el-button>
+                <el-button size="mini" @click="onAddItem" type="primary">确定</el-button>
+            </div>
+        </el-dialog>
+
+        <el-dialog :visible.sync="showPrivilegeEditDialog" width="600px" :title="privilegeForm.name">
+            <el-form
+                ref="privilegeForm"
+                :model="privilegeForm"
+                label-position="right"
+                label-width="80px"
+                :rules="privelegeRules"
+            >
+                <el-form-item
+                    prop="detail"
+                    label="详细内容"
+                    v-if="privilegeForm.type === 'text' || privilegeForm.type === 'exchange'"
+                >
+                    <el-input type="textarea" :autosize="{ minRows: 3 }" v-model="privilegeForm.detail"></el-input>
+                </el-form-item>
+                <el-form-item prop="detail" label="二维码" v-if="privilegeForm.type === 'qrcode'">
+                    <single-upload v-model="privilegeForm.detail"></single-upload>
+                </el-form-item>
+                <el-form-item
+                    prop="remark"
+                    label="说明"
+                    v-if="privilegeForm.type === 'qrcode' || privilegeForm.type === 'code'"
+                >
+                    <el-input type="textarea" :autosize="{ minRows: 3 }" v-model="privilegeForm.remark"></el-input>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button @click="showPrivilegeEditDialog = false">取消</el-button>
+                <el-button type="primary" @click="savePrivilege">保存</el-button>
+            </div>
+        </el-dialog>
+
+        <el-dialog title="包含作品" :visible.sync="showBox" width="800px" @close="showBox = false">
+            <el-table :data="blindBoxItems" max-height="300px">
+                <el-table-column prop="collectionId" label="ID"></el-table-column>
+                <el-table-column prop="name" label="名称"></el-table-column>
+                <el-table-column prop="total" label="数量" width="100"></el-table-column>
+                <el-table-column prop="rare" label="稀有" width="100">
+                    <template v-slot="{ row }">
+                        <el-tag :type="row.rare ? 'success' : 'info'">{{ row.rare ? '是' : '否' }}</el-tag>
+                    </template>
+                </el-table-column>
+                <el-table-column width="100">
+                    <template v-slot="{ $index }">
+                        <el-button @click="removeItem($index)" type="danger" plain size="mini" :disabled="!canEdit">
+                            移除
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import resolveUrl from 'resolve-url';
+import ModelUpload from '../components/ModelUpload.vue';
+import { format, parse, isBefore } from 'date-fns';
+export default {
+    name: 'BlindBoxEdit',
+    created() {
+        // if (this.$route.query.id) {
+        //     this.$http
+        //         .get('collection/get/' + this.$route.query.id)
+        //         .then(res => {
+        //             res.properties = res.properties || [];
+        //             this.formData = res;
+        //         })
+        //         .catch(e => {
+        //             console.log(e);
+        //             this.$message.error(e.error);
+        //         });
+        //     this.$http
+        //         .post(
+        //             '/blindBoxItem/all',
+        //             { query: { blindBoxId: this.$route.query.id }, size: 10000 },
+        //             { body: 'json' }
+        //         )
+        //         .then(res => {
+        //             this.blindBoxItems = res.content;
+        //         });
+        // }
+        // this.$http
+        //     .post(
+        //         '/collection/all',
+        //         { query: { del: false, source: 'OFFICIAL' }, size: 10000, sort: 'sort,desc;createdAt,desc' },
+        //         { body: 'json' }
+        //     )
+        //     .then(res => {
+        //         this.collections = res.content;
+        //     });
+
+        Promise.all([
+            new Promise((resolve, reject) => {
+                if (this.$route.query.id) {
+                    return this.$http
+                        .get('collection/get/' + this.$route.query.id)
+                        .then(res => {
+                            res.properties = res.properties || [];
+                            res.privileges = res.privileges || [];
+                            this.formData = res;
+                            if (res.totalQuota !== res.vipQuota) {
+                                this.editQuota = false;
+                            }
+                            resolve();
+                        })
+                        .catch(e => {
+                            console.log(e);
+                            this.$message.error(e.error);
+                            resolve();
+                        });
+                }
+                return resolve();
+            }),
+            this.$http
+                .post('/privilegeOption/all', { size: 10000, query: { del: false } }, { body: 'json' })
+                .then(res => {
+                    this.privilegeOptions = res.content;
+                    return Promise.resolve();
+                })
+        ]).then(() => {
+            console.log(this.formData, this.privilegeOptions);
+            this.formData.privileges.forEach(p => {
+                let idx = this.privilegeOptions.findIndex(i => i.name === p.name);
+                if (idx > -1) {
+                    this.$set(this.privilegeOptions[idx], 'added', true);
+                }
+            });
+        });
+    },
+    computed: {
+        canEdit() {
+            return !!!this.$route.query.id;
+        }
+    },
+    data() {
+        return {
+            saving: false,
+            showBox: false,
+            formData: {
+                onShelf: false,
+                salable: true,
+                properties: [],
+                type: 'BLIND_BOX',
+                source: 'OFFICIAL',
+                pic: [{}],
+                scheduleSale: true,
+                privileges: [],
+                sort: 0,
+                maxCount: 0,
+                countId: null,
+                scanCode: false,
+                noSoldOut: true,
+                assignment: 0,
+                couponPayment: false,
+                chainFlag: 3
+            },
+            rules: {
+                name: [
+                    {
+                        required: true,
+                        message: '请输入名称',
+                        trigger: 'blur'
+                    }
+                ],
+                prefixName: [
+                    {
+                        required: true,
+                        message: '请输入系列名称',
+                        trigger: 'blur'
+                    }
+                ],
+                pic: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('请上传内容'));
+                                    return;
+                                } else {
+                                    for (let f of value) {
+                                        if (!f.url) {
+                                            callback(new Error('请上传内容'));
+                                            return;
+                                        }
+                                    }
+                                }
+                                callback();
+                            } else {
+                                callback(new Error('请上传内容'));
+                            }
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                minter: [
+                    {
+                        required: true,
+                        message: '请输入创建者',
+                        trigger: 'blur'
+                    }
+                ],
+                minterId: [
+                    {
+                        required: true,
+                        message: '请输入铸造者ID',
+                        trigger: 'blur'
+                    }
+                ],
+                minterAvatar: [
+                    {
+                        required: true,
+                        message: '请输入铸造者头像',
+                        trigger: 'blur'
+                    }
+                ],
+                detail: [
+                    {
+                        required: true,
+                        message: '请输入详情',
+                        trigger: 'blur'
+                    }
+                ],
+                type: [
+                    {
+                        required: true,
+                        message: '请输入类型',
+                        trigger: 'blur'
+                    }
+                ],
+                source: [
+                    {
+                        required: true,
+                        message: '请输入来源',
+                        trigger: 'blur'
+                    }
+                ],
+                total: [
+                    {
+                        required: true,
+                        message: '请输入发行数量',
+                        trigger: 'blur'
+                    }
+                ],
+                onShelf: [
+                    {
+                        required: true,
+                        message: '请输入上架',
+                        trigger: 'blur'
+                    }
+                ],
+                price: [
+                    {
+                        required: true,
+                        message: '请输入价格',
+                        trigger: 'blur'
+                    }
+                ],
+                properties: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('properties must be array!'));
+                                    return;
+                                } else {
+                                    for (let i = 0; i < value.length; i++) {
+                                        if (value[i].name === '' || value[i].name === undefined) {
+                                            callback(new Error('请填写名称'));
+                                            return;
+                                        }
+                                        if (value[i].value === '' || value[i].value === undefined) {
+                                            callback(new Error('请添加作品'));
+                                            return;
+                                        }
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                startTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.scheduleSale) {
+                                if (!value) {
+                                    callback(new Error('请填写发布时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('发布时间不能小于当前时间'));
+                                } else {
+                                    callback();
+                                }
+                            } else {
+                                callback();
+                            }
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                items: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.canEdit && this.blindBoxItems.length === 0) {
+                                callback(new Error('请添加盲盒内容'));
+                                return;
+                            }
+                            callback();
+                        }
+                    }
+                ],
+                category: [{ required: true, message: '请填写分类' }],
+                saleTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.timeDelay) {
+                                if (!value) {
+                                    callback(new Error('请填写销售时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('销售时间不能小于当前时间'));
+                                } else if (this.formData.scheduleSale) {
+                                    if (
+                                        isBefore(
+                                            parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()),
+                                            parse(this.formData.startTime, 'yyyy-MM-dd HH:mm:ss', new Date())
+                                        )
+                                    ) {
+                                        callback(new Error('销售时间不能小于发布时间'));
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                timeDelay: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请选择是否延迟销售'));
+                                    return;
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                totalQuota: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请输入白名单额度'));
+                                    return;
+                                }
+                            }
+
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                chainFlag: [{ required: true, message: '请选择区块链', trigger: 'blur' }]
+            },
+            typeOptions: [
+                { label: '默认', value: 'DEFAULT' },
+                { label: '盲盒', value: 'BLIND_BOX' },
+                { label: '拍卖', value: 'AUCTION' }
+            ],
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ],
+            collections: [],
+            showAddItemDialog: false,
+            blindBoxItems: [],
+            addItemForm: {},
+            addItemFormRules: {
+                id: [{ required: true, message: '请选择作品' }],
+                total: [{ required: true, message: '请输入数量' }]
+            },
+            cateogories: ['勋章', '收藏品', '数字艺术', '门票', '游戏', '音乐', '使用', '其他'],
+            customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel'),
+            privilegeOptions: [],
+            showPrivilegeEditDialog: false,
+            privilegeForm: {},
+            privelegeRules: {
+                detail: [{ required: true, message: '请填写内容' }],
+                remark: [{ required: true, message: '请填写说明' }]
+            },
+            editQuota: true
+        };
+    },
+    methods: {
+        getBox() {
+            this.$http
+                .post(
+                    '/blindBoxItem/all',
+                    { query: { blindBoxId: this.$route.query.id }, size: 10000 },
+                    { body: 'json' }
+                )
+                .then(res => {
+                    this.showBox = true;
+                    this.blindBoxItems = res.content;
+                });
+        },
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            if (this.editQuota && this.formData.totalQuota) {
+                this.formData.vipQuota = this.formData.totalQuota;
+            }
+            if (this.formData.id) {
+                this.saving = true;
+                this.$http
+                    .post('/collection/save', this.formData, { 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);
+                    });
+            } else {
+                let data = { blindBox: { ...this.formData }, items: this.blindBoxItems };
+
+                this.saving = true;
+                this.$http
+                    .post('/collection/createBlindBox', data, { body: 'json' })
+                    .then(res => {
+                        this.saving = false;
+                        this.$message.success('成功');
+                        this.$router.go(-1);
+                    })
+                    .catch(e => {
+                        console.log(e);
+                        this.saving = false;
+                        this.$message.error(e.error);
+                    });
+            }
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error((e || {}).error || '删除失败');
+                    }
+                });
+        },
+        onMinterDetail(e) {
+            console.log(e);
+            this.$set(this.formData, 'minter', e.nickname);
+            this.$set(this.formData, 'minterAvatar', e.avatar);
+        },
+        addProperty() {
+            this.formData.properties.push({
+                name: '',
+                value: ''
+            });
+        },
+        delProperty(i) {
+            this.formData.properties.splice(i, 1);
+        },
+        addItem() {
+            this.addItemForm = { rare: false };
+            this.showAddItemDialog = true;
+        },
+        onAddItem() {
+            this.$refs.addItemForm
+                .validate()
+                .then(_ => {
+                    this.blindBoxItems.push({ ...this.addItemForm });
+                    this.showAddItemDialog = false;
+                })
+                .catch(_ => {});
+        },
+        collectionNameFormatter(row, column, value, index) {
+            return (this.collections.find(i => i.id === value) || {}).name || '';
+        },
+        removeItem(i) {
+            this.blindBoxItems.splice(i, 1);
+        },
+        addPrivilege(row, i) {
+            this.privilegeForm = { ...row };
+            this.showPrivilegeEditDialog = true;
+            if (this.$refs.privilegeForm) {
+                this.$nextTick(() => {
+                    this.$refs.privilegeForm.clearValidate();
+                });
+            }
+        },
+        editPrivilege(row, i) {
+            this.privilegeForm = { ...(this.formData.privileges.find(e => e.name === row.name) || {}) };
+            this.showPrivilegeEditDialog = true;
+            if (this.$refs.privilegeForm) {
+                this.$nextTick(() => {
+                    this.$refs.privilegeForm.clearValidate();
+                });
+            }
+        },
+        delPrivilege(row, i) {
+            let idx = this.formData.privileges.findIndex(e => e.name === row.name);
+            if (idx > -1) {
+                this.formData.privileges.splice(idx, 1);
+            }
+            this.$set(this.privilegeOptions[i], 'added', false);
+        },
+        savePrivilege() {
+            this.$refs.privilegeForm
+                .validate()
+                .then(() => {
+                    let i = this.formData.privileges.findIndex(e => e.name === this.privilegeForm.name);
+                    if (i > -1) {
+                        this.$set(this.formData.privileges, i, { ...this.privilegeForm });
+                    } else {
+                        this.formData.privileges.push({ ...this.privilegeForm });
+                    }
+                    let ii = this.privilegeOptions.findIndex(i => i.name === this.privilegeForm.name);
+                    console.log(ii);
+                    this.$set(this.privilegeOptions[ii], 'added', true);
+                    this.showPrivilegeEditDialog = false;
+                })
+                .catch(e => {
+                    console.log(e);
+                });
+        },
+        selectItem(item) {
+            this.$set(this.addItemForm, 'max', item.stock || 0);
+            this.$set(this.addItemForm, 'name', item.name);
+        }
+    },
+    watch: {
+        blindBoxItems() {
+            this.$set(
+                this.formData,
+                'total',
+                this.blindBoxItems.map(i => i.total).reduce((a, b) => a + b, 0)
+            );
+        },
+        'formData.scanCode'(val) {
+            if (val === true) {
+                this.$set(this.formData, 'onShelf', false);
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.tip {
+    font-size: 12px;
+    color: @text3;
+    margin-top: 5px;
+}
+.inline-wrapper {
+    .el-form-item {
+        display: inline-block;
+    }
+}
+</style>

+ 235 - 0
src/main/vue/src/views/CompanyBlindBoxList.vue

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

+ 856 - 0
src/main/vue/src/views/CompanyCollectionEdit.vue

@@ -0,0 +1,856 @@
+<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="120px"
+                    label-position="right"
+                    size="small"
+                >
+                    <el-form-item prop="name" label="名称">
+                        <el-input v-model="formData.name" :disabled="!canEdit" style="width: 500px"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="pic" label="图片">
+                        <object-upload
+                            v-model="formData.pic[0]"
+                            :disabled="!canEdit"
+                            compress
+                            width="3000"
+                            height="3000"
+                        ></object-upload>
+                        <div class="tip">支持JPG、PNG、GIF、MP4,推荐长宽比1:1</div>
+                    </el-form-item>
+                    <el-form-item prop="model3d" label="3D模型">
+                        <model-upload
+                            :limit="1"
+                            v-model="formData.model3d"
+                            :customUrl="customUrl"
+                            accept="application/zip"
+                            format="json"
+                            single
+                        ></model-upload>
+                        <div class="tip">请将FBX文件与贴图打包成zip压缩包上传</div>
+                    </el-form-item>
+                    <el-form-item label="相机距离" v-if="formData.model3d">
+                        <el-input-number v-model="scale" :min="0.1" :step="0.1"></el-input-number>
+                    </el-form-item>
+                    <el-form-item label="Y轴偏移" v-if="formData.model3d">
+                        <el-input-number v-model="yOffset"></el-input-number>
+                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="minterId" label="铸造者">
+                            <minter-select
+                                v-model="formData.minterId"
+                                @detail="onMinterDetail"
+                                :disabled="!canEdit"
+                            ></minter-select>
+                        </el-form-item>
+                        <el-form-item prop="category" label="分类">
+                            <el-select v-model="formData.category" :disabled="!canEdit">
+                                <el-option
+                                    v-for="item in cateogories"
+                                    :label="item"
+                                    :value="item"
+                                    :key="item"
+                                ></el-option>
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item prop="tags" label="标签">
+                            <tag-select v-model="formData.tags" style="width: 495px"></tag-select>
+                        </el-form-item>
+                    </div>
+                    <el-form-item prop="detail" label="详情" style="width: calc(100vw - 450px)">
+                        <rich-text v-model="formData.detail"></rich-text>
+                    </el-form-item>
+                    <el-form-item prop="empower" label="赋能列表" style="width: calc(100vw - 450px)">
+                        <rich-text v-model="formData.empower"></rich-text>
+                    </el-form-item>
+                    <el-form-item prop="properties" label="特性" style="width: calc(100vw - 450px)" size="mini">
+                        <el-table :data="formData.properties">
+                            <el-table-column prop="name" label="名称">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.name" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column prop="value" label="内容">
+                                <template v-slot="{ row }">
+                                    <el-input v-model="row.value" placeholder="20字以内" maxlength="20"></el-input>
+                                </template>
+                            </el-table-column>
+                            <el-table-column width="80" align="center">
+                                <template v-slot="{ row, $index }">
+                                    <el-button type="danger" plain size="mini" @click="delProperty($index)">
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button size="mini" @click="addProperty"> 添加特性 </el-button>
+                    </el-form-item>
+                    <el-form-item label="特权" prop="privileges" style="width: calc(100vw - 450px)">
+                        <el-table :data="privilegeOptions">
+                            <el-table-column prop="name" label="可选特权" width="150"></el-table-column>
+                            <el-table-column prop="description"></el-table-column>
+                            <el-table-column width="155" align="right">
+                                <template v-slot="{ row, $index }">
+                                    <el-button size="mini" v-if="!row.added" @click="addPrivilege(row, $index)">
+                                        添加
+                                    </el-button>
+                                    <el-button size="mini" v-if="!!row.added" plain @click="editPrivilege(row, $index)">
+                                        编辑
+                                    </el-button>
+                                    <el-button
+                                        size="mini"
+                                        v-if="!!row.added"
+                                        type="danger"
+                                        plain
+                                        @click="delPrivilege(row, $index)"
+                                    >
+                                        删除
+                                    </el-button>
+                                </template>
+                            </el-table-column>
+                        </el-table>
+                    </el-form-item>
+                    <!-- <el-form-item prop="type" label="类型">
+                        <el-select v-model="formData.type" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in typeOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="source" label="来源">
+                        <el-select v-model="formData.source" clearable filterable placeholder="请选择">
+                            <el-option
+                                v-for="item in sourceOptions"
+                                :key="item.value"
+                                :label="item.label"
+                                :value="item.value"
+                            >
+                            </el-option>
+                        </el-select>
+                    </el-form-item> -->
+                    <div class="inline-wrapper">
+                        <el-form-item prop="price" label="价格">
+                            <el-input-number
+                                type="number"
+                                v-model="formData.price"
+                                :disabled="!canEdit"
+                            ></el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="price" label="原价">
+                            <el-input-number
+                                type="number"
+                                v-model="formData.originalPrice"
+                                :disabled="!canEdit"
+                            ></el-input-number>
+                        </el-form-item>
+                    </div>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="royalties" label="版税(%)">
+                            <el-input-number v-model="formData.royalties" :min="0" :max="99" :disabled="!canEdit">
+                            </el-input-number>
+                        </el-form-item>
+                        <el-form-item prop="serviceCharge" label="手续费(%)">
+                            <el-input-number v-model="formData.serviceCharge" :min="0" :max="99" :disabled="!canEdit">
+                            </el-input-number>
+                        </el-form-item>
+                    </div>
+                    <el-form-item prop="total" label="发行数量">
+                        <el-input-number v-model="formData.total" :disabled="!canEdit"></el-input-number>
+                    </el-form-item>
+
+                    <el-form-item prop="prefixName" label="系列名称">
+                        <el-input
+                            v-model="formData.prefixName"
+                            style="width: 200px"
+                            placeholder="请输入系列名称"
+                        ></el-input>
+                    </el-form-item>
+                    <el-form-item prop="chainFlag" label="上链">
+                        <el-select v-model="formData.chainFlag" placeholder="请选择">
+                            <el-option label="蚂蚁链+华储链" :value="3" :key="3"></el-option>
+                            <el-option label="华储链" :value="2" :key="2"></el-option>
+                            <el-option label="蚂蚁链" :value="1" :key="1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                    <!-- <el-form-item prop="likes" label="点赞">
+                        <el-input-number v-model="formData.likes"></el-input-number>
+                    </el-form-item> -->
+                    <el-form-item prop="scanCode" label="仅扫码可见">
+                        <el-radio v-model="formData.scanCode" :label="true">是</el-radio>
+                        <el-radio v-model="formData.scanCode" :label="false">否</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="onShelf" label="上架" v-if="!formData.scanCode">
+                        <el-radio v-model="formData.onShelf" :label="true">是</el-radio>
+                        <el-radio v-model="formData.onShelf" :label="false">否</el-radio>
+                    </el-form-item>
+
+                    <div class="inline-wrapper">
+                        <el-form-item prop="startTime" label="定时发布">
+                            <el-radio v-model="formData.scheduleSale" :label="true">是</el-radio>
+                            <el-radio v-model="formData.scheduleSale" :label="false">否</el-radio>
+                        </el-form-item>
+                        <el-form-item
+                            prop="startTime"
+                            label="发布时间"
+                            v-if="formData.scheduleSale"
+                            style="margin-left: 22px"
+                        >
+                            <el-date-picker
+                                v-model="formData.startTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="发布时间"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </div>
+
+                    <el-form-item
+                        prop="salable"
+                        label="可售"
+                        v-if="
+                            formData.onShelf === true || (formData.scanCode === true && formData.scheduleSale === false)
+                        "
+                    >
+                        <el-radio v-model="formData.salable" :label="true">是</el-radio>
+                        <el-radio v-model="formData.salable" :label="false">仅展示</el-radio>
+                    </el-form-item>
+
+                    <el-form-item prop="sort" label="排序">
+                        <el-input-number v-model="formData.sort" :min="0"></el-input-number>
+                        <div class="tip">数字越大排序越靠前,相同数值按创建时间倒序排列</div>
+                    </el-form-item>
+
+                    <div class="inline-wrapper">
+                        <el-form-item prop="maxCount" label="限购">
+                            <el-input-number v-model="formData.maxCount" :min="0" :step="1"></el-input-number>
+                            <div class="tip">0表示不限购</div>
+                        </el-form-item>
+                        <el-form-item prop="countId" label="限购识别码" v-if="formData.maxCount > 0">
+                            <el-input v-model="formData.countId" style="width: 300px"></el-input>
+                            <div class="tip">相同识别码的藏品共享限购数量</div>
+                        </el-form-item>
+                    </div>
+
+                    <el-form-item prop="holdDays" label="持有天数">
+                        <el-input-number
+                            type="number"
+                            :min="0"
+                            :step="1"
+                            :max="2147483647"
+                            v-model="formData.holdDays"
+                            style="width: 180px"
+                        ></el-input-number>
+                        <div class="tip">持有多少天可转赠/转让。为空时,按系统设置天数。</div>
+                    </el-form-item>
+
+                    <el-form-item prop="minimumCharge" label="最低消费">
+                        <el-input-number type="number" :min="0" v-model="formData.minimumCharge" style="width: 180px">
+                        </el-input-number>
+                    </el-form-item>
+
+                    <el-form-item prop="noSoldOut" label="售罄">
+                        <el-radio v-model="formData.noSoldOut" :label="false">是</el-radio>
+                        <el-radio v-model="formData.noSoldOut" :label="true">否</el-radio>
+                    </el-form-item>
+                    <el-form-item prop="couponPayment" label="支付方式">
+                        <el-radio-group v-model="formData.couponPayment">
+                            <el-radio :label="true">兑换券</el-radio>
+                            <el-radio :label="false">支付宝/微信</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+
+                    <el-form-item prop="vip" label="享有特权">
+                        <el-radio-group v-model="formData.vip">
+                            <el-radio :label="true">白名单权利</el-radio>
+                            <el-radio :label="false">无特权</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+
+                    <div class="inline-wrapper">
+                        <el-form-item prop="assignment" label="拉新任务指标">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                :max="5"
+                                v-model="formData.assignment"
+                            ></el-input-number>
+                            <div class="tip">0表示无拉新任务限制</div>
+                        </el-form-item>
+                        <el-form-item prop="totalQuota" label="白名单额度" v-if="formData.assignment > 0">
+                            <el-input-number
+                                type="number"
+                                :min="0"
+                                :step="1"
+                                v-model="formData.totalQuota"
+                            ></el-input-number>
+                            <div class="tip">多少人拉新可获得积分</div>
+                        </el-form-item>
+                    </div>
+                    <el-form-item prop="openQuota" label="白名单分享" v-if="formData.assignment > 0">
+                        <el-radio v-model="formData.openQuota" :label="true">开启</el-radio>
+                        <el-radio v-model="formData.openQuota" :label="false">关闭</el-radio>
+                    </el-form-item>
+                    <div class="inline-wrapper">
+                        <el-form-item prop="timeDelay" label="延迟销售" v-if="formData.assignment > 0">
+                            <el-radio v-model="formData.timeDelay" :label="true">是</el-radio>
+                            <el-radio v-model="formData.timeDelay" :label="false">否</el-radio>
+                        </el-form-item>
+                        <el-form-item
+                            prop="saleTime"
+                            label="销售时间"
+                            v-if="formData.assignment > 0 && formData.timeDelay"
+                            style="margin-left: 22px"
+                        >
+                            <el-date-picker
+                                v-model="formData.saleTime"
+                                type="datetime"
+                                value-format="yyyy-MM-dd HH:mm:ss"
+                                placeholder="销售时间"
+                            ></el-date-picker>
+                        </el-form-item>
+                    </div>
+
+                    <el-form-item label="分享海报" v-if="formData.assignment > 0">
+                        <single-upload v-model="formData.shareBg"></single-upload>
+                    </el-form-item>
+                    <el-form-item label="注册海报" v-if="formData.assignment > 0">
+                        <single-upload v-model="formData.registerBg"></single-upload>
+                    </el-form-item>
+                    <el-form-item label="活动规则" v-if="formData.assignment > 0">
+                        <rich-text style="width:500px" onlyText v-model="formData.rule"></rich-text>
+                    </el-form-item>
+
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary"> 保存 </el-button>
+                        <!-- <el-button @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
+                            删除
+                        </el-button> -->
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+
+        <el-dialog :visible.sync="showPrivilegeEditDialog" width="600px" :title="privilegeForm.name">
+            <el-form
+                ref="privilegeForm"
+                :model="privilegeForm"
+                label-position="right"
+                label-width="80px"
+                :rules="privelegeRules"
+            >
+                <el-form-item
+                    prop="detail"
+                    label="详细内容"
+                    v-if="privilegeForm.type === 'text' || privilegeForm.type === 'exchange'"
+                >
+                    <el-input type="textarea" :autosize="{ minRows: 3 }" v-model="privilegeForm.detail"></el-input>
+                </el-form-item>
+                <el-form-item prop="detail" label="二维码" v-if="privilegeForm.type === 'qrcode'">
+                    <single-upload v-model="privilegeForm.detail"></single-upload>
+                </el-form-item>
+                <el-form-item
+                    prop="remark"
+                    label="说明"
+                    v-if="privilegeForm.type === 'qrcode' || privilegeForm.type === 'code'"
+                >
+                    <el-input type="textarea" :autosize="{ minRows: 3 }" v-model="privilegeForm.remark"></el-input>
+                </el-form-item>
+            </el-form>
+            <div slot="footer">
+                <el-button @click="showPrivilegeEditDialog = false">取消</el-button>
+                <el-button type="primary" @click="savePrivilege">保存</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import resolveUrl from 'resolve-url';
+import ModelUpload from '../components/ModelUpload.vue';
+import { format, parse, isBefore } from 'date-fns';
+import SingleUpload from '../components/SingleUpload.vue';
+import TagSelect from '../components/TagSelect.vue';
+export default {
+    name: 'CollectionEdit',
+    components: { ModelUpload, SingleUpload, TagSelect },
+    created() {
+        Promise.all([
+            new Promise((resolve, reject) => {
+                if (this.$route.query.id) {
+                    return this.$http
+                        .get('collection/getInfo/' + this.$route.query.id)
+                        .then(res => {
+                            if (res.model3d) {
+                                let url = new URL(res.model3d.url);
+                                this.scale = Number(url.searchParams.get('scale')) || 1;
+                                this.yOffset = Number(url.searchParams.get('yOffset')) || 0;
+                                res.model3d.url = url.origin + url.pathname;
+                            }
+                            res.properties = res.properties || [];
+                            res.privileges = res.privileges || [];
+                            this.formData = res;
+                            if (res.totalQuota !== res.vipQuota) {
+                                this.editQuota = false;
+                            }
+                            resolve();
+                        })
+                        .catch(e => {
+                            console.log(e);
+                            this.$message.error(e.error);
+                            resolve();
+                        });
+                }
+                return resolve();
+            }),
+            this.$http
+                .post('/privilegeOption/all', { size: 10000, query: { del: false } }, { body: 'json' })
+                .then(res => {
+                    this.privilegeOptions = res.content;
+                    return Promise.resolve();
+                })
+        ]).then(() => {
+            console.log(this.formData, this.privilegeOptions);
+            this.formData.privileges.forEach(p => {
+                let idx = this.privilegeOptions.findIndex(i => i.name === p.name);
+                if (idx > -1) {
+                    this.$set(this.privilegeOptions[idx], 'added', true);
+                }
+            });
+        });
+    },
+    computed: {
+        canEdit() {
+            return !!!this.$route.query.id;
+        }
+    },
+    data() {
+        return {
+            saving: false,
+            formData: {
+                onShelf: false,
+                salable: false,
+                properties: [],
+                type: 'DEFAULT',
+                source: 'OFFICIAL',
+                pic: [],
+                scheduleSale: true,
+                sort: 0,
+                privileges: [],
+                maxCount: 0,
+                countId: null,
+                canResale: true,
+                scanCode: false,
+                noSoldOut: true,
+                assignment: 0,
+                couponPayment: false
+            },
+            rules: {
+                name: [
+                    {
+                        required: true,
+                        message: '请输入名称',
+                        trigger: 'blur'
+                    }
+                ],
+                prefixName: [
+                    {
+                        required: true,
+                        message: '请输入系列名称',
+                        trigger: 'blur'
+                    }
+                ],
+                pic: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('请上传内容'));
+                                    return;
+                                } else {
+                                    for (let f of value) {
+                                        if (!f.url) {
+                                            callback(new Error('请上传内容'));
+                                            return;
+                                        }
+                                    }
+                                }
+                                callback();
+                            } else {
+                                callback(new Error('请上传内容'));
+                            }
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                minter: [
+                    {
+                        required: true,
+                        message: '请输入铸造者',
+                        trigger: 'blur'
+                    }
+                ],
+                minterId: [
+                    {
+                        required: true,
+                        message: '请输入铸造者ID',
+                        trigger: 'blur'
+                    }
+                ],
+                minterAvatar: [
+                    {
+                        required: true,
+                        message: '请输入铸造者头像',
+                        trigger: 'blur'
+                    }
+                ],
+                detail: [
+                    {
+                        required: true,
+                        message: '请输入详情',
+                        trigger: 'blur'
+                    }
+                ],
+                type: [
+                    {
+                        required: true,
+                        message: '请输入类型',
+                        trigger: 'blur'
+                    }
+                ],
+                source: [
+                    {
+                        required: true,
+                        message: '请输入来源',
+                        trigger: 'blur'
+                    }
+                ],
+                total: [
+                    {
+                        required: true,
+                        message: '请输入发行数量',
+                        trigger: 'blur'
+                    }
+                ],
+                onShelf: [
+                    {
+                        required: true,
+                        message: '请输入上架',
+                        trigger: 'blur'
+                    }
+                ],
+                price: [
+                    {
+                        required: true,
+                        message: '请输入价格',
+                        trigger: 'blur'
+                    }
+                ],
+                properties: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (value) {
+                                if (!(value instanceof Array)) {
+                                    callback(new Error('properties must be array!'));
+                                    return;
+                                } else {
+                                    for (let i = 0; i < value.length; i++) {
+                                        if (value[i].name === '' || value[i].name === undefined) {
+                                            callback(new Error('请填写名称'));
+                                            return;
+                                        }
+                                        if (value[i].value === '' || value[i].value === undefined) {
+                                            callback(new Error('请填写内容'));
+                                            return;
+                                        }
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                category: [{ required: true, message: '请填写分类' }],
+                royalties: [{ required: true, message: '请填写版税' }],
+                serviceCharge: [{ required: true, message: '请填手续费' }],
+                startTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.scheduleSale) {
+                                if (!value) {
+                                    callback(new Error('请填写发布时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('发布时间不能小于当前时间'));
+                                } else {
+                                    callback();
+                                }
+                            } else {
+                                callback();
+                            }
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                saleTime: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.timeDelay) {
+                                if (!value) {
+                                    callback(new Error('请填写销售时间'));
+                                } else if (isBefore(parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()), new Date())) {
+                                    callback(new Error('销售时间不能小于当前时间'));
+                                } else if (this.formData.scheduleSale) {
+                                    if (
+                                        isBefore(
+                                            parse(value, 'yyyy-MM-dd HH:mm:ss', new Date()),
+                                            parse(this.formData.startTime, 'yyyy-MM-dd HH:mm:ss', new Date())
+                                        )
+                                    ) {
+                                        callback(new Error('销售时间不能小于发布时间'));
+                                    }
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                timeDelay: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请选择是否延迟销售'));
+                                    return;
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                totalQuota: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请输入白名单额度'));
+                                    return;
+                                }
+                            }
+
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                openQuota: [
+                    {
+                        validator: (rule, value, callback) => {
+                            if (this.formData.assignment > 0) {
+                                if (value === '' || value === undefined) {
+                                    callback(new Error('请选择开启/关闭白名单分享'));
+                                    return;
+                                }
+                            }
+                            callback();
+                        },
+                        trigger: 'blur'
+                    }
+                ],
+                chainFlag: [{ required: true, message: '请选择区块链', trigger: 'blur' }]
+            },
+            typeOptions: [
+                { label: '默认', value: 'DEFAULT' },
+                { label: '盲盒', value: 'BLIND_BOX' },
+                { label: '拍卖', value: 'AUCTION' }
+            ],
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ],
+            cateogories: ['勋章', '收藏品', '数字艺术', '门票', '游戏', '音乐', '使用', '其他'],
+            privilegeOptions: [],
+            showPrivilegeEditDialog: false,
+            privilegeForm: {},
+            privelegeRules: {
+                detail: [{ required: true, message: '请填写内容' }],
+                remark: [{ required: true, message: '请填写说明' }]
+            },
+            customUrl: resolveUrl(this.$baseUrl, 'upload/3dModel'),
+            scale: 1,
+            yOffset: 0,
+            editQuota: true
+        };
+    },
+    methods: {
+        onSave() {
+            this.$refs.form.validate(valid => {
+                if (valid) {
+                    this.submit();
+                } else {
+                    return false;
+                }
+            });
+        },
+        submit() {
+            let data = { ...this.formData };
+            if (data.model3d) {
+                data.model3d.url = data.model3d.url + '?scale=' + this.scale + '&yOffset=' + this.yOffset;
+            }
+            if (this.editQuota && data.totalQuota) {
+                data.vipQuota = data.totalQuota;
+            }
+            this.saving = true;
+            this.$http
+                .post(this.formData.id ? '/collection/save' : '/collection/create', data, { body: 'json' })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        },
+        onDelete() {
+            this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${this.formData.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.$router.go(-1);
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        console.log(e);
+                        this.$message.error((e || {}).error || '删除失败');
+                    }
+                });
+        },
+        onMinterDetail(e) {
+            console.log(e);
+            this.$set(this.formData, 'minter', e.nickname);
+            this.$set(this.formData, 'minterAvatar', e.avatar);
+        },
+        addProperty() {
+            this.formData.properties.push({
+                name: '',
+                value: ''
+            });
+        },
+        delProperty(i) {
+            this.formData.properties.splice(i, 1);
+        },
+        addPrivilege(row, i) {
+            this.privilegeForm = { ...row };
+            this.showPrivilegeEditDialog = true;
+            if (this.$refs.privilegeForm) {
+                this.$nextTick(() => {
+                    this.$refs.privilegeForm.clearValidate();
+                });
+            }
+        },
+        editPrivilege(row, i) {
+            this.privilegeForm = { ...(this.formData.privileges.find(e => e.name === row.name) || {}) };
+            this.showPrivilegeEditDialog = true;
+            if (this.$refs.privilegeForm) {
+                this.$nextTick(() => {
+                    this.$refs.privilegeForm.clearValidate();
+                });
+            }
+        },
+        delPrivilege(row, i) {
+            let idx = this.formData.privileges.findIndex(e => e.name === row.name);
+            if (idx > -1) {
+                this.formData.privileges.splice(idx, 1);
+            }
+            this.$set(this.privilegeOptions[i], 'added', false);
+        },
+        savePrivilege() {
+            this.$refs.privilegeForm
+                .validate()
+                .then(() => {
+                    let i = this.formData.privileges.findIndex(e => e.name === this.privilegeForm.name);
+                    if (i > -1) {
+                        this.$set(this.formData.privileges, i, { ...this.privilegeForm });
+                    } else {
+                        this.formData.privileges.push({ ...this.privilegeForm });
+                    }
+                    let ii = this.privilegeOptions.findIndex(i => i.name === this.privilegeForm.name);
+                    console.log(ii);
+                    this.$set(this.privilegeOptions[ii], 'added', true);
+                    this.showPrivilegeEditDialog = false;
+                })
+                .catch(e => {
+                    console.log(e);
+                });
+        }
+    },
+    watch: {
+        'formData.scanCode'(val) {
+            if (val === true) {
+                this.$set(this.formData, 'onShelf', false);
+            }
+        },
+        'formData.onShelf'(val) {
+            if (val === false && this.formData.scanCode === false) {
+                this.$set(this.formData, 'salable', false);
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+/deep/ .el-switch__label--left {
+    width: 50px;
+    text-align: right;
+}
+.number-percent {
+    display: flex;
+    align-items: center;
+    .percent {
+        border: 1px solid #dcdfe6;
+        border-radius: 4px;
+        width: 30px;
+        text-align: center;
+        line-height: 30px;
+        color: @text2;
+        font-size: 13px;
+    }
+}
+.tip {
+    font-size: 12px;
+    color: @text3;
+    margin-top: 5px;
+}
+.inline-wrapper {
+    .el-form-item {
+        display: inline-block;
+    }
+}
+.right-margin {
+    margin-left: 50px;
+}
+</style>

+ 272 - 0
src/main/vue/src/views/CompanyCollectionList1.vue

@@ -0,0 +1,272 @@
+<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">
+            <created-at-picker v-model="createdAt" @change="getData" class="filter-item"></created-at-picker>
+            <minter-filter v-model="minterId" @input="getData" class="filter-item"></minter-filter>
+            <el-input
+                placeholder="搜索..."
+                v-model="search"
+                clearable
+                class="filter-item search"
+                @keyup.enter.native="getData"
+            >
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+            v-loading="fetchingData"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="createdAt" label="创建时间" width="150">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column prop="pic" label="作品内容" width="90" align="center">
+                <template slot-scope="{ row }">
+                    <el-image
+                        style="width: 30px; height: 30px"
+                        :src="row.pic[0].thumb || row.pic[0].url"
+                        fit="cover"
+                        :preview-src-list="row.pic.map(i => i.thumb || i.url)"
+                    ></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="minter" label="铸造者"> </el-table-column>
+            <el-table-column prop="minterId" label="铸造者ID"> </el-table-column>
+            <el-table-column prop="type" label="类型" :formatter="typeFormatter"> </el-table-column>
+            <el-table-column prop="source" label="来源" :formatter="sourceFormatter"> </el-table-column>
+            <el-table-column prop="total" label="发行数量" width="105" align="center">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column prop="stock" label="剩余库存" width="105" align="center">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </el-table-column>
+            <el-table-column prop="onShelf" label="上架" width="90" align="center">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="row.onShelf">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="salable" label="仅展示" width="90" align="center">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="!row.salable">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="price" label="价格" width="90"> </el-table-column>
+            <el-table-column prop="noSoldOut" label="售罄">
+                <template v-slot="{ row }">
+                    <el-tag type="success" v-if="!row.noSoldOut">是</el-tag>
+                    <el-tag type="info" v-else>否</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column prop="sort" label="排序" width="90" align="center">
+                <template slot="header" slot-scope="{ column }">
+                    <sortable-header :column="column" :current-sort="sort" @changeSort="changeSort"> </sortable-header>
+                </template>
+            </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';
+import SortableHeader from '../components/SortableHeader.vue';
+import MinterSelect from '../components/MinterSelect.vue';
+import { format, addDays } from 'date-fns';
+
+export default {
+    components: { SortableHeader, MinterSelect },
+    name: 'CollectionList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/collection/all',
+            downloading: false,
+            typeOptions: [
+                { label: '默认', value: 'DEFAULT' },
+                { label: '展厅', value: 'SHOWROOM' },
+                { label: '盲盒', value: 'BLIND_BOX' },
+                { label: '拍卖', value: 'AUCTION' }
+            ],
+            sourceOptions: [
+                { label: '官方', value: 'OFFICIAL' },
+                { label: '用户铸造', value: 'USER' },
+                { label: '转让', value: 'TRANSFER' }
+            ],
+            sort: { sort: 'desc' },
+            sortStr: 'sort,desc',
+            createdAt: '',
+            minterId: ''
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        typeFormatter(row, column, cellValue, index) {
+            let selectedOption = this.typeOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        sourceFormatter(row, column, cellValue, index) {
+            let selectedOption = this.sourceOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            return {
+                search: this.search,
+                query: {
+                    del: false,
+                    source: 'OFFICIAL',
+                    createdAt: this.createdAt || ['1970-01-01 00:00:00', format(new Date(), 'yyyy-MM-dd HH:mm:ss')],
+                    minterId: this.minterId
+                }
+            };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/collectionEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/collectionEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/collection/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/collection/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 83 - 0
src/main/vue/src/views/CompanyGas.vue

@@ -0,0 +1,83 @@
+<template>
+    <div class="edit-view">
+        <el-card class="gas-card" shadow="never">
+            <div slot="header" class="title">剩余GAS</div>
+            <div class="balance">
+                <span class="text"> {{ balance }}</span>
+                <el-button type="primary" @click="recharge">充值</el-button>
+            </div>
+        </el-card>
+        <el-dialog title="充值" :visible.sync="showQr" @close="getBalance">
+            <vue-qrcode :value="qr" :options="{ width: 300, margin: 2 }" style="margin-left: 25px"></vue-qrcode>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import VueQrcode from '@chenfengyuan/vue-qrcode';
+import resolveUrl from 'resolve-url';
+export default {
+    name: 'CompanyGas',
+    components: { VueQrcode },
+    created() {
+        this.getBalance();
+    },
+    data() {
+        return {
+            balance: 0,
+            qr: '',
+            showQr: false
+        };
+    },
+    methods: {
+        getBalance() {
+            this.$http.get('/company/balance').then(res => {
+                this.balance = res.balance;
+            });
+        },
+        recharge() {
+            this.$prompt('请输入充值金额', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                inputType: 'number',
+                inputValidator: input => {
+                    if (input < 1) {
+                        return '最少充值1元';
+                    }
+                    return true;
+                }
+            })
+                .then(({ value }) => {
+                    this.qr = resolveUrl(
+                        this.$baseUrl,
+                        `/payOrder/v2/recharge/sandQuick?userId=${this.companyId}&amount=${value}`
+                    );
+                    this.showQr = true;
+                })
+                .catch(e => {
+                    if (e === 'cancel') {
+                        return;
+                    }
+                    this.$message.error(e.error || '充值失败');
+                });
+        }
+    },
+    watch: {}
+};
+</script>
+<style lang="less" scoped>
+.gas-card {
+    margin: 20px;
+    .title {
+        font-size: 14px;
+        color: @text2;
+    }
+    .balance {
+        font-size: 24px;
+        font-weight: bold;
+        .flex();
+        .text {
+            flex-grow: 1;
+        }
+    }
+}
+</style>

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

@@ -12,7 +12,7 @@
             </el-button>
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" name="支付" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="支付" class="filter-item"></created-at-picker>
             <el-input
                 placeholder="搜索..."
                 v-model="search"

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

@@ -21,7 +21,7 @@
             </el-button>
         </page-title>
         <div class="filters-container">
-            <created-at-picker v-model="createdAt" @input="getData" name="支付" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="支付" class="filter-item"></created-at-picker>
             <el-input
                 placeholder="搜索手机号..."
                 v-model="search"

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

@@ -64,7 +64,7 @@
                 @keyup.enter.native="getData"
             >
             </el-input>
-            <created-at-picker v-model="createdAt" @input="getData" name="下单" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="下单" class="filter-item"></created-at-picker>
         </div>
         <el-table
             :data="tableData"

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

@@ -62,7 +62,7 @@
                 @keyup.enter.native="getData"
             >
             </el-input>
-            <created-at-picker v-model="createdAt" @input="getData" name="下单" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="下单" class="filter-item"></created-at-picker>
         </div>
         <el-table
             :data="tableData"

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

@@ -12,7 +12,7 @@
                 <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
             </el-input>
             <template v-if="$store.state.userInfo && $store.state.userInfo.username === 'xiong'">
-                <el-popover class="filter-item" placement="bottom" width="350" trigger="click" v-model="showSettlePop">
+                <el-popover class="filter-item" ="bottom" width="350" trigger="click" v-model="showSettlePop">
                     <el-date-picker
                         type="daterange"
                         value-format="yyyy-MM-dd"

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

@@ -9,7 +9,7 @@
             </el-button>
         </page-title>
         <div class="filters-container" @keyup.enter="getData">
-            <created-at-picker v-model="createdAt" @input="getData" name="注册" class="filter-item"></created-at-picker>
+            <created-at-picker v-model="createdAt" @change="getData" name="注册" class="filter-item"></created-at-picker>
             <el-select placeholder="白名单筛选" v-model="vip" clearable filterable @change="getData">
                 <el-option label="白名单用户" :value="true"></el-option>
                 <el-option label="普通用户" :value="false"></el-option>

+ 15 - 19
src/main/vue/src/views/company/CompanyTheme.vue

@@ -80,7 +80,13 @@ import phoneModule from '../../components/phone/module.vue';
 export default {
     components: { phoneModule },
     name: 'ShowroomEdit',
-    created() {},
+    created() {
+        this.$http.get(`/company/get/${this.companyId}`).then(res => {
+            if (res.theme) {
+                this.formData.theme = res.theme;
+            }
+        });
+    },
     data() {
         return {
             saving: false,
@@ -107,21 +113,11 @@ export default {
     },
     methods: {
         onSave() {
-            // this.$refs.form.validate(valid => {
-            //     if (valid) {
-            //         this.$confirm('提交后将重新审核,确认提交吗', '提示', {
-            //             confirmButtonText: '确定',
-            //             cancelButtonText: '取消',
-            //             type: 'warning'
-            //         })
-            //             .then(() => {
-            //                 this.submit();
-            //             })
-            //             .catch(() => {});
-            //     } else {
-            //         return false;
-            //     }
-            // });
+            this.$http
+                .post('/company/save', { id: this.companyId, theme: this.formData.theme }, { body: 'json' })
+                .then(res => {
+                    this.$message.success('保存成功');
+                });
         },
         submit() {
             let data = { ...this.formData };
@@ -264,15 +260,15 @@ export default {
     }
 }
 
-.edit-view__content-section{
+.edit-view__content-section {
     display: flex;
-    .phone{
+    .phone {
         width: 50%;
         display: flex;
         justify-content: center;
         // padding: 20px 0;
     }
-    .el-form{
+    .el-form {
         flex-grow: 1;
     }
 }

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

@@ -3717,10 +3717,10 @@ element-resize-detector@^1.2.1:
   dependencies:
     batch-processor "1.0.0"
 
-element-ui@^2.15.1:
-  version "2.15.9"
-  resolved "https://registry.yarnpkg.com/element-ui/-/element-ui-2.15.9.tgz#b03548e007b7ab7496c49a282db92a0fffd7efc7"
-  integrity sha512-dx45nQLt4Hn87/Z9eRr3ex6KFZbxlFAwEU3QoW3wA5EsYftvHTyL9Pq7VnXXD7hu1Eiaup2jcs6kp+/VSFmXuA==
+element-ui@2.15.8:
+  version "2.15.8"
+  resolved "https://registry.npmmirror.com/element-ui/-/element-ui-2.15.8.tgz#2f505a3de33c52df1fc69be739844c1b774cfcdc"
+  integrity sha512-N54zxosRFqpYax3APY3GeRmtOZwIls6Z756WM0kdPZ5Q92PIeKHnZgF1StlamIg9bLxP1k+qdhTZvIeQlim09A==
   dependencies:
     async-validator "~1.8.1"
     babel-helper-vue-jsx-merge-props "^2.0.0"
@@ -8451,11 +8451,6 @@ vue-avatar-cropper@^1.0.5:
   resolved "https://registry.yarnpkg.com/vue-avatar-cropper/-/vue-avatar-cropper-1.0.9.tgz#b4e04cdb407fe16d159a7b2d0db6cb170ab1bf80"
   integrity sha512-qTHlx+PLtU/iZc9Pu6muP2hGrBbL0CpaBDGsenRvS7qKDsV/WzWQSM8z8CD5XpBWXm7l2toNAYwkhmWepk0YMg==
 
-vue-awesome-swiper@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/vue-awesome-swiper/-/vue-awesome-swiper-5.0.1.tgz#ba0d20ec9ca4dff2b7b4e99592cf59308335dfcc"
-  integrity sha512-mWjFJzUqA4lG+DmsmibvMpoiBnl+IH2SSeiiQ3i5M0t1y9FknTxnGT0DsMb2YdJLgjYMEK3sYOWzqgLnZMH8Lg==
-
 vue-axios@^2.1.5:
   version "2.1.5"
   resolved "https://registry.yarnpkg.com/vue-axios/-/vue-axios-2.1.5.tgz#1af4bf1218ed71309c76afb38d0f683e312c24a7"