Ver Fonte

购买NFT

nieyuge há 2 anos atrás
pai
commit
d7c5fd954f

+ 378 - 0
components/MobileBuyNft.vue

@@ -0,0 +1,378 @@
+<template>
+    <div class="page">
+        <template v-if="data === null">
+            <img class="loading" src="../static/svg/icon-loading.svg" />
+        </template>
+        <template v-else>
+            <div class="swipe" v-if="curItem">
+                <div class="show">
+                    <img :src="curItem.imagePath" />
+                </div>
+                <div class="list">
+                    <div
+                        class="item"
+                        :class="{ on: curItem.nftItemId === item.nftItemId }"
+                        :key="index"
+                        @click="select(item)"
+                        v-for="(item, index) in listData">
+                        <img :src="item.imagePath" />
+                    </div>
+                </div>
+            </div>
+            <div class="desc line" v-if="nftMetaData.description">
+                <div class="title">Description</div>
+                <div class="desc-content" v-html="nftMetaData.description"></div>
+            </div>
+            <div class="prop line" v-if="nftMetaData.properties && nftMetaData.properties.length">
+                <div class="title">Properties</div>
+                <div class="prop-content">
+                    <div
+                        class="prop-item"
+                        v-for="(filedValueItem, filedValueIndex) in nftMetaData.properties"
+                        :key="filedValueIndex"
+                    >
+                        {{ filedValueItem.name }}
+                        <div class="prop-name">
+                            {{ filedValueItem.value }}
+                        </div>
+                        {{ filedValueItem.description }}
+                    </div>
+                </div>
+            </div>
+            <div class="about line" v-if="nftMetaData.about">
+                <div class="title">About</div>
+                <div class="about-content" v-html="nftMetaData.about"></div>
+            </div>
+            <div style="height:150px"></div>
+            <div class="buy">
+                <div class="random">
+                    <img src="../static/img/icon_nft_random.png" />
+                    <span>Randomly Get Different Styles of NFTs</span>
+                </div>
+                <template v-if="data.salePlans">
+                    <div v-for="(item, index) in data.salePlans" :key="index">
+                        <template v-if="item.itemCount == 1">
+                            <div
+                                class="btn"
+                                :class="{ disable: !((data.perUserBuyLimit - data.userBuyCount) >= 1 && (data.itemTotalCount - data.itemSoldCount) >= 1) }"
+                                @click="goBuy(((data.perUserBuyLimit - data.userBuyCount) >= 1 && (data.itemTotalCount - data.itemSoldCount) >= 1))">
+                                <div class="l">BUY {{item.itemCount}}</div>
+                                <FontZoom width="220">
+                                    <img class="icon" :src="item.currencyInfo.iconPath" /> {{ item.price }} {{ item.currencyInfo.tokenSymbol }} (${{item.usdPrice}})
+                                </FontZoom>
+                            </div>
+                        </template>
+                    </div>
+                </template>
+                <template v-else>
+                    <div
+                        class="btn"
+                        @click="goBuy()">
+                        BUY 1
+                    </div>
+                </template>
+                <div class="sale">
+                    <div class="l">SOLD: {{ data.itemSoldCount || 0 }}/{{ data.itemTotalCount }}</div>
+                    <div class="r" v-if="data.perUserBuyLimit < data.itemTotalCount">Buy Limit: {{ data.userBuyCount || 0 }}/{{ data.perUserBuyLimit }}</div>
+                </div>
+            </div>
+        </template>
+
+        <van-popup
+            round
+            v-model="loginLayer"
+            position="bottom">
+            <div class="login">
+                <div class="title">In order to purchase the NFT, you need to</div>
+                <div class="btn">
+                    <button-login
+                        @success="loginSuccess"
+                        @error="loginError">
+                    </button-login>
+                    <div class="text">Login Twitter</div>
+                </div>
+            </div>
+        </van-popup>
+    </div>
+</template>
+
+<script>
+import { postRequest } from '../http';
+import { Toast } from 'vant';
+import Api from '../http/api';
+import FontZoom from './FontZoom';
+import ButtonLogin from './buttonLogin';
+import { getStorage, storageKey } from '../utils/help';
+
+export default {
+    name: 'mobileBuyNft',
+    components: {
+        FontZoom,
+        ButtonLogin,
+    },
+    data() {
+        return {
+            data: {},
+            curItem: null,
+            listData: [],
+            nftMetaData: {},
+            loginLayer: false,
+        }
+    },
+    created() {
+        this.getSaleInfo()
+    },
+    methods: {
+        select(item) {
+            this.curItem = item;
+            this.nftMetaData = JSON.parse(item['metadata']);
+        },
+        getSaleInfo() {
+            postRequest(Api.getNftMysteryBoxSaleInfo, {
+                params: {
+                    nftProjectId: this.$route.params.id
+                },
+            }).then(res => {
+                let { code, data } = res;
+                if (code === 0) {
+                    this.data = data;
+                    this.listData = data.showItems;
+                    this.curItem = data.showItems[0];
+                    this.nftMetaData = JSON.parse(data.showItems[0]['metadata'])
+                }
+            })
+        },
+        goBuy(isNext = true) {
+            if (isNext === false) return;
+
+            let userInfo = getStorage(storageKey.userInfo)
+            if (!!userInfo) {
+                this.loginSuccess(userInfo)
+            } else {
+                this.loginLayer = true;
+            }
+        },
+        loginSuccess(userInfo) {
+            this.$router.push({
+                name: 'Payment',
+                query: {
+                    nftProjectId: this.$route.params.id,
+                    account: this.$route.params.account,
+                }
+            });
+        },
+        loginError() {
+            Toast('login fail');
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.page {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+    .loading {
+        position: absolute;
+        transform: translate(-50%, -50%);
+        top: 50%;
+        left: 50%;
+        margin: auto;
+        width: 40px;
+        border-radius: 50%;
+    }
+}
+.swipe {
+    .show {
+        overflow: hidden;
+        width: 18rem;
+        height: 18rem;
+        margin: 1rem auto;
+        border-radius: 6px;
+        background-color: #efefef;
+        img {
+            width: 100%;
+            height: 100%;
+        }
+    }
+    .list {
+        height: 70px;
+        margin: 0 16px;
+        font-size: 0;
+        overflow-x: auto;
+        text-align: center;
+        white-space: nowrap;
+        .item {
+            display: inline-block;
+            overflow: hidden;
+            opacity: .2;
+            width: 58px;
+            height: 58px;
+            margin: 0 3px;
+            border-radius: 4px;
+            background-color: #efefef;
+            img {
+                width: 100%;
+                height: 100%;
+            }
+            &:first-child {
+                margin-left: 0;
+            }
+            &:last-child {
+                margin-right: 0;
+            }
+            &.on {
+                opacity: 1;
+            }
+        }
+    }
+}
+.line {
+    border: 1px solid #e3e3e3;
+    border-radius: 10px;
+    padding: 14px;
+    margin: 0 16px 12px 16px;
+    box-sizing: border-box;
+
+    .title {
+        font-weight: 600;
+        font-size: 14px;
+    }
+}
+
+.desc {
+    margin-top: 10px;
+    .desc-content {
+        font-weight: 500;
+        font-size: 14px;
+        color: #929292;
+
+        span {
+            color: #1d9bf0;
+        }
+    }
+}
+
+.prop {
+    .prop-content {
+        display: flex;
+        flex-wrap: wrap;
+        margin-top: 12px;
+
+        .prop-item {
+            width: 48%;
+            min-height: 88px;
+            background: #f8f8f8;
+            border-radius: 10px;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            padding: 8px;
+            box-sizing: border-box;
+            align-items: center;
+            font-weight: 500;
+            font-size: 12px;
+            color: #929292;
+            margin-bottom: 10px;
+
+            .prop-name {
+                font-weight: 700;
+                font-size: 17px;
+                margin-top: 6px;
+                margin-bottom: 8px;
+                color: #000;
+                word-break: break-all;
+            }
+        }
+        .prop-item:nth-child(odd) {
+            margin-right: 8px;
+        }
+    }
+}
+
+.about-content {
+    margin-top: 22px;
+    .section {
+        font-weight: 400;
+        font-size: 14px;
+        margin-bottom: 20px;
+    }
+}
+
+.buy {
+    position: fixed;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    box-sizing: border-box;
+    background-color: #fff;
+    padding: 10px 16px 25px 16px;
+    border-top: solid 1px #E3E3E3;
+    .random {
+        color: #8C7D43;
+        font-size: 14px;
+        height: 30px;
+        img {
+            width: 20px;
+            height: 20px;
+            vertical-align: middle;
+        }
+    }
+    .btn {
+        display: flex;
+        font-size: 16px;
+        color: #ffffff;
+        align-items: center;
+        justify-content: space-between;
+        width: 100%;
+        height: 50px;
+        padding: 0 20px;
+        font-weight: 700;
+        border-radius: 50px;
+        background: #1D9BF0;
+        &.disable {
+            background: #CDCDCD;
+            cursor: not-allowed;
+        }
+        .icon {
+            width: 22px;
+            margin-right: 5px;
+        }
+    }
+    .sale {
+        display: flex;
+        justify-content: space-between;
+        color: #868686;
+        font-size: 12px;
+        line-height: 14px;
+        padding: 10px 0;
+        letter-spacing: 0.3px;
+    }
+}
+
+.login {
+    box-sizing: border-box;
+    padding: 38px 16px;
+    width: 100%;
+    .title {
+        font-size: 16px;
+        font-weight: 500;
+        margin-bottom: 20px;
+    }
+    .btn {
+        position: relative;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 50px;
+        border-radius: 50px;
+        background: #1D9BF0;
+        .text {
+            font-size: 18px;
+            font-weight: 700;
+            color: #ffffff;
+        }
+    }
+}
+</style>

+ 92 - 0
components/buttonLogin.vue

@@ -0,0 +1,92 @@
+<template>
+    <!-- 外层样式添加 position:relative 样式 -->
+    <div class="btnLogin" @click="login"></div>
+</template>
+
+<script>
+import { getStorage, storageKey, getOauthUrl, jumpUrl } from '../utils/help';
+import { postRequest } from '../http';
+
+export default {
+    name: 'buttonLogin',
+    computed: {
+        userInfo() {
+			return getStorage(storageKey.userInfo);
+		}
+    },
+    data() {
+        return {
+            timer: null,
+        }
+    },
+    unmounted() {
+        clearInterval(this.timer);
+    },
+    methods: {
+        login() {
+            if (!!this.userInfo) {
+                // 已登录
+                this.$emit('success', this.userInfo)
+            } else {
+                // 未登录
+                this.twitterAuth()
+            }
+        },
+        async twitterAuth() {
+            let win = window.open();
+			    win.opener = null;
+            postRequest(`/denet/user/twitterRequestToken`, {
+				params: {
+					oauthCallback: `${jumpUrl}authlogin`,
+				},
+			}).then(({ code, data }) => {
+                if (code == 0) {
+                    win.location.href = getOauthUrl(data.authToken);
+                    // timer
+                    this.timer = setInterval(() => {
+						if (win && win.closed) {
+							clearInterval(this.timer);
+							this.twitterLogin(data);
+						}
+					}, 500);
+                } else {
+                    this.$emit('error')
+                }
+            })
+        },
+        twitterLogin(authData) {
+            let verifier = getStorage(storageKey.verifier);
+            if (verifier) {
+				postRequest(`/denet/user/twitterLogin`, {
+					params: {
+						consumerKey: authData.consumerKey,
+						oauthToken: authData.authToken,
+						oauthVerifier: verifier,
+					},
+				}).then(({ code, data }) => {
+					if (code == 0) {
+						setStorage(storageKey.userInfo, data);
+						removeStorage(storageKey.verifier);
+                        this.$emit('success', data)
+					} else {
+                        this.$emit('error')
+					}
+				});
+			} else {
+                this.$emit('error')
+			}
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.btnLogin {
+    position: absolute;
+    z-index: 10;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+}
+</style>

+ 4 - 0
http/api.js

@@ -0,0 +1,4 @@
+export default {
+    getNftMysteryBoxSaleInfo: `/denet/nft/project/getMobileNftMysteryBoxSaleInfo`,
+    redeemNft: `/denet/nft/project/redeemNft`,
+}

+ 6 - 1
nuxt.config.js

@@ -72,7 +72,7 @@ export default {
 				},
 				{
 					name: 'NFT',
-					path: '/nft/:id/:account',
+					path: '/nft/:id/:account/:type?',
 					component: resolve(__dirname, 'pages/nft/index.vue'),
 				},
 				{
@@ -90,6 +90,11 @@ export default {
 					path: '/treasure/invite/:id/:channel?',
 					component: resolve(__dirname, 'pages/treasure/invite.vue'),
 				},
+				{
+					name: 'Payment',
+					path: '/payment',
+					component: resolve(__dirname, 'pages/payment/index.vue'),
+				},
 				{
 					name: 'custom',
 					path: '*',

+ 6 - 2
pages/nft/index.vue

@@ -4,7 +4,10 @@
 			<img class="loading" src="../../static/svg/icon-loading.svg" />
 		</template>
 		<template v-else>
-			<MobileLandPage v-if="isMobile" :prizePicPath="detail.pageImagePath" :playType="PlayType.NFT" :prize="detail.nftProjectName" :useFul="detail.purchaseStatus === 1" :nftProjectId="detail.nftProjectId"> </MobileLandPage>
+			<template v-if="isMobile">
+				<MobileLandPage v-if="isSwipe" :prizePicPath="detail.pageImagePath" :playType="PlayType.NFT" :prize="detail.nftProjectName" :useFul="detail.purchaseStatus === 1" :nftProjectId="detail.nftProjectId"> </MobileLandPage>
+				<mobileBuyNft v-else></mobileBuyNft>
+			</template>
 			<template v-else>
 				<div class="logo">
 					<img src="/img/icon-logo.png" alt />
@@ -66,6 +69,7 @@ export default {
 			title: 'DeNet Giveaway',
 			isMobile: false,
 			isChrome: false,
+			isSwipe: false,
 			linkHref: '',
 			metaTitle: 'DeNet: An Easy Web3 Tool For GIVEAWAY / AIRDROP',
 		};
@@ -246,6 +250,7 @@ export default {
 		checkBrowser() {
 			this.linkHref = window.location.href;
 			this.isChrome = isBrowser() == 'chrome';
+			this.isSwipe = this.$route.params.type ? true : false;
 			this.isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
 		},
 		setNftInfo() {
@@ -273,7 +278,6 @@ body,
 }
 
 .nft-content {
-	overflow: hidden;
 	width: 100%;
 	height: 100%;
 	background: linear-gradient(180deg, #ffffff 0%, #f0f7fe 94.31%);

+ 461 - 0
pages/payment/index.vue

@@ -0,0 +1,461 @@
+<template>
+    <div class="payment">
+        <!-- 选择支付方式 -->
+        <template v-if="step === 1">
+            <div class="moneyInfo">
+                <div class="tips">You Neet to Pay</div>
+                <div class="money" v-if="salePlans">
+                    <FontZoom width="320">
+                        <template v-if="salePlans && salePlans.currencyCode && salePlans.currencyCode !== 'USD'">
+                            <img class="icon" :src="salePlans.currencyInfo.iconPath" /><span class="info"> {{ salePlans.price }} {{ salePlans.currencyInfo.tokenSymbol }} (${{salePlans.usdPrice}})</span>
+                        </template>
+                        <template v-else>
+                            <span class="info usd">${{salePlans.usdPrice}}</span>
+                        </template>
+                    </FontZoom>
+                </div>
+            </div>
+            <div class="payList">
+                <div class="detail" v-if="salePlans && salePlans.currencyCode && salePlans.currencyCode !== 'USD'">
+                    <div class="title">Crypto Wallet</div>
+                    <div class="item denet">
+                        <div>
+                            <img src="../../static/payment/icon_wallet.png" />
+                            <span>DeNet Pay</span>
+                        </div>
+                        <div class="wallet">
+                            Balance:150 SHIB
+                        </div>
+                    </div>
+                    <div class="item">
+                        <img src="../../static/payment/icon_meta_mask.png" />
+                        <span>MetaMask</span>
+                    </div>
+                    <div class="item">
+                        <img src="../../static/payment/icon_coinbase_wallet.png" />
+                        <span>Coinbase Wallet</span>
+                    </div>
+                </div>
+                <div class="detail" v-if="salePlans && salePlans.currencyCode && salePlans.currencyCode === 'USD'">
+                    <div class="title">Credit/Debit Card</div>
+                    <div class="item denet">
+                        <div>
+                            <img src="../../static/payment/icon_wallet.png" />
+                            <span>DeNet Pay</span>
+                        </div>
+                        <div class="wallet">
+                            Balance:150 SHIB
+                        </div>
+                    </div>
+                    <div class="item">
+                        <img src="../../static/payment/icon_master_card.png" />
+                        <span>MasterCard</span>
+                    </div>
+                    <div class="item">
+                        <img src="../../static/payment/icon_visa.png" />
+                        <span>VISA</span>
+                    </div>
+                </div>
+                <div class="detail">
+                    <div class="title">Redemption Code</div>
+                    <div class="item" @click="showRedeem">
+                        <img src="../../static/payment/icon_enter_code.png" />
+                        <span>Enter Code</span>
+                    </div>
+                </div>
+            </div>
+        </template>
+
+        <!-- 成功支付 -->
+        <template v-if="step === 4">
+            <div class="payInfo">
+                <div class="picShow" v-if="buyData">
+                    <div class="tip">Wow, NFT in the Mystery box is</div>
+                    <img class="pic" :src="buyData.imagePath" />
+                    <div class="name">{{buyData.nftItemName}}</div>
+                </div>
+            </div>
+            <div class="payBtn">
+                <button class="btn" @click="jumpGuide">Done</button>
+            </div>
+        </template>
+
+        <!-- loading -->
+        <div class="loading" v-if="loading">
+            <van-loading color="#1D9BF0"></van-loading>
+        </div>
+        <div class="loadingBg" v-if="loading"></div>
+
+        <!-- redeem -->
+        <van-popup
+            round
+            v-model="redeemLayer"
+            position="bottom">
+            <div class="redeem">
+                <div class="tips">Enter Redemption Code</div>
+                <div class="footer">
+                    <div class="input">
+                        <input
+                            type="text"
+                            ref="input"
+                            v-model="redeemStr"
+                            @input="textInput"
+                            @blur="textBlur"
+                            @focus="textFocus"
+                            @keydown.enter="redeemPayment" />
+                    </div>
+                    <button
+                        class="enter"
+                        :class="{ disabled: !redeemNext }"
+                        @click="redeemPayment">
+                        Redeem
+                    </button>
+                </div>
+            </div>
+        </van-popup>
+    </div>
+</template>
+
+<script>
+import FontZoom from '../../components/FontZoom.vue';
+import { postRequest } from '../../http/index';
+import { Dialog } from 'vant';
+import Api from '../../http/api';
+
+export default {
+    name: 'payment',
+    data() {
+        return {
+            step: 1,        // 1: 支付方式列表 2: 余额支付 3: 充值支付 4: 支付完成
+            loading: true,
+            salePlans: null,
+            redeemStr: '',
+            redeemNext: false,
+            redeemLayer: false,
+            buyData: {},
+        }
+    },
+    head() {
+        return {
+            title: 'Buy NFT Mystery Box',
+        }
+    },
+    components: {
+        FontZoom,
+    },
+    mounted() {
+        this.saleInfo()
+    },
+    methods : {
+        saleInfo () {
+            postRequest(Api.getNftMysteryBoxSaleInfo, {
+                params: {
+                    nftProjectId: this.$route.query.nftProjectId
+                }
+            }).then(res => {
+                let { code, data } = res;
+                if (code === 0) {
+                    this.salePlans = data.salePlans[0];
+                }
+            }).finally(() => {
+                this.loading = false;
+            })
+        },
+        showRedeem() {
+            this.redeemStr = '';
+            this.redeemLayer = true;
+            this.$nextTick(() => {
+                this.$refs.input.focus();
+            })
+        },
+        textInput(e) {
+            let len = 16
+            let str = this.redeemStr.replace(/[^a-zA-Z0-9]/g, '')
+                str = str.toUpperCase();
+                str = str.slice(0, len);
+            
+            // set
+            this.redeemStr = str;
+            this.redeemNext = str !== '' && str.length === len;
+        },
+        textBlur(e) {
+            let isiOS = !!window.navigator.userAgent.match(/\(i\[^;]+;( U;)? CPU.+Mac OS X/)
+            if (isiOS) {
+                setTimeout(() => {
+                    const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0
+                    window.scrollTo(0, Math.max(scrollHeight - 1, 0))
+                }, 100)
+            }
+        },
+        textFocus(e) {
+            let isiOS = !!window.navigator.userAgent.match(/\(i\[^;]+;( U;)? CPU.+Mac OS X/)
+            if (isiOS) {
+                setTimeout(() => {
+                    const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0
+                    window.scrollTo(0, Math.max(scrollHeight - 1, 0))
+                }, 100)
+            }
+        },
+        redeemPayment() {
+            if (!this.redeemNext) return;
+
+            let params = {
+                redeemCode: this.redeemStr,
+                nftProjectId: this.$route.query.nftProjectId,
+            }
+
+            this.redeemNext = false;
+            postRequest(Api.redeemNft, {
+                params
+            }).then(res => {
+                let { code, data } = res;
+                if (code === 0) {
+                    this.redeemLayer = false;
+                    if (data.length > 0) {
+                        this.buyData = data[0];
+                    }
+                    // 购买成功
+                    this.step = 4;
+                } else {
+                    let msg;
+                    switch (code.toString()) {
+                        case '5001':
+                            msg = 'nft project not exist'
+                            break;
+                        case '5002':
+                            msg = 'nft project not available'
+                            break
+                        case '5101':
+                            msg = 'nft sale plan not exist'
+                            break
+                        case '5102':
+                            msg = 'nft sold out'
+                            break
+                        case '5103':
+                            msg = 'Purchase limit reached'
+                            break
+                        case '5104':
+                        case '5105':
+                        case '5106':
+                            msg = 'Invalid redemption code'
+                            break;
+                        default:
+                            msg = 'Invalid redemption code, please try again'
+                            break;
+                    }
+                    // Dialog
+                    Dialog({
+                        message: msg,
+                        confirmButtonText: 'OK',
+                        confirmButtonColor: '#1D9BF0',
+                    });
+                }
+            }).finally(() => {
+                this.redeemNext = true;
+            })
+        },
+
+        jumpGuide() {
+            this.$router.push({
+                path: `/nft/${this.$route.query.nftProjectId}/${this.$route.query.account}/show`,
+            })
+        },
+    }
+}
+</script>
+
+<style lang="scss">
+html,
+body,
+#__nuxt,
+#__layout {
+    width: 100%;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+}
+
+.payment {
+    width: 100%;
+    height: 100%;
+}
+.moneyInfo {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    height: 90px;
+    border-bottom: solid 8px rgba($color: #f5f5f5, $alpha: .6);
+    .tips {
+        font-size: 12px;
+        font-weight: 500;
+    }
+    .money {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-top: 10px;
+        .icon {
+            overflow: hidden;
+            width: 26px;
+            height: 26px;
+            border-radius: 50%;
+            margin-right: 10px;
+            vertical-align: middle;
+            background-color: #f5f5f5;
+        }
+        .info {
+            color: #000;
+            font-size: 20px;
+            font-weight: 700;
+            &.usd {
+                margin-top: -5px;
+            }
+        }
+    }
+}
+.payList {
+    overflow-y: auto;
+    height: calc(100% - 90px);
+    .detail {
+        padding: 18px 16px;
+        .title {
+            color: #ADADAD;
+            height: 26px;
+            font-size: 12px;
+            font-weight: 500;
+        }
+        .item {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-weight: 500;
+            font-size: 16px;
+            height: 50px;
+            margin-bottom: 14px;
+            border-radius: 100px;
+            border: 1px solid #ECECEC;
+            &:last-child {
+                margin-bottom: 0;
+            }
+            img {
+                width: 24px;
+                height: 24px;
+                margin-right: 4px;
+            }
+            &.denet {
+                flex-direction: column;
+                img {
+                    margin-top: -6px;
+                }
+            }
+            .wallet {
+                width: 100%;
+                color: #ADADAD;
+                font-size: 11px;
+                text-align: center;
+                margin-top: -2px;
+            }
+        }
+    }
+}
+
+.loading {
+    position: absolute;
+    z-index: 3;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+}
+.loadingBg {
+    position: absolute;
+    z-index: 2;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba($color: #ffffff, $alpha: .1);
+}
+
+.redeem {
+    padding: 18px 16px 38px;
+    .tips {
+        height: 31px;
+        font-size: 15px;
+        font-weight: 500;
+    }
+    .footer {
+        display: flex;
+        justify-content: space-between;
+        .input {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: calc(100% - 93px);
+            height: 45px;
+            border-radius: 5px;
+            border: 1px solid #E0E0E0;
+            input {
+                width: 94%;
+                border: 0;
+                outline: 0;
+            }
+        }
+        .enter {
+            width: 83px;
+            height: 45px;
+            border: 0;
+            color: #ffffff;
+            font-size: 17px;
+            font-weight: 500;
+            border-radius: 5px;
+            background: #1D9BF0;
+            &.disabled {
+                background: #DEDEDE;
+            }
+        }
+    }
+}
+
+.payInfo {
+    width: 100%;
+    height: calc(100% - 88px);
+    .picShow {
+        padding: 54px 48px;
+        .tip {
+            height: 44px;
+            font-size: 18px;
+            font-weight: 600;
+            text-align: center;
+        }
+        .pic {
+            width: 280px;
+            height: 280px;
+            border-radius: 3px;
+            margin-bottom: 20px;
+        }
+        .name {
+            font-size: 18px;
+            font-weight: 600;
+            line-height: 21px;
+            text-align: center;
+        }
+    }
+}
+.payBtn {
+    padding: 0 16px 38px;
+    .btn {
+        width: 100%;
+        height: 50px;
+        color: #FFFFFF;
+        font-size: 18px;
+        font-weight: 700;
+        border-radius: 50px;
+        background: #1D9BF0;
+    }
+}
+</style>

+ 6 - 2
plugins/vant.js

@@ -1,4 +1,8 @@
 import Vue from 'vue';
-import { Toast } from 'vant';
+import { Toast, Dialog, Popup, Image, Loading } from 'vant';
 import 'vant/lib/index.css'
-Vue.use(Toast);
+Vue.use(Toast);
+Vue.use(Dialog);
+Vue.use(Popup);
+Vue.use(Image);
+Vue.use(Loading);

BIN
static/img/icon_nft_random.png


BIN
static/payment/icon_coinbase_wallet.png


BIN
static/payment/icon_enter_code.png


BIN
static/payment/icon_master_card.png


BIN
static/payment/icon_meta_mask.png


BIN
static/payment/icon_visa.png


BIN
static/payment/icon_wallet.png