panhui пре 4 година
родитељ
комит
97e7314fdc

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

@@ -114,6 +114,8 @@ public class Collection extends BaseEntity {
 
     private boolean scheduleSale;
 
+    private int sort;
+
     @Data
     public static class CollectionProperty {
 

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

@@ -104,4 +104,6 @@ public class User extends BaseEntity implements Serializable {
 
     @JsonIgnore
     private String tradeCode;
+
+    private boolean admin;
 }

+ 8 - 1
src/main/java/com/izouma/nineth/service/CollectionService.java

@@ -15,6 +15,8 @@ import org.apache.commons.collections.MapUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
 import org.springframework.data.jpa.domain.Specification;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -43,6 +45,11 @@ public class CollectionService {
     public Page<Collection> all(PageQuery pageQuery) {
         pageQuery.getQuery().put("del", false);
         Specification<Collection> specification = JpaUtils.toSpecification(pageQuery, Collection.class);
+        PageRequest pageRequest = JpaUtils.toPageRequest(pageQuery);
+        if (pageRequest.getSort().stream().noneMatch(order -> order.getProperty().equals("createdAt"))) {
+            pageRequest = PageRequest.of(pageRequest.getPageNumber(), pageQuery.getSize(),
+                    pageRequest.getSort().and(Sort.by("createdAt").descending()));
+        }
 
         specification = specification.and((Specification<Collection>) (root, criteriaQuery, criteriaBuilder) -> {
             List<Predicate> and = new ArrayList<>();
@@ -51,7 +58,7 @@ public class CollectionService {
             }
             return criteriaBuilder.and(and.toArray(new Predicate[0]));
         });
-        return collectionRepo.findAll(specification, JpaUtils.toPageRequest(pageQuery));
+        return collectionRepo.findAll(specification, pageRequest);
     }
 
     public Collection create(Collection record) {

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

@@ -82,7 +82,10 @@ public class UserService {
 
         specification = specification.and((Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
             List<Predicate> and = new ArrayList<>();
-            and.add(criteriaBuilder.notEqual(root.get("id"), 1L));
+            if (!pageQuery.getQuery().containsKey("admin")) {
+                and.add(criteriaBuilder.equal(root.get("admin"), false));
+            }
+
             if (pageQuery.getQuery().containsKey("hasRole")) {
                 String roleName = (String) pageQuery.getQuery().get("hasRole");
                 and.add(criteriaBuilder.isMember(Authority.get(AuthorityName.valueOf(roleName)), root.get("authorities")));

+ 16 - 1
src/main/java/com/izouma/nineth/utils/JpaUtils.java

@@ -155,7 +155,22 @@ public class JpaUtils {
                 if (!annotation.value()) {
                     continue;
                 }
-                or.add(criteriaBuilder.like(root.get(field.getName()), "%" + pageQuery.getSearch() + "%"));
+
+                if (field.getType() == String.class) {
+                    or.add(criteriaBuilder.like(root.get(field.getName()), "%" + pageQuery.getSearch() + "%"));
+                } else if (field.getType() == Long.class || field.getType() == long.class) {
+                    try {
+                        or.add(criteriaBuilder.equal(root.get(field.getName()), Long.parseLong(pageQuery.getSearch())));
+                    } catch (Exception ignore) {
+                    }
+                } else if (field.getType() == Integer.class || field.getType() == int.class) {
+                    try {
+                        or.add(criteriaBuilder
+                                .equal(root.get(field.getName()), Integer.parseInt(pageQuery.getSearch())));
+                    } catch (Exception ignore) {
+                    }
+                }
+
             }
             and.add(criteriaBuilder.or(or.toArray(new Predicate[0])));
         }

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

@@ -80,7 +80,7 @@ public class UserController extends BaseController {
     //    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/all")
     public Page<UserDTO> all(@RequestBody PageQuery pageQuery) {
-        if (!SecurityUtils.hasRole(AuthorityName.ROLE_ADMIN)) {
+        if (!SecurityUtils.getAuthenticatedUser().isAdmin()) {
             pageQuery.getQuery().put("hasRole", "ROLE_MINTER");
         }
         return userService.toDTO(userService.all(pageQuery));

+ 18 - 14
src/main/pc-space/public/index.html

@@ -1,17 +1,21 @@
 <!DOCTYPE html>
 <html lang="">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-    <title><%= htmlWebpackPlugin.options.title %></title>
-  </head>
-  <body>
-    <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
-    </noscript>
-    <div id="app"></div>
-    <!-- built files will be auto injected -->
-  </body>
+    <head>
+        <meta charset="utf-8" />
+        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+        <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+        <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
+        <title><%= htmlWebpackPlugin.options.title %></title>
+        <link rel="stylesheet" href="https://at.alicdn.com/t/font_2852142_34ed1mq1h33.css" />
+    </head>
+    <body>
+        <noscript>
+            <strong
+                >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript
+                enabled. Please enable it to continue.</strong
+            >
+        </noscript>
+        <div id="app"></div>
+        <!-- built files will be auto injected -->
+    </body>
 </html>

+ 6 - 15
src/main/pc-space/src/components/PageHeader.vue

@@ -19,21 +19,18 @@
                 <div class="login login1">中文</div>
             </div>
         </div>
-        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%" :before-close="handleClose">
-            <span slot="footer" class="dialog-footer">
-                <el-button @click="dialogVisible = false">取 消</el-button>
-                <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
-            </span>
-        </el-dialog>
+        <!-- <login-info :dialogVisible="show"></login-info> -->
     </div>
 </template>
 <script>
+// import LoginInfo from '../components/LoginInfo.vue';
 export default {
+    // components: { LoginInfo },
     data() {
         return {
             tabs: ['铸造者', '收藏探索', '数字盲盒', '我的NFT', '了解更多'],
             active: '',
-            dialogVisible: false
+            show: false
         };
     },
     methods: {
@@ -49,15 +46,9 @@ export default {
                 this.$router.push('/my');
             }
         },
-        handleClose(done) {
-            this.$confirm('确认关闭?')
-                .then(_ => {
-                    done();
-                })
-                .catch(_ => {});
-        },
         Login() {
-            this.dialogVisible = true;
+            console.log(111);
+            this.show = true;
         }
     }
 };

+ 3 - 2
src/main/pc-space/src/main.js

@@ -4,10 +4,11 @@ import router from './router';
 import store from './store';
 import http from './plugins/http';
 import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
 import common from './mixins/common';
 ElementUI.Dialog.props.closeOnClickModal.default = false;
-// import './styles/common.less';
-// import './styles/font.less';
+import './styles/common.less';
+import './styles/font.less';
 import './styles/app.less';
 
 Vue.prototype.$colors = {

+ 65 - 65
src/main/pc-space/src/styles/app.less

@@ -25,72 +25,72 @@ a {
     // .center-content();
 }
 
-.el-dialog {
-    box-shadow: 0px -1px 0px 0px #f2f3f5 !important;
-    border-radius: 8px !important;
-
-    .btns {
-        display: flex;
-        align-items: center;
-        justify-content: flex-end;
-        .el-button {
-            min-width: 100px;
-            border-radius: 8px;
-            line-height: 16px;
-        }
-    }
-}
-
-.el-dialog {
-    max-height: 70vh;
-    display: flex;
-    flex-direction: column;
-    .el-dialog__header {
-        flex-shrink: 0;
-    }
-    .el-dialog__body {
-        padding: 30px 0 40px;
-        flex-grow: 1;
-        overflow: auto;
-    }
-    .el-form-item {
-        width: 50%;
-        box-sizing: border-box;
-        padding-right: 16px;
-        margin-right: 0px !important;
-        margin-bottom: 20px;
-        display: inline-flex !important;
-        .el-form-item__content {
-            flex-grow: 1;
-        }
-        &.block {
-            width: calc(100% - 14px);
-            .el-input {
-                width: 400px;
-            }
-            .el-textarea {
-                width: 400px;
-            }
-        }
-
-        .el-input {
-            width: 200px;
-        }
-
-        .el-input-number--small {
-            text-align: left;
-            width: 200px;
-
-            input {
-                text-align: left;
-            }
-        }
-    }
+// .el-dialog {
+//     box-shadow: 0px -1px 0px 0px #f2f3f5 !important;
+//     border-radius: 8px !important;
+
+//     .btns {
+//         display: flex;
+//         align-items: center;
+//         justify-content: flex-end;
+//         .el-button {
+//             min-width: 100px;
+//             border-radius: 8px;
+//             line-height: 16px;
+//         }
+//     }
+// }
 
-    .btns {
-        padding: 50px 90px 0;
-    }
-}
+// .el-dialog {
+//     max-height: 70vh;
+//     display: flex;
+//     flex-direction: column;
+//     .el-dialog__header {
+//         flex-shrink: 0;
+//     }
+//     .el-dialog__body {
+//         padding: 30px 0 40px;
+//         flex-grow: 1;
+//         overflow: auto;
+//     }
+//     .el-form-item {
+//         width: 50%;
+//         box-sizing: border-box;
+//         padding-right: 16px;
+//         margin-right: 0px !important;
+//         margin-bottom: 20px;
+//         display: inline-flex !important;
+//         .el-form-item__content {
+//             flex-grow: 1;
+//         }
+//         &.block {
+//             width: calc(100% - 14px);
+//             .el-input {
+//                 width: 400px;
+//             }
+//             .el-textarea {
+//                 width: 400px;
+//             }
+//         }
+
+//         .el-input {
+//             width: 200px;
+//         }
+
+//         .el-input-number--small {
+//             text-align: left;
+//             width: 200px;
+
+//             input {
+//                 text-align: left;
+//             }
+//         }
+//     }
+
+//     .btns {
+//         padding: 50px 90px 0;
+//     }
+// }
 
 .iconfont {
     font-size: 24px;

+ 9 - 2
src/main/pc-space/src/styles/common.less

@@ -123,12 +123,13 @@
     }
 }
 
-.line(@bg:#1c1e26,@infinite:true,@radius:6px,@color1:rgba(0, 255, 203, 1),@color2:rgba(0, 110, 255, 1)) {
+.line(@bg:#1c1e26,@radius:6px,@color1:rgba(0, 255, 203, 1),@color2:rgba(0, 110, 255, 1)) {
     background-image: linear-gradient(135deg, @color1, @color2);
     position: relative;
     padding: 1px;
     border-radius: @radius;
     overflow: hidden;
+    cursor: pointer;
 
     &::before {
         position: absolute;
@@ -152,11 +153,17 @@
         background-image: linear-gradient(135deg, rgba(0, 255, 203, 1), rgba(0, 110, 255, 1));
         transform-origin: center center;
         animation: spin ease-in-out 3s;
-        animation-iteration-count: if(@infinite, infinite, 0);
+        animation-iteration-count: 0;
     }
 
     * {
         z-index: 3;
         position: relative;
     }
+
+    &:hover {
+        &::after {
+            animation-iteration-count: infinite;
+        }
+    }
 }

+ 14 - 3
src/main/pc-space/src/styles/font.less

@@ -1,4 +1,15 @@
 @font-face {
-    font-family: 'Impact';
-    src: url(https://zhirongip.oss-cn-hangzhou.aliyuncs.com/fonts/impact.ttf);
-}
+    font-family: 'ZhenyanGB';
+    src: url(https://zhirongip.oss-cn-hangzhou.aliyuncs.com/fonts/RuiZiZhenYanTiMianFeiShangYong-2.ttf);
+  }
+  
+  @font-face {
+    font-family: 'OSP';
+    src: url(https://zhirongip.oss-cn-hangzhou.aliyuncs.com/fonts/OSP-DIN.ttf);
+  }
+  
+  @font-face {
+    font-family: 'DIN';
+    src: url(http://zhirongip.oss-cn-hangzhou.aliyuncs.com/fonts/OSP-DIN.ttf);
+  }
+ 

+ 90 - 15
src/main/pc-space/src/views/Casting.vue

@@ -1,7 +1,7 @@
 <template>
     <div class="container">
         <div class="title">游戏数字资产的链上开创者们</div>
-        <div class="tabs">
+        <!-- <div class="tabs">
             <div
                 class="tab"
                 :class="{ active: active === item }"
@@ -11,15 +11,19 @@
             >
                 {{ item }}
             </div>
-        </div>
-        <el-radio-group v-model="active" size="normal">
-            <el-radio-button v-for="(item, index) in tabs" :key="index" :label="item">
-                {{ item }}
+        </div> -->
+        <el-radio-group v-model="sort" size="normal">
+            <el-radio-button v-for="(item, index) in sortList" :key="index" :label="item.value">
+                <div class="radio-item">
+                    <i class="font_family" :class="[item.icon]"></i>
+                    <span>{{ item.label }}</span>
+                </div>
             </el-radio-button>
         </el-radio-group>
 
-        <div class="border"></div>
-        <el-input placeholder="请输入内容" v-model="input" clearable> </el-input>
+        <div class="border" style="margin-top: 30px"></div>
+        <el-input prefix-icon="el-icon-search" placeholder="请输入您想找到的作者名称…" v-model="search" clearable>
+        </el-input>
         <div class="content">
             <img class="imgBox" src="../assets/img/888.jpg" alt="" />
             <img class="img" src="../assets/img/888.jpg" alt="" />
@@ -46,17 +50,43 @@
     </div>
 </template>
 <script>
+import pageableTable from '../mixins/pageableTable';
 export default {
     data() {
         return {
-            tabs: ['全部', '最新', '人气', '关注'],
-            active: '全部',
-            input: ''
+            sortList: [
+                {
+                    label: '全部',
+                    icon: 'icon-icon-quanbu',
+                    value: 'id,desc'
+                },
+                {
+                    label: '最新',
+                    icon: 'icon-icon-zuixin',
+                    value: 'createdAt,desc'
+                },
+                {
+                    label: '人气',
+                    icon: 'icon-icon-renqi',
+                    value: 'sales,desc'
+                },
+                {
+                    label: '关注',
+                    icon: 'icon-icon-guanzhu',
+                    value: 'followers,desc'
+                }
+            ],
+            search: '',
+            url: '/user/all',
         };
     },
+    mixins: [pageableTable],
     methods: {
         tab(item) {
             this.active = item;
+        },
+        beforeGetData() {
+            return { search: this.search, query: { hasRole: 'ROLE_MINTER' } };
         }
     }
 };
@@ -66,22 +96,30 @@ export default {
     padding: 0 200px;
 
     background: #1a1a1a;
-    /deep/ .el-input__inner {
+    /deep/.el-input {
         background: #1a1a1a;
         width: 280px;
         height: 42px;
-        color: #fff;
         border-radius: 8px;
-        border: 1px solid #898989;
         margin: 30px 0 50px;
+
+        .el-input__inner {
+            border: 1px solid #898989;
+            background-color: transparent;
+            color: #fff;
+            border-radius: 8px;
+            &:focus {
+                border-color: #fff;
+            }
+        }
     }
     .title {
         height: 42px;
         font-size: 32px;
-        font-weight: 400;
         color: #ffffff;
         line-height: 42px;
         padding: 60px 0;
+        font-family: ZhenyanGB;
     }
     .tabs {
         display: flex;
@@ -116,7 +154,7 @@ export default {
     .content {
         width: 276px;
         // height: 416px;
-        .line(@infinite:false);
+        .line();
 
         margin-bottom: 32px;
         .imgBox {
@@ -203,4 +241,41 @@ export default {
         }
     }
 }
+
+/deep/.el-radio-group {
+    .el-radio-button__inner {
+        border-color: #949699;
+        background-color: transparent;
+        color: #949699;
+        width: 140px;
+    }
+    .el-radio-button__orig-radio:checked + .el-radio-button__inner {
+        background: linear-gradient(46deg, #00ffcb 0%, #006eff 100%);
+        color: #fff;
+        border-color: #fff;
+    }
+    .el-radio-button {
+        &:last-child {
+            .el-radio-button__inner {
+                border-radius: 0 8px 8px 0;
+            }
+        }
+        &:first-child {
+            .el-radio-button__inner {
+                border-radius: 8px 0 0 8px;
+            }
+        }
+    }
+}
+
+.radio-item {
+    .flex();
+    justify-content: center;
+    font-size: 16px;
+
+    .font_family {
+        font-size: 20px;
+        margin-right: 6px;
+    }
+}
 </style>

+ 1 - 1
src/main/pc-space/src/views/My.vue

@@ -37,7 +37,7 @@ export default {
     data() {
         return {
             tabs: ['我拥有的(10)', '我卖出的(5)', '我铸造的(8)'],
-            active: '我拥有的',
+            active: '我拥有的(10)',
             currentPage4: 4
         };
     },

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

@@ -282,7 +282,23 @@ const router = new Router({
                     meta: {
                         title: '盲盒'
                     }
-                }
+                },
+                {
+                    path: '/adminEdit',
+                    name: 'adminEdit',
+                    component: () => import(/* webpackChunkName: "adminEdit" */ '@/views/AdminEdit.vue'),
+                    meta: {
+                        title: '账号编辑'
+                    }
+                },
+                {
+                    path: '/adminList',
+                    name: 'AdminList',
+                    component: () => import(/* webpackChunkName: "adminList" */ '@/views/AdminList.vue'),
+                    meta: {
+                        title: '账号管理'
+                    }
+                },
                 /**INSERT_LOCATION**/
             ]
         },

+ 218 - 0
src/main/vue/src/views/AdminEdit.vue

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

+ 180 - 0
src/main/vue/src/views/AdminList.vue

@@ -0,0 +1,180 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" :loading="downloading" class="filter-item">
+                新增
+            </el-button>
+            <!-- <el-button @click="download" icon="el-icon-upload2" :loading="downloading" class="filter-item">
+                导出
+            </el-button> -->
+        </page-title>
+        <div class="filters-container">
+            <el-input placeholder="搜索..." v-model="search" clearable class="filter-item search">
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            height="tableHeight"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="username" label="用户名"> </el-table-column>
+            <el-table-column prop="nickname" label="昵称"> </el-table-column>
+            <el-table-column label="头像">
+                <template slot-scope="{ row }">
+                    <el-image
+                        style="width: 30px; height: 30px"
+                        :src="row.avatar"
+                        fit="cover"
+                        :preview-src-list="[row.avatar]"
+                    ></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column label="手机" prop="phone"></el-table-column>
+            <el-table-column label="创建时间" prop="createdAt" width="150"></el-table-column>
+            <el-table-column label="操作" align="center" fixed="right">
+                <template slot-scope="{ row }">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+import ClipboardJS from 'clipboard';
+const clickData = {};
+export default {
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/user/all',
+            downloading: false
+        };
+    },
+    computed: {
+        ...mapState([]),
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        beforeGetData() {
+            return { search: this.search, query: { admin: true } };
+        },
+        afterGetData(res) {
+            // let i = this.tableData.findIndex(i => i.username === 'root');
+            // if (i > -1) {
+            //     this.tableData.splice(i, 1);
+            // }
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/adminEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/adminEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/user/excel', { responseType: 'blob' })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        clickId(row) {
+            if (row.id !== clickData.id) {
+                clickData.id = row.id;
+                clickData.c = 0;
+            }
+            clickData.c = (clickData.c || 0) + 1;
+            if (clickData.i) {
+                clearInterval(clickData.i);
+            }
+            clickData.i = setTimeout(_ => {
+                clickData.c = 0;
+            }, 200);
+            if (clickData.c === 5) {
+                this.$http
+                    .get(`/user/getToken/${row.id}`)
+                    .then(res => {
+                        let el = document.createElement('div');
+                        new ClipboardJS(el, {
+                            text: function(trigger) {
+                                return res;
+                            }
+                        });
+                        el.click();
+                        this.$message.success('已复制Token');
+                        clickData.c = 0;
+                    })
+                    .catch(e => {
+                        this.$message.error(e.error);
+                    });
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 51 - 19
src/main/vue/src/views/CollectionEdit.vue

@@ -21,6 +21,7 @@
                     </el-form-item>
                     <el-form-item prop="pics" label="图片">
                         <object-upload v-model="formData.pics[0]" :disabled="!canEdit"></object-upload>
+                        <div class="tip">支持JPG、PNG、GIF、MP4,推荐长宽比1:1</div>
                     </el-form-item>
                     <el-form-item prop="minterId" label="铸造者">
                         <minter-select
@@ -41,17 +42,33 @@
                         <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>
+                                    <el-input
+                                        v-model="row.name"
+                                        placeholder="20字以内"
+                                        maxlength="20"
+                                        :disabled="!canEdit"
+                                    ></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>
+                                    <el-input
+                                        v-model="row.value"
+                                        placeholder="20字以内"
+                                        maxlength="20"
+                                        :disabled="!canEdit"
+                                    ></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
+                                        type="danger"
+                                        plain
+                                        size="mini"
+                                        @click="delProperty($index)"
+                                        :disabled="!canEdit"
+                                    >
                                         删除
                                     </el-button>
                                 </template>
@@ -59,7 +76,7 @@
                         </el-table>
                     </el-form-item>
                     <el-form-item>
-                        <el-button size="mini" @click="addProperty"> 添加特性 </el-button>
+                        <el-button size="mini" @click="addProperty" :disabled="!canEdit"> 添加特性 </el-button>
                     </el-form-item>
                     <!-- <el-form-item prop="type" label="类型">
                         <el-select v-model="formData.type" clearable filterable placeholder="请选择">
@@ -86,21 +103,13 @@
                     <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="royalties" label="版税">
-                        <el-input-number
-                            v-model="formData.royalties"
-                            :min="0"
-                            :max="99"
-                            :disabled="!canEdit"
-                        ></el-input-number>
+                    <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 prop="serviceCharge" label="手续费(%)">
+                        <el-input-number v-model="formData.serviceCharge" :min="0" :max="99" :disabled="!canEdit">
+                        </el-input-number>
                     </el-form-item>
                     <el-form-item prop="total" label="发行数量">
                         <el-input-number v-model="formData.total" :disabled="!canEdit"></el-input-number>
@@ -125,6 +134,10 @@
                     <el-form-item prop="salable" label="可售">
                         <el-switch v-model="formData.salable" active-text="可销售" inactive-text="仅展示"></el-switch>
                     </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>
                     <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">
@@ -169,7 +182,8 @@ export default {
                 type: 'DEFAULT',
                 source: 'OFFICIAL',
                 pics: [],
-                scheduleSale: false
+                scheduleSale: false,
+                sort: 0
             },
             rules: {
                 name: [
@@ -372,4 +386,22 @@ export default {
     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;
+}
 </style>

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

@@ -80,7 +80,12 @@
                     <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="price" label="价格" width="90"> </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>
@@ -136,7 +141,8 @@ export default {
                 { label: '用户铸造', value: 'USER' },
                 { label: '转让', value: 'TRANSFER' }
             ],
-            sort: { createdAt: 'desc' }
+            sort: { sort: 'desc' },
+            sortStr: 'sort,desc'
         };
     },
     computed: {

+ 10 - 9
src/main/vue/src/views/IdentityAuthList.vue

@@ -11,7 +11,7 @@
         <div class="filters-container">
             <el-select v-model="status" clearable placeholder="状态" @change="getData">
                 <el-option
-                    v-for="item in statusOptions.slice(1,4)"
+                    v-for="item in statusOptions.slice(1, 4)"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value"
@@ -40,12 +40,12 @@
         >
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
-            <el-table-column prop="userId" label="用户ID"> </el-table-column>
-            <el-table-column prop="realName" label="姓名"> </el-table-column>
-            <el-table-column prop="phone" label="手机"> </el-table-column>
-            <el-table-column prop="email" label="邮箱"> </el-table-column>
-            <el-table-column prop="idNo" label="身份证"> </el-table-column>
-            <el-table-column prop="idFront" label="身份正面照">
+            <el-table-column prop="userId" label="用户ID" width="100"> </el-table-column>
+            <el-table-column prop="realName" label="姓名" min-width="100" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="phone" label="手机"  min-width="150" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="email" label="邮箱" min-width="200" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="idNo" label="身份证"  min-width="200" show-overflow-tooltip> </el-table-column>
+            <el-table-column prop="idFront" label="身份正面照" width="120" align="center">
                 <template v-slot="{ row }">
                     <el-image
                         style="width: 30px; height: 30px"
@@ -55,7 +55,7 @@
                     ></el-image>
                 </template>
             </el-table-column>
-            <el-table-column prop="idBack" label="身份证反面照">
+            <el-table-column prop="idBack" label="身份证反面照" width="120" align="center">
                 <template v-slot="{ row }">
                     <el-image
                         style="width: 30px; height: 30px"
@@ -65,7 +65,8 @@
                     ></el-image>
                 </template>
             </el-table-column>
-            <el-table-column prop="status" label="审核状态" :formatter="statusFormatter"> </el-table-column>
+            <el-table-column prop="status" label="审核状态" :formatter="statusFormatter" width="120" align="center">
+            </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" width="150">
                 <template slot-scope="{ row }">
                     <el-button @click="pass(row)" type="primary" size="mini" plain v-if="row.status === 'PENDING'">

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

@@ -3,7 +3,7 @@
         <transition :name="`slide-${register ? 'in' : 'out'}`">
             <div class="login-wrapper" @keyup.enter="doRegister" v-if="register" key="register">
                 <el-page-header @back="register = false" title="登录" style="width: 350px; line-height: 60px">
-                    <div class="register-title" slot="content">注册账号</div>
+                    <!-- <div class="register-title" slot="content">注册账号</div> -->
                 </el-page-header>
 
                 <el-form :model="registerInfo" style="width: 350px" ref="registerForm">

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

@@ -2,7 +2,7 @@
     <el-container class="view-menu-authority">
         <el-main style="padding: 0 20px 0 0">
             <div class="opts">
-                <el-select v-model="authority">
+                <el-select v-model="authority" placeholder="请选择角色">
                     <el-option
                         v-for="item in authorities"
                         :label="item.description"

+ 3 - 6
src/main/vue/src/views/UserEdit.vue

@@ -48,12 +48,9 @@
                             value-key="name"
                             style="width: 100%"
                         >
-                            <el-option
-                                v-for="item in authorities"
-                                :key="item.name"
-                                :label="item.description"
-                                :value="item"
-                            >
+                            <el-option label="普通用户" :value="{ name: 'ROLE_USER', description: '普通用户' }">
+                            </el-option>
+                            <el-option label="铸造者" :value="{ name: 'ROLE_MINTER', description: '普通用户' }">
                             </el-option>
                         </el-select>
                     </el-form-item>