nieyuge před 2 roky
rodič
revize
a345b2cea4

+ 1 - 1
package.json

@@ -14,7 +14,7 @@
     "@sentry/tracing": "^7.12.0",
     "@sentry/vue": "^7.12.0",
     "@vue/composition-api": "^1.7.0",
-    "ant-design-vue": "^2.2.8",
+    "ant-design-vue": "^3.2.11",
     "axios": "^0.27.2",
     "core-js": "^3.8.3",
     "element-plus": "2.1.10",

+ 476 - 0
src/components/currency-list.vue

@@ -0,0 +1,476 @@
+<!-- 货币列表 -->
+<template>
+    <div class="currency-list-wrapper">
+        <div class="search-input-wrapper">
+            <input class="input" :style="{ width: !showRefresh ? '100%' : '88%' }" v-model="keywords"
+                placeholder="Search name" @input="onInput" />
+
+            <img :src="require('@/assets/svg/icon-form-refresh.svg')" v-if="showRefresh" class="icon"
+                :class="{ 'icon-refresh-rotate': refreshRotate }" @click="refresh">
+
+            <img :src="require('@/assets/svg/icon-clear-search.svg')" class="icon-clear" v-if="keywords"
+                :style="{ right: !showRefresh ? '6%' : '16%' }" @click="clearIpt">
+        </div>
+        <div class="list-wrapper" :style="{ 'paddingBottom': showGeneralLottery ? '50px' : '0' }" ref="listWrapperDom"
+            @scroll="listScroll">
+            <div class="page-list" ref="listContentDom" v-if="!showSearch">
+                <div class="list-item" v-for="(item, index) in currencyInfoList" :key="index">
+
+                    <template v-if="props.page != 'top-up' || item.type != 1">
+                        <div class="item-title" v-if="item.data.length">
+                            <img class="icon"
+                                :src="item.type == 1 ? require('@/assets/svg/icon-currency-category-01.svg') : require('@/assets/svg/icon-currency-category-02.svg')" />
+                            {{ item.type == 1 ? 'Cash' : 'Crypto' }}
+                        </div>
+                        <div class="item-detail" v-for="(data, idx) in item.data" :key="idx"
+                            @click="selectCurrency(data)">
+                            <div class="left">
+                                <img class="icon-currency" :src="data.currencies[0].iconPath" />
+                                <div class="currency-info">
+                                    <div class="name">{{ data.currencies[0].currencyCode == 'USD' ? 'USD' :
+                                            data.currencies[0].tokenSymbol
+                                    }}</div>
+                                    <div class="desc">{{ data.currencies[0].currencyName }}
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="right">
+                                <div class="num">
+                                    <a-tooltip :title="data.totalBalance">
+                                        {{ getBit(data.totalBalance) }}
+                                    </a-tooltip>
+                                </div>
+                                <div class="amount" v-if="data.currencies[0].currencyType == 2">
+                                    <a-tooltip :title="'$' + data.totalUsdEstimateBalance">
+                                        ${{ getBit(data.totalUsdEstimateBalance) }}
+                                    </a-tooltip>
+                                </div>
+                            </div>
+                        </div>
+                    </template>
+                </div>
+                <div class="no-data" v-if="show_empty">
+                    Not found
+                </div>
+            </div>
+            <!-- 显示搜索结果列表 -->
+            <div class="search-list" v-else>
+                <div class="item-detail" v-for="(data, idx) in searchList" :key="idx" @click="selectCurrency(data)">
+                    <div class="left">
+                        <img class="icon-currency" :src="data.currencies[0].iconPath" />
+                        <div class="currency-info">
+                            <div class="name">{{ data.currencies[0].currencyCode == 'USD' ? 'USD' :
+                                    data.currencies[0].tokenSymbol
+                            }}</div>
+                            <div class="desc">{{ data.currencies[0].currencyName }}</div>
+                        </div>
+                    </div>
+                    <div class="right">
+                        <div class="num">
+                            <a-tooltip :title="data.currencies[0].balance">
+                                {{ getBit(data.currencies[0].balance) }}
+                            </a-tooltip>
+                        </div>
+                        <div class="amount" v-if="data.currencies[0].currencyType == 2">
+                            <a-tooltip :title="'$' + data.currencies[0].usdEstimateBalance">
+                                ${{ getBit(data.currencies[0].usdEstimateBalance) }}
+                            </a-tooltip>
+                        </div>
+                    </div>
+                </div>
+                <div class="no-data" v-if="!searchList.length">
+                    Not found
+                </div>
+            </div>
+            <!-- 添加通用奖品 -->
+            <div class="add-general-lottery" v-if="showGeneralLottery" @click="addGeneralLottery">
+                <img class="add-general-lottery-icon" :src="require('@/assets/svg/icon-add-white.svg')" />
+                <span class="add-general-lottery-text">Customize</span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+/* eslint-disable */
+import { defineEmits, ref, onMounted, defineProps, defineExpose } from "vue";
+import { getCurrencyInfo, searchCurrencyInfo, syncChainTokenRechargeRecord } from "@/http/publishApi";
+import { debounce, getBit } from "@/uilts/help";
+
+const props = defineProps({
+    page: {
+        type: String,
+        default: '',
+    },
+    filterEmptyBalance: {
+        type: Boolean,
+        default: false
+    },
+    showRefresh: {
+        type: Boolean,
+        default: true
+    },
+    // 是否显示 通用奖品选项
+    showGeneralLottery: {
+        type: Boolean,
+        default: false
+    }
+})
+let keywords = ref('');
+let showSearch = ref(false);
+let currencyInfoList = ref([]);
+let searchList = ref([]);
+let refreshRotate = ref(false);
+let listWrapperDom = ref(null);
+let listContentDom = ref(null);
+let listReqParams = {
+    params: {
+        pageNum: 1,
+        pageSize: 100,
+    },
+    loadMore: false,
+    requestIng: false
+};
+let show_empty = ref(false);
+const emits = defineEmits(["selectCurrency", "setCurrencyList", "addGeneralLottery"]);
+
+const selectCurrency = (params) => {
+    emits("selectCurrency", params);
+};
+
+const onInput = (val) => {
+    console.log(keywords.value);
+    if (keywords.value) {
+        showSearch.value = true;
+        searchCurrency(keywords.value);
+    } else {
+        showSearch.value = false;
+        searchList.value = [];
+    }
+}
+
+const clearIpt = () => {
+    keywords.value = '';
+    showSearch.value = false;
+    searchList.value = [];
+}
+
+const refresh = () => {
+    if (!refreshRotate.value) {
+        refreshRotate.value = true;
+        setTimeout(() => {
+            refreshRotate.value = false;
+        }, 1000)
+        asyncTokenRechRecord(() => {
+            listReqParams.params.pageNum = 1;
+            getCurrencyInfoList();
+        })
+    }
+}
+
+/**
+ * 搜索结果列表
+ */
+const searchCurrency = debounce(function (searchWords) {
+    searchCurrencyInfo({
+        params: {
+            pageNum: 1,
+            pageSize: 100,
+            searchWords,
+            filterFiatCurrency: props.page == 'top-up'
+        }
+    }).then(res => {
+        if (res.code == 0) {
+            if (res.data.currencyCategories && res.data.currencyCategories.length) {
+                let currencyCategories = res.data.currencyCategories;
+
+                let arr = [];
+                for (let i = 0; i < currencyCategories.length; i++) {
+                    let item = currencyCategories[i];
+                    arr = arr.concat(item.data);
+                }
+
+                currencyCategories[0]['data'] = arr;
+
+                let list = currencyCategories[0];
+
+                if (list && list.data && list.data.length) {
+                    searchList.value = list.data;
+                } else {
+                    searchList.value = [];
+                }
+            } else {
+                searchList.value = [];
+            }
+        }
+    })
+}, 500)
+
+/**
+ * 获取货币列表
+ */
+const getCurrencyInfoList = (_params = {}) => {
+
+    if (listReqParams.requestIng) {
+        return;
+    }
+    listReqParams.requestIng = true;
+    if (_params.pageNum) {
+        listReqParams.params.pageNum = _params.pageNum;
+    }
+    let params = {
+        params: {
+            pageNum: listReqParams.params.pageNum,
+            pageSize: listReqParams.params.pageSize,
+            filterFiatCurrency: props.page == 'top-up',
+            filterEmptyBalance: props.filterEmptyBalance
+        }
+    };
+    getCurrencyInfo(params).then(res => {
+        listReqParams.requestIng = false;
+        if (res.code == 0) {
+            let resData = res.data;
+            if (resData && resData.currencyCategories.length) {
+                if (listReqParams.params.pageNum < 2) {
+                    currencyInfoList.value = res.data.currencyCategories;
+                    emits('setCurrencyList', { list: currencyInfoList.value })
+                    if (resData.currencyCategories.length == 1 && (!resData.currencyCategories[0]['data'] || !resData.currencyCategories[0]['data'].length)) {
+                        show_empty.value = true
+                    }
+                } else {
+                    let data = currencyInfoList.value;
+                    let currencyCategories = resData.currencyCategories;
+
+                    if (currencyCategories.length) {
+                        let tokenData = currencyCategories.find(item => item.type == 2);
+                        if (tokenData && tokenData.data && tokenData.data.length) {
+                            let index = data.findIndex(item => item.type == 2);
+                            let tokenList = data[index]['data'];
+                            data[index]['data'] = tokenList.concat(tokenData.data);
+                            currencyInfoList.value = data;
+                        }
+                    }
+
+                }
+                listReqParams.loadMore = false;
+            } else {
+                show_empty.value = true
+            }
+        }
+    }).catch(err => {
+        listReqParams.requestIng = false;
+    })
+}
+
+
+/**
+ * 同步链上交易
+ */
+const asyncTokenRechRecord = (cb) => {
+    syncChainTokenRechargeRecord({
+        params: {
+            currencyCode: ''
+        }
+    }).then(res => {
+        if (res.code == 0) {
+            cb && cb(res.data)
+        }
+    })
+}
+
+const listScroll = (e) => {
+    let wrapperHeight = listWrapperDom.value.offsetHeight;
+    let listContentHeight = listContentDom.value.offsetHeight;
+    let scrollTop = e.target.scrollTop || 0;
+    if (
+        listReqParams.loadMore === false &&
+        wrapperHeight + scrollTop >= (listContentHeight - 50)
+    ) {
+        listReqParams.loadMore = true;
+        listReqParams.params.pageNum++;
+        getCurrencyInfoList();
+    }
+}
+
+/**
+ * 添加通用奖品 按钮点击
+ */
+const addGeneralLottery = () => {
+    emits('addGeneralLottery');
+}
+
+onMounted(() => {
+    getCurrencyInfoList();
+})
+
+defineExpose({
+    getCurrencyInfoList,
+    refresh
+})
+
+</script>
+
+<style scoped lang="scss">
+.currency-list-wrapper {
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+    box-sizing: border-box;
+
+    .search-input-wrapper {
+        padding: 10px;
+        box-sizing: border-box;
+        background: #f7f7f7;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        position: relative;
+
+        .input {
+            background: #f1f1f1;
+            border-radius: 110px;
+            width: 88%;
+            height: 40px;
+            box-sizing: border-box;
+            border: none;
+            outline: none;
+            padding: 10px 80px 10px 22px;
+            color: #adadad;
+        }
+
+        input::placeholder {
+            color: #adadad;
+        }
+
+        .icon {
+            width: 32px;
+            cursor: pointer;
+        }
+
+        .icon-refresh-rotate {
+            transform: rotate(360deg);
+            transition-duration: 1s;
+        }
+
+
+        .icon-clear {
+            position: absolute;
+            right: 16%;
+            cursor: pointer;
+        }
+    }
+
+    .list-wrapper {
+        height: calc(100% - 60px);
+        overflow-y: auto;
+
+        .list-item {
+            .item-title {
+                display: flex;
+                align-items: center;
+                height: 42px;
+                padding: 0 10px;
+                box-sizing: border-box;
+                background: #f7f7f7;
+                font-size: 14px;
+                color: #a2a2a2;
+
+                .icon {
+                    width: 24px;
+                    height: 24px;
+                    margin-right: 6px;
+                }
+            }
+        }
+
+        .item-detail {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding: 12px 20px;
+            box-sizing: border-box;
+            cursor: pointer;
+
+            .left {
+                display: flex;
+
+                .icon-currency {
+                    width: 24px;
+                    height: 24px;
+                    margin-right: 12px;
+                    margin-top: 4px;
+                }
+
+                .currency-info {
+                    .name {
+                        font-weight: 500;
+                        font-size: 15px;
+                        margin-bottom: 5px;
+                        word-break: break-all;
+                    }
+
+                    .desc {
+                        font-weight: 400;
+                        font-size: 12px;
+                        color: #a2a2a2;
+                    }
+                }
+            }
+
+            .right {
+                text-align: right;
+                max-width: calc(100% - 150px);
+
+                .num,
+                .amount {
+                    word-break: break-all;
+                }
+
+                .num {
+                    font-weight: 500;
+                    font-size: 15px;
+                    margin-bottom: 5px;
+                }
+
+                .amount {
+                    font-weight: 400;
+                    font-size: 12px;
+                    color: #a2a2a2;
+                }
+            }
+        }
+
+        .no-data {
+            font-weight: 500;
+            font-size: 22px;
+            color: #BBBBBB;
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+        }
+
+        .add-general-lottery {
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            display: flex;
+            width: 100%;
+            height: 50px;
+            align-items: center;
+            justify-content: center;
+            background-color: #1D9BF0;
+            color: #fff;
+            cursor: pointer;
+
+            &-icon {
+                margin-right: 8px;
+            }
+
+            &-text {
+                font-size: 16px;
+            }
+        }
+    }
+}
+</style>

+ 126 - 0
src/components/currency-select.vue

@@ -0,0 +1,126 @@
+<!-- 货币列表 -->
+<template>
+    <div class="list-item" v-for="(item, index) in props.list" :key="index">
+        <div class="item-title">
+            <img class="icon" :src="item.chainInfo?.iconPath" />
+            {{ item.chainInfo?.chainName }}
+        </div>
+        <div class="item-detail" @click="selectCurrency(item)">
+            <div class="left">
+                <img class="icon-currency" :src="item?.iconPath" />
+                <div class="currency-info">
+                    <div class="name">{{ item.currencyCode == 'USD' ? 'USD' : item.tokenSymbol }}</div>
+                    <div class="desc">{{ item.currencyCode == 'USD' ? 'Paypal' : item.currencyName }}</div>
+                </div>
+            </div>
+            <div class="right">
+                <div class="num">
+                    <a-tooltip :title="item.balance">
+                        {{ getBit(item.balance) }}
+                    </a-tooltip>
+                </div>
+                <div class="amount" v-if="item.currencyType == 2">
+                    <a-tooltip :title="'$' + item.usdEstimateBalance">
+                        ${{ getBit(item.usdEstimateBalance) }}
+                    </a-tooltip>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</template>
+
+<script setup>
+import { defineProps, defineEmits } from 'vue';
+import { getBit } from "@/uilts/help";
+
+let props = defineProps({
+    list: {
+        type: Array,
+        default: function() {
+            return [];
+        },
+    }
+})
+let emits = defineEmits(['selectCurrency']);
+let selectCurrency = (params) => {
+    emits('selectCurrency', params);
+}
+</script>
+
+<style scoped lang="scss">
+.list-item {
+    .item-title {
+        display: flex;
+        align-items: center;
+        height: 42px;
+        padding: 0 10px;
+        box-sizing: border-box;
+        background: #f7f7f7;
+        font-weight: 500;
+        font-size: 14px;
+        color: #a2a2a2;
+
+        .icon {
+            width: 24px;
+            height: 24px;
+            margin-right: 6px;
+        }
+    }
+
+    .item-detail {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 12px 20px;
+        box-sizing: border-box;
+        cursor: pointer;
+
+        .left {
+            display: flex;
+
+            .icon-currency {
+                width: 24px;
+                height: 24px;
+                margin-right: 12px;
+            }
+
+            .currency-info {
+                .name {
+                    font-weight: 500;
+                    font-size: 15px;
+                    margin-bottom: 5px;
+                }
+
+                .desc {
+                    font-weight: 400;
+                    font-size: 12px;
+                    color: #a2a2a2;
+                }
+            }
+        }
+
+        .right {
+            text-align: right;
+            max-width: calc(100% - 150px);
+
+            .num,
+            .amount {
+                word-break: break-all;
+            }
+
+            .num {
+                font-weight: 500;
+                font-size: 15px;
+                margin-bottom: 5px;
+            }
+
+            .amount {
+                font-weight: 400;
+                font-size: 12px;
+                color: #a2a2a2;
+            }
+        }
+    }
+}
+</style>

+ 196 - 0
src/components/input-action-sheet.vue

@@ -0,0 +1,196 @@
+<template>
+    <div class="input-action-sheet-wrapper" v-if="visible" :style="{ position: position }">
+        <div class="input-action-sheet-content">
+            <div class="title">
+                {{ title }}
+            </div>
+            <div class="input-wrapper">
+                <input class="input" v-model="inputVal" placeholder="0" autofocus @input="onValueInput"
+                    @blur="onValueBlur">
+            </div>
+            <div class="desc">
+                {{ desc }}
+            </div>
+            <div class="btn-wrapper">
+                <div class="btn cancel" @click="cancel">
+                    {{ cancelText }}
+                </div>
+                <div class="btn confirm" @click="confirm">
+                    {{ confirmText }}
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, defineProps, defineEmits, watch } from "vue";
+
+let inputVal = ref('');
+
+const props = defineProps({
+    visible: {
+        type: Boolean,
+        default: false
+    },
+    position: {
+        type: String,
+        default: 'fixed'
+    },
+    title: {
+        type: String,
+        default: '',
+    },
+    cancelText: {
+        type: String,
+        default: 'Cancel',
+    },
+    confirmText: {
+        type: String,
+        default: 'Continue',
+    },
+    desc: {
+        type: String,
+        defautl: ''
+    }
+});
+
+watch(() => props.visible, (newVal) => {
+    if (!newVal) {
+        inputVal.value = ''
+    }
+})
+
+const emits = defineEmits(["cancel", "confirm", "onInput"]);
+
+const onValueInput = () => {
+    let val = inputValHandler();
+    emits("onInput", { inputVal: val });
+    return val;
+}
+
+const onValueBlur = () => {
+    return inputValHandler();
+}
+
+const inputValHandler = () => {
+    let val = inputVal.value;
+    val = val.replace(/^\D*(\d*(?:\.\d{0,18})?).*$/g, '$1');
+
+    if (val == '00') {
+        val = '0'
+    }
+    if (val.indexOf('.') > -1) { //校验 例:00.12 => 0.12
+        let arr = val.split('.');
+        if (arr[0].startsWith('0')) {
+            let num = +arr[0];
+            val = num + '.' + arr[1];
+        }
+    }
+
+    inputVal.value = val;
+
+    return val;
+}
+
+const cancel = () => {
+    emits("cancel", {});
+}
+
+const confirm = () => {
+    if (inputVal.value > 0) {
+        emits("confirm", { inputVal: inputVal.value });
+    }
+}
+
+</script>
+
+<style scoped lang="scss">
+.input-action-sheet-wrapper {
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    position: fixed;
+    top: 0;
+    left: 0;
+
+    .input-action-sheet-content {
+        width: 335px;
+        min-height: 186px;
+        background: #fff;
+        border-radius: 20px;
+        padding: 20px 15px;
+        box-sizing: border-box;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        z-index: 1000;
+        transform: translate(-50%, -50%);
+
+        .title {
+            font-weight: 600;
+            font-size: 16px;
+        }
+
+        .input-wrapper {
+            width: 100%;
+            border: 1px solid #DFDFDF;
+            border-radius: 5px;
+            box-sizing: border-box;
+            height: 42px;
+            margin: 17px 0 10px 0;
+
+            .input {
+                width: 100%;
+                height: 100%;
+                outline: none;
+                border: none;
+                padding: 0 10px;
+                box-sizing: border-box;
+                border-radius: 5px;
+                font-weight: 600;
+                font-size: 16px;
+            }
+
+            .input::placeholder {
+                color: #B3B3B3;
+            }
+        }
+
+        .desc {
+            font-weight: 500;
+            font-size: 13px;
+            color: #888888;
+            min-height: 40px;
+        }
+
+        .btn-wrapper {
+            display: flex;
+            justify-content: space-between;
+            margin-top: 20px;
+
+            .btn {
+                width: 150px;
+                height: 47px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                font-weight: 500;
+                font-size: 16px;
+                box-sizing: border-box;
+                border-radius: 1000px;
+                cursor: pointer;
+            }
+
+            .cancel {
+                border: 1px solid #CFCFCF;
+            }
+
+            .confirm {
+                color: #fff;
+                background: #1D9BF0;
+            }
+        }
+    }
+}
+</style>

+ 112 - 0
src/components/join-group-finish-dialog.vue

@@ -0,0 +1,112 @@
+<template>
+    <div class="join-group-overlay" :style="{'position': position}" v-if="dialogVisible">
+        <div class="content-wrapper" :style="contentStyle">
+            <img :src="require('@/assets/svg/icon-celebration.svg')" 
+                class="icon-celebration"
+                :style="iconStyle">
+            <div class="desc" :style="descStyle">{{content}}</div>
+            <div class="btn-wrapper">
+                <div class="btn confirm" @click="confirm">Finish</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { defineEmits, defineProps } from "vue";
+defineProps({
+    dialogVisible: {
+        type: Boolean,
+        default: false,
+    },
+    content: {
+        type: String,
+        default: 'Joined Successfully'
+    },
+    position: {
+        type: String,
+        default: 'fixed'
+    },
+    contentStyle: {
+        type: Object,
+        default: () => {
+            return {}
+        }
+    },
+    iconStyle: {
+        type: Object,
+        default: () => {
+            return {}
+        }
+    },
+    descStyle: {
+        type: Object,
+        default: () => {
+            return {}
+        }
+    }
+});
+
+const emits = defineEmits(["confirm"]);
+
+const confirm = () => {
+    emits("confirm", {});
+};
+
+</script>
+
+<style lang="scss" scoped>
+.join-group-overlay {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 3000;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    overflow: auto;
+
+    .content-wrapper {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        background: #FFFFFF;
+        border-radius: 20px;
+        box-sizing: border-box;
+        transform: translate(-50%, -50%);
+        text-align: center;
+        width: 500px;
+
+        .icon-celebration {
+            width: 120px;
+            margin-top: 60px;
+        }
+
+        .desc {
+            font-weight: 600;
+            font-size: 22px;
+            margin-top: 36px;
+            margin-bottom: 58px;
+        }
+
+        .btn-wrapper {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            .confirm {
+                width: 100%;
+                padding: 14px;
+                box-sizing: border-box;
+                background: #1D9BF0;
+                color: #fff;
+                border-radius: 100px;
+                font-weight: 600;
+                font-size: 16px;
+                cursor: pointer;
+                margin: 0px 20px 20px 20px;
+            }
+        }
+    }
+}
+</style>

+ 122 - 0
src/components/modal-layer.vue

@@ -0,0 +1,122 @@
+<template>
+    <div class="msg-box-overlay" v-if="visible">
+        <div class="content-wrapper">
+            <div class="title">{{title}}</div>
+            <div class="desc">{{content}}</div>
+            <div class="btn-wrapper">
+                <div class="btn cancel" @click="cancel">
+                    {{cancelText}}
+                </div>
+                <div class="btn confirm" @click="confirm">
+                    {{confirmText}}
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+/* eslint-disable */
+import { defineEmits, defineProps } from "vue";
+const props = defineProps({
+    visible: {
+        type: Boolean,
+        default: false,
+    },
+    title: {
+        type: String,
+        default: ''
+    },
+    content: {
+        type: String,
+        default: ''
+    },
+    cancelText: {
+        type: String, 
+        default: 'Cancel'
+    },
+    confirmText: {
+        type: String, 
+        default: 'Confirm'
+    }
+});
+
+const emits = defineEmits(["cancel", "confirm"]);
+
+
+const cancel = () => {
+    emits("cancel", {});
+};
+
+const confirm = () => {
+    emits("confirm", {});
+};
+
+</script>
+
+<style lang="scss" scoped>
+.msg-box-overlay {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1000;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.8);
+    overflow: auto;
+
+    .content-wrapper {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        width: 335px;
+        min-height: 190px;
+        background: #FFFFFF;
+        border-radius: 20px;
+        padding: 20px 13px;
+        box-sizing: border-box;
+        transform: translate(-50%, -50%);
+        text-align: center;
+
+        .title {
+            font-weight: 600;
+            font-size: 18px;
+            margin-bottom: 13px;
+        }
+        .desc {
+            min-height: 44px;
+            font-weight: 400;
+            font-size: 15px;
+            margin-bottom: 20px;
+        }
+        .btn-wrapper {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .btn {
+                width: 150px;
+                height: 46px;
+                border-radius: 100px;
+                box-sizing: border-box;
+                font-weight: 600;
+                font-size: 16px;
+                cursor: pointer;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+            }
+
+            .cancel {
+                color: #fff;
+                background: #FF0000;
+            }
+            .confirm {
+                color: #000;
+                border: 1px solid #000000;
+            }
+        }
+    }
+}
+</style>

+ 116 - 0
src/components/nft-card.vue

@@ -0,0 +1,116 @@
+<template>
+    <div class="show" :style="{ zoom: zoom }">
+        <div class="card" :class="item.modelName">
+            <div class="logo">
+                <img v-if="item.logoImagePath" :src="item.logoImagePath" alt="" />
+            </div>
+            <div class="member">{{ item.projectName === '' ? 'xxxx' : item.projectName }}</div>
+            <div class="number">{{ nftItemId === '' ? '0000' : nftItemId }}</div>
+        </div>
+        <img class="bg" :src="item.modelImagePath" />
+    </div>
+</template>
+
+<script setup>
+import { onBeforeMount, defineProps, ref } from 'vue'
+
+const zoom = ref(1);
+
+const props = defineProps({
+    item: {
+        type: Object,
+        default: function() {
+            return {}
+        },
+    },
+    nftItemId: {
+        type: String,
+        default: '0000',
+    },
+    width: {
+        type: Number,
+        default: 400
+    }
+})
+
+onBeforeMount(() => {
+    if (props.width) {
+        zoom.value = props.width / 400
+    }
+})
+</script>
+
+<style lang='scss' scoped>
+.show {
+    position: relative;
+    overflow: hidden;
+    width: 400px;
+    height: 400px;
+    .card {
+        position: absolute;
+        left: 53px;
+        top: 103px;
+        width: 294px;
+        height: 186px;
+        .logo {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            width: 100px;
+            height: 100px;
+            border-radius: 50%;
+            background-color: #fff;
+            img {
+                width: 100%;
+                height: 100%;
+                border-radius: 50%;
+                object-fit: cover;
+            }
+        }
+        .member {
+            position: absolute;
+            top: 11px;
+            left: 11px;
+            width: 228px;
+            font-size: 12px;
+            text-align: left;
+            font-weight: 800;
+            line-height: 13px;
+        }
+        .number {
+            position: absolute;
+            top: 11px;
+            right: 10px;
+            font-size: 12px;
+            font-weight: 800;
+            line-height: 13px;
+            letter-spacing: 1px;
+        }
+        &.s1 {
+            .member, .number {
+                color: #ffffff;
+            }
+        }
+        &.s2 {
+            .member, .number {
+                color: #4AC3E1;
+            }
+        }
+        &.s3 {
+            .member, .number {
+                color: #606C94;
+            }
+        }
+        &.s4 {
+            .member, .number {
+                color: #504215;
+            }
+        }
+    }
+    .bg {
+        width: 100%;
+        height: 100%;
+    }
+}
+</style>

+ 526 - 0
src/components/popup-transactions.vue

