648 lines
18 KiB
Vue
648 lines
18 KiB
Vue
<template>
|
|
<div class="detail">
|
|
<div class="detail-bg"></div>
|
|
<div v-if="detailData" class="detail-content">
|
|
<div class="content">
|
|
<div class="top-left">
|
|
<div class="top-left-img">
|
|
<!-- <LazyLoadImg :src="detailData.nft.image" :src-placeholder="placeholderImg" alt="" /> -->
|
|
<ImgCard :nftData="detailData.nft" />
|
|
</div>
|
|
</div>
|
|
<div class="top-right">
|
|
<h2>{{ detailData.nft.name }}</h2>
|
|
<div class="top-right-owner">
|
|
<div>Owner:</div>
|
|
<div class="address">{{ sliceAddress(detailData.nft.owner_address) }}</div>
|
|
</div>
|
|
<div class="top-right-price" v-if="detailData.event">
|
|
<li>
|
|
<div>Price</div>
|
|
<div class="time">
|
|
<img src="@/assets/img/marketplace/time.png" alt="图片">
|
|
<div>Time remaining: <StarTimer :getAddress="detailData.event.data.end_at" /></div>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="price">
|
|
<span class="bold">{{ price }} </span>
|
|
<img :src="icon" alt="ICON">
|
|
<span>( $ {{ usd }} )</span>
|
|
</div>
|
|
</li>
|
|
</div>
|
|
<div class="top-right-btns">
|
|
<!--
|
|
1、添加购物车
|
|
2、移除购物车
|
|
3、购买
|
|
4、
|
|
-->
|
|
<div v-if="(myAddress != detailData.nft.owner_address) && detailData.event">
|
|
<div class="buy" @click="buyNow">Buy Now</div>
|
|
<div class="add" v-if="detailData.in_shopcart == 0" @click="addCart">
|
|
<span>Add to cart </span>
|
|
<div>
|
|
<img src="@/assets/img/marketplace/Add_shopping_cart.png" alt="">
|
|
</div>
|
|
</div>
|
|
<div class="remove" v-if="detailData.in_shopcart == 1" @click="clearCart">
|
|
<span>Remove from cart</span>
|
|
<div>
|
|
<img src="@/assets/img/marketplace/Move_out.png" alt="">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!--
|
|
1、上架
|
|
2、下架
|
|
3、使用
|
|
-->
|
|
<div v-else>
|
|
<div class="sell" @click="beginSell" v-if="detailData.nft.on_sale == 0">BList</div>
|
|
<div class="cancel" @click="cancelSell" v-if="detailData.nft.on_sale == 1">Cancel listing</div>
|
|
<div class="redeem" @click="lockToGame" v-if="detailData.nft.on_sale == 0 && detailData.nft.type == 1">Convert</div>
|
|
<div class="redeem" @click="lockToGame" v-if="detailData.nft.on_sale == 0 && detailData.nft.type == 0">Redeem</div>
|
|
</div>
|
|
</div>
|
|
<div class="info">
|
|
<h2>Info</h2>
|
|
<li>
|
|
<div>Contract address</div>
|
|
<div>{{ sliceAddress(detailData.nft.contract_address) }}</div>
|
|
</li>
|
|
<li>
|
|
<div>Token ID</div>
|
|
<div>{{ detailData.nft.token_id }}</div>
|
|
</li>
|
|
<li>
|
|
<div>Blockchain</div>
|
|
<div>Arbitrum</div>
|
|
</li>
|
|
<li>
|
|
<div>Metadata</div>
|
|
<div><a :href="detailData.nft.meta_url" target="_blank">{{ sliceAddress(detailData.nft.meta_url) }}</a></div>
|
|
</li>
|
|
<li>
|
|
<div>Royalties</div>
|
|
<div>2%</div>
|
|
</li>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="content">
|
|
<div class="btm-left">
|
|
<h2>Property</h2>
|
|
<div class="btm-detail">
|
|
<li v-for="(item, val, index) in nftAbilities" :key="index">
|
|
<div v-show="val == 'quality'">
|
|
<h5>Tier</h5>
|
|
<p>{{ item }}</p>
|
|
</div>
|
|
<div v-show="val == 'max_mining_days'">
|
|
<h5>Active Days</h5>
|
|
<p>{{ item }}</p>
|
|
</div>
|
|
<div v-show="val == 'wealth'">
|
|
<h5>Wealth Value</h5>
|
|
<p>{{ parseInt(item) }}</p>
|
|
</div>
|
|
<div v-show="val == 'lucky'">
|
|
<h5>Luck Value</h5>
|
|
<p>{{ parseInt(item) }}</p>
|
|
</div>
|
|
<div v-show="val == 'hp'">
|
|
<h5>HP</h5>
|
|
<p>{{ parseInt(item) }}</p>
|
|
</div>
|
|
<div v-show="val == 'atk'">
|
|
<h5>Attack</h5>
|
|
<p>{{ parseInt(item) }}</p>
|
|
</div>
|
|
<div v-show="val == 'def'">
|
|
<h5>Defense</h5>
|
|
<p>{{ Number(item).toFixed(2) }}%</p>
|
|
</div>
|
|
<div v-show="val == 'block'">
|
|
<h5>Block Rate</h5>
|
|
<p>{{ Number(item).toFixed(2) }}%</p>
|
|
</div>
|
|
<div v-show="val == 'crit'">
|
|
<h5>Crit Rate</h5>
|
|
<p>{{ Number(item).toFixed(2) }}%</p>
|
|
</div>
|
|
</li>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<SellDialog v-if="detailData" :sellDialogVisible="sellDialogVisible" :floorPrice="floorPrice" :sellDataArr="detailData.nft" @handleClose="sellHandleClose" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, toRaw, onMounted , inject} from "vue"
|
|
const message = inject('$message')
|
|
import { useRouter } from "vue-router";
|
|
import BuyDialog from "@/components/Dialogs/buyDialog.vue"
|
|
import ImgCard from "@/components/common/imgCard.vue"
|
|
import StarTimer from "@/components/common/starTimer.vue"
|
|
import SellDialog from "@/components/Dialogs/sellDialog.vue"
|
|
import { nftDetail, apiGetPrice } from "@/utils/marketplace"
|
|
import {priceCalculated} from "@/configs/priceCalculate.js"
|
|
import { BlockChain } from "@/components/chain/BlockChain"
|
|
import {walletStore} from "@/store/wallet";
|
|
import { useMarketplaceStore } from "@/store/marketplace"
|
|
import LazyLoadImg from "@/components/lazyloadimg"
|
|
import {formatPrice} from "@/components/chain/utils"
|
|
import {createModal} from "@/utils/model.util";
|
|
const router = useRouter();
|
|
const localWalletStore = walletStore()
|
|
const marketplaceList = useMarketplaceStore()
|
|
const props = defineProps({
|
|
address: String,
|
|
tokenid: String
|
|
})
|
|
import placeholderImg from '@/assets/img/marketplace/GenesisHeroes_NFT.png'
|
|
const detailData = ref(null)
|
|
const myAddress = localWalletStore.address
|
|
const nftAbilities = ref()
|
|
const icon = ref('')
|
|
const usd = ref('')
|
|
const price = ref('')
|
|
|
|
// 购买
|
|
const buyDataArr = ref([])
|
|
const buyNow = async () => {
|
|
// console.log(toRaw(detailData.value))
|
|
// return
|
|
// debugger
|
|
buyDataArr.value = []
|
|
buyDataArr.value.push(detailData.value)
|
|
const buyResult = await createModal(BuyDialog, {
|
|
buyDataArr: buyDataArr.value,
|
|
}).show()
|
|
if(buyResult.errcode) {
|
|
console.log('buy fail')
|
|
message.success('buy fail')
|
|
return
|
|
}
|
|
return
|
|
// ------------------------
|
|
let tokenIds = [detailData.value.event.data.id]
|
|
try {
|
|
await new BlockChain().market.batchBuy(tokenIds)
|
|
console.log('buy success')
|
|
} catch (e) {
|
|
console.log('buy fail', e.message)
|
|
}
|
|
}
|
|
|
|
// 添加购物车
|
|
const addCart = async () => {
|
|
const data = {
|
|
net_id: import.meta.env.VUE_APP_NET_ID,
|
|
tokens: [
|
|
{
|
|
token_id: detailData.value.nft.token_id,
|
|
contract_address: detailData.value.nft.contract_address,
|
|
}
|
|
]
|
|
}
|
|
try {
|
|
const { errcode, errmsg } = await marketplaceList.addCartListState(data)
|
|
console.log(errcode, errmsg)
|
|
if(errcode == 0) {
|
|
message.success('success! Add from cart')
|
|
marketplaceList.getCartList = await marketplaceList.getCartListState()
|
|
getDetail()
|
|
}
|
|
} catch (e) {
|
|
message.error('fail! Add from cart')
|
|
}
|
|
}
|
|
|
|
// 移除购物车
|
|
const clearCart = async () => {
|
|
// TODO:
|
|
const data = {
|
|
net_id: import.meta.env.VUE_APP_NET_ID,
|
|
tokens: [
|
|
{
|
|
token_id: detailData.value.nft.token_id,
|
|
contract_address: detailData.value.nft.contract_address,
|
|
}
|
|
]
|
|
}
|
|
try {
|
|
const { errcode, errmsg } = await marketplaceList.delCartListState(data)
|
|
if(errcode == 0) {
|
|
message.success('success! Remove from cart')
|
|
marketplaceList.getCartList = await marketplaceList.getCartListState()
|
|
getDetail()
|
|
}
|
|
} catch (e) {
|
|
console.log(e)
|
|
}
|
|
}
|
|
|
|
const lockToGame = async() => {
|
|
try {
|
|
await new BlockChain().locker.lock(detailData.value.nft.contract_address, [detailData.value.nft.token_id])
|
|
console.log('lockToGame success')
|
|
router.go(-1)
|
|
} catch (e) {
|
|
console.log('lockToGame fail', e.message)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// 售卖弹窗
|
|
const floorPrice = ref('0')
|
|
const sellDialogVisible = ref(false)
|
|
const beginSell = async() => {
|
|
if (detailData.value.event) return
|
|
floorPrice.value = await getFloorPrice()
|
|
sellDialogVisible.value = true
|
|
return
|
|
try {
|
|
//TODO:: 根据输入的内容出售
|
|
const sellData = {
|
|
contractAddress: detailData.contract_address,
|
|
tokenId: detailData.token_id,
|
|
currencyAddress: import.meta.env.VUE_APP_MARKET_CURRENCY,
|
|
currencyAmount: '1000000000000000000',
|
|
orderExpiry: null
|
|
}
|
|
await new BlockChain().market.beginSellERC721(sellData)
|
|
console.log('beginSell success')
|
|
} catch (e) {
|
|
console.log('beginSell fail', e.message)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// 取消售卖
|
|
const cancelSell = async() => {
|
|
console.log('cancelSell', detailData.value.event)
|
|
|
|
if (!detailData.value.event) return
|
|
try {
|
|
let res = await new BlockChain().market.cancelOrder([detailData.value.event.data.id])
|
|
if(res.result) {
|
|
let timer = setTimeout(() => {
|
|
getDetail()
|
|
clearTimeout(timer)
|
|
}, 2000);
|
|
}
|
|
console.log('cancelSell success')
|
|
} catch (e) {
|
|
try {
|
|
let res = await new BlockChain().market.cancelOrdersOnChain([detailData.value.event.data.id])
|
|
if(res.result) {
|
|
let timer = setTimeout(() => {
|
|
getDetail()
|
|
clearTimeout(timer)
|
|
}, 2000);
|
|
}
|
|
} catch (e2) {
|
|
console.log('cancelSell fail', e.message)
|
|
}
|
|
console.log('cancelSell fail', e.message)
|
|
}
|
|
}
|
|
|
|
// 处理地址
|
|
const sliceAddress = (address) => {
|
|
if (!address) return "-";
|
|
if (address.length >= 10) {
|
|
return `${address.substring(0, 6)}......${address.slice(-4)}`;
|
|
}
|
|
return address;
|
|
}
|
|
|
|
// 获取地板价
|
|
const getFloorPrice = async () => {
|
|
const data = {
|
|
net_id: detailData.value.nft.net_id,
|
|
contract_address: detailData.value.nft.contract_address,
|
|
item_id: detailData.value.nft.item_id,
|
|
quality: detailData.value.nft.detail.quality,
|
|
}
|
|
let res = await apiGetPrice(data)
|
|
if(res.lowest_price_goods) {
|
|
floorPrice.value = res.lowest_price_goods.event.data.buy[0].amount
|
|
} else {
|
|
floorPrice.value = '0'
|
|
}
|
|
return floorPrice.value
|
|
}
|
|
|
|
// 关闭出售弹窗
|
|
const sellHandleClose = (val) => {
|
|
if(val) {
|
|
let timer = setTimeout(() => {
|
|
getDetail()
|
|
clearTimeout(timer)
|
|
}, 2000);
|
|
sellDialogVisible.value = false
|
|
} else {
|
|
sellDialogVisible.value = false
|
|
}
|
|
}
|
|
|
|
const getDetail = async () => {
|
|
// let address = localStorage.getItem('assessAddress')
|
|
let { errcode, errmsg, data} = await nftDetail(props.address, props.tokenid)
|
|
if (errcode) {
|
|
console.log(errmsg)
|
|
//TODO:: 提示错误信息
|
|
return
|
|
}
|
|
const nftData = data.nft
|
|
nftData.event = data.event
|
|
nftAbilities.value = data.nft.detail
|
|
detailData.value = data
|
|
if (data.event?.data) {
|
|
const _data = formatPrice(data.event?.data)
|
|
icon.value = _data.icon
|
|
usd.value = _data.usd
|
|
price.value = priceCalculated(_data.amount.toString())
|
|
}
|
|
console.log(data,'----')
|
|
}
|
|
|
|
onMounted(() => {
|
|
getDetail()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.detail {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
background: url('@/assets/img/marketplace/BG01.jpg') no-repeat;
|
|
background-size: 100% 100%;
|
|
.detail-bg {
|
|
width: 100%;
|
|
height: 84px;
|
|
}
|
|
.detail-content {
|
|
width: 100%;
|
|
height: 100%;
|
|
.content {
|
|
width: 1266px;
|
|
margin: 0 auto;
|
|
padding-top: 76px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: #fff;
|
|
.top-left {
|
|
width: 453px;
|
|
.top-left-img {
|
|
height: 678px;
|
|
:deep(.card-img-common) {
|
|
.img-top {
|
|
width: 120px;
|
|
height: 35px;
|
|
top: 20px;
|
|
left: 20px;
|
|
font-size: 18px;
|
|
}
|
|
.img-btm {
|
|
bottom: 35px;
|
|
left: 65px;
|
|
>div {
|
|
width: 120px;
|
|
height: 43px;
|
|
font-size: 18px;
|
|
}
|
|
div:nth-child(2) {
|
|
margin-left: 15px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.top-right {
|
|
width: 719px;
|
|
h2 {
|
|
font-size: 42px;
|
|
font-family: 'Poppins';
|
|
font-weight: bold;
|
|
}
|
|
.top-right-owner {
|
|
display: flex;
|
|
font-family: 'Poppins';
|
|
font-weight: bold;
|
|
font-size: 30px;
|
|
.address {
|
|
margin-left: 20px;
|
|
}
|
|
}
|
|
.top-right-price {
|
|
width: 719px;
|
|
height: 133px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
background: #2d2738;
|
|
border-radius: 20px;
|
|
margin-top: 10px;
|
|
color: #BB7FFF;
|
|
padding: 15px 20px;
|
|
box-sizing: border-box;
|
|
li {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
>div {
|
|
display: flex;
|
|
font-size: 28px;
|
|
color: #9950FD;
|
|
}
|
|
.time {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: end;
|
|
font-size: 18px;
|
|
img {
|
|
width: 25px;
|
|
height: 28px;
|
|
margin-right: 5px;
|
|
}
|
|
div {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
}
|
|
.price {
|
|
display: flex;
|
|
align-items: center;
|
|
span {
|
|
font-family: 'Poppins';
|
|
font-weight: 400;
|
|
font-size: 24px;
|
|
color: #BB7FFF;
|
|
}
|
|
.bold {
|
|
font-weight: bold;
|
|
font-size: 48px;
|
|
}
|
|
img {
|
|
width: 30px;
|
|
height: 30px;
|
|
margin-right: 20px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.top-right-btns {
|
|
div {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin: 10px 0;
|
|
>div {
|
|
height: 57px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
font-family: 'Poppins';
|
|
font-weight: bold;
|
|
font-size: 28px;
|
|
color: #FFFFFF;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
}
|
|
.buy {
|
|
background: #6336d7;
|
|
width: 348px;
|
|
}
|
|
.add {
|
|
span {
|
|
width: 269px;
|
|
height: 100%;
|
|
line-height: 57px;
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
display: inline-block;
|
|
background: #1778f1;
|
|
}
|
|
div {
|
|
width: 76px;
|
|
height: 100%;
|
|
background: #1778f1;
|
|
margin-left: 2px;
|
|
img {
|
|
width: 43px;
|
|
height: 44px;
|
|
}
|
|
}
|
|
}
|
|
.remove {
|
|
span {
|
|
width: 269px;
|
|
height: 100%;
|
|
line-height: 57px;
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
display: inline-block;
|
|
background: #1778f1;
|
|
}
|
|
div {
|
|
width: 76px;
|
|
height: 100%;
|
|
background: #1778f1;
|
|
margin-left: 2px;
|
|
img {
|
|
width: 43px;
|
|
height: 44px;
|
|
}
|
|
}
|
|
}
|
|
.sell {
|
|
width: 348px;
|
|
background: #6336d7;
|
|
}
|
|
.cancel {
|
|
width: 348px;
|
|
background: #ff6271;
|
|
}
|
|
.redeem {
|
|
width: 348px;
|
|
background: #1778f1;
|
|
}
|
|
}
|
|
}
|
|
.info {
|
|
color: #B3B5DA;
|
|
margin-top: 40px;
|
|
h2 {
|
|
font-family: 'Poppins';
|
|
font-weight: bold;
|
|
font-size: 30px;
|
|
color: #fff;
|
|
border-bottom: 2px solid #3A3B57;
|
|
}
|
|
li {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 10px;
|
|
div {
|
|
width: 300px;
|
|
font-size: 22px;
|
|
text-align: left;
|
|
margin-top: 10px;
|
|
a {
|
|
color: #B3B5DA;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.btm-left {
|
|
padding-bottom: 90px;
|
|
h2 {
|
|
font-size: 30px;
|
|
font-family: 'Poppins';
|
|
font-weight: bold;
|
|
margin-bottom: 10px;
|
|
}
|
|
.btm-detail {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
clear: both;
|
|
width: 489px;
|
|
li {
|
|
width: 154px;
|
|
height: 84px;
|
|
background: #2d2738;
|
|
border-radius: 20px;
|
|
margin-right: 9px;
|
|
margin-bottom: 10px;
|
|
div {
|
|
text-align: center;
|
|
font-size: 22px;
|
|
color: #B3B5DA;
|
|
font-family: "Poppins";
|
|
h5 {
|
|
font-weight: bold;
|
|
}
|
|
p {
|
|
font-weight: 400;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
&:nth-child(2) {
|
|
padding-top: 20px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style> |