sunkean 3 ani în urmă
părinte
comite
d97d89d159

+ 62 - 0
src/main/java/com/izouma/nineth/domain/MetaStore.java

@@ -0,0 +1,62 @@
+package com.izouma.nineth.domain;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.izouma.nineth.enums.MetaStoreCommodityType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙商店")
+public class MetaStore extends BaseEntity {
+
+    @ApiModelProperty("商品名称")
+    @ExcelProperty("商品名称")
+    private String name;
+
+    @ApiModelProperty("商品类型")
+    @ExcelProperty("商品类型")
+    @Enumerated(EnumType.STRING)
+    private MetaStoreCommodityType commodityType;
+
+    @ApiModelProperty("商品图片")
+    @ExcelProperty("商品图片")
+    private String pic;
+
+    @ApiModelProperty("商品介绍")
+    @ExcelProperty("商品介绍")
+    private String introduction;
+
+    @ApiModelProperty("金币价格")
+    @ExcelProperty("金币价格")
+    private int price;
+
+    @ApiModelProperty("道具id")
+    @ExcelProperty("道具id")
+    private Long metaPropId;
+
+    @ApiModelProperty("库存数量")
+    @ExcelProperty("库存数量")
+    private int stockNum;
+
+    @ApiModelProperty("限购数量")
+    @ExcelProperty("限购数量")
+    private int purchaseLimitNum;
+
+    @ApiModelProperty("是否上架")
+    @ExcelProperty("是否上架")
+    @Column(columnDefinition = "tinyint unsigned")
+    private boolean onShelf;
+
+}

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

@@ -0,0 +1,32 @@
+package com.izouma.nineth.domain;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙商店购买记录")
+public class MetaStorePurchaseRecord extends BaseEntity {
+
+    @ApiModelProperty("商品id")
+    @ExcelProperty("商品id")
+    private Long metaStoreId;
+
+    @ApiModelProperty("用户id")
+    @ExcelProperty("用户id")
+    private Long userId;
+
+    @ApiModelProperty("购买时间")
+    @ExcelProperty("购买时间")
+    private LocalDateTime purchaseTime;
+}

+ 19 - 0
src/main/java/com/izouma/nineth/enums/MetaStoreCommodityType.java

