Procházet zdrojové kódy

Merge branch 'feature-nft-buy-ui-6.6' into dev_1.1.0

zhangwei před 2 roky
rodič
revize
312fce570f

binární
src/assets/gif/box.gif


binární
src/assets/img/img-box1.png


binární
src/assets/img/img-box5.png


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
src/assets/svg/icon-default.svg


+ 6 - 0
src/assets/svg/icon-loading-while.svg

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgb(255, 255, 255,0); display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
+<circle cx="50" cy="50" fill="none" stroke="#fff" stroke-width="12" r="35" stroke-dasharray="164.93361431346415 56.97787143782138">
+  <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform>
+</circle>
+<!-- [ldio] generated by https://loading.io/ --></svg>

+ 8 - 0
src/http/nft.js

@@ -23,3 +23,11 @@ export function getNFTDetail(params) {
         data: params
     })
 }
+
+export function getNftMysteryBoxSaleInfo(params) {
+    return service({
+        url: `/nft/project/getNftMysteryBoxSaleInfo`,
+        method: 'post',
+        data: params
+    })
+}

+ 7 - 0
src/http/pay.js

@@ -33,5 +33,12 @@ export function getTokenRechargeAddress(params) {
     });
 }
 
+export function payNftMysteryBoxWithBalance(params) {
+    return service({
+        url: `/wallet/pay/payNftMysteryBoxWithBalance`,
+        method: "post",
+        data: params,
+    });
+}
 
 // ---- 提现 ----

+ 22 - 0
src/iframe/buy-nft.js

@@ -0,0 +1,22 @@
+import { createApp } from 'vue'
+import App from '@/view/iframe/buy-nft/index.vue'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+
+import "ant-design-vue/dist/antd.css"; // or 'ant-design-vue/dist/antd.less'
+import router from '@/router/buy-nft.js'
+import {Button,message,Tooltip} from "ant-design-vue";
+message.config({
+    top: `10px`,
+    duration: 3,
+    maxCount: 1,
+});
+
+const app = createApp(App);
+
+app.use(Button);
+app.use(Tooltip);
+app.use(message);
+app.use(router)
+app.use(ElementPlus);
+app.mount('#app');

+ 2 - 1
src/manifest.json

@@ -67,7 +67,8 @@
                 "/iframe/home.html",
                 "/iframe/publish-tips.html",
                 "/iframe/bind-tweet.html",
