xiongzhu vor 4 Jahren
Ursprung
Commit
b3f7f1f61d

+ 6 - 0
pom.xml

@@ -338,6 +338,12 @@
             <artifactId>javafaker</artifactId>
             <version>1.0.2</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.bytedeco</groupId>
+            <artifactId>javacv-platform</artifactId>
+            <version>1.5.6</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 15 - 1
src/main/java/com/izouma/nineth/converter/FileObjectListConverter.java

@@ -5,7 +5,10 @@ import com.izouma.nineth.domain.FileObject;
 import org.apache.commons.lang3.StringUtils;
 
 import javax.persistence.AttributeConverter;
+import java.util.Arrays;
 import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 public class FileObjectListConverter implements AttributeConverter<List<FileObject>, String> {
     @Override
@@ -18,7 +21,18 @@ public class FileObjectListConverter implements AttributeConverter<List<FileObje
     @Override
     public List<FileObject> convertToEntityAttribute(String s) {
         if (StringUtils.isNotEmpty(s)) {
-            return JSON.parseArray(s, FileObject.class);
+            if (!Pattern.matches("\\[.+]", s)) {
+                return Arrays.stream(s.split(",")).map(ss -> {
+                    FileObject fileObject = new FileObject();
+                    fileObject.setUrl(ss);
+                    return fileObject;
+                }).collect(Collectors.toList());
+            }
+            try {
+                return JSON.parseArray(s, FileObject.class);
+            } catch (Exception e) {
+
+            }
         }
         return null;
     }

+ 3 - 2
src/main/java/com/izouma/nineth/domain/Asset.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.domain;
 
 import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.StringArrayConverter;
 import com.izouma.nineth.enums.AssetStatus;
 import io.swagger.annotations.ApiModel;
@@ -75,9 +76,9 @@ public class Asset extends BaseEntity {
     private String minterAvatar;
 
     @ApiModelProperty("图片")
-    @Convert(converter = StringArrayConverter.class)
+    @Convert(converter = FileObjectListConverter.class)
     @Column(columnDefinition = "TEXT")
-    private List<String> pic;
+    private List<FileObject> pic;
 
     @ApiModelProperty("tokenId")
     private String tokenId;

+ 3 - 2
src/main/java/com/izouma/nineth/domain/BlindBoxItem.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.domain;
 
 import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.StringArrayConverter;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
@@ -31,8 +32,8 @@ public class BlindBoxItem extends BaseEntity {
 
     @ApiModelProperty("图片")
     @Column(columnDefinition = "TEXT")
-    @Convert(converter = StringArrayConverter.class)
-    private List<String> pics;
+    @Convert(converter = FileObjectListConverter.class)
+    private List<FileObject> pics;
 
     @ApiModelProperty("铸造者")
     @Searchable

+ 3 - 2
src/main/java/com/izouma/nineth/domain/BlinkBox.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.domain;
 
 import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.StringArrayConverter;
 import com.izouma.nineth.enums.CollectionSource;
 import com.izouma.nineth.enums.CollectionType;
@@ -29,8 +30,8 @@ public class BlinkBox extends BaseEntity {
 
     @ApiModelProperty("图片")
     @Column(columnDefinition = "TEXT")
-    @Convert(converter = StringArrayConverter.class)
-    private List<String> pics;
+    @Convert(converter = FileObjectListConverter.class)
+    private List<FileObject> pics;
 
     @ApiModelProperty("铸造者")
     @Searchable

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

@@ -3,6 +3,7 @@ package com.izouma.nineth.domain;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.StringArrayConverter;
 import com.izouma.nineth.enums.CollectionSource;
 import com.izouma.nineth.enums.CollectionType;
@@ -34,8 +35,8 @@ public class Collection extends BaseEntity {
 
     @ApiModelProperty("图片")
     @Column(columnDefinition = "TEXT")
-    @Convert(converter = StringArrayConverter.class)
-    private List<String> pics;
+    @Convert(converter = FileObjectListConverter.class)
+    private List<FileObject> pics;
 
     @ApiModelProperty("铸造者")
     @Searchable

+ 8 - 0
src/main/java/com/izouma/nineth/domain/FileObject.java

@@ -1,10 +1,18 @@
 package com.izouma.nineth.domain;
 
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class FileObject {
     String name;
 
     String url;
+
+    String thumb;
+
+    String type;
 }

+ 3 - 2
src/main/java/com/izouma/nineth/domain/Order.java

@@ -1,6 +1,7 @@
 package com.izouma.nineth.domain;
 
 import com.izouma.nineth.annotations.Searchable;
+import com.izouma.nineth.converter.FileObjectListConverter;
 import com.izouma.nineth.converter.StringArrayConverter;
 import com.izouma.nineth.enums.CollectionType;
 import com.izouma.nineth.enums.OrderStatus;
@@ -43,9 +44,9 @@ public class Order extends BaseEntity {
     private String name;
 
     @ApiModelProperty("图片")
-    @Convert(converter = StringArrayConverter.class)
+    @Convert(converter = FileObjectListConverter.class)
     @Column(columnDefinition = "TEXT")
-    private List<String> pic;
+    private List<FileObject> pic;
 
     @ApiModelProperty("详情")
     @Column(columnDefinition = "TEXT")

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

@@ -56,7 +56,7 @@ public class AssetService {
         }
         try {
             NFT nft = nftService.createToken(user.getNftAccount());
-            String ipfsUrl = ipfsUpload(order.getPic().get(0));
+            String ipfsUrl = ipfsUpload(order.getPic().get(0).getUrl());
             if (nft != null) {
                 Asset asset = Asset.builder()
                         .userId(user.getId())
@@ -125,7 +125,7 @@ public class AssetService {
                         .gasUsed(nft.getGasUsed())
                         .price(order.getPrice())
                         .status(AssetStatus.NORMAL)
-                        .ipfsUrl(ipfsUpload(winItem.getPics().get(0)))
+                        .ipfsUrl(ipfsUpload(winItem.getPics().get(0).getUrl()))
                         .build();
                 assetRepo.save(asset);
                 applicationContext.publishEvent(new CreateAssetEvent(this, true, order, asset));

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

@@ -1,10 +1,18 @@
 package com.izouma.nineth.web;
 
+import com.izouma.nineth.domain.FileObject;
 import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.service.storage.StorageService;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.Frame;
+import org.bytedeco.javacv.Java2DFrameConverter;
+import org.pngquant.PngQuant;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,13 +20,15 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
 import java.net.URLConnection;
 import java.text.SimpleDateFormat;
 import java.util.Base64;
 import java.util.Date;
+import java.util.Objects;
+import java.util.Optional;
 
 
 @RestController
@@ -39,8 +49,8 @@ public class FileUploadController {
             } catch (Exception ignored) {
             }
             path = basePath + "/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
-                   + RandomStringUtils.randomAlphabetic(8)
-                   + "." + FilenameUtils.getExtension(file.getOriginalFilename());
+                    + RandomStringUtils.randomAlphabetic(8)
+                    + "." + FilenameUtils.getExtension(file.getOriginalFilename());
         }
         InputStream is;
         try {
@@ -66,7 +76,7 @@ public class FileUploadController {
             } catch (Exception ignored) {
             }
             path = "image/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
-                   + RandomStringUtils.randomAlphabetic(8) + ext;
+                    + RandomStringUtils.randomAlphabetic(8) + ext;
         }
         InputStream is;
         try {
@@ -77,4 +87,50 @@ public class FileUploadController {
         }
         return storageService.uploadFromInputStream(is, path);
     }
+
+    @PostMapping("/fileObject")
+    public FileObject uploadFileObject(@RequestParam("file") MultipartFile file) throws IOException {
+        String ext = Optional.ofNullable(FilenameUtils.getExtension(file.getOriginalFilename())).orElse("");
+        String[] extList = new String[]{"jpg", "jpeg", "png", "gif", "mp4"};
+        if (!ArrayUtils.contains(extList, ext.toLowerCase())) {
+            throw new BusinessException("仅支持" + StringUtils.join(extList, "、") + "格式");
+        }
+        File tmpFile = File.createTempFile("upload_", "." + ext);
+        FileUtils.copyToFile(file.getInputStream(), tmpFile);
+        String path = "nft/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+                + RandomStringUtils.randomAlphabetic(8)
+                + "." + ext;
+        String url = storageService.uploadFromInputStream(new FileInputStream(tmpFile), path);
+        String thumbUrl = null;
+        if ("mp4".equalsIgnoreCase(ext)) {
+            FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(tmpFile);
+            frameGrabber.start();
+            Java2DFrameConverter aa = new Java2DFrameConverter();
+
+            try {
+                BufferedImage bi;
+                Frame f = frameGrabber.grabKeyFrame();
+
+                bi = aa.convert(f);
+                File thumbFile = null;
+                while (bi != null) {
+                    thumbFile = File.createTempFile("video_thumb_", ".png");
+                    PngQuant pngQuant = new PngQuant();
+                    ImageIO.write(pngQuant.getRemapped(bi), "png", thumbFile);
+                    f = frameGrabber.grabKeyFrame();
+                    bi = aa.convert(f);
+                }
+                Objects.requireNonNull(thumbFile);
+                String thumbPath = "thumb_image/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+                        + RandomStringUtils.randomAlphabetic(8) + ".png";
+                thumbUrl = storageService.uploadFromInputStream(new FileInputStream(thumbFile), thumbPath);
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                frameGrabber.stop();
+            }
+        }
+
+        return new FileObject(file.getOriginalFilename(), url, thumbUrl, file.getContentType());
+    }
 }

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

@@ -54,8 +54,8 @@ jwt:
   expiration: 2592000 #30days
 wx:
   mp:
-    app_id: wx2375cba2eec2c479
-    app_secret: 28e4829124860d9ef9e2f32aeefd1111
+    app_id: wx32eeacbe7e6e7f6e
+    app_secret: fbeb320efd8401ac52c28f4c94389771
   ma:
     app_id: wx6517cbf58115c508
     app_secret: 8af0e8ba28ffddfb7e574e6c7cb6aaf1
@@ -63,7 +63,7 @@ wx:
     msg_aes_key: aesKey
     msg_format: JSON
   pay:
-    app-id: wx2375cba2eec2c479
+    app-id: wx32eeacbe7e6e7f6e
     mch-id: 1614898941
     mch-key: A2qWTQzN5EkvgeWgmnnTSY4vV3Y6Xxbj
     sub-app-id:

+ 9 - 1
src/main/vue/src/components/MinterSelect.vue

@@ -8,6 +8,7 @@
         :remote-method="searchMinter"
         :loading="searchingMinter"
         @change="onChange"
+        :disabled="disabled"
     >
         <el-option
             v-for="item in mergedOptions"
@@ -26,7 +27,14 @@
 </template>
 <script>
 export default {
-    props: ['value'],
+    props: {
+        value: {},
+        disabled: {
+            default() {
+                return false;
+            }
+        }
+    },
     data() {
         return {
             minters: [],

+ 159 - 0
src/main/vue/src/components/ObjectUpload.vue

@@ -0,0 +1,159 @@
+<template>
+    <div class="preview" v-if="fileObject.url">
+        <el-image
+            style="width: 100%; height: 100%"
+            :src="fileObject.thumb || fileObject.url"
+            :preview-src-list="[fileObject.thumb || fileObject.url]"
+            fit="contain"
+            ref="img"
+        ></el-image>
+        <div class="preview-btns">
+            <i class="el-icon-view" @click="preview"></i>
+            <i class="el-icon-delete" @click="del" v-if="!disabled"></i>
+        </div>
+        <el-dialog :visible.sync="showVideoDialog" width="800px" :before-close="beforeCloseVideo">
+            <video
+                ref="video"
+                :src="fileObject.url"
+                :poster="fileObject.thumb"
+                controls
+                style="width: 100%; height: 60vh; object-fit: contain"
+            ></video>
+        </el-dialog>
+    </div>
+    <el-upload
+        v-else
+        class="avatar-uploader"
+        :action="uploadUrl"
+        :show-file-list="false"
+        :on-success="onSuccess"
+        :before-upload="beforeUpload"
+        :on-error="onError"
+        :disabled="disabled"
+    >
+        <div class="upload" v-loading="loading">
+            <i class="el-icon-plus"></i>
+        </div>
+    </el-upload>
+</template>
+<script>
+import resolveUrl from 'resolve-url';
+export default {
+    props: {
+        value: {},
+        disabled: {
+            default: false
+        }
+    },
+    data() {
+        return {
+            fileObject: {},
+            loading: false,
+            showVideoDialog: false
+        };
+    },
+    created() {
+        this.uploadUrl = resolveUrl(this.$baseUrl, 'upload/fileObject');
+        this.fileObject = this.value || {};
+    },
+    methods: {
+        onSuccess(e) {
+            console.log(e);
+            this.loading = false;
+            this.fileObject = e;
+        },
+        beforeUpload(file) {
+            if (!/\.(jpg|jpeg|png|gif|mp4)$/i.test(file.name)) {
+                this.$message.error('仅支持jpg、png、gif、mp4');
+                return false;
+            }
+            this.loading = true;
+            return true;
+        },
+        onError(e) {
+            this.$message.error('上传失败');
+            this.loading = false;
+        },
+        del() {
+            this.fileObject = {};
+        },
+        preview() {
+            if (/\.mp4$/i.test(this.fileObject.url)) {
+                this.showVideoDialog = true;
+                this.$nextTick(() => {
+                    this.$refs.video.play();
+                });
+            } else {
+                this.$refs.img.showViewer = true;
+            }
+        },
+        beforeCloseVideo(done) {
+            this.$refs.video.pause();
+            done();
+        }
+    },
+    watch: {
+        value(val) {
+            this.fileObject = val || {};
+        },
+        fileObject(val) {
+            this.$emit('input', val);
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+.upload {
+    width: 150px;
+    height: 150px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border: 1px dashed #409eff;
+    border-radius: 8px;
+    overflow: hidden;
+    i {
+        font-size: 24px;
+        color: #409eff;
+    }
+}
+.preview {
+    width: 150px;
+    height: 150px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border: 1px dashed #409eff;
+    border-radius: 8px;
+    overflow: hidden;
+    position: relative;
+    img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+    }
+    .preview-btns {
+        display: none;
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background: rgba(0, 0, 0, 0.5);
+        align-items: center;
+        justify-content: center;
+        color: white;
+        font-size: 24px;
+        i {
+            margin: 0 10px;
+            cursor: pointer;
+            text-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
+        }
+    }
+    &:hover {
+        .preview-btns {
+            display: flex;
+        }
+    }
+}
+</style>

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

@@ -16,6 +16,7 @@ import DistrictChoose from '@/components/DistrictChoose';
 import Formatters from '@/mixins/formatters';
 import PageTitle from '@/components/PageTitle';
 import MinterSelect from '@/components/MinterSelect';
+import ObjectUpload from '@/components/ObjectUpload';
 
 import 'normalize.css/normalize.css';
 import './styles/element_theme/index.css';
@@ -56,6 +57,7 @@ Vue.component('crop-upload', CropUpload);
 Vue.component('district-choose', DistrictChoose);
 Vue.component('page-title', PageTitle);
 Vue.component('minter-select', MinterSelect);
+Vue.component('object-upload', ObjectUpload);
 Vue.mixin(Formatters);
 Vue.prototype.$theme = theme;
 console.log(theme);

+ 56 - 14
src/main/vue/src/views/BlindBoxEdit.vue

@@ -3,7 +3,7 @@
         <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" 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">
@@ -17,16 +17,20 @@
                     style="max-width: 500px"
                 >
                     <el-form-item prop="name" label="名称">
-                        <el-input v-model="formData.name"></el-input>
+                        <el-input v-model="formData.name" :disabled="!canEdit"></el-input>
                     </el-form-item>
                     <el-form-item prop="pics" label="图片">
-                        <multi-upload v-model="formData.pics"></multi-upload>
+                        <object-upload v-model="formData.pics[0]" :disabled="!canEdit"></object-upload>
                     </el-form-item>
                     <el-form-item prop="minterId" label="创建者">
-                        <minter-select v-model="formData.minterId" @detail="onMinterDetail"></minter-select>
+                        <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">
+                        <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>
@@ -80,7 +84,7 @@
                         </el-select>
                     </el-form-item> -->
                     <el-form-item prop="price" label="价格">
-                        <el-input-number type="number" v-model="formData.price"></el-input-number>
+                        <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>
@@ -100,15 +104,21 @@
                             </el-table-column>
                             <el-table-column width="100">
                                 <template v-slot="{ row, $index }">
-                                    <el-button @click="removeItem($index)" type="danger" plain size="mini"
-                                        >移除</el-button
+                                    <el-button
+                                        @click="removeItem($index)"
+                                        type="danger"
+                                        plain
+                                        size="mini"
+                                        :disabled="!canEdit"
                                     >
+                                        移除
+                                    </el-button>
                                 </template>
                             </el-table-column>
                         </el-table>
                     </el-form-item>
                     <el-form-item>
-                        <el-button size="mini" @click="addItem">添加作品</el-button>
+                        <el-button size="mini" @click="addItem" :disabled="!canEdit">添加作品</el-button>
                     </el-form-item>
                     <!-- <el-form-item prop="likes" label="点赞">
                         <el-input-number v-model="formData.likes"></el-input-number>
@@ -120,7 +130,11 @@
                         <el-switch v-model="formData.salable" active-text="可销售" inactive-text="仅展示"></el-switch>
                     </el-form-item>
                     <el-form-item prop="startTime" label="开售时间">
-                        <el-date-picker type="datetime" value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
+                        <el-date-picker
+                            v-model="formData.startTime"
+                            type="datetime"
+                            value-format="yyyy-MM-dd HH:mm:ss"
+                        ></el-date-picker>
                     </el-form-item>
                     <el-form-item class="form-submit">
                         <el-button @click="onSave" :loading="saving" type="primary" v-if="!formData.id">
@@ -233,10 +247,22 @@ export default {
         //     }
         // ];
     },
+    computed: {
+        canEdit() {
+            return !!!this.$route.query.id;
+        }
+    },
     data() {
         return {
             saving: false,
-            formData: { onShelf: true, salable: true, properties: [], type: 'BLIND_BOX', source: 'OFFICIAL' },
+            formData: {
+                onShelf: true,
+                salable: true,
+                properties: [],
+                type: 'BLIND_BOX',
+                source: 'OFFICIAL',
+                pics: [{}]
+            },
             rules: {
                 name: [
                     {
@@ -247,8 +273,24 @@ export default {
                 ],
                 pics: [
                     {
-                        required: true,
-                        message: '请输入图片',
+                        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'
                     }
                 ],
@@ -329,7 +371,7 @@ export default {
                                             return;
                                         }
                                         if (value[i].value === '' || value[i].value === undefined) {
-                                            callback(new Error('请填写内容'));
+                                            callback(new Error('请添加作品'));
                                             return;
                                         }
                                     }

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

@@ -45,13 +45,13 @@
             <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
             <el-table-column prop="id" label="ID" width="100"> </el-table-column>
             <el-table-column prop="name" label="名称"> </el-table-column>
-            <el-table-column prop="pics" label="图片">
+            <el-table-column prop="pics" label="作品内容">
                 <template slot-scope="{ row }">
                     <el-image
                         style="width: 30px; height: 30px"
-                        :src="row.pics[0]"
+                        :src="row.pics[0].thumb || row.pics[0].url"
                         fit="cover"
-                        :preview-src-list="row.pics"
+                        :preview-src-list="row.pics.map(i => i.thumb || i.url)"
                     ></el-image>
                 </template>
             </el-table-column>

+ 58 - 15
src/main/vue/src/views/CollectionEdit.vue

@@ -2,7 +2,7 @@
     <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="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">
@@ -17,16 +17,20 @@
                     style="max-width: 500px"
                 >
                     <el-form-item prop="name" label="名称">
-                        <el-input v-model="formData.name"></el-input>
+                        <el-input v-model="formData.name" :disabled="!canEdit"></el-input>
                     </el-form-item>
                     <el-form-item prop="pics" label="图片">
-                        <multi-upload v-model="formData.pics"></multi-upload>
+                        <object-upload v-model="formData.pics[0]" :disabled="!canEdit"></object-upload>
                     </el-form-item>
                     <el-form-item prop="minterId" label="铸造者">
-                        <minter-select v-model="formData.minterId" @detail="onMinterDetail"></minter-select>
+                        <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">
+                        <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>
@@ -80,14 +84,30 @@
                         </el-select>
                     </el-form-item> -->
                     <el-form-item prop="price" label="价格">
-                        <el-input-number type="number" v-model="formData.price"></el-input-number>
+                        <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>
+                    <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"></el-input-number>
+                        <el-input-number v-model="formData.total" :disabled="!canEdit"></el-input-number>
                     </el-form-item>
-                    <el-form-item prop="likes" label="点赞">
+                    <!-- <el-form-item prop="likes" label="点赞">
                         <el-input-number v-model="formData.likes"></el-input-number>
-                    </el-form-item>
+                    </el-form-item> -->
                     <el-form-item prop="onShelf" label="上架">
                         <el-switch v-model="formData.onShelf" active-text="上架" inactive-text="下架"></el-switch>
                     </el-form-item>
@@ -96,9 +116,9 @@
                     </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 @click="onDelete" :disabled="saving" type="danger" v-if="formData.id">
                             删除
-                        </el-button>
+                        </el-button> -->
                         <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
                     </el-form-item>
                 </el-form>
@@ -123,10 +143,15 @@ export default {
                 });
         }
     },
+    computed: {
+        canEdit() {
+            return !!!this.$route.query.id;
+        }
+    },
     data() {
         return {
             saving: false,
-            formData: { onShelf: true, salable: true, properties: [], type: 'DEFAULT', source: 'OFFICIAL' },
+            formData: { onShelf: true, salable: true, properties: [], type: 'DEFAULT', source: 'OFFICIAL', pics: [] },
             rules: {
                 name: [
                     {
@@ -137,8 +162,24 @@ export default {
                 ],
                 pics: [
                     {
-                        required: true,
-                        message: '请输入图片',
+                        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'
                     }
                 ],
@@ -230,7 +271,9 @@ export default {
                         trigger: 'blur'
                     }
                 ],
-                category: [{ required: true, message: '请填写分类' }]
+                category: [{ required: true, message: '请填写分类' }],
+                royalties: [{ required: true, message: '请填写版税' }],
+                serviceCharge: [{ required: true, message: '请填手续费' }]
             },
             typeOptions: [
                 { label: '默认', value: 'DEFAULT' },

+ 5 - 5
src/main/vue/src/views/CollectionList.vue

@@ -45,13 +45,13 @@
             <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="pics" label="图片" width="90" align="center">
+            <el-table-column prop="pics" label="作品内容" width="90" align="center">
                 <template slot-scope="{ row }">
                     <el-image
                         style="width: 30px; height: 30px"
-                        :src="row.pics[0]"
+                        :src="row.pics[0].thumb || row.pics[0].url"
                         fit="cover"
-                        :preview-src-list="row.pics"
+                        :preview-src-list="row.pics.map(i => i.thumb || i.url)"
                     ></el-image>
                 </template>
             </el-table-column>
@@ -70,8 +70,8 @@
             <el-table-column prop="price" label="价格"> </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" width="150">
                 <template slot-scope="{ row }">
-                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
-                    <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button>
+                    <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>

+ 12 - 14
src/main/vue/src/views/Login.vue

@@ -2,11 +2,11 @@
     <div class="container" :style="{ backgroundImage: 'url(' + require('../assets/bg_login.jpg') + ')' }">
         <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;">
+                <el-page-header @back="register = false" title="登录" style="width: 350px; line-height: 60px">
                     <div class="register-title" slot="content">注册账号</div>
                 </el-page-header>
 
-                <el-form :model="registerInfo" style="width: 350px;" ref="registerForm">
+                <el-form :model="registerInfo" style="width: 350px" ref="registerForm">
                     <el-form-item prop="username" :rules="{ required: true, message: '请输入用户名', trigger: 'blur' }">
                         <el-input v-model="registerInfo.username" placeholder="用户名"> </el-input>
                     </el-form-item>
@@ -14,9 +14,9 @@
                         <el-input v-model="registerInfo.password" placeholder="密码" type="password"></el-input>
                     </el-form-item>
                     <el-form-item>
-                        <el-button :loading="loading" @click="doRegister" type="primary" style="width: 100%;"
-                            >注册</el-button
-                        >
+                        <el-button :loading="loading" @click="doRegister" type="primary" style="width: 100%">
+                            注册
+                        </el-button>
                     </el-form-item>
                 </el-form>
             </div>
@@ -24,7 +24,7 @@
                 <div class="title">欢迎登录</div>
                 <el-tabs v-model="activeName" stretch class="tab-list">
                     <el-tab-pane label="用户名登陆" name="first">
-                        <el-form :model="userInfo" style="width: 350px;" ref="form">
+                        <el-form :model="userInfo" style="width: 350px" ref="form">
                             <el-form-item
                                 prop="username"
                                 :rules="{ required: true, message: '请输入用户名', trigger: 'blur' }"
@@ -38,7 +38,7 @@
                                 <el-input v-model="userInfo.password" placeholder="密码" type="password"></el-input>
                             </el-form-item>
                             <el-form-item>
-                                <el-button :loading="loading" @click="login" type="primary" style="width: 100%;"
+                                <el-button :loading="loading" @click="login" type="primary" style="width: 100%"
                                     >登录
                                 </el-button>
                             </el-form-item>
@@ -49,14 +49,14 @@
                             </el-form-item> -->
                             <el-form-item label="">
                                 <el-checkbox v-model="rememberMe">7天内免登录 </el-checkbox>
-                                <el-button type="text" style="float: right;" @click="register = true"
+                                <el-button type="text" style="float: right" @click="register = true"
                                     >注册账号
                                 </el-button>
                             </el-form-item>
                         </el-form>
                     </el-tab-pane>
                     <el-tab-pane label="验证码登陆" name="second">
-                        <el-form :model="userInfo" style="width: 350px;" ref="form2">
+                        <el-form :model="userInfo" style="width: 350px" ref="form2">
                             <el-form-item
                                 prop="phone"
                                 :rules="{ required: true, message: '请输入手机号', trigger: 'blur' }"
@@ -83,13 +83,13 @@
                                 </el-button>
                             </el-form-item> -->
                             <el-form-item>
-                                <el-button :loading="loading" @click="phonelogin" type="primary" style="width: 100%;"
+                                <el-button :loading="loading" @click="phonelogin" type="primary" style="width: 100%"
                                     >手机登录</el-button
                                 >
                             </el-form-item>
                             <el-form-item label="">
                                 <el-checkbox v-model="rememberMe">7天内免登录 </el-checkbox>
-                                <el-button type="text" style="float: right;" @click="register = true"
+                                <el-button type="text" style="float: right" @click="register = true"
                                     >注册账号
                                 </el-button>
                             </el-form-item>
@@ -140,9 +140,7 @@ export default {
                         .then(res => {
                             this.loading = false;
                             this.$store.commit('updateUserInfo', res);
-                            this.$router.replace({
-                                name: this.$route.params.name || 'dashboard'
-                            });
+                            this.$router.replace('/dashboard');
                         })
                         .catch(e => {
                             console.log(e);

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

@@ -59,9 +59,9 @@
                 <template slot-scope="{ row }">
                     <el-image
                         style="width: 30px; height: 30px"
-                        :src="row.pic[0]"
+                        :src="row.pic[0].thumb || row.pic[0].url"
                         fit="cover"
-                        :preview-src-list="row.pic"
+                        :preview-src-list="row.pic.map(i => i.thumb || i.url)"
                     ></el-image>
                 </template>
             </el-table-column>