@@ -0,0 +1,19 @@
+package com.izouma.nineth.enums;
+
+public enum MetaStoreCommodityType {
+
+    NFT("NFT"),
+
+    META_PROP("元宇宙道具");
+
+    private final String description;
+
+    MetaStoreCommodityType(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
+

+ 13 - 0
src/main/java/com/izouma/nineth/repo/MetaStorePurchaseRecordRepo.java

@@ -0,0 +1,13 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.MetaStorePurchaseRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+
+
+public interface MetaStorePurchaseRecordRepo extends JpaRepository<MetaStorePurchaseRecord, Long>, JpaSpecificationExecutor<MetaStorePurchaseRecord> {
+
+    List<MetaStorePurchaseRecord> findAllByMetaStoreIdAndUserId(Long metaStoreId, Long userId);
+}

+ 23 - 0
src/main/java/com/izouma/nineth/repo/MetaStoreRepo.java

@@ -0,0 +1,23 @@
+package com.izouma.nineth.repo;
+
+import com.izouma.nineth.domain.MetaStore;
+import com.izouma.nineth.enums.MetaStoreCommodityType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+
+public interface MetaStoreRepo extends JpaRepository<MetaStore, Long>, JpaSpecificationExecutor<MetaStore> {
+    @Query("update MetaStore t set t.del = true where t.id = ?1")
+    @Modifying
+    @Transactional
+    void softDelete(Long id);
+
+    MetaStore findByCommodityTypeAndMetaPropIdAndDel(MetaStoreCommodityType commodityType, Long metaPropId, boolean del);
+
+    MetaStore findByCommodityTypeAndNameAndDel(MetaStoreCommodityType commodityType, String name, boolean del);
+
+    MetaStore findByIdAndDel(Long id, boolean del);
+}

+ 127 - 0
src/main/java/com/izouma/nineth/service/MetaStoreService.java

@@ -0,0 +1,127 @@
+package com.izouma.nineth.service;
+
+import com.izouma.nineth.annotations.RedisLock;
+import com.izouma.nineth.config.Constants;
+import com.izouma.nineth.domain.*;
+import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.enums.MetaPropOperationType;
+import com.izouma.nineth.enums.MetaPropUsedType;
+import com.izouma.nineth.enums.MetaStoreCommodityType;
+import com.izouma.nineth.repo.MetaPropRepo;
+import com.izouma.nineth.repo.MetaStorePurchaseRecordRepo;
+import com.izouma.nineth.repo.MetaStoreRepo;
+import com.izouma.nineth.repo.MetaUserPropRepo;
+import com.izouma.nineth.utils.JpaUtils;
+import com.izouma.nineth.utils.SecurityUtils;
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+@Service
+@AllArgsConstructor
+public class MetaStoreService {
+
+    private MetaStoreRepo metaStoreRepo;
+
+    private MetaPropRepo metaPropRepo;
+
+    private MetaUserPropRepo metaUserPropRepo;
+
+    private MetaUserGoldService metaUserGoldService;
+
+    private MetaUserPropRecordService metaUserPropRecordService;
+
+    private MetaStorePurchaseRecordRepo metaStorePurchaseRecordRepo;
+
+    public Page<MetaStore> all(PageQuery pageQuery) {
+        return metaStoreRepo.findAll(JpaUtils.toSpecification(pageQuery, MetaStore.class), JpaUtils.toPageRequest(pageQuery));
+    }
+
+    @Transactional
+    public MetaRestResult<Void> purchase(Long id) {
+        MetaStore metaStore = metaStoreRepo.findByIdAndDel(id, false);
+        if (Objects.isNull(metaStore)) {
+            return MetaRestResult.returnError("商品不存在");
+        }
+        if (!metaStore.isOnShelf()) {
+            return MetaRestResult.returnError("商品未上架");
+        }
+        Long userId = SecurityUtils.getAuthenticatedUser().getId();
+        if (MetaStoreCommodityType.META_PROP.equals(metaStore.getCommodityType())) {
+            return purchaseProp(metaStore, userId);
+        }
+        return purchaseNFT(metaStore, userId);
+    }
+
+    @RedisLock("#userId")
+    @Transactional
+    public MetaRestResult<Void> purchaseNFT(MetaStore metaStore, Long userId) {
+        int price = metaStore.getPrice();
+        if (0 >= metaStore.getPrice()) {
+            return MetaRestResult.returnError("购买失败,道具价格不合法");
+        }
+        if (metaStore.getStockNum() <= 0) {
+            return MetaRestResult.returnError("购买失败,库存不足");
+        }
+        // 限购
+        List<MetaStorePurchaseRecord> metaStorePurchaseRecords = metaStorePurchaseRecordRepo.findAllByMetaStoreIdAndUserId(metaStore.getId(), userId);
+        if (CollectionUtils.isNotEmpty(metaStorePurchaseRecords) && metaStorePurchaseRecords.size() >= metaStore.getPurchaseLimitNum()) {
+            return MetaRestResult.returnError(String.format("购买失败,当前商品限购[%S]个", metaStore.getPurchaseLimitNum()));
+        }
+        // 扣减金币
+        MetaRestResult<MetaUserGold> restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买NFT:[%S],消耗金币[%s]", metaStore.getName(), price));
+        if (restResult.getCode() != Constants.MetaRestCode.success) {
+            return MetaRestResult.returnError(restResult.getMessage());
+        }
+        // 保存购买记录
+        metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now()));
+        // 商品库存减1
+        metaStore.setStockNum(metaStore.getStockNum() - 1);
+        metaStoreRepo.save(metaStore);
+        return MetaRestResult.returnSuccess("购买成功!");
+    }
+
+    @Transactional
+    public MetaRestResult<Void> purchaseProp(MetaStore metaStore, Long userId) {
+        int price = metaStore.getPrice();
+        if (0 >= metaStore.getPrice()) {
+            return MetaRestResult.returnError("道具价格不合法");
+        }
+        MetaProp metaProp = metaPropRepo.findByIdAndDel(metaStore.getMetaPropId(), false);
+        if (Objects.isNull(metaProp)) {
+            return MetaRestResult.returnError("道具信息为空");
+        }
+        MetaUserProp dbMetaUserProp = metaUserPropRepo.findByUserIdAndMetaPropIdAndDel(userId, metaProp.getId(), false);
+        if (Objects.isNull(dbMetaUserProp)) {
+            dbMetaUserProp = MetaUserProp.create(userId, metaProp, 1);
+            MetaRestResult<MetaUserGold> restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买道具:[%S],消耗金币[%s]", metaProp.getId(), price));
+            if (restResult.getCode() != Constants.MetaRestCode.success) {
+                return MetaRestResult.returnError(restResult.getMessage());
+            }
+            metaUserPropRepo.save(dbMetaUserProp);
+            metaUserPropRecordService.save(userId, metaProp, MetaPropOperationType.RECEIVE, 1);
+            metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now()));
+            return MetaRestResult.returnSuccess("购买成功!");
+        }
+        if (MetaPropUsedType.PERMANENT.equals(metaProp.getUsedType()) && dbMetaUserProp.getNum() >= 1) {
+            return MetaRestResult.returnError("已拥有永久道具,不可购买");
+        }
+        MetaRestResult<MetaUserGold> restResult = metaUserGoldService.changeNum(userId, -price, String.format("购买道具:[%S],消耗金币[%s]", metaProp.getId(), price));
+        if (restResult.getCode() != Constants.MetaRestCode.success) {
+            return MetaRestResult.returnError(restResult.getMessage());
+        }
+        dbMetaUserProp.setNum(dbMetaUserProp.getNum() + 1);
+        metaUserPropRepo.save(dbMetaUserProp);
+        metaUserPropRecordService.save(userId, metaProp, MetaPropOperationType.RECEIVE, 1);
+        metaStorePurchaseRecordRepo.save(new MetaStorePurchaseRecord(metaStore.getId(), userId, LocalDateTime.now()));
+        return MetaRestResult.returnSuccess("购买成功!");
+
+    }
+}