-                "/iframe/nft-card.html"
+                "/iframe/nft-card.html",
+                "/iframe/buy-nft.html"
             ],
             "matches": [
                 "<all_urls>"

+ 33 - 0
src/router/buy-nft.js

@@ -0,0 +1,33 @@
+import { createRouter, createWebHashHistory, createWebHistory } from "vue-router"
+import Home from '@/view/iframe/buy-nft/buy/home.vue'
+import Pay from '@/view/iframe/buy-nft/buy/pay.vue'
+import OpenBox from '@/view/iframe/buy-nft/buy/open-box.vue'
+
+let routes = [
+    {
+        path: '/',
+        name: 'Home',
+        component: Home,
+    },
+    {
+        path: "/pay",
+        name: 'Pay',
+        component: Pay
+    }, {
+        path: "/open_box",
+        name: 'OpenBox',
+        component: OpenBox
+    }
+]
+// 3. 创建路由实例
+const router = createRouter({
+
+    // 4. 采用hash 模式
+    history: createWebHashHistory('buy-nft.html'),
+    // 采用 history 模式
+    // history: createWebHistory('popup.html'),
+    routes, // short for `routes: routes`
+});
+
+export default router
+

+ 136 - 0
src/view/components/preview-balance.vue

@@ -0,0 +1,136 @@
+<template>
+    <div class="card-amount">
+        <img class="icon" src="@/assets/subject/icon-balance.png" />
+        <div class="con">
+            <div class="desc">Balance</div>
+            <div class="price">{{ currentCurrencyInfo.balance }} {{ props.tokenSymbol }}</div>
+        </div>
+        <img class="refresh" :class="{ 'icon-refresh-rotate': refreshRotate }" @click="updateCurrencyBanlce"
+            :src="require('@/assets/svg/icon-form-refresh.svg')" />
+    </div>
+</template>
+<script setup>
+import { ref, onMounted, reactive, defineProps } from "vue";
+import { syncChainTokenRechargeRecord } from "@/http/publishApi";
+
+const props = defineProps({
+    dialogVisible: {
+        type: Object,
+        default: {},
+    },
+    currencyCode: {
+        type: String,
+        default: ''
+    }
+});
+
+/**
+ * 同步链上交易
+ */
+const asyncTokenRechRecord = (cb) => {
+    syncChainTokenRechargeRecord({
+        params: {
+            currencyCode: props.currencyCode
+        }
+    }).then(res => {
+        if (res.code == 0) {
+            cb && cb(res.data)
+        }
+    })
+}
+// 刷新按钮旋转
+let refreshRotate = ref(false);
+
+// 当前选择的货币信息
+let currentCurrencyInfo = reactive({
+    currencyCode: "",
+    currencyName: "",
+    balance: "",
+    currencyType: "",
+    iconPath: "",
+    minAmount: "",
+    tokenChain: "",
+    tokenSymbol: "",
+    usdEstimateBalance: ""
+});
+
+/**
+ * 更新货币余额
+ */
+const updateCurrencyBanlce = () => {
+    if (!refreshRotate.value) {
+        refreshRotate.value = true;
+        setTimeout(() => {
+            refreshRotate.value = false;
+        }, 1000)
+    }
+    asyncTokenRechRecord((res) => {
+        let currencyInfo = res[0];
+        if (currencyInfo.currencyCode == props.currencyCode) {
+            currentCurrencyInfo.balance = currencyInfo.balance;
+        }
+    })
+}
+
+
+onMounted(() => {
+    console.log(props.currencyCode)
+    if(!props.currencyCode){
+        return
+    }
+    asyncTokenRechRecord((res) => {
+        let currencyInfo = res[0];
+        if (currencyInfo.currencyCode == props.currencyCode) {
+            currentCurrencyInfo.balance = currencyInfo.balance;
+        }
+    })
+})
+
+
+</script>
+
+<style lang="scss" scoped>
+.card-amount {
+    overflow: hidden;
+    display: flex;
+    height: 80px;
+    padding: 20px;
+    border-radius: 20px;
+    border: 1px solid #E6E6E6;
+
+    .icon {
+        width: 40px;
+        height: 40px;
+    }
+
+    .con {
+        flex: 1;
+        padding: 0 10px;
+
+        .desc {
+            color: rgba($color: #000000, $alpha: 0.5);
+            font-size: 12px;
+            margin-bottom: 4px;
+        }
+
+        .price {
+            font-size: 16px;
+            font-weight: bold;
+        }
+    }
+
+    .refresh {
+        cursor: pointer;
+        width: 50px;
+        height: 50px;
+        margin-top: -5px;
+    }
+
+
+}
+
+.icon-refresh-rotate {
+    transform: rotate(360deg);
+    transition-duration: 1s;
+}
+</style>

+ 294 - 0
src/view/iframe/buy-nft/buy/home.vue

@@ -0,0 +1,294 @@
+<template>
+    <div class="dialog">
+        <!-- home -->
+        <div class="area-title">
+            <img :src="require('@/assets/svg/icon-close.svg')" @click="clickClose" />
+            <div class="title">NFT Mystery box</div>
+        </div>
+        <!-- 内容 -->
+        <div class="area-content">
+            <img :src="state.data.mysteryBoxImagePath" class="box" v-show="state.data.mysteryBoxImagePath" />
+            <img :src="require('@/assets/svg/icon-default.svg')" class="box" v-show="!state.data.mysteryBoxImagePath" />
+        </div>
+
+        <!-- 底部 -->
+        <div class="footer">
+            <!-- 首页 -->
+            <div class="mark">
+                <div class="sold">SOLD: {{ state.data.itemSoldCount || 0 }}/{{ state.data.itemTotalCount || 0 }} </div>
+                <div class="limit">Buy Limit: {{ state.data.userBuyCount || 0 }}/{{ state.data.perUserBuyLimit || 0 }}
+                </div>
+            </div>
+            <!-- <div class="buy1">
+                <btn-loading :color="'while'"></btn-loading>
+            </div> -->
+            <template v-for="item in state.data.salePlans.splice(0, 2)">
+                <div class="buy5"
+                    v-show="item.itemCount == 5 && (state.data.perUserBuyLimit - state.data.userBuyCount) >= 5"
+                    @click="clickJump(item)">
+                    <div class="left">Buy {{ item.itemCount }}</div>
+                    <div class="right" v-if="(item.price.length + item.currencyCode) > 10">
+                        <div class="usdt">{{ item.price }} {{ item.currencyCode }}</div>
+                        <div class="off">{{ item.discount }} OFF</div>
+                    </div>
+                    <div class="right" v-else>
+                        <div class="usdt">
+                            <p>{{ item.price }}</p>
+                            <p>{{ item.currencyCode }}</p>
+                        </div>
+                        <div class="off">
+                            <p>{{ item.discount }} OFF</p>
+                        </div>
+                    </div>
+
+                </div>
+
+                <!-- <btn-loading></btn-loading> -->
+                <div class="buy1" @click="clickJump(item)"
+                    v-show="item.itemCount == 1 && (state.data.perUserBuyLimit - state.data.userBuyCount) >= 1">
+                    <template v-if="(item.price.length + item.currencyCode) > 30">
+                        <div class="left">Buy 1</div>
+                        <div class="right">
+                            <p>{{ item.price }}</p>
+                            <p>{{ item.currencyCode }}</p>
+                        </div>
+                    </template>
+                    <template v-else>
+                        <div class="left">Buy 1</div>
+                        <div class="right">
+                            {{ item.price }}
+                            {{ item.currencyCode }}
+                        </div>
+                    </template>
+                </div>
+                <div class="buy1 grey"
+                    v-show="item.itemCount == 1 && (state.data.perUserBuyLimit - state.data.userBuyCount) <= 0">
+                    <template v-if="(item.price.length + item.currencyCode) > 30">
+                        <div class="left">Buy 1</div>
+                        <div class="right">
+                            <p>{{ item.price }}</p>
+                            <p>{{ item.currencyCode }}</p>
+                        </div>
+                    </template>
+                    <template v-else>
+                        <div class="left">Buy 1</div>
+                        <div class="right">
+                            {{ item.price }}
+                            {{ item.currencyCode }}
+                        </div>
+                    </template>
+                </div>
+            </template>
+        </div>
+    </div>
+</template>
+<script setup>
+import router from "@/router/buy-nft.js";
+import { onMounted, reactive, inject } from "vue";
+import { getNftMysteryBoxSaleInfo } from "@/http/nft";
+import BtnLoading from '../components/btn-loading.vue'
+import { getQueryString } from "@/uilts/help";
+let pay_info = inject('pay_info');
+
+let state = reactive({
+    data: {
+        salePlans: [
+            {
+                currencyCode: 'BSC_TESTNET_BF_6X',
+                discount: '20$',
+                itemCount: 5,
+                price: 23,
+                salePlanId: '2'
+            },
+            {
+                currencyCode: 'BSC_TESTNET_BF_6X',
+                discount: '20$',
+                itemCount: 1,
+                price: 123,
+                salePlanId: '123'
+            }
+        ]
+    }
+})
+const clickClose = () => {
+
+}
+const clickJump = (item) => {
+    pay_info.home.sale_plan = item
+    router.push({ path: '/pay' });
+}
+onMounted(() => {
+    let nft_project_Id = getQueryString('nftProjectId') || '9b491fe0fa51499ca0fc3a78abdf0eb0'
+    getNftMysteryBoxSaleInfo({
+        params: {
+            nftProjectId: nft_project_Id
+        }
+    }).then((res) => {
+        if (res.code == 0) {
+            state.data = res.data
+            pay_info.home = res.data
+        } else {
+
+        }
+    }).catch(() => {
+
+    })
+})
+</script>
+
+<style lang="scss" scoped>
+.dialog {
+    background: #fff;
+    border-radius: 25px;
+    max-width: 90%;
+    min-width: 800px;
+    max-height: 90%;
+    min-height: 90%;
+    z-index: 23;
+    display: flex;
+    flex-direction: column;
+
+
+    .area-title {
+        width: 100%;
+        min-height: 48px;
+        display: flex;
+        align-items: center;
+        border-bottom: 1px solid #D9D9D9;
+        font-weight: 500;
+        font-size: 16px;
+        letter-spacing: 0.3px;
+        color: #000000;
+
+        img {
+            width: 24p;
+            height: 24px;
+            margin-left: 14px;
+            margin-right: 12px;
+            cursor: pointer;
+        }
+
+    }
+
+    .area-content {
+        flex: 1;
+        overflow-y: auto;
+
+        img {
+            width: 100%;
+            height: 100%;
+        }
+    }
+
+    .footer {
+        border-top: 1px solid #D9D9D9;
+        min-height: 80px;
+        padding: 15px 0;
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        position: relative;
+
+        .loading {
+            width: 24px;
+        }
+
+        .mark {
+            position: absolute;
+            left: 20px;
+            .sold {
+                
+            }
+
+            .limit {
+                color: #AF934E;
+                margin-right: 25px;
+            }
+        }
+
+        .buy5 {
+            border: 1px solid #1D9BF0;
+            background: rgba(29, 155, 240, 0.01);
+            border-radius: 100px;
+            color: #1D9BF0;
+            min-width: 217px;
+            height: 50px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 0 15px 0 20px;
+            font-weight: 700;
+            font-size: 14px;
+            cursor: pointer;
+            margin-right: 12px;
+
+            .left {
+                margin-right: 20px;
+            }
+
+            .right {
+                text-align: right;
+
+                p {
+                    margin: 0;
+                    padding: 0;
+                    line-height: 17px;
+                }
+            }
+
+
+            .off {
+                color: #AF934E;
+                font-weight: 700;
+                font-size: 14px;
+
+                letter-spacing: 0.3px;
+            }
+
+            .usdt {
+                color: #1D9BF0;
+                font-size: 14px;
+                font-weight: 700;
+
+            }
+        }
+
+        .buy1 {
+            cursor: pointer;
+            background: #1D9BF0;
+            color: #fff;
+            border-radius: 100px;
+            min-width: 217px;
+            height: 50px;
+            display: flex;
+            align-items: center;
+            font-size: 14px;
+            font-weight: 700;
+            justify-content: space-between;
+            padding: 0 15px 0 20px;
+            margin-right: 25px;
+
+            .left {
+                margin-right: 20px;
+            }
+
+            .right {
+                text-align: right;
+
+                p {
+                    margin: 0;
+                    padding: 0;
+                    line-height: 17px;
+                }
+            }
+
+        }
+
+        .grey {
+            background: #CDCDCD;
+            cursor: auto;
+        }
+    }
+}
+</style>

+ 129 - 0
src/view/iframe/buy-nft/buy/open-box.vue

@@ -0,0 +1,129 @@
+<template>
+    <!-- gif -->
+    <div class="box_content">
+        <img :src="require('@/assets/gif/box.gif')" class="box" v-show="state.box.show" />
+        <div class="nft" v-for="item in state.nft.data" v-show="item.show">
+            <div class="detail">
+                <img :src="item.imagePath" alt="" />
+            </div>
+            <p>{{item.nftItemName}}</p>
+        </div>
+    </div>
+</template>
+<script setup>
+import { reactive, onMounted, inject } from 'vue'
+let pay_info = inject('pay_info');
+let state = reactive({
+    box: {
+        show: true
+    },
+    nft: {
+        data: []
+    }
+})
+
+const setAllNoShow = () => {
+    state.nft.data.forEach((item) => {
+        item.show = false
+    })
+}
+
+const showNFTs = () => {
+    let len = state.nft.data.length
+    if (len == 0) {
+        return
+    }
+    let i = 0
+    setAllNoShow()
+    state.nft.data[i].show = true
+    i++
+    let timer = setInterval(() => {
+        if (len > i) {
+            setAllNoShow()
+            state.nft.data[i].show = true
+        } else {
+            clearInterval(timer)
+        }
+        i++
+    }, 2000)
+}
+
+onMounted(() => {
+    state.nft.data = pay_info.buy_items || []
+    setTimeout(() => {
+        state.box.show = false
+        showNFTs()
+    }, 2000)
+})
+</script>
+<style lang="scss" scoped>
+.box_content {
+    position: fixed;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    z-index: 2;
+
+    .box {
+        width: 200px;
+        position: absolute;
+        top: 40%;
+        left: 50%;
+        margin-top: -100px;
+        margin-left: -100px;
+    }
+
+    .nft {
+        position: absolute;
+        top: 40%;
+        margin-top: -200px;
+        width: 400px;
+        height: 450px;
+        animation: myfirst 0.5s;
+
+        display: flex;
+        flex-direction: column;
+
+        .detail {
+            flex: 1;
+            text-align: center;
+
+            img {
+                width: 100%;
+                height: 100%;
+                border: 3px solid white;
+                border-radius: 10px;
+            }
+        }
+
+        p {
+            margin: 0;
+            padding: 0;
+            margin-top: 20px;
+            color: #FFFFFF;
+            font-size: 16px;
+            font-weight: 700;
+        }
+
+    }
+}
+
+@keyframes myfirst {
+    0% {
+        width: 300px;
+        height: 300px;
+    }
+
+    50% {
+        width: 450px;
+        height: 500px;
+        margin-top: -210px;
+    }
+
+    100% {
+        margin-top: -200px;
+        width: 400px;
+        height: 450px;
+    }
+}
+</style>

+ 339 - 0
src/view/iframe/buy-nft/buy/pay.vue

@@ -0,0 +1,339 @@
+<template>
+    <div class="dialog">
+        <!-- home -->
+        <div class="area-title">
+            <img :src="require('@/assets/svg/icon-back.svg')" @click="clickBack" />
+            <div class="title">Payment</div>
+        </div>
+        <!-- 内容 -->
+        <div class="area-content">
+            <div class="left">
+                <img :src="require('@/assets/img/img-box5.png')" v-show="pay_info.home.sale_plan.itemCount == 5"
+                    alt="" />
+                <img :src="require('@/assets/img/img-box1.png')" v-show="pay_info.home.sale_plan.itemCount == 1"
+                    alt="" />
+                <div class="tip">
+                    <span>Mystery box*{{ pay_info.home.sale_plan.itemCount }}</span>
+                    <span>
+                        <img :src="pay_info.home.sale_plan.currencyInfo.iconPath" alt="">
+                        {{ pay_info.home.sale_plan.price }}
+                    </span>
+                </div>
+            </div>
+            <div class="right">
+                <div class="card-content">
+                    <div class="card-title">
+                        <img class="img" :src="require('@/assets/subject/top-01.svg')" />
+                        <div class="font">Deposit to Send Giveaway</div>
+                    </div>
+                    <top-up2 v-if="tempCurrentCurrencyInfo.currencyCode" :asyncIng="asyncIng"
+                        :currentCurrencyInfo="tempCurrentCurrencyInfo" @topUpDone="topUpDone">
+                    </top-up2>
+
+                    <div class="card-title">
+                        <img class="img" :src="require('@/assets/subject/top-02.svg')" />
+                        <div class="font">Wait for the amount to arrive</div>
+                    </div>
+                    <preview-balance v-if="tempCurrentCurrencyInfo.currencyCode"
+                        :currencyCode="tempCurrentCurrencyInfo.currencyCode"></preview-balance>
+                </div>
+            </div>
+        </div>
+
+        <!-- 底部 -->
+        <div class="footer">
+            <div class="pay" v-show="state.loading.show">
+                <btn-loading :color="'while'"></btn-loading>
+            </div>
+            <div class="pay" v-show="!state.loading.show" @click="clickPlay">Pay {{ pay_info.home.sale_plan.price }}
+                {{ pay_info.home.sale_plan.currencyCode }}</div>
+        </div>
+    </div>
+</template>
+<script setup >
+import router from "@/router/buy-nft.js";
+import { ref, onMounted, inject, reactive } from 'vue'
+import topUp2 from "@/view/iframe/publish/components/top-up2.vue";
+import { getCurrencyInfoByCode } from "@/http/publishApi";
+import PreviewBalance from "@/view/components/preview-balance.vue";
+import BtnLoading from '../components/btn-loading.vue'
+import { payNftMysteryBoxWithBalance } from "@/http/pay";
+import { getChromeStorage } from "@/uilts/chromeExtension"
+let pay_info = inject('pay_info');
+let state = reactive({
+    loading: {
+        show: false
+    }
+})
+
+let currentCurrencyInfo = reactive({
+    currencyCode: "",
+    currencyName: "",
+    balance: "",
+});
+
+const clickBack = () => {
+    router.back()
+}
+const clickPlay = () => {
+    state.loading.show = true
+    payNftMysteryBoxWithBalance({
+        params: {
+            nftProjectId: pay_info.home.nftProjectId,
+            salePlanId: pay_info.home.sale_plan.salePlanId
+        }
+    }).then((res) => {
+        state.loading.show = false
+        if (res.code == 0) {
+            pay_info.buy_items = res.data.buyItems
+            router.push({ path: '/open_box' });
+        } else {
+
+        }
+    }).catch(() => {
+        state.loading.show = false
+
+    })
+}
+// 余额是否同步中
+let asyncIng = ref(false);
+
+// 刷新按钮旋转
+let refreshRotate = ref(false);
+
+//临时货币信息
+let tempCurrentCurrencyInfo = ref({});
+
+
+const getLocalCurrencyInfoByCode = () => {
+    if (!currentCurrencyInfo.currencyCode) {
+        getCurrencyInfo();
+    }
+}
+const getCurrencyInfo = async () => {
+    let { accessToken = '' } = await getChromeStorage('userInfo') || {};
+    if (accessToken) {
+        getCurrencyInfoByCode({
+            params: {
+                currencyCode: currentCurrencyInfo.currencyCode
+            }
+        }).then(res => {
+            if (res.code == 0 && res.data) {
+                currentCurrencyInfo = res.data;
+                tempCurrentCurrencyInfo.value = res.data;
+            }
+        });
+
+    }
+}
+
+
+onMounted(() => {
+    currentCurrencyInfo.currencyCode = pay_info.home.sale_plan.currencyCode
+    console.log(pay_info.home)
+    getCurrencyInfo()
+})
+
+
+</script>
+<style lang="scss" scoped>
+.dialog {
+    background: #fff;
+    border-radius: 25px;
+    max-width: 90%;
+    min-width: 800px;
+    height: 90%;
+    z-index: 23;
+    display: flex;
+    flex-direction: column;
+
+
+    .area-title {
+        width: 100%;
+        height: 48px;
+        display: flex;
+        align-items: center;
+        border-bottom: 1px solid #D9D9D9;
+        font-weight: 500;
+        font-size: 16px;
+        letter-spacing: 0.3px;
+        color: #000000;
+
+        img {
+            width: 24p;
+            height: 24px;
+            margin-left: 14px;
+            margin-right: 12px;
+            cursor: pointer;
+        }
+
+    }
+
+    .area-content {
+        display: flex;
+        overflow-y: auto;
+        flex:1;
+
+        .left {
+            width: 400px;
+            margin: 40px 56px 0 56px;
+
+            img {
+                max-width: 400px;
+                max-height: 400px;
+                width: 100%;
+                height: auto;
+            }
+            p{
+                margin: 0;
+                padding:0;
+            }
+
+            .tip {
+                margin-top: 15px;
+                display: flex;
+                justify-content: space-between;
+
+                img {
+                    width: 14px;
+                    height: 14px;
+                }
+            }
+        }
+
+        .right {
+            margin: 30px 56px 0px 30px;
+
+            .card-content {
+                float: right;
+                width: 430px;
+
+                .card-title {
+                    height: 32px;
+
+                    .img {
+                        float: left;
+                        width: 20px;
+                        height: 20px;
+                        margin-right: 8px;
+                    }
+
+                    .font {
+                        float: left;
+                        font-size: 17px;
+                        font-weight: 500;
+
+                        span {
+                            color: #0091e9;
+                        }
+                    }
+                }
+
+
+
+                .card-list {
+                    padding: 20px;
+                    border-radius: 20px;
+                    border: 1px solid #E6E6E6;
+
+                    .item {
+                        display: flex;
+                        justify-content: space-between;
+                        align-items: center;
+                        height: 47px;
+                        font-size: 14px;
+                        font-weight: 500;
+                        box-shadow: inset 0px -1px 0px #EAEAEA;
+                    }
+                }
+            }
+        }
+    }
+
+    .pay {
+        width: 200px;
+        height: 50px;
+        font-weight: 700;
+        font-size: 18px;
+        margin-right: 30px;
+        color: #FFFFFF;
+        background: #1D9BF0;
+        border-radius: 10000px;
+        text-align: center;
+        line-height: 50px;
+        cursor: pointer;
+        p{
+                margin: 0;
+                padding:0;
+            }
+    }
+
+    .footer {
+        border-top: 1px solid #D9D9D9;
+        height: 80px;
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        position: relative;
+
+        .sold {
+            position: absolute;
+            left: 20px;
+        }
+
+        .limit {
+            color: #AF934E;
+            margin-right: 25px;
+        }
+
+        .buy5 {
+            border: 1px solid #1D9BF0;
+            background: rgba(29, 155, 240, 0.01);
+            border-radius: 100px;
+            color: #1D9BF0;
+            width: 217px;
+            height: 50px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 0 15px 0 20px;
+            font-weight: 700;
+            font-size: 18px;
+            cursor: pointer;
+            margin-right: 12px;
+
+            .off {
+                color: #AF934E;
+                font-weight: 700;
+                font-size: 14px;
+
+                letter-spacing: 0.3px;
+            }
+
+            .usdt {
+                color: #1D9BF0;
+                font-size: 14px;
+                font-weight: 700;
+
+            }
+        }
+
+        .buy1 {
+            cursor: pointer;
+            background: #1D9BF0;
+            color: #fff;
+            border-radius: 100px;
+            width: 217px;
+            height: 50px;
+            display: flex;
+            align-items: center;
+            font-size: 18px;
+            font-weight: 700;
+            justify-content: space-between;
+            padding: 0 15px 0 20px;
+            margin-right: 25px;
+        }
+    }
+}
+</style>

+ 29 - 0
src/view/iframe/buy-nft/components/btn-loading.vue

@@ -0,0 +1,29 @@
+<template>
+    <div class="btn-loading">
+        <img :src="require('@/assets/svg/icon-loading.svg')" class="loading" v-if="props.color == 'blue'"/>
+        <img :src="require('@/assets/svg/icon-loading-while.svg')" class="loading" v-else-if="props.color == 'while'"/>
+    </div>
+</template>
+<script setup>
+import {  defineProps } from 'vue'
+const props = defineProps({
+    color: {
+        type: String,
+        default: 'blue',
+    },
+})
+</script>
+<style lang="scss" scoped>
+.btn-loading{
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: auto;
+    img{
+        width:24px;
+        height: 24px;
+    }
+}
+</style>

+ 46 - 0
src/view/iframe/buy-nft/index.vue

@@ -0,0 +1,46 @@
+<template>
+    <div class="content">
+        <div class="background"></div>
+        <router-view></router-view>
+    </div>
+</template>
+<script setup>
+import { reactive,provide } from 'vue'
+let pay_info = reactive({})
+provide('pay_info', pay_info)
+let state = reactive({
+    // 
+    show: 'dialog-home'
+})
+
+
+
+</script>
+<style lang="scss" >
+html,
+body,
+#app {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+
+}
+
+.content {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    .background {
+        width: 100%;
+        height: 100%;
+        background: #000000;
+        opacity: 0.7;
+        position: fixed;
+        z-index: 1;
+    }
+}
+</style>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů