Compare commits
8 Commits
65f5d164de
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| de7d9b29b2 | |||
| d03cef9685 | |||
| 4e1ca61828 | |||
| 7785e2bd5a | |||
| 9ca6c78f66 | |||
| d4c4605d7e | |||
|
|
4b2ce62cea | ||
| 33dc44c4c7 |
13
.env.example
@@ -1,10 +1,17 @@
|
||||
NODE_ENV=production
|
||||
NODE_ENV=development
|
||||
BASE_URL=/
|
||||
|
||||
VUE_APP_VUEX_KEY=vuex
|
||||
VUE_APP_VUEX_KEY=ex_vuex
|
||||
|
||||
VUE_APP_TITLE=Jason.Chen
|
||||
VUE_APP_TITLE=联盟链浏览器
|
||||
VUE_APP_API_URL=/api
|
||||
VUE_APP_BLOCK_URL=https://explorer.lianshang.vip/api
|
||||
|
||||
VUE_APP_MAIN_COIN_SYMBOL=XHC
|
||||
|
||||
VUE_APP_USERNAME=
|
||||
VUE_APP_PASSWORD=
|
||||
|
||||
VUE_APP_HOME_LIST_SIZE=6
|
||||
VUE_APP_BLOCK_LIST_SIZE=20
|
||||
VUE_APP_BLOCK_DETAIL_LIST_SIZE=10
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import Chain33Rpc from '@33cn/chain33-rpc-api'
|
||||
|
||||
export default new Chain33Rpc('http://47.100.214.15:8080/api', null)
|
||||
export default new Chain33Rpc(process.env.VUE_APP_BLOCK_URL, null)
|
||||
|
||||
BIN
src/assets/home/box.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/home/cicle-inner.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/home/cicle-out.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/assets/home/left-line.png
Normal file
|
After Width: | Height: | Size: 720 B |
BIN
src/assets/home/line-left.gif
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/assets/home/line-right.gif
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/assets/home/right-line.png
Normal file
|
After Width: | Height: | Size: 763 B |
@@ -1,5 +1,16 @@
|
||||
<template>
|
||||
<div class="banner">
|
||||
<div class="left-trans">
|
||||
<div class="animation-area">
|
||||
<img src="../assets/home/line-right.gif" class="line-left" alt="">
|
||||
<img src="../assets/home/box.png" class="box-left" alt="">
|
||||
<div class="circle-area">
|
||||
<img src="../assets/home/cicle-out.png" class="left-out-cicle" alt="">
|
||||
<img src="../assets/home/cicle-inner.png" class="left-inner-cicle" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="middle">
|
||||
<div class="search-box">
|
||||
<h1>区块链浏览器</h1>
|
||||
@@ -15,6 +26,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-trans">
|
||||
<div class="animation-area flex-between-stright">
|
||||
<div class="circle-area">
|
||||
<img src="../assets/home/cicle-out.png" class="right-out-cicle" alt="">
|
||||
<img src="../assets/home/cicle-inner.png" class="right-inner-cicle" alt="">
|
||||
</div>
|
||||
<img src="../assets/home/box.png" class="box-right" alt="">
|
||||
<img src="../assets/home/line-left.gif" class="line-right" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -124,12 +146,107 @@ const onSearch = () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.left-trans,
|
||||
.middle,
|
||||
.right-trans {
|
||||
display: inline-block;
|
||||
height: 556px;
|
||||
}
|
||||
|
||||
.left-trans {
|
||||
width: 150px;
|
||||
background: url("../assets/home/left-line.png") no-repeat top 60px left;
|
||||
overflow: hidden;
|
||||
|
||||
.animation-area {
|
||||
position: relative;
|
||||
margin-top: 60px;
|
||||
height: 450px;
|
||||
float: right;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.box-left {
|
||||
margin-top: -40px;
|
||||
margin-left: -40px;
|
||||
animation: jump 2s ease-in-out 1s infinite alternate;
|
||||
}
|
||||
|
||||
.circle-area {
|
||||
position: relative;
|
||||
width: 91px;
|
||||
height: 91px;
|
||||
|
||||
.left-out-cicle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation: clockwise 2.5s linear infinite;
|
||||
}
|
||||
|
||||
.left-inner-cicle {
|
||||
width: 70px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 11px;
|
||||
animation: anticlockwise 2.5s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-trans {
|
||||
width: 150px;
|
||||
background: url("../assets/home/right-line.png") no-repeat top 60px right;
|
||||
overflow: hidden;
|
||||
|
||||
.animation-area {
|
||||
position: relative;
|
||||
margin-top: 60px;
|
||||
height: 450px;
|
||||
float: left;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.box-right {
|
||||
margin-right: -30px;
|
||||
animation: jump 2s ease-in-out 0.1s infinite alternate;
|
||||
}
|
||||
|
||||
.circle-area {
|
||||
position: relative;
|
||||
width: 91px;
|
||||
height: 91px;
|
||||
|
||||
.right-out-cicle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
animation: clockwise 2.5s linear infinite;
|
||||
}
|
||||
|
||||
.right-inner-cicle {
|
||||
width: 70px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 12px;
|
||||
animation: anticlockwise 2.5s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.middle {
|
||||
height: 556px;
|
||||
position: relative;
|
||||
width: 1000px;
|
||||
flex-shrink: 0;
|
||||
background: url("/assets/images/ball.png") center center no-repeat;
|
||||
background: url("../assets/ball.png") center center no-repeat;
|
||||
|
||||
.search-box {
|
||||
width: 700px;
|
||||
@@ -190,4 +307,32 @@ const onSearch = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 动画区
|
||||
@keyframes clockwise {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes anticlockwise {
|
||||
0% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes jump {
|
||||
from {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
to {
|
||||
transform: translate(0, 15px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,10 +24,10 @@ const navList = ref<NavItem[]>([
|
||||
title: '查看区块',
|
||||
route: 'Block'
|
||||
},
|
||||
{
|
||||
title: '查看数据',
|
||||
route: 'Trade'
|
||||
},
|
||||
// {
|
||||
// title: '查看数据',
|
||||
// route: 'Trade'
|
||||
// },
|
||||
{
|
||||
title: 'Token',
|
||||
route: 'Token'
|
||||
@@ -43,10 +43,6 @@ const navList = ref<NavItem[]>([
|
||||
{
|
||||
title: '广播数据',
|
||||
route: 'Broadcast'
|
||||
},
|
||||
{
|
||||
title: '我的钱包',
|
||||
route: 'Wallet'
|
||||
}
|
||||
])
|
||||
const linkTo = (name: string) => {
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
<div class="records">
|
||||
<div class="head">
|
||||
<h2>{{ title }}</h2>
|
||||
<el-pagination
|
||||
background
|
||||
layout="total,prev,pager,next,jumper"
|
||||
:total="length"
|
||||
:page-size="pageSize"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
</el-pagination>
|
||||
<slot name="page">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total,prev,pager,next,jumper"
|
||||
:total="length"
|
||||
:page-size="pageSize"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
</el-pagination>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<slot name="default"></slot>
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
import { block } from '@/api'
|
||||
import vuex from '@/store'
|
||||
import { TotalFee } from '@/types/block'
|
||||
import { hexCharCodeToStr } from '@/utils/filters'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, ComputedRef, onActivated, ref } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
|
||||
export default function () {
|
||||
const maxHeight = computed(() => vuex.getters.maxHeight)
|
||||
export default function (): {
|
||||
maxHeight: ComputedRef<number>;
|
||||
queryTotalFee: () => Promise<TotalFee>;
|
||||
lastHash: ComputedRef<string>;
|
||||
} {
|
||||
const maxHeight: ComputedRef<number> = computed(() => vuex.getters.maxHeight)
|
||||
const lastHash: ComputedRef<string> = computed(() => vuex.getters.lastHash)
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const interval = ref<NodeJS.Timeout | null>()
|
||||
|
||||
onMounted(() => {
|
||||
onActivated(() => {
|
||||
getLastHeader()
|
||||
console.log('开始轮询头信息')
|
||||
interval.value = setInterval(() => {
|
||||
getLastHeader()
|
||||
}, 5000)
|
||||
})
|
||||
|
||||
const getLastHeader = () => {
|
||||
/**
|
||||
* 获取最新的区块
|
||||
*/
|
||||
const getLastHeader = (): void => {
|
||||
block.getLastHeader().then(res => {
|
||||
console.log('获取最新区块', res.result.height)
|
||||
if (res.error) {
|
||||
clearInterval(Number(interval.value))
|
||||
return ElMessage.error({
|
||||
@@ -25,15 +39,26 @@ export default function () {
|
||||
})
|
||||
} else if (maxHeight.value !== res.result.height) {
|
||||
vuex.dispatch('setMaxHeight', res.result.height).then()
|
||||
vuex.dispatch('setLastHash', res.result.hash).then()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
/**
|
||||
* 查询从交易量和交易费
|
||||
*/
|
||||
const queryTotalFee = (): Promise<TotalFee> => {
|
||||
return block.queryTotalFee(hexCharCodeToStr(lastHash.value))
|
||||
}
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
console.log('结束轮询')
|
||||
clearInterval(Number(interval.value))
|
||||
})
|
||||
|
||||
return {
|
||||
maxHeight
|
||||
maxHeight,
|
||||
lastHash,
|
||||
queryTotalFee
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,17 +99,6 @@ export default [
|
||||
showTabBar: true
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "other" */ '@/views/Token/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/wallet',
|
||||
name: 'Wallet',
|
||||
meta: {
|
||||
title: '我的钱包',
|
||||
keepAlive: false,
|
||||
requiresAuth: true,
|
||||
showTabBar: true
|
||||
},
|
||||
component: () => import(/* webpackChunkName: "wallet" */ '@/views/Wallet/index.vue')
|
||||
},
|
||||
}
|
||||
] as MyRouteRecordRaw[]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface State {
|
||||
openId: string
|
||||
loginAt: number
|
||||
maxHeight: number
|
||||
lastHash: string
|
||||
user: BaseInfo
|
||||
auth?: AuthState
|
||||
refresh?: RefreshState
|
||||
@@ -31,6 +32,7 @@ export default createStore<State>({
|
||||
openId: '',
|
||||
loginAt: 0,
|
||||
maxHeight: 0,
|
||||
lastHash: '',
|
||||
user: {} as BaseInfo
|
||||
},
|
||||
getters: {
|
||||
@@ -48,6 +50,9 @@ export default createStore<State>({
|
||||
},
|
||||
symbol: (): string => {
|
||||
return process.env.VUE_APP_MAIN_COIN_SYMBOL as string
|
||||
},
|
||||
lastHash: (state: State): string => {
|
||||
return state.lastHash
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
@@ -74,17 +79,23 @@ export default createStore<State>({
|
||||
},
|
||||
setMaxHeight: (state: State, height: number): void => {
|
||||
state.maxHeight = height
|
||||
},
|
||||
setLastHash: (state: State, hash: string): void => {
|
||||
state.lastHash = hash
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setUserInfo: ({ commit }, info: BaseInfo): void => {
|
||||
setUserInfo: ({commit}, info: BaseInfo): void => {
|
||||
commit('setUserInfo', info)
|
||||
},
|
||||
setOpenId: ({ commit }, openId: string): void => {
|
||||
setOpenId: ({commit}, openId: string): void => {
|
||||
commit('setOpenId', openId)
|
||||
},
|
||||
setMaxHeight: ({ commit }, height: number): void => {
|
||||
setMaxHeight: ({commit}, height: number): void => {
|
||||
commit('setMaxHeight', height)
|
||||
},
|
||||
setLastHash: ({commit}, hash: string): void => {
|
||||
commit('setLastHash', hash)
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
|
||||
20
src/types/block.d.ts
vendored
@@ -95,3 +95,23 @@ export declare type BlockDetail = {
|
||||
tyName: string
|
||||
}
|
||||
}
|
||||
|
||||
export declare type TradeItem = {
|
||||
txHash: string
|
||||
blockTime: number
|
||||
fromAddr: string
|
||||
amount: number
|
||||
assets: any
|
||||
tx: {
|
||||
to: string
|
||||
}
|
||||
}
|
||||
|
||||
export declare type TotalFee = {
|
||||
error: string | null
|
||||
id: number
|
||||
result: {
|
||||
fee: number
|
||||
txCount: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const parseSymbol = (assets?: AssetType[]): string => {
|
||||
}
|
||||
}
|
||||
|
||||
export const timestampToTime = (timestamp: number) => {
|
||||
export const timestampToTime = (timestamp: number): string => {
|
||||
const date = new Date(timestamp * 1000)
|
||||
const Y = date.getFullYear() + '-'
|
||||
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
|
||||
@@ -28,3 +28,56 @@ export const timestampToTime = (timestamp: number) => {
|
||||
const s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
|
||||
return Y + M + D + h + m + s
|
||||
}
|
||||
|
||||
export function bin2hex (str: string): string {
|
||||
let ret = ''
|
||||
const r = /[0-9a-zA-Z_.~!*()]/
|
||||
for (let i = 0, l = str.length; i < l; i++) {
|
||||
if (r.test(str.charAt(i))) {
|
||||
ret += str.charCodeAt(i).toString(16)
|
||||
console.log(ret)
|
||||
} else {
|
||||
ret += encodeURIComponent(str.charAt(i)).replace(/%/g, '')
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export function hexCharCodeToStr (hexCharCodeStr: string): string[] {
|
||||
const trimedStr = hexCharCodeStr.trim()
|
||||
const rawStr = trimedStr.substr(0, 2).toLowerCase() === '0x' ? trimedStr.substr(2) : trimedStr
|
||||
const len = rawStr.length
|
||||
|
||||
let curCharCode
|
||||
const resultStr = []
|
||||
for (let i = 0; i < len; i = i + 2) {
|
||||
curCharCode = parseInt(rawStr.substr(i, 2), 16)
|
||||
resultStr.push(String.fromCharCode(curCharCode))
|
||||
}
|
||||
return [base64Encode(`TotalFeeKey:${resultStr.join('')}`)]
|
||||
}
|
||||
|
||||
const base64Encode = (input: string): string => {
|
||||
const _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
|
||||
let output = ''
|
||||
let chr1, chr2, chr3, enc1, enc2, enc3, enc4
|
||||
let i = 0
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++)
|
||||
chr2 = input.charCodeAt(i++)
|
||||
chr3 = input.charCodeAt(i++)
|
||||
enc1 = chr1 >> 2
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
|
||||
enc4 = chr3 & 63
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64
|
||||
}
|
||||
output = output +
|
||||
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
|
||||
_keyStr.charAt(enc3) + _keyStr.charAt(enc4)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -60,73 +60,83 @@
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
:length="balance.txCount"
|
||||
:title="`数据记录(` + balance.txCount + `)`"
|
||||
:page-size="pageSize"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<el-table :data="records" stripe class="table">
|
||||
<template #empty>
|
||||
<el-empty description="暂无数据"></el-empty>
|
||||
</template>
|
||||
<template #page>
|
||||
<el-pagination
|
||||
background
|
||||
:page-size="pageSize"
|
||||
layout="prev,next"
|
||||
:total="balance.txCount"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
</el-pagination>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-table :data="records" v-loading="loading" stripe class="table">
|
||||
<template #empty>
|
||||
<el-empty description="暂无数据"></el-empty>
|
||||
</template>
|
||||
|
||||
<el-table-column width="34" align="right">
|
||||
<template #default="scope">
|
||||
<el-icon v-if="scope.row.receipt.ty == 1">
|
||||
<Warning class="warning"/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="80" prop="height" label="高度"/>
|
||||
<el-table-column width="34" align="right">
|
||||
<template #default="scope">
|
||||
<el-icon v-if="scope.row.receipt.ty == 1">
|
||||
<Warning class="warning"/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="交易哈希">
|
||||
<template #default="scope">
|
||||
<router-link :to="{name: 'TradeDetail', params: { hash: scope.row.txHash }}">
|
||||
{{ filterHash(scope.row.txHash, 32) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" label="发送方">
|
||||
<template #default="scope">
|
||||
<router-link :to="{name: 'Address', params: { address: scope.row.fromAddr }}">
|
||||
{{ filterHash(scope.row.fromAddr) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交易哈希">
|
||||
<template #default="scope">
|
||||
<router-link :to="{name: 'TradeDetail', params: { hash: scope.row.txHash }}">
|
||||
{{ filterHash(scope.row.txHash, 32) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" label="发送方">
|
||||
<template #default="scope">
|
||||
<router-link :to="{name: 'Address', params: { address: scope.row.fromAddr }}">
|
||||
{{ filterHash(scope.row.fromAddr) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="40" align="center">
|
||||
<template #default="scope">
|
||||
<el-icon v-if="scope.row.fromAddr == address">
|
||||
<DArrowRight/>
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<DArrowLeft/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="40" align="center">
|
||||
<template #default="scope">
|
||||
<el-icon v-if="scope.row.fromAddr == address">
|
||||
<DArrowRight/>
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<DArrowLeft/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="200" label="接收方">
|
||||
<template #default="scope">
|
||||
<router-link :to="{name: 'Address', params: { address: scope.row.tx.to }}">
|
||||
{{ filterHash(scope.row.tx.to) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" label="交易量" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.amount }} {{ parseSymbol(scope.row.assets) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" label="手续费" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.tx.feefmt }} {{ parseSymbol(scope.row.assets) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="" label="上链时间" width="165" align="center">
|
||||
<template #default="scope">
|
||||
<TimeFormat :time="scope.row.blockTime"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table-column width="200" label="接收方">
|
||||
<template #default="scope">
|
||||
<router-link :to="{name: 'Address', params: { address: scope.row.tx.to }}">
|
||||
{{ filterHash(scope.row.tx.to) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" label="交易量" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.amount }} {{ parseSymbol(scope.row.assets) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" label="手续费" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.tx.feefmt }} {{ parseSymbol(scope.row.assets) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="" label="上链时间" width="165" align="center">
|
||||
<template #default="scope">
|
||||
<TimeFormat :time="scope.row.blockTime"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</Pagination>
|
||||
</div>
|
||||
</template>
|
||||
@@ -138,12 +148,12 @@ import { useStore } from '@/store'
|
||||
import { AddrOverview, BlockDetail, TokenAssetItem } from '@/types/block'
|
||||
import { filterHash, parseSymbol } from '@/utils/filters'
|
||||
import { DArrowLeft, DArrowRight, Warning } from '@element-plus/icons'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const address = route.params.address as string
|
||||
const address = computed<string>(() => route.params.address as string)
|
||||
const pageSize = Number(process.env.VUE_APP_BLOCK_DETAIL_LIST_SIZE)
|
||||
|
||||
const balance = ref<AddrOverview>({
|
||||
@@ -151,61 +161,92 @@ const balance = ref<AddrOverview>({
|
||||
reciver: 0,
|
||||
txCount: 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取地址的基本信息
|
||||
*/
|
||||
block.getAddrOverview(address).then(res => {
|
||||
balance.value.balance = res.result.balance ? res.result.balance / 1e8 : 0
|
||||
balance.value.reciver = res.result.reciver ? res.result.reciver / 1e8 : 0
|
||||
balance.value.txCount = res.result.txCount ? res.result.txCount : 0
|
||||
})
|
||||
|
||||
const sended = computed(() => balance.value.reciver - balance.value.balance)
|
||||
const frozen = ref<number>(0)
|
||||
/**
|
||||
* 获取冻结的主代币
|
||||
*/
|
||||
block.getAllExecBalance(address).then(res => {
|
||||
if (res.result.execAccount) {
|
||||
frozen.value = res.result.execAccount.find((item: { execer: string }) => item.execer == 'coins').account.frozen / 1e8
|
||||
const records = ref<BlockDetail[]>([])
|
||||
const token = ref<string>('')
|
||||
const assets = ref<TokenAssetItem[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
watch(route, (to) => {
|
||||
if (to.name == 'Address') {
|
||||
records.value = []
|
||||
initAddressData()
|
||||
}
|
||||
})
|
||||
|
||||
const records = ref<BlockDetail[]>([])
|
||||
/**
|
||||
* 获取全部交易
|
||||
*/
|
||||
block.getTxByAddr({
|
||||
addr: address,
|
||||
onMounted(() => {
|
||||
initAddressData()
|
||||
})
|
||||
|
||||
const initAddressData = () => {
|
||||
/**
|
||||
* 获取地址的基本信息
|
||||
*/
|
||||
block.getAddrOverview(address.value).then(res => {
|
||||
balance.value.balance = res.result.balance ? res.result.balance / 1e8 : 0
|
||||
balance.value.reciver = res.result.reciver ? res.result.reciver / 1e8 : 0
|
||||
balance.value.txCount = res.result.txCount ? res.result.txCount : 0
|
||||
})
|
||||
/**
|
||||
* 获取冻结的主代币
|
||||
*/
|
||||
block.getAllExecBalance(address.value).then(res => {
|
||||
if (res.result.execAccount) {
|
||||
frozen.value = res.result.execAccount.find((item: { execer: string }) => item.execer == 'coins').account.frozen / 1e8
|
||||
}
|
||||
})
|
||||
|
||||
block.getAddrTokenAssets(address.value, 'token').then(res => {
|
||||
if (res.error == null) {
|
||||
console.log(res)
|
||||
assets.value = res.result.tokenAssets
|
||||
}
|
||||
})
|
||||
loadTradeList()
|
||||
}
|
||||
|
||||
const initParams = {
|
||||
addr: address.value,
|
||||
flag: 0,
|
||||
count: pageSize,
|
||||
direction: 0,
|
||||
height: -1,
|
||||
index: 1
|
||||
}).then(res => {
|
||||
if (res.error == null) {
|
||||
let hashes = res.result.txInfos.map((item: { hash: string }) => item.hash)
|
||||
|
||||
block.getTxByHashes(hashes).then(res => {
|
||||
records.value = res.result.txs
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
index: 0
|
||||
}
|
||||
const pageParams = reactive(initParams)
|
||||
const handleCurrentChange = (e: number) => {
|
||||
console.log(e)
|
||||
if (e === 1) {
|
||||
pageParams.direction = 0
|
||||
pageParams.height = -1
|
||||
} else {
|
||||
pageParams.direction = 0
|
||||
pageParams.height = records.value[records.value.length - 1].height
|
||||
pageParams.index = records.value[records.value.length - 1].index
|
||||
}
|
||||
console.log(pageParams)
|
||||
records.value = []
|
||||
loadTradeList()
|
||||
}
|
||||
|
||||
const token = ref<string>('')
|
||||
const assets = ref<TokenAssetItem[]>([])
|
||||
const loadTradeList = () => {
|
||||
loading.value = true
|
||||
|
||||
block.getAddrTokenAssets(address, 'token').then(res => {
|
||||
if (res.error == null) {
|
||||
console.log(res)
|
||||
assets.value = res.result.tokenAssets
|
||||
}
|
||||
})
|
||||
block.getTxByAddr(pageParams).then(res => {
|
||||
if (res.error == null) {
|
||||
let hashes = res.result.txInfos.map((item: { hash: string }) => item.hash)
|
||||
|
||||
block.getTxByHashes(hashes).then(res => {
|
||||
console.log(' pageParams.height ', pageParams.height)
|
||||
records.value = res.result.txs
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
登录
|
||||
<button @click="onLogin">一键登录</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +13,7 @@ const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const onLogin = () => {
|
||||
store.dispatch('auth/Login', { username: '15555555555', password: '123123' }).then(() => {
|
||||
store.dispatch('auth/Login', { username: '', password: '' }).then(() => {
|
||||
route.query.to ? router.replace({ path: route.query.to as string }) : router.replace({ name: 'Home' })
|
||||
}).catch(err => {
|
||||
alert(err.message)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
注册
|
||||
|
||||
<button @click="onRegister">注册一个号</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -71,6 +71,7 @@ const end = computed(() => {
|
||||
const blockList = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
console.log('BLOCK INDEX')
|
||||
getBlockList()
|
||||
})
|
||||
|
||||
|
||||
@@ -30,15 +30,40 @@
|
||||
<div class="trades">
|
||||
<div class="head">
|
||||
<h1>最新交易</h1>
|
||||
<router-link :to="{name: 'Trade'}">查看全部</router-link>
|
||||
<!-- <router-link :to="{name: 'Trade'}">查看全部</router-link>-->
|
||||
</div>
|
||||
|
||||
<div class="items">
|
||||
<div class="items" v-loading="tradeLoading">
|
||||
<div class="item" v-for="(item, index) in tradeList" :key="index">
|
||||
{{ item.from }}
|
||||
{{ item.to }}
|
||||
{{ item.hash }}
|
||||
{{ item.feefmt }}
|
||||
<div class="hash">
|
||||
<div>
|
||||
交易哈希:
|
||||
<router-link :to="{name: 'TradeDetail', params: {hash: item.txHash}}">
|
||||
{{ filterHash(item.txHash, 10) }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="time">
|
||||
<TimeFormat :time="item.blockTime"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="addr">
|
||||
<div>
|
||||
发送方:
|
||||
<router-link :to="{name: 'Address', params: {address: item.fromAddr}}">
|
||||
{{ filterHash(item.fromAddr, 10) }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div>
|
||||
接收方:
|
||||
<router-link :to="{name: 'Address', params: {address: item.tx.to}}">
|
||||
{{ filterHash(item.tx.to, 10) }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset">
|
||||
<div>交易资产</div>
|
||||
<div>{{ item.amount }} {{ parseSymbol(item.assets) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,8 +80,8 @@ export default {
|
||||
import { block } from '@/api'
|
||||
import { Banner, TimeFormat } from '@/components'
|
||||
import useGetMaxHeight from '@/hooks/useGetMaxHeight'
|
||||
import { BlockItem } from '@/types/block'
|
||||
import { filterHash } from '@/utils/filters'
|
||||
import { BlockItem, TradeItem } from '@/types/block'
|
||||
import { filterHash, parseSymbol } from '@/utils/filters'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -65,6 +90,7 @@ const { maxHeight } = useGetMaxHeight()
|
||||
const pageSize = Number(process.env.VUE_APP_HOME_LIST_SIZE)
|
||||
|
||||
const blockLoading = ref<boolean>(true)
|
||||
const tradeLoading = ref<boolean>(true)
|
||||
|
||||
onMounted(() => {
|
||||
getBlockList()
|
||||
@@ -77,50 +103,65 @@ watch(maxHeight, (newValue, oldValue) => {
|
||||
})
|
||||
|
||||
const blockList = ref<BlockItem[]>([])
|
||||
const tradeList = ref([])
|
||||
const tradeList = ref<TradeItem[]>([])
|
||||
|
||||
if (blockList.value.length === 0) {
|
||||
let initBlock = {
|
||||
const initBlock = {
|
||||
height: 0,
|
||||
hash: 'x',
|
||||
hash: ' ',
|
||||
txCount: 0,
|
||||
blockTime: 0
|
||||
}
|
||||
} as BlockItem
|
||||
for (let i = 0; i < 6; i++) {
|
||||
blockList.value.push(initBlock)
|
||||
}
|
||||
}
|
||||
|
||||
if (tradeList.value.length === 0) {
|
||||
const initTrade = {
|
||||
txHash: ' ',
|
||||
blockTime: 0,
|
||||
fromAddr: ' ',
|
||||
amount: 0,
|
||||
assets: null,
|
||||
tx: {
|
||||
to: ' '
|
||||
}
|
||||
} as TradeItem
|
||||
for (let i = 0; i < 6; i++) {
|
||||
tradeList.value.push(initTrade)
|
||||
}
|
||||
}
|
||||
|
||||
const getBlockList = () => {
|
||||
const start = maxHeight.value - pageSize + 1 > 0 ? maxHeight.value - pageSize + 1 : 0
|
||||
|
||||
block.getHeaders(start, maxHeight.value, false).then(res => {
|
||||
blockList.value = res.result.items.reverse()
|
||||
getTradeList()
|
||||
}).finally(() => {
|
||||
blockLoading.value = false
|
||||
tradeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
async function getTradeList () {
|
||||
let txHashes = []
|
||||
for (let i = 0; i < blockList.value.length; i++) {
|
||||
let res = await block.getBlockOverview(blockList.value[i].hash)
|
||||
txHashes.push(...res.result.txHashes)
|
||||
|
||||
if (txHashes.length > pageSize) {
|
||||
txHashes = txHashes.slice(0, 6)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
block.getTxByHashes(txHashes).then(res => {
|
||||
tradeList.value = res.result.txs
|
||||
}).finally(() => {
|
||||
tradeLoading.value = false
|
||||
})
|
||||
// block.getBlocks(start, maxHeight.value, false).then(res => {
|
||||
// res.result.items.reverse().forEach((item) => {
|
||||
//
|
||||
// blockList.value.push({
|
||||
// height: item.block.height,
|
||||
// blockTime: item.block.blockTime,
|
||||
// txCount: item.block.txs.length,
|
||||
// // hash: block.getBlockHash(item.block.height)
|
||||
// })
|
||||
//
|
||||
// // block.getBlockHash(item.block.height).then(hash => {
|
||||
// // blockList.value.push({
|
||||
// // height: item.block.height,
|
||||
// // blockTime: item.block.blockTime,
|
||||
// // txCount: item.block.txs.length,
|
||||
// // hash: hash.result.hash
|
||||
// // })
|
||||
// // })
|
||||
// tradeList.value.push(...item.block.txs)
|
||||
// console.log(tradeList.value)
|
||||
// })
|
||||
// })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -161,6 +202,10 @@ const getBlockList = () => {
|
||||
height: 90px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
|
||||
.time {
|
||||
color: #9ea2a9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,18 +243,26 @@ const getBlockList = () => {
|
||||
|
||||
.num {
|
||||
color: #6368de;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: 16px;
|
||||
color: #9ea2a9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trades {
|
||||
|
||||
.item {
|
||||
justify-content: space-between;
|
||||
|
||||
.hash,
|
||||
.addr,
|
||||
.asset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
</router-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total" label="发行数量"/>
|
||||
<el-table-column label="发行数量">
|
||||
<template #default="scope">
|
||||
{{ (scope.row.total / 1e8).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发行时间" width="165" align="center">
|
||||
<template #default="scope">
|
||||
<TimeFormat :time="Number(scope.row.createdTime)"/>
|
||||
@@ -36,7 +40,7 @@ export default {
|
||||
import { block } from '@/api'
|
||||
import { Breadcrumb, TimeFormat } from '@/components'
|
||||
import { filterHash } from '@/utils/filters'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const tokens = ref([])
|
||||
|
||||
|
||||
@@ -110,7 +110,6 @@ const detail = ref<BlockDetail>({
|
||||
} as BlockDetail)
|
||||
const blockHash = ref('')
|
||||
block.queryTransaction(route.params.hash as string).then(res => {
|
||||
console.log(res)
|
||||
detail.value = res.result
|
||||
|
||||
block.getBlockHash(res.result.height).then(res => {
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<div class="container">
|
||||
<Breadcrumb :path="[{name: '全部数据'}]"/>
|
||||
|
||||
|
||||
<Pagination
|
||||
:length="maxHeight"
|
||||
:title="`全部区块(` + maxHeight + `)`"
|
||||
:length="txCount"
|
||||
:title="`数据总数(` + txCount + `)`"
|
||||
:page-size="pageSize"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
@@ -23,13 +22,29 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Trade'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Breadcrumb, Pagination } from '@/components'
|
||||
import useGetMaxHeight from '@/hooks/useGetMaxHeight'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
import { ref } from 'vue'
|
||||
const { queryTotalFee } = useGetMaxHeight()
|
||||
|
||||
const currentPage = ref<number>(1)
|
||||
const pageSize = ref<number>(20)
|
||||
onMounted(() => {
|
||||
console.log('TRADE onMounted')
|
||||
})
|
||||
const txCount = ref<number>(0)
|
||||
|
||||
queryTotalFee().then(res => {
|
||||
txCount.value = res.result.txCount
|
||||
})
|
||||
|
||||
const pageSize = Number(process.env.VUE_APP_BLOCK_LIST_SIZE)
|
||||
const handleCurrentChange = (e: number) => {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ function resolve (dir) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
productionSourceMap: false,
|
||||
configureWebpack: {
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||