@@ -0,0 +1,526 @@
+<template>
+    <div class="com-wrapper" ref="listWrapper" @scroll="listScroll">
+        <template v-if="!dataList.length">
+            <img class="icon-empty" :src="require('@/assets/svg/icon-empty-list.svg')" />
+        </template>
+        <div class="list-wrapper" ref="listContent">
+            <div>
+                <div class="cell" v-for="(item, index) in dataList" :key="index">
+                    <red-dot class="red-dots" v-if="item.unReadMsgCount > 0 && isReadMsg"></red-dot>
+                    <div class="img-wrapper">
+                        <!-- 收入- 任务红包领取 -->
+                        <template
+                            v-if="item.bizType == 1 || item.bizType == 5 || item.bizType == 10 || item.bizType == 11 || item.bizType == 12">
+                            <img class="icon-avatar" style="margin-right: 0"
+                                :src="item.bizType != 12 && item.bizData ? item.bizData.avatarUrl : require('@/assets/svg/icon-treasure-return.svg')" />
+                            <img class="icon-give" style="right: -4px"
+                                :src="item.bizType == 10 || item.bizType == 11 || item.bizType == 12 ? require('@/assets/svg/icon-small-treasure.svg') : require('@/assets/svg/icon-get-giveaways-s.svg')" />
+                        </template>
+
+                        <!-- 收入- 任务红包结余退款, -->
+                        <template v-else-if="item.bizType == 2 || item.bizType == 6">
+                            <img style="margin-left:-4px; width: 38px" :src="
+                                require('@/assets/svg/icon-give-refund-list.svg')
+                            " />
+                        </template>
+                        <!-- 收入 - 提现 -- 失败退款 -->
+                        <template v-else-if="item.bizType == 3">
+                            <img style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-list-withdraw.svg')
+                            " />
+                        </template>
+                        <!-- 收入-充值 -->
+                        <template v-else-if="item.bizType == 4">
+                            <img style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-list-top-up.svg')
+                            " />
+                        </template>
+                        <!-- 收入 - 盲盒 -->
+                        <template v-else-if="item.bizType == 7">
+                            <img class="icon-avatar" style="margin-left:-4px" :src="item.bizData.imagePath"
+                                v-if="item.bizData && item.bizData.imagePath" />
+                            <img class="icon-avatar" style="margin-left:-4px"
+                                :src="require('@/assets/svg/icon-wallter-list-blind-box.svg')" v-else />
+                            <img class="icon-give" :src="require('@/assets/svg/icon-list-withdraw-s.svg')" />
+                        </template>
+                        <!-- 收入 - 盲盒 -->
+                        <template v-else-if="item.bizType == 8">
+                            <img class="icon-avatar" style="margin-left:-4px" :src="item.bizData.imagePath"
+                                v-if="item.bizData && item.bizData.imagePath" />
+                            <img class="icon-avatar" style="margin-left:-4px"
+                                :src="require('@/assets/svg/icon-wallter-list-blind-box.svg')" v-else />
+                            <img class="icon-give" :src="require('@/assets/svg/icon-get-giveaways-s.svg')" />
+                        </template>
+                        <!-- 支出 - 提现 -->
+                        <template v-else-if="item.bizType == -1">
+                            <img style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-list-withdraw.svg')
+                            " />
+                        </template>
+                        <!-- 支出-任务红包余额支付 -->
+                        <template v-else-if="item.bizType == -2">
+                            <img class="icon-avatar" style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-big-give.svg')
+                            " />
+                            <img class="icon-give" :src="
+                                require('@/assets/svg/icon-list-withdraw-s.svg')
+                            " />
+                        </template>
+                        <!-- 支出-买盲盒 -->
+                        <template v-else-if="item.bizType == -3">
+                            <img style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-wallter-list-blind-box.svg')
+                            " />
+                        </template>
+                        <template v-else-if="item.bizType == -4">
+                            <img class="icon-avatar" style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-big-give.svg')
+                            " />
+                            <img class="icon-give" :src="
+                                require('@/assets/svg/icon-list-withdraw-s.svg')
+                            " />
+                        </template>
+                        <!-- 交易链手续费 -->
+                        <template v-else-if="item.bizType == -5">
+                            <img class="icon-avatar" style="margin-left:-4px" :src="item.bizData.imagePath"
+                                v-if="item.bizData && item.bizData.imagePath" />
+                            <img class="icon-avatar" style="margin-left:-4px"
+                                :src="require('@/assets/img/icon-box2.png')" v-else />
+                            <img class="icon-give" :src="require('@/assets/svg/icon-transaction-s.svg')" />
+                        </template>
+                        <!-- 支出-夺宝 -->
+                        <template v-else-if="item.bizType == -6">
+                            <img class="icon-avatar" style="margin-left:-4px" :src="
+                                require('@/assets/svg/icon-big-give.svg')
+                            " />
+                            <img class="icon-give" :src="
+                                require('@/assets/svg/icon-list-withdraw-s.svg')
+                            " />
+                        </template>
+                    </div>
+                    <div class="info-wrapper">
+                        <div class="left">
+                            <div class="nickname">
+                                <template v-if="item.bizType == 1">
+                                    Giveaways ({{ item.bizData.nickName }})
+                                </template>
+                                <template v-else-if="item.bizType == 2">
+                                    Giveaways Refund
+                                </template>
+                                <template v-else-if="item.bizType == 3">
+                                    Withdrawal Refund
+                                </template>
+                                <template v-else-if="item.bizType == 4">
+                                    Deposit
+                                </template>
+                                <template v-else-if="item.bizType == 5">
+                                    Winning the lottery
+                                </template>
+                                <template v-else-if="item.bizType == 6">
+                                    Lottery Refund
+                                </template>
+                                <template v-else-if="item.bizType == 7">
+                                    Sell NFT Mystery box*{{ (item.bizData && item.bizData.nftItemCount || '') }}
+                                </template>
+                                <template v-else-if="item.bizType == 8">
+                                    NFT Refund
+                                </template>
+                                <template v-else-if="item.bizType == 10 || item.bizType == 11">
+                                    Get Treasure Chest
+                                </template>
+                                <template v-else-if="item.bizType == 12">
+                                    Treasure Chest Refund
+                                </template>
+                                <template v-else-if="item.bizType == -1">
+                                    Withdrawal
+                                </template>
+                                <template v-else-if="item.bizType == -2">
+                                    Giveaways
+                                </template>
+                                <template v-else-if="item.bizType == -3">
+                                    Buy NFT Mystery box*{{ (item.bizData && item.bizData.nftItemCount || '') }}
+                                </template>
+                                <template v-else-if="item.bizType == -4">
+                                    Lottery Giveaway
+                                </template>
+                                <template v-else-if="item.bizType == -5">
+                                    Transaction Royalties
+                                </template>
+                                <template v-else-if="item.bizType == -6">
+                                    Send Treasure Chest
+                                </template>
+                            </div>
+                            <div class="time">{{ moment(item.createTimestamp).format('MM-DD HH:mm:ss') }}</div>
+                        </div>
+                        <div class="right">
+                            <div class="msg">
+                                <template v-if="item.bizType == -1">
+                                    <!-- 提现支出-状态(0:已申请,1:支付中,2:提现成功,3:提现失败) -->
+                                    <template
+                                        v-if="item.bizData.withdrawStatus == 0 || item.bizData.withdrawStatus == 1">
+                                        <div>
+                                            <div class="balance"
+                                                :class="{ 'balance-direction': item.trxAmountCurrencyInfo.tokenSymbol.length + ('' + item.trxAmountValue).length > 12 }">
+                                                <span class="amount">
+                                                    <a-tooltip :title="'-' + item.trxAmountValue">
+                                                        -{{ getBit(item.trxAmountValue) || 0 }}
+                                                    </a-tooltip>
+                                                </span>
+                                                <div class="trx-amount-currency-info">
+                                                    <span class="name">{{ item.trxAmountCurrencyInfo.tokenSymbol
+                                                    }}</span>
+                                                    <img :src="item.trxAmountCurrencyInfo.iconPath" alt="">
+                                                </div>
+                                            </div>
+                                            <div class="desc">
+                                                in progress
+                                            </div>
+                                        </div>
+                                    </template>
+                                    <template v-else-if="item.bizData.withdrawStatus == 2">
+                                        <div class="balance"
+                                            :class="{ 'balance-direction': item.trxAmountCurrencyInfo.tokenSymbol.length + ('' + item.trxAmountValue).length > 12 }">
+                                            <span class="amount">
+                                                <a-tooltip :title="'-' + item.trxAmountValue">
+                                                    -{{ getBit(item.trxAmountValue) || 0 }}
+                                                </a-tooltip>
+                                            </span>
+                                            <div class="trx-amount-currency-info">
+                                                <span class="name">{{ item.trxAmountCurrencyInfo.tokenSymbol }}</span>
+                                                <img :src="item.trxAmountCurrencyInfo.iconPath" alt="">
+                                            </div>
+                                        </div>
+                                    </template>
+                                    <template v-else-if="item.bizData.withdrawStatus == 3">
+                                        <div>
+                                            <div class="balance"
+                                                :class="{ 'balance-direction': item.trxAmountCurrencyInfo.tokenSymbol.length + ('' + item.trxAmountValue).length > 12 }">
+                                                <span class="amount">
+                                                    <a-tooltip :title="'-' + item.trxAmountValue">
+                                                        -{{ getBit(item.trxAmountValue) || 0 }}
+                                                    </a-tooltip>
+                                                </span>
+                                                <div class="trx-amount-currency-info">
+                                                    <span class="name">{{ item.trxAmountCurrencyInfo.tokenSymbol
+                                                    }}</span>
+                                                    <img :src="item.trxAmountCurrencyInfo.iconPath" alt="">
+                                                </div>
+                                            </div>
+                                            <div class="desc">
+                                                Withdrawal failed
+                                            </div>
+                                        </div>
+                                    </template>
+                                    <template v-else>
+                                        <div class="balance"
+                                            :class="{ 'balance-direction': item.trxAmountCurrencyInfo.tokenSymbol.length + ('' + item.trxAmountValue).length > 12 }">
+                                            <span class="amount">
+                                                <a-tooltip :title="'-' + item.trxAmountValue">
+                                                    -{{ getBit(item.trxAmountValue) || 0 }}
+                                                </a-tooltip>
+                                            </span>
+                                            <div class="trx-amount-currency-info">
+                                                <span class="name">{{ item.trxAmountCurrencyInfo.tokenSymbol }}</span>
+                                                <img :src="item.trxAmountCurrencyInfo.iconPath" alt="">
+                                            </div>
+                                        </div>
+                                    </template>
+                                </template>
+
+                                <template v-else>
+                                    <div class="balance"
+                                        :class="{ 'balance-direction': item.trxAmountCurrencyInfo.tokenSymbol.length + ('' + item.trxAmountValue).length > 12 }">
+
+                                        <!--支出—— -2:零钱余额支付 、-3: NFT盲盒余额支付 -->
+                                        <span class="amount"
+                                            v-if="item.bizType == -2 || item.bizType == -3 || item.bizType == -4 || item.bizType == -5 || item.bizType == -6">
+                                            <a-tooltip :title="'-' + item.trxAmountValue">
+                                                -{{ getBit(item.trxAmountValue) || 0 }}
+                                            </a-tooltip>
+                                        </span>
+                                        <!-- 收入—— bizType:1、2、3、4 -->
+                                        <span class="amount" v-else>
+                                            <a-tooltip :title="'+' + item.trxAmountValue">
+                                                +{{ getBit(item.trxAmountValue) || 0 }}
+                                            </a-tooltip>
+                                        </span>
+
+                                        <div class="trx-amount-currency-info">
+                                            <span class="name">{{ item.trxAmountCurrencyInfo.tokenSymbol }}</span>
+                                            <img :src="item.trxAmountCurrencyInfo.iconPath" alt="">
+                                        </div>
+                                    </div>
+                                </template>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import redDot from "@/components/red-dot.vue";
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import { transactionsList } from "@/http/account";
+import { readAllMsgByType, getAllMessageInfo } from "@/http/messageApi"
+import { getBit } from "@/uilts/help";
+
+let moment = require('moment');
+let dataList = ref([]);
+let listWrapper = ref(null);
+let listContent = ref(null);
+let listReqParams = {
+    params: {
+        pageNum: 1,
+        pageSize: 20,
+    },
+    loadMore: false,
+};
+
+let isReadMsg = ref(true);
+
+const getTransactionsList = () => {
+    transactionsList({
+        params: listReqParams.params,
+    }).then((res) => {
+        console.log(res);
+        if (res.code == 0) {
+            let resData = res.data;
+            if (resData.length) {
+                for (let i = 0; i < resData.length; i++) {
+                    resData[i]["bizData"] = JSON.parse(resData[i]["bizData"]);
+                }
+
+                if (listReqParams.params.pageNum < 2) {
+                    dataList.value = resData;
+                } else {
+                    let data = dataList.value;
+                    data = data.concat(resData);
+                    dataList.value = data;
+                }
+                listReqParams.loadMore = false;
+            }
+        }
+    });
+};
+
+onMounted(() => {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_RUNTIME_CONNECT_POPUP,
+        data: {}
+    });
+    setTimeout(() => {
+        isReadMsg.value = false;
+        readAllMsgByType({
+            params: {
+                msgType: 2 // 1:任务红包 2:钱包明细
+            }
+        }).then(() => {
+            setMessageCount();
+        })
+    }, 2000)
+    getTransactionsList();
+});
+
+const setMessageCount = () => {
+    getAllMessageInfo({
+        params: {
+        }
+    }).then(res => {
+        if (res.code == 0) {
+            let { unReadCountTotal = 0 } = res.data;
+            if (unReadCountTotal > 0) {
+                let text = unReadCountTotal > 99 ? '99+' : unReadCountTotal + '';
+                messageCenter.send({
+                    actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_SETBADGEINFO,
+                    data: { text }
+                })
+            } else {
+                messageCenter.send({
+                    actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_HIDEBADGE,
+                    data: {}
+                })
+            }
+        }
+    });
+}
+
+const listScroll = (e) => {
+    let wrapperHeight = listWrapper.value.offsetHeight;
+    let listContentHeight = listContent.value.offsetHeight;
+    let scrollTop = e.target.scrollTop || 0;
+    if (
+        listReqParams.loadMore === false &&
+        wrapperHeight + scrollTop >= listContentHeight - 30
+    ) {
+        listReqParams.loadMore = true;
+        listReqParams.params.pageNum++;
+        getTransactionsList();
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.com-wrapper {
+    height: 100%;
+    position: relative;
+    overflow-y: auto;
+
+    .icon-empty {
+        position: absolute;
+        left: 50%;
+        top: 20%;
+        transform: translateX(-50%);
+    }
+
+    .com-nav-bar {
+        padding: 14px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        font-weight: 500;
+        font-size: 13px;
+
+        .icon {
+            width: 16px;
+            margin-right: 6px;
+            cursor: pointer;
+        }
+    }
+
+    .list-wrapper {
+        width: 100%;
+        // height: calc(100% - 51px);
+        // height: 100%;
+
+        .cell {
+            display: flex;
+            justify-content: space-between;
+            min-height: 66px;
+            box-sizing: border-box;
+            padding-left: 20px;
+            position: relative;
+
+            .red-dots {
+                position: absolute;
+                right: 4px;
+                top: 4px;
+            }
+
+            .img-wrapper {
+                position: relative;
+                margin-right: 16px;
+                box-sizing: border-box;
+                margin-top: 11px;
+
+                .icon-avatar {
+                    width: 34px;
+                    height: 34px;
+                    border-radius: 50%;
+                    margin-right: 4px;
+                }
+
+                .icon-give {
+                    position: absolute;
+                    right: -2px;
+                    top: 19px;
+                }
+            }
+
+            .info-wrapper {
+                flex: 1;
+                height: 100%;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                box-sizing: border-box;
+                padding: 10px 12px 10px 0;
+
+                .left {
+                    .nickname {
+                        font-weight: 500;
+                        font-size: 14px;
+                        margin-bottom: 5px;
+                        word-break: break-all;
+                        max-width: 140px;
+                    }
+
+                    .time {
+                        font-size: 12px;
+                        color: #B0B0B0;
+                    }
+                }
+
+                .right {
+                    display: flex;
+                    align-items: center;
+                    cursor: pointer;
+
+                    .msg {
+                        font-weight: 500;
+                        font-size: 14px;
+                        display: flex;
+                        align-items: center;
+
+                        .amount {
+                            color: #E86F00;
+                            max-width: 110px;
+                            min-width: 20px;
+                            display: inline-block;
+                            word-break: break-all;
+                            text-align: right;
+                        }
+
+                        .name {
+                            margin-left: 3px;
+                            max-width: 130px;
+                            line-height: 14px;
+                            font-size: 13px;
+                            word-break: break-all;
+                        }
+
+                        img {
+                            width: 14px;
+                            height: 14px;
+                            margin-left: 4px;
+                        }
+
+                        .balance {
+                            text-align: right;
+                            display: flex;
+                            align-items: center;
+
+                            .trx-amount-currency-info {
+                                display: flex;
+                                align-items: center;
+                            }
+                        }
+
+                        .balance-direction {
+                            flex-direction: column;
+                            align-items: flex-end;
+                        }
+
+                        .desc {
+                            text-align: right;
+                            font-weight: 400;
+                            font-size: 12px;
+                            color: #797979;
+                            margin-top: 4px;
+                        }
+                    }
+
+                    .icon {
+                        width: 18px;
+                        height: 24px;
+                    }
+                }
+            }
+        }
+    }
+}
+</style>

+ 26 - 0
src/components/red-dot.vue

@@ -0,0 +1,26 @@
+<template>
+    <div class="red-dot" 
+        :style="{width: size + 'px', height: size + 'px',
+        background: color}">
+    </div>
+</template>
+
+<script setup>
+import { defineProps } from "vue";
+defineProps({
+    size: {
+        type: String,
+        default: '8'
+    },
+    color: {
+        type: String,
+        default: '#FF0000'
+    },
+});
+</script>
+
+<style lang="scss" scoped>
+.red-dot {
+    border-radius: 50%;
+}
+</style>

+ 234 - 0
src/components/v-head.vue

@@ -0,0 +1,234 @@
+<template>
+    <div class="head" :class="{ 'border': show_state != 'home', 'home': show_state == 'home' }">
+        <template v-if="show_state == 'home'">
+            <div class="desc">
+                <img class="img" :src="user_info.avatarUrl" />
+                <font class="name">{{user_info.nickName}}</font>
+            </div>
+            <div class="logo">
+                <img :src="require('@/assets/svg/icon-denet-logo.svg')" />
+            </div>
+        </template>
+        <template v-else>
+            <img :src="require('@/assets/svg/icon-back.svg')" alt="" class="back" @click="clickBack">
+            <div class="title">{{ props.title }}</div>
+            <img :src="require('@/assets/svg/icon-back-head-list.svg')"
+                v-if="show_list" 
+                class="list" 
+                @click="clickList" />
+            <img :src="require('@/assets/svg/icon-refresh.svg')" alt="" class="refresh" v-if="show_refresh"
+                @click="clickRefresh" :class="{ transform_rotate: state.rotate }">
+            <img :src="require('@/assets/svg/icon-head-help.svg')" alt="" class="help" v-if="props.show_help"
+                @click="clickHelp">
+            <img :src="require('@/assets/svg/icon-more-l.svg')" alt="" class="more" v-if="props.show_more"
+                @click="state.show_option = true">
+            <div class="area-option" v-if="state.show_option" @click="state.show_option = false">
+                <div class="option">
+                    <div class="item" @click="clickItem('/transactions')">
+                        <img :src="require('@/assets/svg/icon-menu.svg')" alt="">
+                        <span>Transactions History</span>
+                    </div>
+                </div>
+            </div>
+        </template>
+    </div>
+</template>
+<script setup>
+import { defineProps, defineEmits, reactive, ref } from "vue";
+import { useRouter } from "vue-router";
+
+let props = defineProps({
+    title: String,
+    show_state: String,
+    show_refresh: Boolean,
+    show_option: Boolean,
+    show_more: Boolean,
+    show_help: Boolean,
+    back_url: String,
+    user_info: Object,
+    show_list: Boolean,
+    transactionsRouterParams: Object,
+})
+
+let state = reactive({
+    show_option: ref(props.show_option),
+    rotate: false
+})
+
+const emit = defineEmits(['on-refresh', 'onBack'])
+
+const router = useRouter()
+
+function clickBack() {
+    if (props.back_url) {
+        router.replace(props.back_url)
+    } else {
+        router.back()
+    }
+    emit('onBack')
+}
+
+function clickRefresh() {
+    if (state.rotate) {
+        return
+    }
+    state.rotate = true
+    emit('on-refresh')
+    setTimeout(() => {
+        state.rotate = false
+    }, 1000)
+}
+
+function clickItem(path) {
+    let params = props.transactionsRouterParams || {};
+    router.push({
+        path: path,
+        query: {
+            params: JSON.stringify(params)
+        }
+    })
+}
+
+function clickHelp() {
+    window.open(`https://aboard-cattle-610.notion.site/How-to-withdraw-assets-from-DeNet-to-MetaMask-01c679bb9ff441429e31e8f7c1f67411`)
+}
+
+function clickList() {
+    let params = props.transactionsRouterParams || {};
+    console.log('transactionsRouterParams',params);
+    router.push({
+        path: '/transactions',
+        query: {
+            params: JSON.stringify(params)
+        }
+    })
+}
+
+</script>
+<style lang="scss" scoped>
+.border {
+    border-bottom: 1px solid #DBDBDB;
+}
+
+.head {
+    height: 48px;
+    display: flex;
+    flex-wrap: nowrap;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 12px;
+    overflow: hidden;
+
+    &.home {
+        height: 64px;
+    }
+
+    .logo {
+        display: flex;
+        align-items: center;
+        img {
+            width: 26px;
+            height: 26px;
+        }
+    }
+
+    .desc {
+        .img {
+            width: 34px;
+            height: 34px;
+            overflow: hidden;
+            border-radius: 50%;
+            margin-right: 10px;
+        }
+        .name {
+            display: inline-block;
+            width: 200px;
+            color: #000000;
+            font-size: 16px;
+            font-weight: bold;
+        }
+    }
+
+    .area-option {
+        width: 100%;
+        height: 100%;
+        position: absolute;
+        top: 0;
+        left: 0;
+        z-index: 111;
+
+        .option {
+            position: absolute;
+            top: 43px;
+            right: 15px;
+            background: #fff;
+            filter: drop-shadow(0px 3px 20px rgba(0, 0, 0, 0.2));
+            width: 240px;
+            border-radius: 15px;
+            overflow: hidden;
+
+            .item {
+                width: 100%;
+                height: 50px;
+                display: flex;
+                align-items: center;
+                cursor: pointer;
+                border-top: 1px solid #E9E9E9;
+
+                img {
+                    margin-left: 15px;
+                    width: 30px;
+                    height: 30px;
+                    margin-right: 6px;
+                }
+
+                span {
+                    font-weight: 500;
+                    font-size: 14px;
+                }
+            }
+
+            .item:first-child {
+                border-top: 0;
+            }
+
+            .item:hover {
+                background: #F5F5F5;
+            }
+        }
+    }
+
+    .transform_rotate {
+        transform: rotate(360deg);
+        transition-duration: 1s;
+    }
+
+    img {
+        cursor: pointer;
+        width: 24px;
+        height: 24px;
+    }
+
+    .list {
+        margin-right: 12px;
+    }
+
+    .refresh {
+        
+    }
+
+    .help {
+        margin-left: 20px;
+        margin-right: 12px;
+    }
+
+    .title {
+        padding-left: 16px;
+        flex: 1;
+        color: #000000;
+        font-size: 16px;
+        font-weight: 500;
+        word-break: break-all;
+    }
+}
+</style>

+ 4 - 4
src/http/configAPI.js

@@ -9,9 +9,9 @@ const api = {
 }
 
 const logApi = {
-	production: 'https://log.weiqumeta.com',
-	pre: 'https://prelog.weiqumeta.com',
-	development: 'https://testlog.weiqumeta.com'
+	production: 'https://log.denetme.net',
+	pre: 'https://prelog.denetme.net',
+	development: 'https://testlog.denetme.net'
 }
 
 const page = {
@@ -22,7 +22,7 @@ const page = {
 
 export const baseAPIUrl = api[process.env.NODE_ENV] + '/denet'
 
-export const logAPIUrl = logApi[process.env.NODE_ENV] + '/log-center'
+export const logAPIUrl = logApi[process.env.NODE_ENV] + '/denet/log'
 
 export const pageUrl = page[process.env.NODE_ENV]
 

+ 3 - 3
src/http/fetch.js

@@ -1,12 +1,12 @@
 import { appVersionCode, baseAPIUrl } from '@/http/configAPI.js'
-import { getChromeStorage } from '@/uilts/chromeExtension.js'
+import { getChromeStorageFromExtension } from '@/uilts/chromeExtension.js'
 
 export async function commonFetch({ url = '', method = 'POST' , params = {}, baseInfo = {}}) {
 
-    let storage_mid = await getChromeStorage('mid') || ''
+    let storage_mid = await getChromeStorageFromExtension('mid') || ''
     const { mid } = storage_mid || {}
     if (!baseInfo.token || !baseInfo.uid) {
-        const { accessToken: token = '', uid = '' } = await getChromeStorage('userInfo') || {}
+        const { accessToken: token = '', uid = '' } = await getChromeStorageFromExtension('userInfo') || {}
         baseInfo.token = token
         baseInfo.uid = uid
     }

+ 1 - 11
src/http/logApi.js

@@ -1,19 +1,9 @@
-import { service } from "./request";
 import { logAPIUrl } from '@/http/configAPI.js'
 import { commonFetch } from '@/http/fetch'
 
-// export function logApi(params) {
-//     return service({
-//         url: `${logAPIUrl}/statistics/uploadLogFromFrontend
-//         `,
-//         method: 'post',
-//         data: params
-//     })
-// }
-
 export function logApi(params = {}) {
     return commonFetch({
-        url: `${logAPIUrl}/statistics/uploadLogFromFrontend`,
+        url: `${logAPIUrl}/uploadLogFromFrontend`,
         baseInfo: {
             pageSource: params.params.pageSource || ''
         },

+ 24 - 0
src/http/nft.js

@@ -71,4 +71,28 @@ export function getTwitterNftGroupInfo(params) {
         method: 'post',
         data: params
     })
+}
+
+export function transferRequest(params) {
+    return service({
+        url: `/nft/transfer/request`,
+        method: 'post',
+        data: params
+    })
+}
+
+export function redeemNft(params) {
+    return service({
+        url: `/nft/project/redeemNft`,
+        method: 'post',
+        data: params
+    })
+}
+
+export function listPossessNftProject(params) {
+    return service({
+        url: `/nft/possess/listPossessNftProject`,
+        method: 'post',
+        data: params
+    })
 }

+ 6 - 6
src/log-center/logger.js

@@ -1,7 +1,7 @@
 import { logApi, reportFrontLogApi } from '@/http/logApi'
 import { getBrowser } from '@/uilts/help.js';
 import { logType } from './logEnum.js';
-import { getChromeStorage } from '@/uilts/chromeExtension'
+import { getChromeStorageFromExtension } from '@/uilts/chromeExtension'
 let userInfo = null;
 let mid = '';
 /**
@@ -19,10 +19,10 @@ export async function reportLog(eventData = {}, extParams = {}) {
     // 2.reportLog 异常 存储到本地,再上报
     try {
         if (!userInfo) {
-            userInfo = await getChromeStorage('userInfo').catch((error) => {console.log(error) }) || null;
+            userInfo = await getChromeStorageFromExtension('userInfo').catch((error) => {console.log(error) }) || null;
         }
         if (!mid) {
-            mid = await getChromeStorage('mid').catch((error) => {console.log(error) }) || '';
+            mid = await getChromeStorageFromExtension('mid').catch((error) => {console.log(error) }) || '';
         }
         let 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);
         let platform = isMobile ? `mobile` : `pc`;
@@ -66,7 +66,7 @@ function paramsPretreatmentAndRequest(logType, eventData, extParams) {
     extParams = wrapObject(extParams)
     let obj = {};
     let pageSource = eventData.pageSource;
-    if (eventData.hasOwnProperty('pageSource')) {
+    if (eventData.pageSource) {
         delete eventData.pageSource;
     }
     obj.logType = logType;
@@ -77,7 +77,7 @@ function paramsPretreatmentAndRequest(logType, eventData, extParams) {
             pageSource,
             ...obj
         }
-    }).then(res => {
+    }).then(() => {
     }).catch(err => {
         reportFrontLogApi({
             logData: JSON.stringify(err)
@@ -102,7 +102,7 @@ function typeDecide(o, type) {
 export async function getReportCommonParams () {
   let commonParams = {};
   if (!userInfo) {
-      userInfo = await getChromeStorage('userInfo') || null;
+      userInfo = await getChromeStorageFromExtension('userInfo') || null;
   }
   let 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);
   let platform = isMobile ? `mobile` : `pc`;

+ 525 - 0
src/pages/currency/detail.vue

@@ -0,0 +1,525 @@
+<template>
+    <div class="currency-detail-page">
+        <v-head
+            :title="currencyInfo.tokenSymbol"
+            :show_more="false"
+            :show_refresh="true"
+            :show_list="true"
+            :transactionsRouterParams="{ backUrl: 'back' }"
+            @on-refresh="onRefresh"
+            @onBack="clickBack">
+        </v-head>
+        <div class="top">
+            <img class="icon-currency" :src="currencyInfo.iconPath" />
+            <div class="amount">
+                <div class="balance"
+                    :class="{ 'direction-column': (currencyInfo.totalBalance.length + currencyInfo.tokenSymbol.length) > 15 }">
+                    <a-tooltip :title="currencyInfo.totalBalance">
+                        {{ getBit(currencyInfo.totalBalance) }}
+                    </a-tooltip>
+                    <template v-if="currencyInfo.totalBalance.length + currencyInfo.tokenSymbol.length < 16">
+                        &nbsp;&nbsp;
+                    </template>
+                    <span class="symbol">
+                        {{ currencyInfo.tokenSymbol }}
+                    </span>
+                </div>
+                <div class="final">
+                    <a-tooltip :title="'$' + currencyInfo.totalUsdEstimateBalance">
+                        ${{ getBit(currencyInfo.totalUsdEstimateBalance) }}
+                    </a-tooltip>
+                </div>
+            </div>
+        </div>
+        <div class="bottom">
+            <template v-if="showSendBtn">
+                <div class="btn send-btn" v-if="enableRecharge == 1" @click="showSendGiveawayDialog(currencyInfo)">
+                    <img :src="require('@/assets/svg/icon-send-giveaway.svg')" />
+                    Send Giveaway
+                </div>
+                <div class="btn-wrapper">
+                    <div class="button left" v-if="enableRecharge == 1" @click="clickDeposit">Deposit</div>
+                    <div class="button" v-if="enableWithdraw == 1" @click="clickWithdraw">Withdrawal</div>
+                </div>
+            </template>
+            <template v-else>
+                <div class="btn deposit-btn" v-if="enableRecharge == 1" @click="clickDeposit">Deposit</div>
+                <div class="btn withdrawal-btn" v-if="enableWithdraw == 1" @click="clickWithdraw">Withdrawal</div>
+            </template>
+        </div>
+
+        <template v-if="showCurrencySelect">
+            <div class="selectDiv">
+                <currency-select ref="currencySelectDom" :list="currenciesData" @selectCurrency="selectCurrency">
+                </currency-select>
+            </div>
+            <div class="selectBg" @click="showCurrencySelect = false"></div>
+        </template>
+        <input-action-sheet :visible="showDepositInput" title="Enter the USD amount to be deposited" :desc="depositDesc"
+            position="absolute" @onInput="onDepositAmountInput" @cancel="cancelDeposit" @confirm="confirmDeposit">
+        </input-action-sheet>
+    </div>
+</template>
+  
+<script setup>
+import { ref, onMounted, inject, onBeforeUnmount } from "vue";
+import { useRouter } from 'vue-router';
+import Report from "@/log-center/log";
+import { getCurrencyInfoBySymbol, syncChainTokenRechargeRecord } from "@/http/publishApi";
+import { setChromeStorage, chromeExtensionUrl } from "@/uilts/chromeExtension"
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import VHead from '@/components/v-head.vue'
+import currencySelect from "@/components/currency-select.vue";
+import inputActionSheet from "@/components/input-action-sheet.vue";
+import { getBit } from "@/uilts/help";
+import { payCalcFee } from "@/http/pay";
+
+let currenciesData = ref([]);
+let currencyInfo = ref({
+    totalBalance: '',
+    tokenSymbol: ''
+});
+let showCurrencySelect = ref(false);
+let router = useRouter();
+let currencyOpertionType = '';
+let showSendBtn = ref(true);
+let enableRecharge = ref(1);
+let enableWithdraw = ref(1);
+let showDepositInput = ref(false);
+let reqCalcIng = false;
+let depositDesc = ref('');
+let finalAmountData = ref({});
+
+const selectCurrency = (params) => {
+    showCurrencySelect.value = false;
+    let { enableRecharge, enableWithdraw } = params;
+
+    if (currencyOpertionType == 'WITHDRAW') {
+        if (enableWithdraw != 1) {
+            return;
+        }
+        withdrawHandle(params);
+    } else if (currencyOpertionType == 'DEPOSIT') {
+        if (enableRecharge != 1) {
+            return;
+        }
+        depositHandle(params);
+    } else if (currencyOpertionType == 'SEND') {
+        if (enableRecharge != 1) {
+            return;
+        }
+        showSendGiveawayDialog(params);
+    }
+}
+
+let withdraw_info = inject('withdraw_info')
+// 点击提现
+const clickWithdraw = () => {
+    Report.reportLog({
+        pageSource: Report.pageSource.denetHomePage,
+        businessType: Report.businessType.buttonClick,
+        objectType: Report.objectType.withdrawButton
+    });
+
+    if (currenciesData.value.length > 1) {
+        showCurrencySelect.value = true;
+        currencyOpertionType = "WITHDRAW";
+    } else if (currenciesData.value.length == 1) {
+        withdrawHandle(currenciesData.value[0]);
+    }
+}
+
+const withdrawHandle = (_params) => {
+    withdraw_info.chainInfo = _params.chainInfo;
+    withdraw_info.source = 'home'
+    withdraw_info.balance = _params.balance
+    withdraw_info.token_symbol = _params.tokenSymbol || ''
+    withdraw_info.currency_name = _params.currencyName || ''
+    withdraw_info.token_chain = _params.tokenChain || ''
+    withdraw_info.currency_code = _params.currencyCode
+    withdraw_info.icon_token = _params.iconPath || ''
+    withdraw_info.icon_net = require('@/assets/svg/icon-BNB.svg')
+
+    if (_params.currencyCode == 'USD') {
+        withdraw_info.currency_code = _params.currencyCode
+        withdraw_info.paypal.amount_value = _params.balance
+        router.push('/withdraw/paypal')
+    } else {
+        console.log(withdraw_info.chainInfo.iconPath)
+        router.push('/withdraw/info')
+    }
+}
+
+let top_up_info = inject('top_up_info');
+
+const clickDeposit = () => {
+    Report.reportLog({
+        pageSource: Report.pageSource.denetHomePage,
+        businessType: Report.businessType.buttonClick,
+        objectType: Report.objectType.topupButton
+    });
+
+    if (currenciesData.value.length > 1) {
+        showCurrencySelect.value = true;
+        currencyOpertionType = "DEPOSIT";
+    } else if (currenciesData.value.length == 1) {
+        let currencyInfo = currenciesData.value[0];
+        if (currencyInfo.currencyCode == 'USD') {
+            // 法币充值
+            showDepositInput.value = true;
+            setDepositDesc();
+
+        } else {
+            depositHandle(currencyInfo);
+        }
+    }
+}
+
+const setDepositDesc = async () => {
+    let res = await payCalcFee({
+        params: {
+            amountValue: 0,
+            currencyCode: currencyInfo.value.currencyCode,
+            payChannel: 'ach',
+        },
+    });
+    if (res.code == 0) {
+        let { feeDesc } = res.data;
+        depositDesc.value = `${feeDesc}`;
+    }
+}
+
+const depositHandle = (_params) => {
+    top_up_info.token = _params.currencyName || ''
+    top_up_info.token_chain = _params.tokenChain
+    top_up_info.token_symbol = _params.tokenSymbol || ''
+    top_up_info.currency_code = _params.currencyCode
+    top_up_info.icon_token = _params.iconPath || ''
+    top_up_info.icon_net = require('@/assets/svg/icon-BNB.svg')
+    top_up_info.chainInfo = {
+        ..._params.chainInfo
+    };
+
+    router.push('/top-up/info');
+};
+
+
+const asyncTokenRechRecord = (currencyCode = '', cb) => {
+    syncChainTokenRechargeRecord({
+        params: {
+            currencyCode: currencyCode
+        }
+    }).then(res => {
+        cb && cb(res.data)
+    }).catch(() => {
+        cb && cb()
+    })
+}
+
+const onRefresh = () => {
+    asyncTokenRechRecord('', () => {
+        getCurrencyInfoBySymbol({
+            params: {
+                symbol: currencyInfo.value.tokenSymbol
+            }
+        }).then(res => {
+            if (res.code == 0) {
+                if (res.data && res.data.currencyCategories && res.data.currencyCategories.length) {
+                    let data = res.data.currencyCategories[0].data;
+                    if (data.length) {
+                        let { totalBalance = '', totalUsdEstimateBalance = '' } = data[0] || {};
+                        currencyInfo.value.totalBalance = totalBalance;
+                        currencyInfo.value.totalUsdEstimateBalance = totalUsdEstimateBalance;
+                    }
+                }
+            }
+        });
+    })
+};
+
+const showSendGiveawayDialog = (params = {}) => {
+    if (currenciesData.value.length > 1 && currencyOpertionType != 'SEND') {
+        showCurrencySelect.value = true;
+        currencyOpertionType = "SEND";
+    } else {
+        setLocalSelectCurrencyInfo(params)
+        setTimeout(() => {
+            // chrome.runtime.sendMessage({
+            //     actionType: "POPUP_SHOW_DENET_PUBLISH_DIALOG",
+            //     data: {}
+            // });
+        }, 600)
+        currencyOpertionType = '';
+    }
+};
+
+const setLocalSelectCurrencyInfo = (params = {}) => {
+    setChromeStorage({ selectCurrencyInfo: JSON.stringify(params) })
+}
+
+const cancelDeposit = () => {
+    showDepositInput.value = false;
+}
+
+const confirmDeposit = () => {
+    if (reqCalcIng) {
+        return;
+    }
+
+    showDepositInput.value = false;
+    depositDesc.value = '';
+
+    let achPayInfo = {
+        amountValue: finalAmountData.value.finalAmountValue
+    };
+    let guideUrl = chromeExtensionUrl + ('iframe/ach-cashier.html');
+    setChromeStorage({ achPayInfo: JSON.stringify(achPayInfo) });
+    let str = window.location.hash + '&refresh=true';
+    let path = str.substring(1, str.length);
+    setChromeStorage({
+        achPayData: JSON.stringify({
+            form: 'popupPage',
+            path
+        })
+    });
+
+    chrome.tabs.create({
+        url: guideUrl
+    });
+}
+
+const onDepositAmountInput = async (params = {}) => {
+    let { inputVal } = params;
+    reqCalcIng = true;
+    if (inputVal === '') {
+        inputVal = 0;
+    }
+    let res = await payCalcFee({
+        params: {
+            amountValue: inputVal,
+            currencyCode: currencyInfo.value.currencyCode,
+            payChannel: 'ach',
+        },
+    });
+    reqCalcIng = false;
+    if (res.code == 0) {
+        let { feeAmountValue, feeDesc } = res.data;
+        finalAmountData.value = res.data;
+        if (inputVal === 0) {
+            depositDesc.value = `${feeDesc}`;
+        } else {
+            depositDesc.value = `Charge Fee:${feeAmountValue} USD(${feeDesc})`;
+        }
+    }
+}
+
+const clickBack = () => {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: true,
+        }
+    })
+}
+
+const onMessage = () => {
+    // chrome.runtime.onMessage.addListener(msgListener)
+}
+
+// const msgListener = (req) => {
+//     let refresh = false;
+//     switch (req.actionType) {
+//         case 'CONTENT_POPUP_PAGE_SHOW':
+//             refresh = router.currentRoute.value.query;
+//             if (refresh && currencyInfo.value.tokenSymbol) {
+//                 onRefresh();
+//             }
+//             break;
+//     }
+// }
+
+onMounted(() => {
+    let { params = '{}' } = router.currentRoute.value.query;
+
+    let { currencies = [], totalBalance = 0, totalUsdEstimateBalance = 0 } = JSON.parse(params);
+
+    currenciesData.value = currencies;
+    if (currencies.length) {
+        currencyInfo.value = {
+            ...currencies[0],
+            totalBalance,
+            totalUsdEstimateBalance
+        };
+        if (currencies.length == 1) {
+            enableRecharge.value = currencyInfo.value.enableRecharge;
+            enableWithdraw.value = currencyInfo.value.enableWithdraw;
+        }
+    }
+    if (window.location.pathname.indexOf('/home.html') > -1) {
+        showSendBtn.value = false;
+    } else {
+        showSendBtn.value = true;
+    }
+    onMessage();
+})
+
+onBeforeUnmount(() => {
+    // chrome.runtime.onMessage.removeListener(msgListener);
+})
+
+</script>
+  
+
+<style lang='scss' scoped>
+.currency-detail-page {
+    width: 100%;
+    height: 100%;
+    position: relative;
+
+    .top {
+        height: calc(100% - 212px);
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+
+        .icon-currency {
+            width: 100px;
+            margin-bottom: 30px;
+        }
+
+        .amount {
+            font-weight: 700;
+            font-size: 28px;
+            text-align: center;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-direction: column;
+            width: 100%;
+
+            .balance {
+                padding: 0 18px;
+                box-sizing: border-box;
+                width: 100%;
+                word-break: break-word;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+
+                .symbol {
+                    word-break: break-all;
+                }
+            }
+
+            .direction-column {
+                flex-direction: column;
+            }
+
+            .final {
+                margin-top: 9px;
+                font-weight: 500;
+                font-size: 22px;
+                color: #a2a2a2;
+                text-align: center;
+            }
+        }
+    }
+
+    .bottom {
+        height: 162px;
+        padding: 0 20px;
+        box-sizing: border-box;
+
+        .btn {
+            width: 100%;
+            height: 57px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-weight: 500;
+            font-size: 17px;
+            border-radius: 100px;
+            cursor: pointer;
+        }
+
+        .deposit-btn {
+            margin-bottom: 18px;
+            background: #1d9bf0;
+            color: #fff;
+        }
+
+        .withdrawal-btn {
+            background: rgba(244, 244, 244, 0.01);
+            border: 1px solid #d7e8f4;
+            box-sizing: border-box;
+            color: #1d9bf0;
+        }
+
+        .send-btn {
+            font-weight: 700;
+            font-size: 18px;
+            margin-bottom: 18px;
+            background: #1d9bf0;
+            color: #fff;
+
+            img {
+                width: 19px;
+                height: 19px;
+                margin-right: 8px;
+            }
+        }
+
+        .btn-wrapper {
+            width: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .button {
+                flex: 1;
+                height: 50px;
+                border: 1px solid #d7e8f4;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                font-weight: 600;
+                font-size: 16px;
+                border-radius: 100px;
+                box-sizing: border-box;
+                color: #1D9BF0;
+                cursor: pointer;
+            }
+
+            .left {
+                margin-right: 20px;
+            }
+        }
+    }
+
+
+    .selectDiv {
+        position: absolute;
+        z-index: 1000;
+        width: 100%;
+        max-height: 480px;
+        padding-bottom: 30px;
+        left: 0;
+        bottom: 0;
+        background-color: #fff;
+        border-radius: 20px 20px 0 0;
+        overflow-y: auto;
+    }
+
+    .selectBg {
+        position: absolute;
+        z-index: 999;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: rgba($color: #000000, $alpha: 0.6);
+    }
+}
+</style>

+ 820 - 0
src/pages/tabbar/message/index.vue

@@ -0,0 +1,820 @@
+<template>
+  <div class="message-wrapper">
+    <div class="tab-content" ref="pageWrapperDom" @scroll="pageScroll">
+      <div class="list-wrapper" ref="pageGiveListDom">
+        <div class="give-list" v-if="currentTabIndex == 0">
+          <template v-if="giveList.length">
+            <div class="cell" :class="{ 'cell-center': item.type == 1 || item.type == 3 || item.type == 5 }"
+              v-for="(item, index) in giveList" :key="index" @click="clickListItem(item, index)">
+              <red-dot class="red-dots" v-if="item.unReadMsgCount > 0 && isReadMsg"></red-dot>
+
+              <div class="img-wrapper">
+                <!-- 收到红包 -->
+                <template v-if="item.type == 1 || item.type == 3 || item.type == 5">
+                  <img class="icon-avatar" :src="item.userInfo.avatarUrl" />
+                  <img class="icon-give" v-if="item.type == 5" :src="require('@/assets/svg/icon-small-treasure.svg')" />
+                  <img class="icon-give" v-else :src="require('@/assets/svg/icon-get-giveaways-s.svg')" />
+                </template>
+                <!-- 发出去红包 -->
+                <template v-else-if="item.type == 2">
+                  <template v-if="item.postTaskLuckdrop.luckdropType === PlayType.treasure">
+                    <img class="icon-big-give" :src="require('@/assets/svg/icon-big-treasure.svg')" />
+                    <img class="icon-mark-give" :src="require('@/assets/svg/icon-send-giveaways-mark.svg')" />
+                  </template>
+                  <template v-else>
+                    <img class="icon-big-give" :src="require('@/assets/svg/icon-send-giveaways-s.svg')" />
+                    <img class="icon-mark-give" :src="require('@/assets/svg/icon-send-giveaways-mark.svg')" />
+                  </template>
+                </template>
+                <!-- 转出NFT记录 -->
+                <template v-else-if="item.type == 4">
+                  <img v-if="item.nftItemVO?.imagePath" class="icon-big-give" :src="item.nftItemVO?.imagePath" />
+                  <div v-else style="margin-top: 12px;">
+                    <nft-card :nftItemId="item.nftItemId" :item="item.nftItemVO.createImageInfo" :width="34">
+                    </nft-card>
+                  </div>
+                </template>
+              </div>
+              <div class="info-wrapper" :class="{ 'info-center': item.type == 1 || item.type == 5 }">
+                <div class="left">
+                  <div class="nickname">
+                    <template v-if="item.type == 1">
+                      Get Giveaway
+                    </template>
+                    <template v-else-if="item.type == 2">
+                      <template v-if="item.postTaskLuckdrop.luckdropType === PlayType.common">
+                        Send Giveaway
+                      </template>
+                      <template v-else-if="item.postTaskLuckdrop.luckdropType === PlayType.lottery">
+                        Lottery Giveaway
+                      </template>
+                      <template v-else-if="item.postTaskLuckdrop.luckdropType === PlayType.treasure">
+                        Treasure Hunt
+                      </template>
+                    </template>
+                    <template v-else-if="item.type == 3">
+                      Lottery
+                    </template>
+                    <template v-else-if="item.type == 4">
+                      Transfer NFT
+                    </template>
+                    <template v-else-if="item.type == 5">
+                      Treasure Hunt
+                    </template>
+                  </div>
+                  <div class="time">
+                    {{  moment(item.timestamp).format("MM-DD HH:mm:ss")  }}
+                  </div>
+                </div>
+                <div class="right">
+                  <div class="msg">
+                    <div class="bold" :class="{
+                      'align-content':
+                        (item.type == 2 ||
+                          (item.type == 1 && item.status == 1 || item.type == 5 && item.status == 1)) &&
+                        item?.amount?.length + item?.currencySymbol?.length > 12,
+                      'custom-bold': item.rewardType === RewardType.custom
+                    }">
+                      <!-- 领取的普通红包 -->
+                      <template v-if="item.type == 1">
+                        <!-- 进行中-->
+                        <template v-if="item.status == 0">
+                          In Progress
+                        </template>
+                        <!-- 已完成 -->
+                        <template v-else-if="item.status == 1">
+                          <!-- 已中奖-货币型奖品展示 -->
+                          <template v-if="item.rewardType === RewardType.money">
+                            <span class="blance">
+                              <a-tooltip :title="item.amount">
+                                {{  getBit(item.amount)  }}</a-tooltip>
+                            </span>
+                            <div class="coin-type-wrapper">
+                              <span class="coin-type">{{
+                                 item.currencySymbol || "" 
+                                }}</span>
+                              <img :src="item.currencyIconPath" alt="" />
+                            </div>
+                          </template>
+
+                          <!-- 已中奖-通用型奖品展示 -->
+                          <template v-else>
+                            <span class="blance cuntom-prize">{{  item.customizedReward  }}</span>
+                          </template>
+                        </template>
+                        <!-- 已过期 -->
+                        <template v-else-if="item.status == 2">
+                          Timeout
+                        </template>
+                      </template>
+                      <!-- 发出去的 -->
+                      <template v-else-if="item.type == 2">
+                        <!-- 已中奖-货币型奖品展示 -->
+                        <template v-if="item.rewardType === RewardType.money">
+                          <span class="blance">
+                            <a-tooltip :title="'-' + item.amount">
+                              -{{  getBit(item.amount)  }}
+                            </a-tooltip>
+                          </span>
+                          <div class="coin-type-wrapper">
+                            <span class="coin-type">{{
+                               item.currencySymbol || "" 
+                              }}</span>
+                            <img :src="item.currencyIconPath" alt="" />
+                          </div>
+                        </template>
+                        <!-- 已中奖-通用型奖品展示 -->
+                        <template v-else>
+                          <span class="blance cuntom-prize">
+                            {{  item.customizedReward || ''  }}<span class="cuntom-prize-icon">X</span>
+                            <span class="cuntom-prize-total">{{  item?.postTaskLuckdrop?.totalCount || ''  }}</span>
+                          </span>
+
+                        </template>
+                      </template>
+                      <!-- 抽奖 -->
+                      <template v-else-if="item.type == 3">
+                        <template v-if="item.status == 1">In Progress</template>
+                        <template v-else-if="item.status == 2">
+                          Open in {{  item.downTime || ''  }}
+                        </template>
+                        <template v-else-if="item.status == 3">
+                          Unfinished
+                        </template>
+                        <template v-else-if="item.status == 4">
+                          <!-- 已中奖-货币型奖品展示 -->
+                          <template v-if="item.rewardType === RewardType.money">
+                            <span class="blance">
+                              <a-tooltip :title="item.amount">
+                                +{{  getBit(item.amount)  }}
+                              </a-tooltip>
+                            </span>
+                            <div class="coin-type-wrapper">
+                              <span class="coin-type">{{
+                                 item.currencySymbol || "" 
+                                }}</span>
+                              <img :src="item.currencyIconPath" alt="" />
+                            </div>
+                          </template>
+                          <!-- 已中奖-通用型奖品展示 -->
+                          <template v-else>
+                            <span class="blance cuntom-prize">{{  item.customizedReward  }}</span>
+                          </template>
+
+                        </template>
+                        <template v-else-if="item.status == 5">
+                          Didn't win
+                        </template>
+                        <template v-else-if="item.status == 6">
+                          Giveaway Expired
+                        </template>
+                      </template>
+                      <!-- NFT 转出记录 -->
+                      <template v-else-if="item.type == 4">
+                        <template v-if="item.status == 0 || item.status == 1">Transferring</template>
+                        <template v-else-if="item.status == 2">Successful</template>
+                        <template v-else-if="item.status == 3">Transfe Failed</template>
+                      </template>
+                      <!-- 领取的夺宝红包 -->
+                      <template v-else-if="item.type == 5">
+                        <!-- 进行中-->
+                        <template v-if="item.status == 0">
+                          In Progress
+                        </template>
+                        <!-- 已完成 -->
+                        <template v-else-if="item.status == 2">
+                          <span class="blance">
+                            <a-tooltip :title="item.amount">
+                              {{  getBit(item.amount)  }}</a-tooltip>
+                          </span>
+                          <div class="coin-type-wrapper">
+                            <span class="coin-type">{{
+                               item.currencySymbol || "" 
+                              }}</span>
+                            <img :src="item.currencyIconPath" alt="" />
+                          </div>
+                        </template>
+                        <!-- 已过期 -->
+                        <template v-else-if="item.status == 3">
+                          Giveaway Expired
+                        </template>
+                      </template>
+                    </div>
+
+                    <!-- 发出的红包显示 -->
+                    <div class="desc" v-if="item.type == 2">
+                      <!-- 未发送-->
+                      <template v-if="item.postTaskLuckdrop.reSendAvailable">
+                        Unpublished
+                      </template>
+                      <!-- 进行中 -->
+                      <template v-else-if="item.status == 1">
+                        <template
+                          v-if="item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == PlayType.lottery">
+                          {{  item.downTime || ''  }}
+                        </template>
+                        <template v-else-if="item.postTaskLuckdrop.luckdropType != PlayType.treasure">
+                          {{  item.postTaskLuckdrop.receivedCount  }}/{{
+                           item.postTaskLuckdrop.totalCount 
+                          }}
+                        </template>
+                      </template>
+
+                      <!-- 2:已结束; 3:提前终止-->
+                      <template v-else-if="item.status == 2 || item.status == 3">
+                        <!-- 普通红包 -->
+                        <template
+                          v-if="item.postTaskLuckdrop.luckdropType == PlayType.common || item.postTaskLuckdrop.luckdropType == PlayType.treasure">
+                          <template v-if="item.status == 2">
+                            {{ item.postTaskLuckdrop.luckdropType == PlayType.common ? '(Time expired)' : 'Complete' }}
+                          </template>
+                          <template v-if="item.status == 3">
+                            {{  item.srcContentId && item.postTaskLuckdrop.luckdropType != PlayType.treasure ?
+                            '(Termination)' : 'Termination'
+ }}
+                          </template>
+                          <template
+                            v-if="(item.status == 2 || item.status == 3) && item.srcContentId && item.postTaskLuckdrop.luckdropType != PlayType.treasure">
+                            {{  item.postTaskLuckdrop.receivedCount  }}/{{
+                             item.postTaskLuckdrop.totalCount 
+                            }}
+                          </template>
+                        </template>
+                        <!-- 抽奖红包 -->
+                        <template v-else>
+                          <!-- 自定义奖品类型 结束时显示 Complete -->
+                          {{  item.rewardType === RewardType.custom && item.status == 2 ? 'Complete' : 'Termination'  }}
+                        </template>
+                      </template>
+                      <!-- 红包提前终止/退款(进行中)显示-->
+                      <template v-if="item.status == 4">
+                        {{  item.postTaskLuckdrop && item.postTaskLuckdrop.luckdropType == 1 ? 'Terminating' : 'Open in '
+                        + item.downTime || ''
+
+                        }}
+                      </template>
+
+                      <!-- 进行中或者未发送成功时显示 -->
+                      <div class="desc-bottom-bar">
+                        <!-- 没有终止红包时显示 -->
+                        <div v-if="item.postTaskLuckdrop.terminatedAvailable" class="btn"
+                          @click.stop="terminaHandler(item, index)">
+                          Termination
+                        </div>
+
+                        <!-- 红包未发出显示 -->
+                        <div class="btn send-btn" v-if="item.postTaskLuckdrop.reSendAvailable"
+                          @click.stop="sendTwitter(item)">
+                          Send
+                        </div>
+                        <div v-else-if="item.srcContentId" class="btn detail-btn"
+                          @click.stop="clickListItem(item, index)">
+                          details
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <!-- 发红包—— 未发出、进行中 隐藏 -->
+                  <img v-if="item.type != 2 && item.type != 4" class="icon"
+                    :src="require('@/assets/svg/icon-cell-arrow-right.svg')" />
+                </div>
+              </div>
+            </div>
+          </template>
+          <template v-else>
+            <img class="icon-empty" :src="require('@/assets/svg/icon-empty-list.svg')" />
+          </template>
+        </div>
+      </div>
+    </div>
+    <modal :visible="modalVisible" :title="modalTitle" :content="modalContent" cancelText="Termination"
+      confirmText="Cancel" @cancel="modalCancel" @confirm="modalConfirm" />
+  </div>
+</template>
+  
+<script setup>
+import { ref, onMounted } from "vue";
+import modal from "@/components/modal-layer.vue";
+import redDot from "@/components/red-dot.vue";
+import nftCard from '@/components/nft-card.vue';
+import { getBit, formatSecondsAsDaysOrTime } from "@/uilts/help";
+import { getMineLuckdropRecords } from "@/http/account";
+import { terminatedLuckdrop } from "@/http/redPacket";
+import { readAllMsgByType, getAllMessageInfo } from "@/http/messageApi"
+import { getChromeStorageFromExtension } from "@/uilts/chromeExtension";
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import { PlayType, RewardType } from '@/types';
+
+var moment = require("moment");
+let currentTabIndex = ref(0);
+let userInfo = ref({});
+let pageWrapperDom = ref(null);
+let pageGiveListDom = ref(null);
+let modalVisible = ref(false);
+let modalTitle = ref('');
+let modalContent = ref('');
+let terminaTask = {};
+let giveList = ref([]);
+let giveReqParams = {
+  params: {
+    pageNum: 1,
+    pageSize: 20,
+  },
+  loadMore: false,
+};
+
+let isReadMsg = ref(true);
+
+/**
+ * 获取红包列表
+ */
+const getLuckdropRecordsList = () => {
+  getMineLuckdropRecords({
+    params: giveReqParams.params,
+  }).then((res) => {
+    messageCenter.send({
+      actionType: MESSAGE_ENUM.IFRAME_RUNTIME_CONNECT_POPUP,
+      data: {}
+    })
+    if (res.data && res.data.length) {
+      if (giveReqParams.params.pageNum < 2) {
+        giveList.value = res.data;
+      } else {
+        let data = giveList.value;
+        data = data.concat(res.data);
+        giveList.value = data;
+      }
+      downTimeBegin()
+      giveReqParams.loadMore = false;
+    }
+  });
+};
+
+const getCurrentList = () => {
+  getMineLuckdropRecords({
+    params: {
+      pageNum: 1,
+      pageSize: giveList.value.length || 20,
+    }
+  }).then((res) => {
+    if (res.data && res.data.length) {
+      giveList.value = res.data;
+    }
+  })
+}
+
+/**
+ * 点击列表跳转到推文
+ */
+const clickListItem = (params) => {
+  if (!params.srcContentId) {
+    return;
+  }
+  let twitterUrl = "https://twitter.com/";
+  let nickName = "";
+  if (params.type == 2) {
+    nickName = userInfo.value.nickName;
+  } else {
+    nickName = params.userInfo.nickName;
+  }
+
+  let url = twitterUrl + nickName + "/status/" + params.srcContentId;
+  messageCenter.send({
+    actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_CREATE_TAB,
+    data: {
+      url
+    }
+  })
+};
+
+const pageScroll = (e) => {
+  let wrapperHeight = pageWrapperDom.value.offsetHeight;
+  let pageGiveListHeight = pageGiveListDom.value.offsetHeight;
+  let scrollTop = e.target.scrollTop || 0;
+  if (currentTabIndex.value != 0) {
+    return;
+  }
+  if (
+    giveReqParams.loadMore === false &&
+    wrapperHeight + scrollTop >= pageGiveListHeight - 60
+  ) {
+    giveReqParams.loadMore = true;
+    giveReqParams.params.pageNum++;
+    getLuckdropRecordsList();
+  }
+};
+
+/**
+ * 点击发送,去发推
+ */
+const sendTwitter = (params) => {
+  messageCenter.send({
+    actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_PUBLISH_TWITTER,
+    data: {
+      srcContent: params.postTaskLuckdrop.srcContent,
+      postId: params.postTaskLuckdrop.postId,
+    }
+  })
+};
+
+const terminaHandler = (params, index) => {
+  terminaTask = params;
+  terminaTask.index = index;
+
+  // set font
+  if (params && params.postTaskLuckdrop) {
+    if (params.postTaskLuckdrop.luckdropType == PlayType.lottery) {
+      modalTitle.value = `Early Termination of Lottery?`
+      modalContent.value = `This operation will terminate the lottery process and refund the lottery prizes.`
+    } else if (params.postTaskLuckdrop.luckdropType == PlayType.treasure) {
+      modalTitle.value = `Early Termination of treasure Hunt?`
+      modalContent.value = `This operation will stop the treasure hunt and refund the remaining amount within 2 days`
+    } else {
+      modalTitle.value = `Early termination of Giveaway?`
+      modalContent.value = `The remaining amount will be returned to your wallet within 1 day.`
+    }
+  } else {
+    modalTitle.value = `Early termination of Giveaway?`
+    modalContent.value = `The remaining amount will be returned to your wallet within 1 day.`
+  }
+
+  modalVisible.value = true;
+};
+
+const modalCancel = () => {
+  //请求终止接口 id terminaTask.id 、 刷新当前列表、 关闭
+  modalVisible.value = false;
+  let index = terminaTask.index;
+  terminatedLuckdrop({
+    params: {
+      luckdropId: terminaTask.id,
+    },
+  }).then((res) => {
+    if (res.code == 0) {
+      giveList.value[index]["status"] = res.data.status;
+      giveList.value[index]["postTaskLuckdrop"]["reSendAvailable"] = false;
+      giveList.value[index]["postTaskLuckdrop"]["terminatedAvailable"] = false;
+      // 重新拉取
+      getCurrentList()
+    }
+  });
+  terminaTask = {};
+};
+
+const modalConfirm = () => {
+  modalVisible.value = false;
+  terminaTask = {};
+};
+
+const readAllMsg = ({ msgType }, cb) => {
+  readAllMsgByType({
+    params: {
+      msgType
+    }
+  }).then(() => {
+    cb && cb();
+  })
+};
+
+const setMessageCount = () => {
+  getAllMessageInfo({
+    params: {
+    }
+  }).then(res => {
+    if (res.code == 0) {
+      let { unReadCountTotal = 0 } = res.data;
+      if (unReadCountTotal > 0) {
+        let text = unReadCountTotal > 99 ? '99+' : unReadCountTotal + '';
+        messageCenter.send({
+          actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_SETBADGEINFO,
+          data: { text }
+        })
+      } else {
+        messageCenter.send({
+          actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_HIDEBADGE,
+          data: {}
+        })
+      }
+    }
+  });
+}
+
+
+const getUserInfo = async () => {
+  const res = await getChromeStorageFromExtension("userInfo");
+  if (res && res.accessToken) {
+    userInfo.value = res;
+  } else {
+    userInfo.value = {};
+  }
+};
+
+const init = () => {
+  getUserInfo();
+  getLuckdropRecordsList();
+
+  setMessageCount();
+  setTimeout(() => {
+    isReadMsg.value = false;
+    readAllMsg({ msgType: 1 }, () => {
+      setMessageCount();
+    });
+  }, 2000);
+}
+
+const onMessage = () => {
+  messageCenter.listen(MESSAGE_ENUM.CONTENT_POPUP_PAGE_SHOW, () => {
+    init();
+  })
+}
+
+// 倒计时
+const downTimeBegin = () => {
+  let list = giveList.value || []
+  let ifDown = false
+  list.forEach((item) => {
+    if (item.endTimestamp) {
+      let time = moment(new Date().getTime())
+      let endTime = moment(item.endTimestamp + 5000)
+      let downTime = (endTime - time) || 0
+      if (downTime > 0) {
+        item.downTime = formatSecondsAsDaysOrTime(downTime / 1000, false);
+        ifDown = true;
+      }
+      if (item && item.downTime && item.downTime == '00:00:00') {
+        getCurrentList()
+      }
+    }
+  })
+
+  if (ifDown) {
+    setTimeout(() => {
+      downTimeBegin()
+    }, 1000)
+  }
+}
+
+onMounted(() => {
+  onMessage();
+  init();
+});
+</script>
+
+
+<style scoped lang="scss">
+.message-wrapper {
+  width: 100%;
+  height: 100%;
+  margin-top: 1px;
+
+  .tab-bar {
+    display: flex;
+    align-items: center;
+    background-color: #fff;
+    box-shadow: 0px 0.5px 0px #d1d9dd;
+
+    .tab-item {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 14px 0px;
+      box-sizing: border-box;
+      border-bottom: 2px solid #fff;
+      margin: 0 12px;
+      font-size: 14px;
+      color: #949494;
+      cursor: pointer;
+    }
+
+    .active {
+      border-bottom: 2px solid #1d9bf0;
+      font-weight: 500;
+      color: #000;
+    }
+  }
+
+  .tab-content {
+    height: 100%;
+    overflow-y: auto;
+
+    .list-wrapper {
+      min-height: 202px;
+
+      .give-list {
+        min-height: 202px;
+        position: relative;
+
+        .cell {
+          display: flex;
+          justify-content: space-between;
+          min-height: 76px;
+          box-sizing: border-box;
+          padding-left: 14px;
+          position: relative;
+          cursor: pointer;
+
+          .red-dots {
+            position: absolute;
+            right: 4px;
+            top: 4px;
+          }
+
+          .img-wrapper {
+            position: relative;
+            width: 38px;
+            margin-right: 16px;
+            box-sizing: border-box;
+
+            .icon-avatar {
+              width: 34px;
+              height: 34px;
+              border-radius: 50%;
+              margin-right: 8px;
+            }
+
+            .icon-give {
+              position: absolute;
+              right: 1px;
+              bottom: -1px;
+              width: 18px;
+              height: 18px;
+            }
+
+            .icon-big-give {
+              margin-top: 12px;
+              width: 34px;
+            }
+
+            .icon-mark-give {
+              position: absolute;
+              right: -2px;
+              top: 31px;
+              width: 16px;
+              height: 16px;
+            }
+          }
+
+          .info-wrapper {
+            flex: 1;
+            height: 100%;
+            display: flex;
+            justify-content: space-between;
+            box-sizing: border-box;
+            padding: 10px 14px 10px 0;
+
+            .left {
+              .nickname {
+                font-weight: 500;
+                font-size: 13px;
+                margin-bottom: 5px;
+                max-width: 132px;
+                word-break: break-all;
+              }
+
+              .time {
+                font-size: 12px;
+                color: #b0b0b0;
+              }
+            }
+
+            .right {
+              display: flex;
+              align-items: center;
+              cursor: pointer;
+
+              .msg {
+                display: flex;
+                align-items: flex-end;
+                flex-direction: column;
+
+                .bold {
+                  font-weight: 500;
+                  font-size: 13px;
+                  text-align: right;
+                  display: flex;
+                  justify-content: flex-end;
+                  align-items: center;
+                  max-width: 140px;
+
+                  .blance {
+                    margin-left: 3px;
+                    display: inline-block;
+                    max-width: 80px;
+                    word-break: break-all;
+                    line-height: 18px;
+                    color: #e86f00;
+                  }
+
+                  .cuntom-prize {
+                    max-width: 130px;
+                    word-break: break-word;
+                    text-align: left;
+                  }
+
+                  .cuntom-prize-icon {
+                    color: #000;
+                  }
+
+                  .cuntom-prize-total {
+                    color: #000;
+                    word-break: break-word;
+                  }
+
+                  .coin-type-wrapper {
+                    display: flex;
+                    align-items: center;
+                  }
+
+                  .coin-type {
+                    margin-left: 3px;
+                    word-break: break-all;
+                  }
+
+                  img {
+                    margin-left: 4px;
+                    width: 14px;
+                    height: 14px;
+                  }
+                }
+
+                .align-content {
+                  flex-direction: column;
+                  align-items: flex-end;
+
+                  .blance {
+                    max-width: 130px;
+                  }
+                }
+
+                .desc {
+                  font-size: 12px;
+                  color: #b6b6b6;
+                  margin-top: 5px;
+                  text-align: right;
+
+                  .desc-bottom-bar {
+                    display: flex;
+                    align-items: center;
+                    justify-content: flex-end;
+                    margin-top: 10px;
+
+                    .btn {
+                      min-width: 80px;
+                      height: 29px;
+                      padding: 0 8px;
+                      box-sizing: border-box;
+                      font-weight: 400;
+                      font-size: 14px;
+                      cursor: pointer;
+                      text-align: center;
+                      border-radius: 100px;
+                      color: #5e5e5e;
+                      border: 1px solid #dfdfdf;
+                      display: flex;
+                      align-items: center;
+                      justify-content: center;
+                    }
+
+                    .send-btn {
+                      border: 1px solid #1d9bf0;
+                      color: #1d9bf0;
+                    }
+
+                    .detail-btn,
+                    .send-btn {
+                      margin-left: 8px;
+                    }
+                  }
+                }
+              }
+
+              .icon {
+                width: 18px;
+                height: 24px;
+                margin-left: 4px;
+                margin-right: -5px;
+              }
+            }
+          }
+
+          .info-center {
+            align-items: center;
+          }
+        }
+
+        .cell-center {
+          align-items: center;
+        }
+
+        .icon-empty {
+          position: absolute;
+          left: 50%;
+          top: 50%;
+          transform: translate(-50%, -50%);
+        }
+      }
+    }
+  }
+}
+</style>

+ 385 - 0
src/pages/tabbar/nft/detail.vue

@@ -0,0 +1,385 @@
+<template>
+    <div class="nft-detail-wrapper">
+        <div class="back-bar">
+            <img :src="require('@/assets/svg/icon-nft-back-arrow.svg')" class="icon-arrow" @click="back" />
+            {{  NFTInfo.nftItemName  }}
+        </div>
+        <div class="content">
+            <div class="nft-img">
+                <img class="img" :src="NFTInfo.imagePath" @click="clickNFTImg" v-if="NFTInfo.imagePath" />
+                <nft-card :nftItemId="NFTInfo.nftItemId" :item="NFTInfo.createImageInfo" :width="343" v-else></nft-card>
+            </div>
+            <div class="desc item" v-if="nftMetaData.description">
+                <div class="title">Description</div>
+                <div class="desc-content" v-html="nftMetaData.description"></div>
+            </div>
+            <div class="prop item" 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 item" v-if="nftMetaData.about">
+                <div class="title">About</div>
+                <div class="about-content" v-html="nftMetaData.about"></div>
+            </div>
+            <div class="detail item" v-if="nftDetailData.details">
+                <div class="title">Details</div>
+                <div class="detail-content">
+                    <div class="detail-item">
+                        <div class="left">Contract Address</div>
+                        <div class="right address" @click="clickAddress">
+                            <span>{{  nftDetailData.details.contractAddress  }}</span>
+                            <span>{{  nftDetailData.details.contractAddress  }}</span>
+                        </div>
+                    </div>
+                    <div class="detail-item">
+                        <div class="left">Token ID</div>
+                        <div class="right token" @click="clickToken">
+                            {{  nftDetailData.details.tokenId  }}
+                        </div>
+                    </div>
+                    <div class="detail-item">
+                        <div class="left">Token Standard</div>
+                        <div class="right">
+                            {{  nftDetailData.details.tokenStandard  }}
+                        </div>
+                    </div>
+                    <div class="detail-item">
+                        <div class="left">Blockchain</div>
+                        <div class="right">
+                            {{  nftDetailData.details.blockChain  }}
+                        </div>
+                    </div>
+                    <div class="detail-item">
+                        <div class="left">Creator Fees</div>
+                        <div class="right">
+                            {{  nftDetailData.details.creatorFees  }}
+                        </div>
+                    </div>
+                    <div class="detail-item">
+                        <div class="left">Transaction Royalties</div>
+                        <div class="right">
+                            {{  nftDetailData.details.transactionRoyalties  }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="date item" v-if="nftDetailData.dateOfPossession">
+                <div class="title">Date of possession</div>
+                <div class="date-content">{{  nftDetailData.dateOfPossession  }}</div>
+            </div>
+
+            <div class="price item" v-if="nftDetailData.purchasePrice">
+                <div class="title">Purchase price</div>
+                <div class="price-content">{{  nftDetailData.purchasePrice  }}</div>
+            </div>
+        </div>
+        <div class="bottom-bar">
+            <div class="sale" @click="transfer">
+                <span>Transfer</span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import nftCard from "@/components/nft-card.vue";
+import { getNFTDetail } from "@/http/nft.js";
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+
+let timer = ref(null);
+let nftMetaData = ref({});
+let nftDetailData = ref({});
+let router = useRouter();
+
+let NFTInfo = ref({
+    imagePath: '',
+    nftItemName: ''
+});
+
+const back = () => {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: true,
+        }
+    })
+    router.back();
+};
+
+const clickAddress = () => {
+    let { contractAddressUrl = '' } = nftDetailData.value.details;
+    if (contractAddressUrl) {
+        window.open(contractAddressUrl);
+    }
+}
+
+const clickToken = () => {
+    let { tokenIdUrl = '' } = nftDetailData.value.details;
+    if (tokenIdUrl) {
+        window.open(tokenIdUrl);
+    }
+}
+
+const clickNFTImg = () => {
+    // window.open(NFTInfo.value.imagePath);
+};
+
+const getDetail = () => {
+    getNFTDetail({
+        params: {
+            nftItemId: NFTInfo.value.nftItemId
+        }
+    }).then(res => {
+        if (res.code == 0) {
+            console.log(res)
+            let { metadata = '{}' } = res.data || {};
+            nftDetailData.value = res.data;
+            nftMetaData.value = JSON.parse(metadata);
+        }
+    }).catch(() => {
+    })
+}
+
+const transfer = () => {
+    if (Object.keys(nftDetailData.value).length) {
+        clearTimeout(timer.value);
+        router.push({
+            name: 'navNftTransfer',
+            query: {
+                params: JSON.stringify({
+                    nftItemId: nftDetailData.value?.nftItemId,
+                    chainInfo: nftDetailData.value?.chainInfo,
+                    transferFeeCurrencyInfo: nftDetailData.value?.transferFeeCurrencyInfo,
+                    transferFeeAmountValue: nftDetailData.value?.transferFeeAmountValue,
+                })
+            }
+        })
+    } else {
+        clearTimeout(timer.value);
+        timer.value = setTimeout(() => {
+            transfer()
+        }, 300)
+    }
+}
+
+onMounted(() => {
+    let { params = '{}' } = router.currentRoute.value.query;
+    NFTInfo.value = JSON.parse(params);
+    getDetail();
+})
+
+
+</script>
+
+<style scoped lang="scss">
+.nft-detail-wrapper {
+    width: 100%;
+    height: 100%;
+
+    .back-bar {
+        height: 48px;
+        background: #ffffff;
+        box-shadow: 0px 0.5px 0px #d1d9dd;
+        box-sizing: border-box;
+        padding: 14px;
+        font-weight: 500;
+        font-size: 16px;
+        display: flex;
+        align-items: center;
+
+        .icon-arrow {
+            width: 24px;
+            margin-right: 12px;
+            cursor: pointer;
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 120px);
+        padding: 0 16px;
+        box-sizing: border-box;
+        overflow-y: auto;
+
+        .nft-img {
+            margin-top: 23px;
+            margin-bottom: 20px;
+            text-align: center;
+            cursor: pointer;
+
+            .img {
+                width: 280px;
+                border-radius: 10px;
+            }
+        }
+
+        .item {
+            border: 1px solid #e3e3e3;
+            border-radius: 10px;
+            padding: 14px;
+            box-sizing: border-box;
+            margin-bottom: 12px;
+
+            .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;
+            }
+        }
+
+        .section {
+            font-weight: 400;
+            font-size: 14px;
+            margin-bottom: 10px;
+        }
+
+        .detail-content {
+            margin-top: 15px;
+
+            .detail-item {
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                height: 24px;
+                font-weight: 400;
+                font-size: 14px;
+
+                .right {
+                    color: #929292;
+                }
+
+                .token {
+                    color: #1d9bf0 !important;
+                    cursor: pointer;
+                }
+
+                .address {
+                    width: 100px;
+                    white-space: nowrap;
+                    color: #1d9bf0 !important;
+                    cursor: pointer;
+
+                    >span {
+                        display: inline-block;
+                        overflow: hidden;
+                        text-overflow: ellipsis;
+                        width: 50%;
+
+                        +span {
+                            width: calc(50% + 10px);
+                            direction: rtl;
+                            margin-left: -11px;
+                        }
+                    }
+                }
+            }
+        }
+
+        .date-content,
+        .price-content {
+            margin-top: 10px;
+            font-weight: 500;
+            font-size: 14px;
+            color: #929292;
+        }
+    }
+
+    .bottom-bar {
+        background: #ffffff;
+        box-shadow: inset 0px 1px 0px #ececec;
+        height: 70px;
+        padding: 0 16px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: right;
+
+        .sale {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            cursor: pointer;
+            width: 97px;
+            height: 37px;
+            font-size: 14px;
+            color: #1D9BF0;
+            border-radius: 20px;
+            border: 1px solid #1D9BF0;
+        }
+    }
+}
+</style>

+ 149 - 0
src/pages/tabbar/nft/index.vue

@@ -0,0 +1,149 @@
+<template>
+    <div class="nft-page-wrapper" ref="pageWrapperDom" @scroll="pageScroll">
+        <div class="content" ref="pageListDom">
+            <div class="item" v-for="(item, index) in listData" :key="index" @click="clickNFT(item)">
+                <img :src="item.imagePath" class="img" v-if="item.imagePath" />
+                <nft-card :nftItemId="item.nftItemId" :item="item.createImageInfo" :width="103" v-else></nft-card>
+                <div class="name">{{  item.nftItemName  }}</div>
+            </div>
+        </div>
+        <join-group-finish-dialog :dialogVisible="joinGroupFinishShow" :position="'absolute'"
+            :contentStyle="{ width: '315px' }" :iconStyle="{ width: '80px', marginTop: '26px' }"
+            :descStyle="{ marginTop: '24px', marginBottom: '25px', fontSize: '19px' }" @confirm="confirmFinish">
+        </join-group-finish-dialog>
+    </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { nftListMine } from "@/http/nft.js";
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import nftCard from "@/components/nft-card.vue";
+import joinGroupFinishDialog from "@/components/join-group-finish-dialog.vue";
+
+let listData = ref([]);
+
+let NFTReqParams = {
+    params: {
+        pageNum: 1,
+        pageSize: 30,
+    },
+    loadMore: false,
+};
+
+let pageWrapperDom = ref(null);
+let pageListDom = ref(null);
+let joinGroupFinishShow = ref(false);
+let router = useRouter()
+
+const clickNFT = (params) => {
+    console.log(params)
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: false,
+        }
+    })
+    router.push({
+        name: 'navNftDetail',
+        query: {
+            params: JSON.stringify(params)
+        }
+    })
+}
+
+const getNFTListMine = () => {
+    nftListMine({
+        params: NFTReqParams.params,
+    }).then((res) => {
+        if (res.data && res.data.length) {
+            if (NFTReqParams.params.pageNum < 2) {
+                listData.value = res.data;
+            } else {
+                let data = listData.value;
+                data = data.concat(res.data);
+                listData.value = data;
+            }
+            NFTReqParams.loadMore = false;
+        }
+    });
+}
+
+const pageScroll = (e) => {
+    let wrapperHeight = pageWrapperDom.value.offsetHeight;
+    let pageListHeight = pageListDom.value.offsetHeight;
+    let scrollTop = e.target.scrollTop || 0;
+    if (
+        NFTReqParams.loadMore === false &&
+        wrapperHeight + scrollTop >= pageListHeight - 60
+    ) {
+        NFTReqParams.loadMore = true;
+        NFTReqParams.params.pageNum++;
+        getNFTListMine();
+    }
+};
+
+const onMessage = () => {
+    messageCenter.listen(MESSAGE_ENUM.CONTENT_POPUP_PAGE_SHOW, (req) => {
+        getNFTListMine();
+        showJoinFinishHandler(req.data);
+    })
+}
+
+const showJoinFinishHandler = (params) => {
+    let { path, showJoinGroupFinish } = params;
+    if (path == '/NFT' && showJoinGroupFinish) {
+        joinGroupFinishShow.value = true;
+    } else if (joinGroupFinishShow.value) {
+        joinGroupFinishShow.value = false;
+    }
+}
+
+const confirmFinish = () => {
+    joinGroupFinishShow.value = false;
+}
+
+onMounted(() => {
+    onMessage();
+    getNFTListMine();
+})
+</script>
+
+<style scoped lang="scss">
+.nft-page-wrapper {
+    width: 100%;
+    height: 100%;
+    overflow-y: auto;
+
+    .content {
+        width: 100%;
+        display: flex;
+        flex-wrap: wrap;
+        padding: 5px 2px 0 16px;
+        box-sizing: border-box;
+
+        .item {
+            width: 33%;
+            box-sizing: border-box;
+            padding-right: 14px;
+            margin-top: 15px;
+            cursor: pointer;
+
+            .img {
+                width: 100%;
+                border-radius: 5px;
+                height: 104px;
+                object-fit: cover;
+            }
+
+            .name {
+                font-weight: 400;
+                font-size: 12px;
+                margin-top: 6px;
+            }
+        }
+    }
+}
+</style>

+ 329 - 0
src/pages/tabbar/nft/transfer.vue

@@ -0,0 +1,329 @@
+<template>
+    <div class="info">
+        <v-head
+            :title="'Transfer'"
+            :show_more="true"
+            :transactionsRouterParams="{
+                backUrl: 'back'
+            }">
+        </v-head>
+        <template v-if="!isSuccess">
+            <div class="content">
+                <div class="token">
+                    <div class="title">Network</div>
+                    <div class="box">
+                        <img :src="transChainInfo?.iconPath" alt="" />
+                        <span>{{  transChainInfo?.chainName  }}</span>
+                    </div>
+                </div>
+                <div class="token">
+                    <div class="title">Address</div>
+                    <div class="box">
+                        <input type="text" v-model="address" placeholder="Click to enter" />
+                    </div>
+                </div>
+            </div>
+            <div class="footer">
+                <div class="left">
+                    <div class="txt">Network fee</div>
+                    <span class="money">
+                        <a-tooltip :title="feeAmountValue || 0">{{ getBit(feeAmountValue || 0) }}</a-tooltip> {{
+                         feeCurrencyInfo?.tokenSymbol  }}
+                    </span>
+                    <div class="tips" v-if="showTips">Insufficient balance</div>
+                </div>
+                <div class="right">
+                    <div class="btn enter" @click="next" v-if="isNext">Confirm</div>
+                    <div class="btn" v-else>Confirm</div>
+                </div>
+            </div>
+        </template>
+        <template v-else>
+            <div class="withdraw-status">
+                <img :src="require('@/assets/svg/icon-withdraw-status.svg')" alt="" />
+                <div>
+                    <div class="title">Submitted successfully</div>
+                    <div class="desc">
+                        Please check the status at the message tab
+                    </div>
+                </div>
+            </div>
+            <div class="confirm-btn" @click="doneHandle">Done</div>
+        </template>
+    </div>
+</template>
+
+<script setup>
+import Report from "@/log-center/log"
+import { ref, onMounted, watchEffect } from 'vue'
+import { message } from 'ant-design-vue';
+import { getCurrencyInfoByCode } from '@/http/publishApi'
+import { transferRequest } from '@/http/nft'
+import { useRouter } from "vue-router";
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import VHead from '@/components/v-head.vue'
+import { getBit } from "@/uilts/help";
+
+const isNext = ref(false)
+const isSuccess = ref(false)
+const isPayment = ref(false)
+const showTips = ref(false)
+const transChainInfo = ref({})
+const feeAmountValue = ref(0)
+const feeCurrencyInfo = ref({})
+const address = ref('')
+const nftId = ref('')
+const router = useRouter()
+
+const getCurrentyInfo = (data) => {
+    getCurrencyInfoByCode({
+        params: {
+            currencyCode: data?.currencyCode
+        }
+    }).then(res => {
+        let { code, data } = res;
+        if (code === 0) {
+            isPayment.value = Number(data?.balance) >= Number(feeAmountValue.value)
+            showTips.value = Number(data?.balance) < Number(feeAmountValue.value)
+        }
+    })
+}
+
+const next = () => {
+    transferRequest({
+        params: {
+            nftItemId: nftId.value,
+            receiveAddress: address.value
+        }
+    }).then(res => {
+        let { code } = res;
+        let transfer;
+        if (code === 0) {
+            isSuccess.value = true;
+            transfer = 'success'
+        } else if (code === 5302) {
+            transfer = 'fail'
+            message.error(`NFT transfer is under system maintenance, try again in two or three hours.`);
+        } else {
+            transfer = 'fail'
+            message.error(`Transfer failed, please try again`);
+        }
+        // report
+        Report.reportLog({
+            pageSource: Report.pageSource.denetNftTransferPage,
+            businessType: Report.businessType.buttonClick,
+            objectType: Report.objectType.confirm_transfer_button,
+        }, {
+            transfer: transfer
+        });
+    })
+}
+
+const doneHandle = () => {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: true,
+        }
+    })
+    router.push({ name: 'navNftIndex' })
+}
+
+watchEffect(() => {
+    isNext.value = address.value !== '' && isPayment.value
+})
+
+onMounted(() => {
+    let { params = '{}' } = router.currentRoute.value.query;
+    let { chainInfo, nftItemId, transferFeeAmountValue, transferFeeCurrencyInfo } = JSON.parse(params);
+    // set
+    nftId.value = nftItemId;
+    transChainInfo.value = chainInfo
+    feeAmountValue.value = transferFeeAmountValue
+    feeCurrencyInfo.value = transferFeeCurrencyInfo
+
+    getCurrentyInfo(feeCurrencyInfo.value)
+
+    // report
+    Report.reportLog({
+        pageSource: Report.pageSource.denetNftTransferPage,
+        businessType: Report.businessType.pageView,
+    });
+})
+</script>
+
+<style lang="scss" scoped>
+.info {
+    position: relative;
+    height: 100%;
+
+    .content {
+        height: calc(100% - 48px - 80px);
+        overflow: auto;
+        padding: 13px 16px 0 13px;
+
+        .token {
+            margin-bottom: 20px;
+
+            .title {
+                font-weight: 500;
+                font-size: 12px;
+                margin-bottom: 6px;
+                color: #8B8B8B;
+            }
+
+            .box {
+                border: 1px solid #DBDBDB;
+                border-radius: 8px;
+                height: 44px;
+                display: flex;
+                align-items: center;
+                padding: 0 15px 0 10px;
+                display: flex;
+                flex-wrap: nowrap;
+                justify-content: space-between;
+
+                img {
+                    width: 20px;
+                    height: 20px;
+                }
+
+                .up {
+                    width: 13px;
+                    height: 12px;
+                    cursor: pointer;
+                }
+
+                input {
+                    outline: none;
+                    border: 0;
+                    flex: 1;
+                    height: 18px;
+                    padding: 0 6px;
+                    font-weight: 500;
+                    font-size: 15px;
+
+                    &::placeholder {
+                        color: #B8B8B8;
+                    }
+                }
+
+                input::-webkit-outer-spin-button,
+                input::-webkit-inner-spin-button {
+                    -webkit-appearance: none;
+                }
+
+                input[type='number'] {
+                    -moz-appearance: textfield;
+                }
+
+                span {
+                    flex: 1;
+                    margin-left: 6px;
+                    font-size: 14px;
+                    font-weight: 500;
+                }
+            }
+        }
+    }
+
+    .footer {
+        z-index: 11;
+        background: #fff;
+        border-top: 1px solid #DBDBDB;
+        bottom: 0;
+        height: 80px;
+        display: flex;
+        position: absolute;
+        justify-content: space-between;
+        width: 100%;
+        bottom: 0;
+        align-items: center;
+
+        .left {
+            margin-left: 16px;
+
+            .txt {
+                color: #9D9D9D;
+                font-weight: 400;
+                font-size: 12px;
+            }
+
+            .money {
+                color: #000000;
+                margin-right: auto;
+                font-size: 15px;
+                font-weight: bold;
+            }
+
+            .tips {
+                color: #FF0000;
+                font-weight: 500;
+                font-size: 12px;
+            }
+        }
+
+        .right {
+            margin-right: 16px;
+
+            .btn {
+                cursor: pointer;
+                width: 140px;
+                height: 46px;
+                line-height: 46px;
+                text-align: center;
+                font-weight: 600;
+                font-size: 18px;
+                color: #FFFFFF;
+                background: #D2D2D2;
+                border-radius: 100px;
+            }
+
+            .enter {
+                background: #1D9BF0;
+            }
+        }
+    }
+}
+
+.withdraw-status {
+    text-align: center;
+
+    img {
+        margin-top: 40px;
+        margin-bottom: 34px;
+    }
+
+    .title {
+        font-weight: 500;
+        font-size: 20px;
+        margin-bottom: 10px;
+    }
+
+    .desc {
+        font-size: 15px;
+        color: rgba($color: #000000, $alpha: 0.5);
+    }
+}
+
+.confirm-btn {
+    width: 335px;
+    height: 60px;
+    text-align: center;
+    line-height: 60px;
+    border-radius: 100px;
+    background: #1D9BF0;
+    font-weight: 600;
+    font-size: 18px;
+    color: #fff;
+    position: absolute;
+    left: 50%;
+    bottom: 35px;
+    transform: translateX(-50%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+}
+</style>

+ 331 - 0
src/pages/tabbar/wallet/index.vue

@@ -0,0 +1,331 @@
+<template>
+    <div class="page-wrapper" ref="pageWrapperDom">
+        <div class="content">
+            <div class="balance">
+                <div class="wallet">
+                    <font>Balance Valuation</font>
+                </div>
+            </div>
+            <div class="amount-wrapper">
+                <div class="amount">
+                    <a-tooltip :title="'$' + canWithdrawBalance">
+                        ${{ getBit(canWithdrawBalance) }}
+                    </a-tooltip>
+                </div>
+
+                <div class="right">
+                    <div class="bill" @click="showTransactions">
+                        <red-dot class="red-dot" v-if="unReadCountWallet > 0"></red-dot>
+                        <img :src="require('@/assets/svg/icon-home-list.svg')" />
+                    </div>
+
+                    <img
+                        class="icon"
+                        :src="require('@/assets/svg/icon-home-refresh.svg')"
+                        :class="{ transform_rotate: iconRotate }"
+                        @click="refreshList" />
+                </div>
+            </div>
+        </div>
+
+        <currency-list
+            ref="currencyListDom"
+            v-if="userInfo.accessToken"
+            style="height: calc(100% - 103px);"
+            :showRefresh="false"
+            @selectCurrency="selectCurrency">
+        </currency-list>
+    </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import redDot from "@/components/red-dot.vue";
+import CurrencyList from "@/components/currency-list.vue";
+import { getChromeStorageFromExtension } from "@/uilts/chromeExtension";
+import { getBalance } from "@/http/account";
+import { getAllMessageInfo } from "@/http/messageApi"
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import Report from "@/log-center/log";
+import { getBit } from "@/uilts/help";
+
+let userInfo = ref({});
+let canWithdrawBalance = ref(0);
+let isRequestWithdrawBalance = ref(false);
+let router = useRouter()
+let currencyListDom = ref('');
+let iconRotate = ref(false)
+
+// 钱包未读数
+let unReadCountWallet = ref(0);
+
+function selectCurrency(_params) {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: false,
+        }
+    })
+    router.push({
+        name: 'pageCurrencyDetail',
+        query: {
+            params: JSON.stringify(_params)
+        }
+    });
+}
+
+const init = () => {
+    checkLoginState((res) => {
+        if (res) {
+            getAccountBalance();
+            setMessageCount();
+            if (window.location.pathname.indexOf('popup-page.html') > -1) {
+                return
+            }
+            Report.reportLog({
+                pageSource: Report.pageSource.denetHomePage,
+                businessType: Report.businessType.pageView,
+            }, {
+                type: window.location.href.indexOf('home.html') > -1 ? 'web' : 'extensions'
+            });
+        } else {
+            Report.reportLog({
+                pageSource: Report.pageSource.denetLogin,
+                businessType: Report.businessType.pageView,
+            });
+        }
+    });
+}
+
+const setMessageCount = () => {
+    getAllMessageInfo({
+        params: {}
+    }).then(res => {
+        if (res.code == 0) {
+            let { unReadCountTotal = 0, unReadCountWalletDetail = 0 } = res.data;
+            unReadCountWallet.value = unReadCountWalletDetail;
+            if (unReadCountTotal > 0) {
+                let text = unReadCountTotal > 99 ? '99+' : unReadCountTotal + '';
+                messageCenter.send({
+                    actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_SETBADGEINFO,
+                    data: { text }
+                })
+            } else {
+                messageCenter.send({
+                    actionType: MESSAGE_ENUM.IFRAME_MESSAGE_PAGE_HIDEBADGE,
+                    data: {}
+                })
+            }
+        }
+    });
+}
+
+
+/**
+ * 获取账户余额
+ */
+const getAccountBalance = () => {
+    isRequestWithdrawBalance.value = false;
+    getBalance({}).then((res) => {
+        isRequestWithdrawBalance.value = true;
+        if (res.code == 0) {
+            if (res.data) {
+                canWithdrawBalance.value = res.data.allAssetValuationUSD;
+            }
+        }
+    });
+};
+
+const getUserInfo = async (cb) => {
+    const res = await getChromeStorageFromExtension("userInfo");
+    cb && cb(res);
+};
+
+/**
+ * 检查登录状态
+ */
+const checkLoginState = (cb) => {
+    getUserInfo((res) => {
+        if (res && res.accessToken) {
+            userInfo.value = res;
+        } else {
+            userInfo.value = {};
+        }
+        cb && cb(res);
+    });
+};
+
+const showTransactions = () => {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: false,
+        }
+    })
+    router.push({ name: 'navWalletTransactions' })
+};
+
+const refreshList = () => {
+    if (iconRotate.value) {
+        return
+    }
+    iconRotate.value = true
+    setTimeout(() => {
+        iconRotate.value = false
+    }, 1000)
+
+    getAccountBalance();
+    if (currencyListDom.value) {
+        currencyListDom.value.refresh && currencyListDom.value.refresh();
+    }
+}
+
+const onMessage = () => {
+    messageCenter.listen(MESSAGE_ENUM.CONTENT_POPUP_PAGE_SHOW, () => {
+        init();
+        if (currencyListDom.value) {
+            currencyListDom.value.getCurrencyInfoList && currencyListDom.value.getCurrencyInfoList();
+        }
+    })
+}
+
+onMounted(() => {
+    onMessage();
+    init();
+});
+
+</script>
+
+<style lang="scss" scoped>
+html,
+body {
+    padding: 0 !important;
+    margin: 0 !important;
+}
+
+.page-wrapper {
+    width: 375px;
+    height: 100%;
+    box-sizing: border-box;
+    overflow-y: auto;
+
+    .nav-bar {
+        padding: 14px;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+        .item {
+            display: flex;
+            align-items: center;
+            font-size: 13px;
+            cursor: pointer;
+
+            img {
+                width: 16px;
+                height: 16px;
+                margin-right: 4px;
+            }
+        }
+
+        .left {
+            font-weight: 500;
+        }
+
+        .right {
+            color: #b6b6b6;
+        }
+    }
+
+    .content {
+        padding: 12px 16px 10px 16px;
+        background: #1D9BF0;
+        box-sizing: border-box;
+
+        .icon-money {
+            width: 70px;
+            height: 70px;
+        }
+
+        .balance {
+            display: flex;
+            justify-content: space-between;
+
+            .wallet {
+                font {
+                    font-size: 13px;
+                    color: #fff;
+                    opacity: 0.7;
+                }
+            }
+        }
+
+        .amount-wrapper {
+            margin-top: 2px;
+            font-weight: 700;
+            font-size: 36px;
+            color: #fff;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .amount {
+                display: inline-block;
+            }
+
+            .right {
+                display: flex;
+                align-items: center;
+
+                .bill {
+                    height: 24px;
+                    width: 24px;
+                    position: relative;
+
+                    img {
+                        width: 24px;
+                        height: 24px;
+                        cursor: pointer;
+                        position: absolute;
+                        left: 0;
+                        top: 0;
+                    }
+
+                    .red-dot {
+                        position: absolute;
+                        right: 0px;
+                        top: -1px;
+                        z-index: 100;
+                    }
+                }
+
+                .icon {
+                    margin-left: 22px;
+                    cursor: pointer;
+                }
+
+                .transform_rotate {
+                    transform: rotate(360deg);
+                    transition-duration: 1s;
+                }
+            }
+        }
+
+        .msg {
+            margin-top: 10px;
+            font-size: 13px;
+            color: #b6b6b6;
+        }
+    }
+
+
+
+}
+
+.page-wrapper::-webkit-scrollbar {
+    display: none;
+}
+</style>

+ 45 - 0
src/pages/tabbar/wallet/transactions.vue

@@ -0,0 +1,45 @@
+<template>
+    <!-- 公共组件 -->
+    <div class="info">
+        <v-head :title="'Transactions'" :show_more="false" @onBack="clickBack"></v-head>
+        <popup-transactions style="height: calc(100% - 48px);"></popup-transactions>
+    </div>
+</template>
+
+<script setup>
+import VHead from '@/components/v-head.vue';
+import PopupTransactions from "@/components/popup-transactions";
+import messageCenter from "@/uilts/messageCenter";
+import MESSAGE_ENUM from "@/uilts/messageCenter/messageEnum";
+import { ref, onMounted } from 'vue';
+import { useRouter } from "vue-router";
+
+let back_url = ref('/');
+let router = useRouter();
+
+onMounted(() => {
+    let { params = '{}' } = router.currentRoute.value.query;
+    let { backUrl = '/' } = JSON.parse(params);
+
+    if (backUrl == 'back') {
+        back_url.value = '';
+    }
+})
+
+const clickBack = () => {
+    messageCenter.send({
+        actionType: MESSAGE_ENUM.IFRAME_SHOW_FOOTER_MENU,
+        data: {
+            showMenu: true,
+        }
+    })
+}
+</script>
+
+
+<style lang='scss' scoped>
+.info {
+    height: 100%;
+    overflow: hidden;
+}
+</style>

+ 36 - 1
src/router/index.js

@@ -10,8 +10,43 @@ const routes = [
     },
     {
         path: '/tab-group',
-        name: '',
+        name: 'tabGroup',
         component: TabGroup
+    },
+    {
+        path: '/nav-message',
+        name: 'navMessage',
+        component: () => require('@/pages/tabbar/message/index')
+    },
+    {
+        path: '/nav-nft-index',
+        name: 'navNftIndex',
+        component: () => require('@/pages/tabbar/nft/index')
+    },
+    {
+        path: '/nav-nft-detail',
+        name: 'navNftDetail',
+        component: () => require('@/pages/tabbar/nft/detail')
+    },
+    {
+        path: '/nav-nft-transfer',
+        name: 'navNftTransfer',
+        component: () => require('@/pages/tabbar/nft/transfer')
+    },
+    {
+        path: '/nav-wallet-index',
+        name: 'navWalletIndex',
+        component: () => require('@/pages/tabbar/wallet/index')
+    },
+    {
+        path: '/nav-wallet-transactions',
+        name: 'navWalletTransactions',
+        component: () => require('@/pages/tabbar/wallet/transactions')
+    },
+    {
+        path: '/page-currency-detail',
+        name: 'pageCurrencyDetail',
+        component: () => require('@/pages/currency/detail')
     }
 ]
 

+ 9 - 2
src/uilts/messageCenter/messageEnum.js

@@ -6,7 +6,13 @@ const SEND_MESSAGE_ENUM =  {
     IFREME_TAB_GROUP_CONTENT_GET_NAV_TOP: 'IFREME_TAB_GROUP_CONTENT_GET_NAV_TOP',
     /** 获取content的localstorge数据 */
     IFRAME_GET_EXTENSION_STORGE_DATA: 'IFRAME_GET_EXTENSION_STORGE_DATA',
-    IFRAME_SET_EXTENSION_STORGE_DATA: 'IFRAME_SET_EXTENSION_STORGE_DATA'
+    IFRAME_SET_EXTENSION_STORGE_DATA: 'IFRAME_SET_EXTENSION_STORGE_DATA',
+    IFRAME_MESSAGE_PAGE_CREATE_TAB: 'IFRAME_MESSAGE_PAGE_CREATE_TAB',
+    IFRAME_MESSAGE_PAGE_PUBLISH_TWITTER: 'IFRAME_MESSAGE_PAGE_PUBLISH_TWITTER',
+    IFRAME_MESSAGE_PAGE_SETBADGEINFO: 'IFRAME_MESSAGE_PAGE_SETBADGEINFO',
+    IFRAME_MESSAGE_PAGE_HIDEBADGE: 'IFRAME_MESSAGE_PAGE_HIDEBADGE',
+    IFRAME_RUNTIME_CONNECT_POPUP: 'IFRAME_RUNTIME_CONNECT_POPUP',
+    IFRAME_SHOW_FOOTER_MENU: 'IFRAME_SHOW_FOOTER_MENU',
 }
 
 /** 接收父窗口的事件定义 */
@@ -16,7 +22,8 @@ const RECEIVE_MESSAGE_ENUM = {
     /** group打开时,页面发生滚动 */
     CONTENT_GROUP_LIST_SCROLL: 'CONTENT_GROUP_LIST_SCROLL',
     CONTENT_SEND_GROUP_NAV_TOP: 'CONTENT_SEND_GROUP_NAV_TOP',
-    CONTENT_SYS_THEME_CHANGE: 'CONTENT_SYS_THEME_CHANGE'
+    CONTENT_SYS_THEME_CHANGE: 'CONTENT_SYS_THEME_CHANGE',
+    CONTENT_POPUP_PAGE_SHOW: 'CONTENT_POPUP_PAGE_SHOW',
 }
 
 export default { ...SEND_MESSAGE_ENUM, ...RECEIVE_MESSAGE_ENUM }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 295 - 288
yarn.lock


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