+ 8 - 0
src/main/java/com/izouma/nineth/service/MetaUserGoldService.java

@@ -66,6 +66,14 @@ public class MetaUserGoldService {
         }
     }
 
+    /**
+     * 操作金币
+     *
+     * @param userId 玩家id
+     * @param num    操作数量(负数代表扣减)
+     * @param remark 备注信息
+     * @return result
+     */
     @Transactional
     public MetaRestResult<MetaUserGold> changeNum(Long userId, int num, String remark) {
         MetaUserGold metaUserGold = metaUserGoldRepo.findByUserIdAndDel(userId, false);

+ 108 - 0
src/main/java/com/izouma/nineth/web/MetaStoreController.java

@@ -0,0 +1,108 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.domain.MetaStore;
+import com.izouma.nineth.dto.MetaRestResult;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.enums.MetaStoreCommodityType;
+import com.izouma.nineth.exception.BusinessException;
+import com.izouma.nineth.repo.MetaStoreRepo;
+import com.izouma.nineth.service.MetaStoreService;
+import com.izouma.nineth.utils.ObjUtils;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+@RestController
+@RequestMapping("/metaStore")
+@AllArgsConstructor
+public class MetaStoreController extends BaseController {
+    private MetaStoreService metaStoreService;
+    private MetaStoreRepo metaStoreRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public MetaStore save(@RequestBody MetaStore record) {
+        if (Objects.isNull(record.getId())) {
+            if (MetaStoreCommodityType.META_PROP.equals(record.getCommodityType()) && !checkMetaProp(record.getMetaPropId())) {
+                throw new BusinessException("商店中已经存在该道具");
+            }
+            if (MetaStoreCommodityType.NFT.equals(record.getCommodityType())) {
+                MetaStore metaStore = metaStoreRepo.findByCommodityTypeAndNameAndDel(MetaStoreCommodityType.NFT, record.getName(), false);
+                if (Objects.nonNull(metaStore)) {
+                    throw new BusinessException("商店中已经存在该NFT");
+                }
+            }
+            return metaStoreRepo.save(record);
+        }
+        MetaStore orig = metaStoreRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+        ObjUtils.merge(orig, record);
+        return metaStoreRepo.save(orig);
+    }
+
+    @PostMapping("/{id}/onShelf")
+    public void onShelf(@PathVariable Long id) {
+        MetaStore metaStore = metaStoreRepo.findByIdAndDel(id, false);
+        if (Objects.isNull(metaStore)) {
+            throw new BusinessException("无记录");
+        }
+        if (metaStore.isOnShelf()) {
+            throw new BusinessException("当前商品已经处于上架状态!");
+        }
+        metaStore.setOnShelf(true);
+        metaStoreRepo.save(metaStore);
+    }
+
+    @PostMapping("/{id}/cancelOnShelf")
+    public void cancelOnShelf(@PathVariable Long id) {
+        MetaStore metaStore = metaStoreRepo.findByIdAndDel(id, false);
+        if (Objects.isNull(metaStore)) {
+            throw new BusinessException("无记录");
+        }
+        if (!metaStore.isOnShelf()) {
+            throw new BusinessException("当前商品已经处于下架状态!");
+        }
+        metaStore.setOnShelf(false);
+        metaStoreRepo.save(metaStore);
+    }
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/all")
+    public Page<MetaStore> all(@RequestBody PageQuery pageQuery) {
+        return metaStoreService.all(pageQuery);
+    }
+
+    @GetMapping("/get/{id}")
+    public MetaStore get(@PathVariable Long id) {
+        return metaStoreRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        metaStoreRepo.softDelete(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<MetaStore> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+
+    @GetMapping("/{metaPropId}/checkMetaProp")
+    public boolean checkMetaProp(@PathVariable Long metaPropId) {
+        MetaStore metaStore = metaStoreRepo.findByCommodityTypeAndMetaPropIdAndDel(MetaStoreCommodityType.META_PROP, metaPropId, false);
+        return Objects.isNull(metaStore);
+    }
+
+    @PostMapping("/{id}/purchase")
+    public MetaRestResult<Void> purchase(@PathVariable Long id) {
+        return metaStoreService.purchase(id);
+    }
+}
+

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/main/resources/genjson/MetaStore.json


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

@@ -1893,6 +1893,22 @@ const router = new Router({
                     meta: {
                        title: '新任务管理',
                     },
+               },
+                {
+                    path: '/metaStoreEdit',
+                    name: 'MetaStoreEdit',
+                    component: () => import(/* webpackChunkName: "metaStoreEdit" */ '@/views/MetaStoreEdit.vue'),
+                    meta: {
+                       title: '商店编辑',
+                    },
+                },
+                {
+                    path: '/metaStoreList',
+                    name: 'MetaStoreList',
+                    component: () => import(/* webpackChunkName: "metaStoreList" */ '@/views/MetaStoreList.vue'),
+                    meta: {
+                       title: '商店',
+                    },
                }
                 /**INSERT_LOCATION**/
             ]

+ 281 - 0
src/main/vue/src/views/MetaStoreEdit.vue

@@ -0,0 +1,281 @@
+<template>
+    <div class="edit-view">
+        <page-title>
+            <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+            <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
+        </page-title>
+        <div class="edit-view__content-wrapper">
+            <div class="edit-view__content-section">
+                <el-form :model="formData" :rules="rules" ref="form" label-width="80px" label-position="right" size="small"
+                    style="max-width: 500px;">
+                    <el-form-item prop="commodityType" label="商品类型">
+                        <el-select v-model="formData.commodityType" clearable filterable placeholder="请选择"
+                            @change="changeCommodityType" :disabled="!canEdit">
+                            <el-option v-for="item in commodityTypeOptions" :key="item.value" :label="item.label"
+                                :value="item.value">
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="metaPropId" label="道具"
+                        v-if="formData.commodityType && formData.commodityType === 'META_PROP'">
+                        <el-select v-model="formData.metaPropId" placeholder="请选择" filterable :disabled="!canEdit"
+                            @change="changeMetaProp($event)">
+                            <el-option v-for="item in metaProps" :key="item.id" :label="item.name" :value="item.id">
+                            </el-option>
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item prop="name" label="商品名称">
+                        <el-input v-model="formData.name"
+                            :disabled="!canEdit || formData.commodityType === 'META_PROP'"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="pic" label="商品图片">
+                        <single-upload v-model="formData.pic"
+                            :disabled="!canEdit || formData.commodityType === 'META_PROP'"> </single-upload>
+                    </el-form-item>
+                    <el-form-item prop="introduction" label="商品介绍">
+                        <el-input type="textarea" v-model="formData.introduction"
+                            :disabled="formData.commodityType === 'META_PROP'"></el-input>
+                    </el-form-item>
+                    <el-form-item prop="price" label="金币价格">
+                        <el-input-number type="price" v-model="formData.price" :step="1" :min="1" :disabled="!canEdit">
+                        </el-input-number>
+                        <div class="tip">输入规则:正整数,最小为1</div>
+                    </el-form-item>
+                    <template v-if="formData.commodityType && formData.commodityType === 'NFT'">
+                        <el-form-item prop="stockNum" label="库存数量">
+                            <el-input-number type="stockNum" v-model="formData.stockNum" :step="1" :min="1"
+                                :disabled="!canEdit">
+                            </el-input-number>
+                            <div class="tip">输入规则:正整数,最小为1</div>
+                        </el-form-item>
+                        <el-form-item prop="purchaseLimitNum" label="限购数量">
+                            <el-input-number type="purchaseLimitNum" v-model="formData.purchaseLimitNum" :step="1" :min="1"
+                                :disabled="!canEdit">
+                            </el-input-number>
+                            <div class="tip">输入规则:正整数,最小为1</div>
+                        </el-form-item>
+                    </template>
+                    <el-form-item prop="onShelf" label="是否上架">
+                        <el-switch v-model="formData.onShelf"> </el-switch>
+                    </el-form-item>
+                    <el-form-item class="form-submit">
+                        <el-button @click="onSave" :loading="saving" type="primary">
+                            保存
+                        </el-button>
+                        <el-button @click="$router.go(-1)" :disabled="saving">取消</el-button>
+                    </el-form-item>
+                </el-form>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import { reg } from '../util/regRules';
+export default {
+	name: 'MetaStoreEdit',
+	created() {
+		if (this.$route.query.id) {
+			this.$http
+				.get('metaStore/get/' + this.$route.query.id)
+				.then(res => {
+					this.formData = res;
+				})
+				.catch(e => {
+					console.log(e);
+					this.$message.error(e.error);
+				});
+		}
+		this.$http.get('/metaProp/findAll').then(res => {
+			this.metaProps = res;
+		});
+	},
+	computed: {
+		canEdit() {
+			return !!!this.$route.query.id;
+		}
+	},
+	data() {
+		return {
+            reg,
+			metaProps: [],
+			saving: false,
+			formData: {
+				introduction: '',
+				name: '',
+				pic: ''
+			},
+			rules: {
+				commodityType: [
+					{
+						required: true,
+						message: '请选择商品类型',
+						trigger: 'blur'
+					}
+				],
+				metaPropId: [
+					{
+						required: true,
+						message: '请选择道具',
+						trigger: 'blur'
+					}
+				],
+				name: [
+					{
+						required: true,
+						message: '请输入商品名称',
+						trigger: 'blur'
+					}
+				],
+				pic: [
+					{
+						required: true,
+						message: '请上传商品图片',
+						trigger: 'blur'
+					}
+				],
+				introduction: [
+					{
+						required: true,
+						message: '请输入商品介绍',
+						trigger: 'blur'
+					}
+				],
+				price: [
+					{
+						required: true,
+						message: '请输入金币价格',
+						trigger: 'blur'
+					},
+					{
+						validator: (rule, value, callback) => {
+							if (!this.reg.test(value)) {
+								callback(new Error('金币价格必须为大于1的整数'));
+								return;
+							} else {
+								callback();
+							}
+						}
+					}
+				],
+				stockNum: [
+					{
+						required: true,
+						message: '请输入库存数量',
+						trigger: 'blur'
+					},
+					{
+						validator: (rule, value, callback) => {
+							if (!this.reg.test(value)) {
+								callback(new Error('库存数量必须为大于1的整数'));
+								return;
+							} else {
+								callback();
+							}
+						}
+					}
+				],
+				purchaseLimitNum: [
+					{
+						required: true,
+						message: '请输入限购数量',
+						trigger: 'blur'
+					},
+					{
+						validator: (rule, value, callback) => {
+							if (!this.reg.test(value)) {
+								callback(new Error('限购数量必须为大于1的整数'));
+								return;
+							} else {
+								callback();
+							}
+						}
+					}
+				]
+			},
+			commodityTypeOptions: [
+				{ label: 'NFT', value: 'NFT' },
+				{ label: '元宇宙道具', value: 'META_PROP' }
+			]
+		};
+	},
+	methods: {
+		changeCommodityType() {
+			this.formData.introduction = '';
+			this.formData.pic = '';
+			this.formData.name = '';
+			this.$delete(this.formData, 'metaPropId');
+			this.$delete(this.formData, 'price');
+			this.$delete(this.formData, 'stockNum');
+			this.$delete(this.formData, 'purchaseLimitNum');
+		},
+		changeMetaProp(value) {
+			this.$http
+				.get('metaStore/' + value + '/checkMetaProp')
+				.then(res => {
+					if (res) {
+						this.$http
+							.get('metaProp/get/' + value)
+							.then(res => {
+								this.formData.introduction = res.introduction;
+								this.formData.name = res.name;
+								this.formData.pic = res.pic;
+							})
+							.catch(e => {
+								console.log(e);
+								this.$message.error(e.error);
+							});
+					} else {
+						this.$message.error('商店中已经存在该道具');
+					}
+				})
+				.catch(e => {
+					console.log(e);
+					this.$message.error(e.error);
+				});
+		},
+		onSave() {
+			this.$refs.form.validate(valid => {
+				if (valid) {
+					this.submit();
+				} else {
+					return false;
+				}
+			});
+		},
+		submit() {
+			let data = { ...this.formData };
+
+			this.saving = true;
+			this.$http
+				.post('/metaStore/save', data, { body: 'json' })
+				.then(res => {
+					this.saving = false;
+					this.$message.success('成功');
+					this.$router.go(-1);
+				})
+				.catch(e => {
+					console.log(e);
+					this.saving = false;
+					this.$message.error(e.error);
+				});
+		},
+		onDelete() {
+			this.$confirm('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+				.then(() => {
+					return this.$http.post(`/metaStore/del/${this.formData.id}`);
+				})
+				.then(() => {
+					this.$message.success('删除成功');
+					this.$router.go(-1);
+				})
+				.catch(e => {
+					if (e !== 'cancel') {
+						console.log(e);
+						this.$message.error((e || {}).error || '删除失败');
+					}
+				});
+		}
+	}
+};
+</script>
+<style lang="less" scoped></style>

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

@@ -0,0 +1,214 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" :disabled="fetchingData || downloading"
+                class="filter-item">
+                新增
+            </el-button>
+            <el-button @click="download" icon="el-icon-upload2" :loading="downloading" :disabled="fetchingData"
+                class="filter-item">
+                导出
+            </el-button>
+        </page-title>
+        <div class="filters-container">
+            <el-input placeholder="搜索..." v-model="search" clearable class="filter-item search"
+                @keyup.enter.native="getData">
+                <el-button @click="getData" slot="append" icon="el-icon-search"> </el-button>
+            </el-input>
+        </div>
+        <el-table :data="tableData" row-key="id" ref="table" header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell" row-class-name="table-row" cell-class-name="table-cell"
+            :height="tableHeight" v-loading="fetchingData">
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50">
+            </el-table-column>
+            <el-table-column prop="name" align="center" label="商品名称">
+            </el-table-column>
+            <el-table-column prop="commodityType" align="center" label="商品类型" :formatter="commodityTypeFormatter">
+            </el-table-column>
+            <el-table-column prop="pic" align="center" label="商品图片">
+                <template slot-scope="{ row }">
+                    <el-image style="width: 30px; height: 30px" :src="row.pic" fit="cover" :preview-src-list="[row.pic]">
+                    </el-image>
+                </template>
+            </el-table-column>
+            <el-table-column prop="introduction" align="center" label="商品介绍">
+            </el-table-column>
+            <el-table-column prop="price" align="center" label="金币价格">
+            </el-table-column>
+            <el-table-column prop="stockNum" align="center" label="库存数量">
+                <template slot-scope="{ row }">
+                    <template v-if="row.commodityType && row.commodityType ==='NFT'">
+                        {{ row.stockNum }}
+                    </template>
+                    <template v-else>
+                        {{ '不限' }}
+                    </template>
+                </template>
+            </el-table-column>
+            <el-table-column prop="purchaseLimitNum" align="center" label="限购数量">
+                <template slot-scope="{ row }">
+                    <template v-if="row.commodityType && row.commodityType ==='NFT'">
+                        {{ row.purchaseLimitNum }}
+                    </template>
+                    <template v-else>
+                        {{ '不限' }}
+                    </template>
+                </template>
+            </el-table-column>
+            <el-table-column prop="onShelf" align="center" label="是否上架">
+                <template slot-scope="{ row }">
+                    <el-tag :type="row.onShelf ? '' : 'info'"> {{ row.onShelf }} </el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" width="350">
+                <template slot-scope="{row}">
+                    <template v-if="!row.onShelf">
+                        <el-button @click="onShelf(row)" type="primary" size="mini" plain>上架</el-button>
+                    </template>
+                    <template v-else>
+                        <el-button @click="cancelOnShelf(row)" type="primary" size="mini" plain>下架</el-button>
+                    </template>
+                    <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">
+            <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: 'MetaStoreList',
+	mixins: [pageableTable],
+	data() {
+		return {
+			multipleMode: false,
+			search: '',
+			url: '/metaStore/all',
+			downloading: false,
+			commodityTypeOptions: [
+				{ label: 'NFT', value: 'NFT' },
+				{ label: '元宇宙道具', value: 'META_PROP' }
+			]
+		};
+	},
+	computed: {
+		selection() {
+			return this.$refs.table.selection.map(i => i.id);
+		}
+	},
+	methods: {
+		onShelf(row) {
+            this.$alert('确定要上架该商品吗?', '警告', { type: 'info' })
+				.then(() => {
+					return this.$http.post('/metaStore/' + row.id + '/onShelf');
+				})
+				.then(() => {
+					this.$message.success('上架成功');
+					this.getData();
+				})
+				.catch(e => {
+					if (e !== 'cancel') {
+						this.$message.error(e.error);
+					}
+				});
+        },
+		cancelOnShelf(row) {
+            this.$alert('确定要下架该商品吗?', '警告', { type: 'info' })
+				.then(() => {
+					return this.$http.post('/metaStore/' + row.id + '/cancelOnShelf');
+				})
+				.then(() => {
+					this.$message.success('下架成功');
+					this.getData();
+				})
+				.catch(e => {
+					if (e !== 'cancel') {
+						this.$message.error(e.error);
+					}
+				});
+        },
+		commodityTypeFormatter(row, column, cellValue, index) {
+			let selectedOption = this.commodityTypeOptions.find(i => i.value === cellValue);
+			if (selectedOption) {
+				return selectedOption.label;
+			}
+			return '';
+		},
+		beforeGetData() {
+			return { search: this.search, query: { del: false } };
+		},
+		toggleMultipleMode(multipleMode) {
+			this.multipleMode = multipleMode;
+			if (!multipleMode) {
+				this.$refs.table.clearSelection();
+			}
+		},
+		addRow() {
+			this.$router.push({
+				path: '/metaStoreEdit',
+				query: {
+					...this.$route.query
+				}
+			});
+		},
+		editRow(row) {
+			this.$router.push({
+				path: '/metaStoreEdit',
+				query: {
+					id: row.id
+				}
+			});
+		},
+		download() {
+			this.downloading = true;
+			this.$axios
+				.get('/metaStore/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);
+				});
+		},
+		deleteRow(row) {
+			this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+				.then(() => {
+					return this.$http.post(`/metaStore/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>

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff