Compare commits

...

12 Commits

Author SHA1 Message Date
73c2c4fc94 update 2022-03-25 16:24:25 +08:00
896a3be236 youhua 2021-12-21 13:33:40 +08:00
de7d9b29b2 第一版本 2021-09-30 17:00:15 +08:00
d03cef9685 数据分页 2021-09-30 16:56:41 +08:00
4e1ca61828 types 2021-09-30 15:42:53 +08:00
7785e2bd5a 首页样式修改 2021-09-30 12:19:32 +08:00
9ca6c78f66 打包不显示源码的配置,获取最新高度的机制更新 2021-09-30 11:57:30 +08:00
d4c4605d7e statsh 2021-09-30 09:14:12 +08:00
zhangdongxue
4b2ce62cea 尝试计算trades 2021-09-29 21:37:55 +08:00
33dc44c4c7 首页最新交易的数据获取 2021-09-29 18:09:29 +08:00
65f5d164de update 2021-09-29 17:23:08 +08:00
aae5520459 update 2021-09-29 10:35:40 +08:00
44 changed files with 1672 additions and 414 deletions

View File

@@ -1,10 +1,18 @@
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_ESDB_URL=/esdb
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

View File

@@ -21,10 +21,10 @@ yarn serve
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Nginx Configuration
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
```
location /api {
proxy_pass http://172.95.27.1:8901/;
}
```

View File

@@ -1,5 +1,6 @@
{
"name": "league-explorer",
"license": "MIT",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -12,6 +13,7 @@
"@33cn/chain33-rpc-api": "^1.5.29",
"@element-plus/icons": "^0.0.11",
"axios": "^0.21.4",
"clipboard": "^2.0.8",
"element-plus": "^1.1.0-beta.16",
"vue": "^3.2.11",
"vue-router": "^4.0.11",

View File

@@ -1,12 +1,12 @@
<template>
<router-view v-slot="{ Component }">
<keep-alive :include="includeList">
<div id="layout">
<Header/>
<div id="layout">
<Header/>
<keep-alive :include="includeList">
<component :is="Component"></component>
<Footer/>
</div>
</keep-alive>
</keep-alive>
<Footer/>
</div>
</router-view>
</template>
@@ -100,6 +100,7 @@ a {
width: 16px;
height: 16px;
}
label {
width: 130px;
margin-left: 10px;

View File

@@ -1,8 +1,11 @@
import auth from './interfaces/auth'
import user from './interfaces/user'
import block from './interfaces/block'
import esdb from './interfaces/esdb'
import user from './interfaces/user'
export {
auth,
user,
block
block,
esdb,
user
}

View File

@@ -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 as string, null)

View File

@@ -0,0 +1,48 @@
import axios, { AxiosRequestConfig } from 'axios'
const request = axios.create({
baseURL: process.env.VUE_APP_API_URL as string + process.env.VUE_APP_ESDB_URL as string,
timeout: 5000
})
const axiosConf = (config: AxiosRequestConfig) => {
config.headers!.Accept = 'application/json'
return config
}
request.interceptors.response.use(async (response) => {
if (response.data?.error !== null) {
return Promise.reject(response.data.error)
}
return Promise.resolve(response.data?.result)
}, () => {
return Promise.reject('网络请求超时')
})
request.interceptors.request.use(axiosConf, err => {
return Promise.reject(err)
})
const txList = (params?: any): Promise<Array<any>> => {
const body = {
id: 1,
method: 'Tx.TxList',
params: [params]
}
return request.post('', body)
}
const txCount = (params?: any): Promise<Array<any>> => {
const body = {
id: 1,
method: 'Tx.TxCount',
params: [params]
}
return request.post('', body)
}
export default {
txList,
txCount
}

BIN
src/assets/home/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

View File

@@ -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>

View File

@@ -1,8 +1,8 @@
<template>
<div class="footer ">
<div class="wrap">
@2021 域展科技 All Rights Reserved.Powered by 哈尔滨域展科技有限公司
ICP备19007143-2
@2021 XXX All Rights Reserved.Powered by XXX
ICP备XXXX-2
</div>
</div>
</template>

View File

@@ -2,7 +2,7 @@
<div class="header">
<div class="wrap">
<div class="logo" @click="router.push({name: 'Home'})">
LOGO
ZH-CHAIN
</div>
<Nav/>
</div>
@@ -34,6 +34,7 @@ const router = useRouter()
}
.logo {
font-size: 32px;
align-items: center;
display: flex;
margin-right: 32px;

View File

@@ -9,12 +9,13 @@
</template>
<script lang="ts" setup>
import { NavItem } from '@/types/nav'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// region 导航
const navList = ref([
const navList = ref<NavItem[]>([
{
title: '首页',
route: 'Home'
@@ -27,6 +28,14 @@ const navList = ref([
title: '查看数据',
route: 'Trade'
},
{
title: 'Token',
route: 'Token'
},
// {
// title: '节点',
// route: 'Nodes'
// },
{
title: '解析数据',
route: 'Analytical'

View File

@@ -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>

View File

@@ -0,0 +1,63 @@
<template>
{{ timeFormat }}
</template>
<script setup lang="ts">
import { timestampToTime } from '@/utils/filters'
import { defineProps, onBeforeUnmount, onMounted, ref } from 'vue'
const props = defineProps({
time: {
type: Number,
default: 0
}
})
const timeFormat = ref('Calculating...')
// eslint-disable-next-line no-undef
const timer = ref<NodeJS.Timeout | null>()
onMounted(() => {
calculateTime()
timer.value = setInterval(() => {
calculateTime()
}, 1000)
})
const calculateTime = () => {
if (props.time == 0) {
return ''
}
let Dt = new Date()
let local = Math.ceil(Dt.getTime() / 1000)
let dValue = local - props.time
if (dValue > 86400) {
timeFormat.value = timestampToTime(props.time)
clearInterval(Number(timer.value))
return
}
let h = Math.floor(dValue / 3600)
dValue -= h * 3600
let m = Math.floor(dValue / 60)
dValue -= m * 60
let s = dValue
if (local - props.time >= 3600) {
timeFormat.value = `${h}小时${m}${s}秒前`
} else if (local - props.time >= 60) {
timeFormat.value = `${m}${s}秒前`
} else {
timeFormat.value = `${s}秒前`
}
}
onBeforeUnmount(() => {
clearInterval(Number(timer.value))
})
</script>
<style scoped>
</style>

View File

@@ -4,6 +4,7 @@ import Footer from './Footer.vue'
import Header from './Header.vue'
import Nav from './Nav.vue'
import Pagination from './Pagination.vue'
import TimeFormat from './TimeFormat.vue'
export {
Banner,
@@ -11,5 +12,6 @@ export {
Footer,
Header,
Nav,
Pagination
Pagination,
TimeFormat
}

View File

@@ -0,0 +1,64 @@
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, ComputedRef, onActivated, ref } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
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>()
onActivated(() => {
getLastHeader()
console.log('开始轮询头信息')
interval.value = setInterval(() => {
getLastHeader()
}, 5000)
})
/**
* 获取最新的区块
*/
const getLastHeader = (): void => {
block.getLastHeader().then(res => {
console.log('获取最新区块', res.result.height)
if (res.error) {
clearInterval(Number(interval.value))
return ElMessage.error({
message: res.error,
offset: 300
})
} else if (maxHeight.value !== res.result.height) {
vuex.dispatch('setMaxHeight', res.result.height).then()
vuex.dispatch('setLastHash', res.result.hash).then()
}
})
}
/**
* 查询从交易量和交易费
*/
const queryTotalFee = (): Promise<TotalFee> => {
return block.queryTotalFee(hexCharCodeToStr(lastHash.value))
}
onBeforeRouteLeave(() => {
console.log('结束轮询')
clearInterval(Number(interval.value))
})
return {
maxHeight,
lastHash,
queryTotalFee
}
}

View File

@@ -5,12 +5,14 @@ import App from './App.vue'
import router from './router'
import store, { key } from './store'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import directives from '@/utils/directives'
const app = createApp(App)
app.use(ElementPlus, {
locale: zhCn,
})
app.use(directives)
app.use(store, key)
app.use(router)
app.mount('#app')

View File

@@ -10,7 +10,7 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Block/index.vue')
component: () => import(/* webpackChunkName: "block" */ '@/views/Block/index.vue')
},
{
path: '/blocks/:hash',
@@ -21,7 +21,7 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Block/detail.vue')
component: () => import(/* webpackChunkName: "block" */ '@/views/Block/detail.vue')
},
{
path: '/trades',
@@ -32,7 +32,7 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Trade/index.vue')
component: () => import(/* webpackChunkName: "trade" */ '@/views/Trade/index.vue')
},
{
path: '/trades/:hash',
@@ -43,7 +43,7 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Trade/detail.vue')
component: () => import(/* webpackChunkName: "trade" */ '@/views/Trade/detail.vue')
},
{
path: '/analytical',
@@ -54,7 +54,7 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Other/analytical.vue')
component: () => import(/* webpackChunkName: "other" */ '@/views/Other/analytical.vue')
},
{
path: '/broadcast',
@@ -65,7 +65,7 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Other/broadcast.vue')
component: () => import(/* webpackChunkName: "other" */ '@/views/Other/broadcast.vue')
},
{
path: '/address/:address',
@@ -76,7 +76,29 @@ export default [
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Address/index.vue')
component: () => import(/* webpackChunkName: "other" */ '@/views/Address/index.vue')
},
{
path: '/nodes',
name: 'Nodes',
meta: {
title: '节点列表',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "other" */ '@/views/Nodes/index.vue')
},
{
path: '/tokens',
name: 'Token',
meta: {
title: 'Token',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "other" */ '@/views/Token/index.vue')
}
] as MyRouteRecordRaw[]

View File

@@ -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: {

113
src/types/block.d.ts vendored
View File

@@ -3,3 +3,116 @@ export declare type AssetType = {
exec: string
symbol: string
}
export declare type AddrOverview = {
balance: number
reciver: number
txCount: number
}
export declare type TokenAssetItem = {
symbol: string
account: {
addr: string
balance: string
currency: number
frozen: string
}
}
export declare type NodeItem = {
addr: string
name: string
header: {
height: number
blockTime: number
version: number
difficulty: number
hash: string
parentHash: string
stateHash: string
txCount: number
txHash: string
}
mempoolSize: number
port: number
self: boolean
}
export declare type BlockOverview = {
height: number
hash: string
txCount: number
txHash: string
parentHash: string
blockTime: number
stateHash: string
}
export declare type BlockItem = {
hash: string
height: number
txCount: number
blockTime: number
difficulty: number
parentHash: string
stateHash: string
txHash: string
version: number
}
export declare type BlockDetail = {
actionName: string
amount: number
assets: []
blockTime: number
fromAddr: string
fullHash: string
height: number
index: number
proofs: any
tx: {
hash: string
from: string
to: string
height: number
execer: string
expire: number
fee: number
feefmt: string
nonce: number
payload: any
rawPayload: string
signature: {
pubkey: string
signature: string
ty: number
}
}
receipt: {
logs: any
ty: number
tyName: string
}
}
export declare type TradeItem = {
hash: string
block_time: number
from: string
execer: string
action_name: string
amount: number
fee: number
assets: any
to: string
}
export declare type TotalFee = {
error: string | null
id: number
result: {
fee: number
txCount: number
}
}

4
src/types/nav.ts Normal file
View File

@@ -0,0 +1,4 @@
export declare type NavItem = {
title: string
route: string
}

22
src/utils/directives.ts Normal file
View File

@@ -0,0 +1,22 @@
import { App, DirectiveBinding } from '@vue/runtime-core'
import ClipboardJS from 'clipboard'
import { ElMessage } from 'element-plus'
export default (Vue: App) => {
Vue.directive('copy', {
updated (el: Element, binding: DirectiveBinding) {
const clipboard = new ClipboardJS(el, {
text: () => {
return binding.value
}
})
clipboard.on('error', (): void => {
ElMessage.warning({
message: '当前浏览器不支持复制'
})
clipboard.destroy()
})
}
})
}

View File

@@ -1,16 +1,83 @@
import { AssetType } from '@/types/block'
import store from '@/store'
import { AssetType } from '@/types/block'
export const filterHash = (str: string, num?: number): string => {
const length = num || 16
if (!str) {
return ''
}
return str.substr(0, length) + '...' + str.substr(-4)
}
// 解析资产符号
export const parseSymbol = (assets?: AssetType[]): string => {
if (assets) {
return assets[0].symbol
return assets[0]?.symbol
} else {
return store.getters.symbol
}
}
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) + '-'
const D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' '
const h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':'
const m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':'
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
}

View File

@@ -12,8 +12,8 @@ const request = axios.create({
* @param config
*/
const axiosConf = (config: AxiosRequestConfig) => {
config.headers.Authorization = vuex.getters.accessToken
config.headers.Accept = 'application/json'
config.headers!.Authorization = vuex.getters.accessToken
config.headers!.Accept = 'application/json'
return config
}

View File

@@ -3,8 +3,12 @@
<Breadcrumb :path="[{name: '地址信息'}]"/>
<div class="top-info">
<span>地址</span>
<span>地址:</span>
<span class="addr">{{ address }}</span>
<el-tag size="medium" v-if="isContract">合约地址</el-tag>
<el-tag size="medium" type="info" v-else>普通地址</el-tag>
<el-button type="primary" v-copy="address" size="mini">复制</el-button>
</div>
<div class="assets">
@@ -16,24 +20,24 @@
<div class="item">
<img src="../../assets/dots/red.png">
<label>余额</label>
<span>{{ balance.balance.toFixed(2) }} {{ store.getters.symbol }}</span>
<span>{{ balance.balance.toFixed(2) }} {{ symbol }}</span>
</div>
<div class="item">
<img src="../../assets/dots/blue.png">
<label>总接收</label>
<span>{{ balance.reciver.toFixed(2) }} {{ store.getters.symbol }}</span>
<span>{{ balance.reciver.toFixed(2) }} {{ symbol }}</span>
</div>
<div class="item">
<img src="../../assets/dots/green.png">
<label>总发送</label>
<span>{{ sended.toFixed(2) }} {{ store.getters.symbol }}</span>
<span>{{ sended.toFixed(2) }} {{ symbol }}</span>
</div>
</div>
<div class="right">
<div class="item">
<img src="../../assets/dots/blue.png">
<label>冻结</label>
<span>{{ frozen.toFixed(2) }} {{ store.getters.symbol }}</span>
<span>{{ frozen.toFixed(2) }} {{ symbol }}</span>
</div>
<div class="item">
<img src="../../assets/dots/green.png">
@@ -43,8 +47,16 @@
<div class="item">
<img src="../../assets/dots/pink.png">
<label>Tokens</label>
<el-select v-model="token" filterable placeholder="Select">
<el-option label="item.label" value="item.value" />
<el-select v-model="token" filterable placeholder="所有资产">
<el-option
v-for="(item, index) in assets"
:key="index"
:label="item.symbol"
:value="item.symbol"
>
<span style="float: left">{{ item.symbol }}</span>
<span style="float: right;">{{ (Number(item.account.balance) / 1e8).toFixed(2) }}</span>
</el-option>
</el-select>
</div>
</div>
@@ -52,143 +64,206 @@
</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"
: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 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 width="200" label="交易哈希">
<template #default="scope">
<router-link :to="{name: 'TradeDetail', params: { hash: scope.row.hash }}">
{{ filterHash(scope.row.hash, 16) }}
</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="160" label="发送方">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.from }}">
{{ filterHash(scope.row.from, 10) }}
</router-link>
</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 width="180" prop="blockTime" label="上链时间" align="center"/>
</el-table>
<el-table-column width="40" align="center">
<template #default="scope">
<el-icon v-if="scope.row.from == address">
<DArrowRight/>
</el-icon>
<el-icon v-else>
<DArrowLeft/>
</el-icon>
</template>
</el-table-column>
<el-table-column width="160" label="接收方">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.to }}">
{{ filterHash(scope.row.to, 10) }}
</router-link>
</template>
</el-table-column>
<el-table-column label="交易量" align="center">
<template #default="scope">
{{ (scope.row.amount / 1e8).toFixed(4) }}
</template>
</el-table-column>
<el-table-column label="手续费" align="center">
<template #default="scope">
{{ (scope.row.fee / 1e8).toFixed(4) }}
</template>
</el-table-column>
<el-table-column label="调用函数" align="center">
<template #default="scope">
{{ scope.row.action_name }}
</template>
</el-table-column>
<el-table-column width="165" label="上链时间" align="center">
<template #default="scope">
<TimeFormat :time="scope.row.block_time"/>
</template>
</el-table-column>
<el-table-column label="交易资产" align="center">
<template #default="scope">
{{ parseSymbol(scope.row.assets) }}
</template>
</el-table-column>
</el-table>
</template>
</Pagination>
</div>
</template>
<script lang="ts" setup>
import { block } from '@/api'
import { Breadcrumb, Pagination } from '@/components'
import { block, esdb } from '@/api'
import { Breadcrumb, Pagination, TimeFormat } from '@/components'
import { useStore } from '@/store'
import { AddrOverview, TokenAssetItem, TradeItem } from '@/types/block'
import { filterHash, parseSymbol } from '@/utils/filters'
import { DArrowLeft, DArrowRight, Warning } from '@element-plus/icons'
import { computed, ref } from 'vue'
import { DArrowLeft, DArrowRight } from '@element-plus/icons'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const store = useStore()
const route = useRoute()
const address = route.params.address as string
const pageSize = 10
const address = computed<string>(() => route.params.address as string)
const pageSize = Number(process.env.VUE_APP_BLOCK_DETAIL_LIST_SIZE)
const symbol = computed<string>(() => store.getters.symbol)
type AddrOverview = {
balance: number
reciver: number
txCount: number
}
const balance = ref<AddrOverview>({
balance: 0,
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<TradeItem[]>([])
const token = ref<string>('')
const assets = ref<TokenAssetItem[]>([])
const loading = ref<boolean>(false)
const isContract = ref<boolean>(false)
watch(route, (to) => {
if (to.name == 'Address') {
records.value = []
initAddressData()
}
})
/**
* 获取全部交易
*/
block.getTxByAddr({
addr: address,
flag: 0,
count: pageSize,
direction: 0,
height: -1,
index: 1
}).then(res => {
let hashes = res.result.txInfos.map((item: { hash: string }) => item.hash)
block.getTxByHashes(hashes).then(res => {
records.value = res.result.txs
})
onMounted(() => {
initAddressData()
})
const records = ref([])
const initAddressData = () => {
block.callPromiseAPI('Query', {
execer: 'evm',
funcName: 'CheckAddrExists',
payload: {
addr: address.value
}
}).then(res => {
isContract.value = res.result.contract
})
/**
* 获取地址的基本信息
*/
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 page = ref<number>(1)
const handleCurrentChange = (e: number) => {
console.log(e)
page.value = e
loadTradeList()
}
const token = ref('')
const loadTradeList = () => {
loading.value = true
esdb.txList({
match_one: [{
key: 'from',
value: address.value
},
{
key: 'to',
value: address.value
}
],
match: [],
sort: [{
key: 'height',
ascending: false
}],
page: {
number: page.value,
size: 10
}
}).then(res => {
records.value = res
}).finally(() => {
loading.value = false
})
}
</script>
<style scoped lang="less">
@@ -201,6 +276,13 @@ const token = ref('')
color: #516379;
font-weight: 500;
}
.el-tag,
.el-button {
vertical-align: middle;
margin-left: 16px;
}
}
.warning {

View File

@@ -1,6 +1,7 @@
<template>
登录
<button @click="onLogin">一键登录</button>
<div class="container">
登录
</div>
</template>
<script lang="ts" setup>
@@ -12,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)

View File

@@ -1,7 +1,5 @@
<template>
注册
<button @click="onRegister">注册一个号</button>
</template>
<script lang="ts" setup>

View File

@@ -5,7 +5,7 @@
<div class="height">
<h2>区块高度 {{ info.height }}</h2>
<div>区块哈希 {{ info.hash }}
<el-button type="primary" round size="mini"> </el-button>
<el-button type="primary" v-copy="info.hash" round size="mini"> </el-button>
</div>
</div>
@@ -28,14 +28,21 @@
<div class="item">
<img src="../../assets/dots/green.png">
<label>上个区块</label>
<span>{{ (info.height - 1) > 0 ? info.height - 1 : '无' }}</span>
<span v-if="info.height - 1 > 0">
<router-link :to="{name: 'BlockDetail', params: {hash: info.parentHash}}">
{{ info.height - 1 }}
</router-link>
</span>
<span v-else>
</span>
</div>
</div>
<div class="right">
<div class="item">
<img src="../../assets/dots/red.png">
<label>时间</label>
<span>{{ info.blockTime }}</span>
<span><TimeFormat :time="info.blockTime"/></span>
</div>
<div class="item">
<img src="../../assets/dots/green.png">
@@ -45,15 +52,22 @@
<div class="item">
<img src="../../assets/dots/red.png">
<label>下个区块</label>
<span>{{ (info.height + 1) > maxHeight ? '无' : (info.height + 1) }}</span>
<span v-if="next.height">
<router-link :to="{name: 'BlockDetail', params: {hash: next.hash}}">
{{ next.height }}
</router-link>
</span>
<span v-else>
</span>
</div>
</div>
</div>
</div>
<Pagination
:length="maxHeight"
:title="`全部区块(` + maxHeight + `)`"
:length="info.txCount"
:title="`交易记录(` + info.txCount + `)`"
:page-size="pageSize"
@current-change="handleCurrentChange"
>
@@ -62,31 +76,44 @@
<el-empty description="暂无数据"></el-empty>
</template>
<el-table-column label="交易哈希">
<el-table-column width="200" label="交易哈希">
<template #default="scope">
<router-link :to="{name: 'TradeDetail', params: { hash: scope.row.txHash }}">
{{ filterHash(scope.row.txHash) }}
{{ filterHash(scope.row.txHash, 16) }}
</router-link>
</template>
</el-table-column>
<el-table-column label="发送方">
<el-table-column width="160" label="发送方">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.fromAddr }}">
{{ filterHash(scope.row.fromAddr) }}
{{ filterHash(scope.row.fromAddr, 10) }}
</router-link>
</template>
</el-table-column>
<el-table-column label="接收方">
<el-table-column width="160" label="接收方">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.tx.to }}">
{{ filterHash(scope.row.tx.to) }}
{{ filterHash(scope.row.tx.to, 10) }}
</router-link>
</template>
</el-table-column>
<el-table-column width="100" prop="amount" label="交易量" align="center"/>
<el-table-column width="100" prop="tx.feefmt" label="手续费" align="center"/>
<el-table-column width="180" prop="blockTime" label="上链时间" align="center"/>
<el-table-column width="100" label="交易资产" align="center">
<el-table-column label="交易量" align="center">
<template #default="scope">
{{ (scope.row.amount / 1e8).toFixed(4) }}
</template>
</el-table-column>
<el-table-column prop="tx.feefmt" label="手续费" align="center"/>
<el-table-column label="上链时间" width="165" align="center">
<template #default="scope">
<TimeFormat :time="scope.row.blockTime"/>
</template>
</el-table-column>
<el-table-column label="调用函数" align="center">
<template #default="scope">
{{ scope.row.actionName }}
</template>
</el-table-column>
<el-table-column label="交易资产" align="center">
<template #default="scope">
{{ parseSymbol(scope.row.assets) }}
</template>
@@ -98,37 +125,63 @@
<script lang="ts" setup>
import { block } from '@/api'
import { Breadcrumb, Pagination } from '@/components'
import { useStore } from '@/store'
import { Breadcrumb, Pagination, TimeFormat } from '@/components'
import { BlockOverview } from '@/types/block'
import { filterHash, parseSymbol } from '@/utils/filters'
import { computed, ref } from 'vue'
import { onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const store = useStore()
const pageSize = 20
const pageSize = Number(process.env.VUE_APP_BLOCK_DETAIL_LIST_SIZE)
const thisHash = ref<string>('')
const maxHeight = computed<number>(() => store.getters.maxHeight)
onMounted(() => {
thisHash.value = route.params.hash as string
loadBlockData()
})
const hash: string = route.params.hash as string
watch(route, (to) => {
if (to.name === 'BlockDetail') {
thisHash.value = info.value.parentHash
loadBlockData()
}
})
const currentPage = ref<number>(1)
const handleCurrentChange = (e: number) => {
console.log(e)
}
const info = ref({})
const info = ref<BlockOverview>({
height: 0,
hash: '',
txCount: 0,
txHash: '',
parentHash: '',
blockTime: 0,
stateHash: ''
})
const next = ref({
height: 0,
hash: ''
})
const records = ref([])
block.getBlockOverview(hash).then(res => {
console.log(res)
const loadBlockData = () => {
block.getBlockOverview(thisHash.value).then(res => {
info.value = res.result.head
info.value = res.result.head
block.getTxByHashes(res.result.txHashes).then(txs => {
records.value = txs.result.txs
})
block.getTxByHashes(res.result.txHashes).then(txs => {
records.value = txs.result.txs
block.getBlockHash(res.result.head.height + 1).then(nxt => {
if (nxt.error == null) {
next.value.height = res.result.head.height + 1
next.value.hash = nxt.result.hash
}
})
})
})
}
</script>
<style scoped lang="less">

View File

@@ -8,7 +8,11 @@
:page-size="pageSize"
@current-change="handleCurrentChange"
>
<el-table :data="blockList" stripe>
<el-table
:data="blockList"
stripe
v-loading="loading"
>
<template #empty>
<el-empty description="暂无数据"></el-empty>
</template>
@@ -29,52 +33,68 @@
</template>
</el-table-column>
<el-table-column prop="txCount" label="数据量" width="100" align="center"/>
<el-table-column prop="blockTime" label="上链时间" width="180" align="center"/>
<el-table-column prop="" label="上链时间" width="165" align="center">
<template #default="scope">
<TimeFormat :time="scope.row.blockTime"/>
</template>
</el-table-column>
</el-table>
</Pagination>
</div>
</template>
<script lang="ts">
export default {
name: 'Block'
}
</script>
<script lang="ts" setup>
import { block } from '@/api'
import { Breadcrumb,Pagination } from '@/components'
import { useStore } from '@/store'
import { computed, onBeforeUnmount, ref } from 'vue'
import { Breadcrumb, Pagination, TimeFormat } from '@/components'
import useGetMaxHeight from '@/hooks/useGetMaxHeight'
import { computed, onMounted, ref, watch } from 'vue'
const store = useStore()
const maxHeight = computed(() => store.getters.maxHeight)
const { maxHeight } = useGetMaxHeight()
const pageSize = Number(process.env.VUE_APP_BLOCK_LIST_SIZE)
const currentPage = ref<number>(1)
const loading = ref<boolean>(false)
const start = computed(() => {
return (maxHeight.value - pageSize) + ((1 - currentPage.value) * pageSize)
})
const end = computed(() => {
return start.value + pageSize
})
const start = maxHeight.value - 19
const blockList = ref([])
// eslint-disable-next-line no-undef
const interval = ref<NodeJS.Timeout | null>()
onMounted(() => {
console.log('BLOCK INDEX')
getBlockList()
})
interval.value = setInterval(() => {
block.getLastHeader().then(res => {
if (maxHeight.value !== res.result.height) {
store.dispatch('setMaxHeight', res.result.height)
} else {
console.log('列表', maxHeight.value, res.result.height)
}
watch(maxHeight, (newValue, oldValue) => {
if (newValue != oldValue) {
getBlockList()
}
})
const getBlockList = () => {
loading.value = true
block.getHeaders(start.value > 0 ? start.value + 1 : 1, end.value, false).then(res => {
blockList.value = []
blockList.value = res.result.items.reverse()
}).finally(() => {
loading.value = false
})
}, 5000)
onBeforeUnmount(() => {
clearInterval(Number(interval.value))
})
block.getHeaders(start, maxHeight.value, false).then(res => {
blockList.value = res.result.items.reverse()
})
const currentPage = ref<number>(1)
const pageSize = ref<number>(20)
const handleCurrentChange = () => {
console.log(currentPage.value)
}
const handleCurrentChange = (e: number) => {
currentPage.value = e
getBlockList()
}
</script>
<style scoped lang="less">

View File

@@ -7,7 +7,7 @@
<router-link :to="{name: 'Block'}">查看全部</router-link>
</div>
<div class="items">
<div class="items" v-loading="blockLoading">
<div class="item" v-for="(item, index) in blockList" :key="index">
<div class="height" @click="router.push({name: 'BlockDetail', params: {hash: item.hash}})">
{{ item.height }}
@@ -20,106 +20,145 @@
</div>
<div class="data">
<span class="num">{{ item.txCount }} 笔交易</span>
<span class="time">出块时间{{ item.blockTime }}</span>
<span class="time">出块时间<TimeFormat :time="item.blockTime"/></span>
</div>
</div>
</div>
</div>
</div>
<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.hash}}">
{{ filterHash(item.hash, 10) }}
</router-link>
</div>
<div class="time">
<TimeFormat :time="item.block_time"/>
</div>
</div>
<div class="addr">
<div>
发送方
<router-link :to="{name: 'Address', params: {address: item.from}}">
{{ filterHash(item.from, 10) }}
</router-link>
</div>
<div>
接收方
<router-link :to="{name: 'Address', params: {address: item.to}}">
{{ filterHash(item.to, 10) }}
</router-link>
</div>
</div>
<div class="asset">
<div>交易资产</div>
<div>{{ (item.amount / 1e8).toFixed(4) }} {{ parseSymbol(item.assets) }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'Home'
}
</script>
<script lang="ts" setup>
import { block } from '@/api'
import { Banner } from '@/components'
import { useStore } from '@/store'
import { filterHash } from '@/utils/filters'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { block, esdb } from '@/api'
import { Banner, TimeFormat } from '@/components'
import useGetMaxHeight from '@/hooks/useGetMaxHeight'
import { BlockItem, TradeItem } from '@/types/block'
import { filterHash, parseSymbol } from '@/utils/filters'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const store = useStore()
const router = useRouter()
const maxHeight = computed(() => store.getters.maxHeight)
const { maxHeight } = useGetMaxHeight()
const pageSize = Number(process.env.VUE_APP_HOME_LIST_SIZE)
const blockLoading = ref<boolean>(true)
const tradeLoading = ref<boolean>(true)
onMounted(() => {
getLastHeader()
getBlockList()
})
// eslint-disable-next-line no-undef
const interval = ref<NodeJS.Timeout | null>()
interval.value = setInterval(() => {
getLastHeader()
}, 5000)
const getLastHeader = () => {
block.getLastHeader().then(res => {
if (maxHeight.value !== res.result.height) {
store.dispatch('setMaxHeight', res.result.height)
getBlockList()
} else {
console.log('首页', maxHeight.value, res.result.height)
}
})
}
onBeforeUnmount(() => {
clearInterval(Number(interval.value))
watch(maxHeight, (newValue, oldValue) => {
if (newValue != oldValue) {
getBlockList()
}
})
type HomeBlock = {
hash: string,
height: number,
txCount: number,
blockTime: number
const blockList = ref<BlockItem[]>([])
const tradeList = ref<TradeItem[]>([])
if (blockList.value.length === 0) {
const initBlock = {
height: 0,
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 = {
hash: ' ',
block_time: 0,
from: ' ',
amount: 0,
assets: null,
to: ' '
} as TradeItem
for (let i = 0; i < 6; i++) {
tradeList.value.push(initTrade)
}
}
const blockList = ref<HomeBlock[]>([])
const tradeList = ref([])
const getBlockList = () => {
const start = maxHeight.value - 5
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
})
}
const getTradeList = () => {
const params = {
'sort': [{
'key': 'height',
'ascending': false
}],
'page': {
'number': 1,
'size': pageSize
}
}
esdb.txList(params).then(res => {
tradeList.value = res
}).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>
@@ -160,6 +199,10 @@ const getBlockList = () => {
height: 90px;
padding: 16px;
display: flex;
.time {
color: #9ea2a9;
}
}
}
}
@@ -197,18 +240,30 @@ 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;
}
.asset {
text-align: right;
}
}
}
}

43
src/views/Nodes/index.vue Normal file
View File

@@ -0,0 +1,43 @@
<template>
<div class="container">
<Breadcrumb :path="[{name: '节点列表'}]"/>
<el-table :data="peers" stripe border>
<template #empty>
<el-empty description="暂无数据"></el-empty>
</template>
<el-table-column prop="addr" width="200" label="节点地址"/>
<el-table-column prop="name" label="节点名称"/>
<el-table-column width="100" prop="header.height" label="当前高度" align="center"/>
<el-table-column prop="" label="最新出块时间" width="165" align="center">
<template #default="scope">
<TimeFormat :time="scope.row.header.blockTime"/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script lang="ts">
export default {
name: 'Nodes'
}
</script>
<script lang="ts" setup>
import { block } from '@/api'
import { Breadcrumb, TimeFormat } from '@/components'
import { NodeItem } from '@/types/block'
import { ref } from 'vue'
const peers = ref<NodeItem[]>([])
block.getPeerInfo().then(res => {
peers.value = res.result.peers
})
</script>
<style scoped>
</style>

View File

@@ -8,9 +8,7 @@
您可以在此页面输入签过名的源数据十六进制格式文本并将其广播到区块链网络
</div>
</div>
<div>
<textarea v-model="data"></textarea>
</div>
<textarea v-model="data"></textarea>
<el-button type="primary" :disabled="disabled" :loading="loading" @click="decodeTransaction">广播数据</el-button>
</div>
</template>
@@ -67,6 +65,7 @@ textarea {
border-radius: 5px;
border: 2px solid #ebeff1;
font-size: 14px;
margin-bottom: 16px;
&:focus {
outline: none;

54
src/views/Token/index.vue Normal file
View File

@@ -0,0 +1,54 @@
<template>
<div class="container">
<Breadcrumb :path="[{name: 'Token'}]"/>
<el-table :data="tokens" stripe border>
<template #empty>
<el-empty description="暂无数据"></el-empty>
</template>
<el-table-column prop="symbol" width="100" label="TOKEN符号"/>
<el-table-column prop="name" label="TOKEN名称"/>
<el-table-column width="200" label="所有者地址">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.owner }}">
{{ filterHash(scope.row.owner) }}
</router-link>
</template>
</el-table-column>
<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)"/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script lang="ts">
export default {
name: 'Token'
}
</script>
<script lang="ts" setup>
import { block } from '@/api'
import { Breadcrumb, TimeFormat } from '@/components'
import { filterHash } from '@/utils/filters'
import { ref } from 'vue'
const tokens = ref([])
block.queryAllTokens().then(res => {
tokens.value = res.result.tokens
})
</script>
<style scoped>
</style>

View File

@@ -5,50 +5,195 @@
<div class="height">
<h2>数据详情</h2>
<div>数据哈希 {{ detail.tx.hash }}
<el-button type="primary" round size="mini"> </el-button>
<el-button type="primary" v-copy="detail.tx.hash" round size="mini"> </el-button>
</div>
</div>
<div>
<div>发送方{{ detail.tx.from }}</div>
<div>接收方{{ detail.tx.to }}</div>
<div>上链时间{{ detail.blockTime }}</div>
<div>资产{{ detail.amount }}</div>
<div>GAS费{{ detail.tx.feefmt }}</div>
<div>随机数{{ detail.tx.nonce }}</div>
<div>执行器{{ detail.tx.execer }}</div>
<div>函数{{ detail.actionName }}</div>
<div>类型{{ detail.index }}</div>
<div>
输入数据
<pre contenteditable="true" >
<code>{{ detail.receipt.logs }}</code>
</pre>
<div class="data">
<div class="head">
<span>概况</span>
</div>
<div class="item">
<label>发送方</label>
<div>
<router-link v-if="detail.tx.from" :to="{name: 'Address', params: {address: detail.tx.from}}">
{{ detail.tx.from }}
</router-link>
</div>
</div>
<div class="item">
<label>接收方</label>
<div>
<router-link v-if="detail.tx.to" :to="{name: 'Address', params: {address: detail.tx.to}}">
{{ detail.tx.to }}
</router-link>
</div>
</div>
<div class="item">
<label>区块高度</label>
<div>
<router-link v-if="blockHash" :to="{name: 'BlockDetail', params: {hash: blockHash}}">
{{ detail.height }}
</router-link>
</div>
</div>
<div class="item">
<label>上链时间</label>
<div>
<TimeFormat :time="detail.blockTime"/>
</div>
</div>
<div class="item">
<label>资产</label>
<div>{{ (detail.amount / 1e8).toFixed(4) }} {{ parseSymbol(detail.assets) }}</div>
</div>
<div class="item">
<label>GAS费</label>
<div>{{ detail.tx.feefmt }} {{ parseSymbol(detail.assets) }}
</div>
</div>
<div class="item">
<label>随机数</label>
<div>{{ detail.tx.nonce }}</div>
</div>
<div class="item">
<label>执行器</label>
<div>{{ detail.tx.execer }}</div>
</div>
<div class="item">
<label>函数</label>
<div>{{ detail.actionName }}</div>
</div>
<div class="item">
<label>类型</label>
<div>{{ detail.index }}</div>
</div>
<div class="item">
<label>输入数据</label>
<div>
<pre contenteditable="true" class="code">
<code>{{ detail.tx.payload }}</code>
</pre>
</div>
</div>
<div class="item">
<label>输出数据</label>
<div>
<pre contenteditable="true" class="code">
<code>{{ detail.receipt }}</code>
</pre>
</div>
</div>
<hr>
输出数据
<pre contenteditable="true" class="inputTxt">
<code>{{ detail.receipt.logs }}</code>
</pre>
</div>
</div>
</template>
<script lang="ts" setup>
import { block } from '@/api'
import { Breadcrumb } from '@/components'
import { Breadcrumb, TimeFormat } from '@/components'
import { BlockDetail } from '@/types/block'
import { parseSymbol } from '@/utils/filters'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const detail = ref({})
block.queryTransaction(route.params.hash as string).then(res => {
console.log(res)
detail.value = res.result
})
const detail = ref<BlockDetail>({
tx: {
hash: '',
from: '',
to: '',
feefmt: '',
nonce: 0,
execer: '',
payload: {}
}
} as BlockDetail)
const blockHash = ref<string>('')
block.queryTransaction(route.params.hash as string).then(res => {
detail.value = res.result
block.getBlockHash(res.result.height).then(res => {
blockHash.value = res.result.hash
})
})
</script>
<style scoped lang="less">
.height {
h2 {
font-size: 24px;
padding: 8px 0;
}
div {
color: #9ea2a9;
vertical-align: middle;
button {
margin-left: 8px;
}
}
}
.data {
margin: 16px 0 32px;
width: 100%;
color: #516379;
font-size: 14px;
background: #fff;
border-radius: 2px;
border: 1px solid #ebeff1;
.head {
height: 70px;
border-bottom: 1px solid #eee;
span {
display: inline-block;
height: 100%;
width: 40px;
margin-left: 32px;
line-height: 67px;
color: #516379;
font-size: 16px;
font-weight: 500;
text-align: center;
border-bottom: 3px solid #6368de;
}
}
.item {
min-height: 60px;
display: flex;
align-items: center;
&:nth-child(even) {
background: #f9fafc;
}
label {
width: 200px;
padding-left: 32px;
}
div {
width: 998px;
padding: 16px;
.code {
border: 1px solid #ebeff1;
cursor: default;
height: 200px;
padding: 15px;
overflow-y: auto;
vertical-align: middle;
width: 100%;
background: #FFFFFF;
word-break: break-all;
}
}
}
}
</style>

View File

@@ -2,38 +2,122 @@
<div class="container">
<Breadcrumb :path="[{name: '全部数据'}]"/>
<Pagination
:length="maxHeight"
:title="`全部区块(` + maxHeight + `)`"
:length="txCount"
:title="`数据总数(` + txCount + `)`"
:page-size="pageSize"
@current-change="handleCurrentChange"
>
<el-table :data="blocks" stripe>
<el-table v-loading="tradeLoading" :data="tradeList" stripe>
<template #empty>
<el-empty description="暂无数据"></el-empty>
</template>
<el-table-column prop="date" label="高度" width="100" align="center"/>
<el-table-column prop="date" label="区块哈希"/>
<el-table-column prop="date" label="数据量" width="180" align="center"/>
<el-table-column prop="date" label="上链时间" width="180" align="center"/>
<el-table-column width="200" label="交易哈希">
<template #default="scope">
<router-link :to="{name: 'TradeDetail', params: { hash: scope.row.hash }}">
{{ filterHash(scope.row.hash, 16) }}
</router-link>
</template>
</el-table-column>
<el-table-column width="160" label="发送方">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.from }}">
{{ filterHash(scope.row.from, 10) }}
</router-link>
</template>
</el-table-column>
<el-table-column width="160" label="接收方">
<template #default="scope">
<router-link :to="{name: 'Address', params: { address: scope.row.to }}">
{{ filterHash(scope.row.to, 10) }}
</router-link>
</template>
</el-table-column>
<el-table-column label="交易量" align="center">
<template #default="scope">
{{ (scope.row.amount / 1e8).toFixed(4) }}
</template>
</el-table-column>
<el-table-column label="手续费" align="center">
<template #default="scope">
{{ (scope.row.fee / 1e8).toFixed(4) }}
</template>
</el-table-column>
<el-table-column label="上链时间" width="165" align="center">
<template #default="scope">
<TimeFormat :time="scope.row.block_time"/>
</template>
</el-table-column>
<el-table-column label="调用函数" align="center">
<template #default="scope">
{{ scope.row.action_name }}
</template>
</el-table-column>
<el-table-column label="交易资产" align="center">
<template #default="scope">
{{ parseSymbol(scope.row.assets) }}
</template>
</el-table-column>
</el-table>
</Pagination>
</div>
</template>
<script lang="ts" setup>
import { Breadcrumb, Pagination } from '@/components'
import { ref } from 'vue'
const currentPage = ref<number>(1)
const pageSize = ref<number>(20)
const handleCurrentChange = (e: number) => {
console.log(e)
<script lang="ts">
export default {
name: 'Trade'
}
</script>
<script lang="ts" setup>
import { esdb } from '@/api'
import { Breadcrumb, Pagination, TimeFormat } from '@/components'
import useGetMaxHeight from '@/hooks/useGetMaxHeight'
import { TradeItem } from '@/types/block'
import { onMounted, ref } from 'vue'
import { filterHash, parseSymbol } from '@/utils/filters'
const { queryTotalFee } = useGetMaxHeight()
const pageSize = Number(process.env.VUE_APP_BLOCK_LIST_SIZE)
const tradeList = ref<TradeItem[]>([])
const txCount = ref<number>(0)
const tradeLoading = ref<boolean>(false)
const page = ref<number>(1)
queryTotalFee().then(res => {
txCount.value = res.result.txCount
})
onMounted(() => {
getTradeList()
})
const getTradeList = () => {
tradeLoading.value = true
const params = {
page: {
number: page.value,
size: 20
},
sort: [{
key: 'height_index',
ascending: false
}]
}
esdb.txList(params).then(res => {
tradeList.value = res
}).finally(() => {
tradeLoading.value = false
})
}
const handleCurrentChange = (e: number) => {
page.value = e
getTradeList()
}
const blocks = ref([])
</script>
<style scoped lang="less">

View File

@@ -0,0 +1,13 @@
<template>
Wallet
</template>
<script>
export default {
name: 'index'
}
</script>
<style scoped>
</style>

View File

@@ -5,6 +5,7 @@ function resolve (dir) {
}
module.exports = {
productionSourceMap: false,
configureWebpack: {
resolve: {
alias: {
@@ -20,15 +21,21 @@ module.exports = {
javascriptEnabled: true
}
}
},
devServer: {
proxy: {
'/api': {
target: 'https://explorer.lianshang.vip/api',
pathRewrite: {
'^/api': ''
}
},
'/esdb': {
target: 'https://explorer.lianshang.vip/esdb',
pathRewrite: {
'^/esdb': ''
}
}
}
}
// devServer: {
// proxy: {
// '/api': {
// target: '',
// pathRewrite: {
// '^/api': ''
// }
// }
// }
// }
}

235
yarn.lock
View File

@@ -263,6 +263,11 @@
resolved "https://registry.nlark.com/@babel/parser/download/@babel/parser-7.15.6.tgz?cache=0&sync_timestamp=1631216301920&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.15.6.tgz#043b9aa3c303c0722e5377fef9197f4cf1796549"
integrity sha1-BDuao8MDwHIuU3f++Rl/TPF5ZUk=
"@babel/parser@^7.16.4":
version "7.17.8"
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240"
integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.15.4":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz#dbdeabb1e80f622d9f0b583efb2999605e0a567e"
@@ -990,10 +995,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@popperjs/core@^2.10.1":
version "2.10.1"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.1.tgz#728ecd95ab207aab8a9a4e421f0422db329232be"
integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw==
"@popperjs/core@^2.10.2":
version "2.11.4"
resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503"
integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==
"@soda/friendly-errors-webpack-plugin@^1.7.1":
version "1.8.0"
@@ -1527,6 +1532,16 @@
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-core@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89"
integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.11":
version "3.2.11"
resolved "https://registry.nlark.com/@vue/compiler-dom/download/@vue/compiler-dom-3.2.11.tgz?cache=0&sync_timestamp=1631141967992&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40vue%2Fcompiler-dom%2Fdownload%2F%40vue%2Fcompiler-dom-3.2.11.tgz#d066f8e1f1812b4e881593819ade0fe6d654c776"
@@ -1535,6 +1550,30 @@
"@vue/compiler-core" "3.2.11"
"@vue/shared" "3.2.11"
"@vue/compiler-dom@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e"
integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==
dependencies:
"@vue/compiler-core" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/compiler-sfc@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f"
integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.31"
"@vue/compiler-dom" "3.2.31"
"@vue/compiler-ssr" "3.2.31"
"@vue/reactivity-transform" "3.2.31"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-sfc@^3.0.0":
version "3.2.11"
resolved "https://registry.nlark.com/@vue/compiler-sfc/download/@vue/compiler-sfc-3.2.11.tgz?cache=0&sync_timestamp=1631142112784&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40vue%2Fcompiler-sfc%2Fdownload%2F%40vue%2Fcompiler-sfc-3.2.11.tgz#628fa12238760d9b9b339ac2e125a759224fadbf"
@@ -1567,6 +1606,14 @@
"@vue/compiler-dom" "3.2.11"
"@vue/shared" "3.2.11"
"@vue/compiler-ssr@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c"
integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==
dependencies:
"@vue/compiler-dom" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.1.2":
version "3.2.2"
resolved "https://registry.nlark.com/@vue/component-compiler-utils/download/@vue/component-compiler-utils-3.2.2.tgz#2f7ed5feed82ff7f0284acc11d525ee7eff22460"
@@ -1583,7 +1630,12 @@
optionalDependencies:
prettier "^1.18.2"
"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.0.0-beta.14":
"@vue/devtools-api@^6.0.0":
version "6.1.3"
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.1.3.tgz#a44c52e8fa6d22f84db3abdcdd0be5135b7dd7cf"
integrity sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg==
"@vue/devtools-api@^6.0.0-beta.11":
version "6.0.0-beta.15"
resolved "https://registry.nlark.com/@vue/devtools-api/download/@vue/devtools-api-6.0.0-beta.15.tgz#ad7cb384e062f165bcf9c83732125bffbc2ad83d"
integrity sha1-rXyzhOBi8WW8+cg3MhJb/7wq2D0=
@@ -1600,12 +1652,23 @@
resolved "https://registry.nlark.com/@vue/preload-webpack-plugin/download/@vue/preload-webpack-plugin-1.1.2.tgz#ceb924b4ecb3b9c43871c7a429a02f8423e621ab"
integrity sha1-zrkktOyzucQ4ccekKaAvhCPmIas=
"@vue/reactivity@3.2.11":
version "3.2.11"
resolved "https://registry.nlark.com/@vue/reactivity/download/@vue/reactivity-3.2.11.tgz?cache=0&sync_timestamp=1631142360902&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40vue%2Freactivity%2Fdownload%2F%40vue%2Freactivity-3.2.11.tgz#ec04d33acaf2b92cca2960535bec81b26cc5772b"
integrity sha1-7ATTOsryuSzKKWBTW+yBsmzFdys=
"@vue/reactivity-transform@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==
dependencies:
"@vue/shared" "3.2.11"
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.31"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd"
integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==
dependencies:
"@vue/shared" "3.2.31"
"@vue/ref-transform@3.2.11":
version "3.2.11"
@@ -1618,28 +1681,41 @@
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/runtime-core@3.2.11":
version "3.2.11"
resolved "https://registry.nlark.com/@vue/runtime-core/download/@vue/runtime-core-3.2.11.tgz?cache=0&sync_timestamp=1631142361235&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40vue%2Fruntime-core%2Fdownload%2F%40vue%2Fruntime-core-3.2.11.tgz#0dbe801be4bd0bfde253226797e7d304c8fdda30"
integrity sha1-Db6AG+S9C/3iUyJnl+fTBMj92jA=
"@vue/runtime-core@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==
dependencies:
"@vue/reactivity" "3.2.11"
"@vue/shared" "3.2.11"
"@vue/reactivity" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/runtime-dom@3.2.11":
version "3.2.11"
resolved "https://registry.nlark.com/@vue/runtime-dom/download/@vue/runtime-dom-3.2.11.tgz?cache=0&sync_timestamp=1631142356576&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40vue%2Fruntime-dom%2Fdownload%2F%40vue%2Fruntime-dom-3.2.11.tgz#04f9054a9e64bdf156c2fc22cad67cfaa8b84616"
integrity sha1-BPkFSp5kvfFWwvwiytZ8+qi4RhY=
"@vue/runtime-dom@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff"
integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==
dependencies:
"@vue/runtime-core" "3.2.11"
"@vue/shared" "3.2.11"
"@vue/runtime-core" "3.2.31"
"@vue/shared" "3.2.31"
csstype "^2.6.8"
"@vue/server-renderer@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141"
integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==
dependencies:
"@vue/compiler-ssr" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/shared@3.2.11":
version "3.2.11"
resolved "https://registry.nlark.com/@vue/shared/download/@vue/shared-3.2.11.tgz?cache=0&sync_timestamp=1631141987851&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40vue%2Fshared%2Fdownload%2F%40vue%2Fshared-3.2.11.tgz#01899f54949caf1ac241de397bd17069632574de"
integrity sha1-AYmfVJScrxrCQd45e9FwaWMldN4=
"@vue/shared@3.2.31":
version "3.2.31"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e"
integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==
"@vue/web-component-wrapper@^1.2.0":
version "1.3.0"
resolved "https://registry.nlark.com/@vue/web-component-wrapper/download/@vue/web-component-wrapper-1.3.0.tgz#b6b40a7625429d2bd7c2281ddba601ed05dc7f1a"
@@ -1647,7 +1723,7 @@
"@vueuse/core@~6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-6.1.0.tgz#8137c291cf49b11c2deda4d5079096e55b36fc28"
resolved "https://registry.npmmirror.com/@vueuse/core/-/core-6.1.0.tgz#8137c291cf49b11c2deda4d5079096e55b36fc28"
integrity sha512-6KienU5QOWKuDqvHytep14274IGKyLlACzXjifOrgDQMkqvWZIUnDhpckT/1+O8n8DN59d5wzzICZI/2sfGCyg==
dependencies:
"@vueuse/shared" "6.1.0"
@@ -1655,7 +1731,7 @@
"@vueuse/shared@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-6.1.0.tgz#1375fd41acefe52f9a1842f3c6a8a348786535ba"
resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-6.1.0.tgz#1375fd41acefe52f9a1842f3c6a8a348786535ba"
integrity sha512-teW0TUQryGnEprHeOI6oH8NPVJBirknxksEiNCtdEjIi8W7JSTg8JPO+e1XlGI6ly24NDlDXUDYaHJayiaXjuw==
dependencies:
vue-demi "*"
@@ -2064,10 +2140,10 @@ async-limiter@~1.0.0:
resolved "https://registry.nlark.com/async-limiter/download/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha1-3TeelPDbgxCwgpH51kwyCXZmF/0=
async-validator@^3.4.0:
version "3.5.2"
resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.5.2.tgz#68e866a96824e8b2694ff7a831c1a25c44d5e500"
integrity sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==
async-validator@^4.0.3:
version "4.0.7"
resolved "https://registry.npmmirror.com/async-validator/-/async-validator-4.0.7.tgz#034a0fd2103a6b2ebf010da75183bec299247afe"
integrity sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==
async@^2.6.2:
version "2.6.3"
@@ -2116,7 +2192,7 @@ aws4@^1.8.0:
axios@^0.21.4:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
resolved "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
@@ -2737,6 +2813,15 @@ cli-width@^3.0.0:
resolved "https://registry.nlark.com/cli-width/download/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha1-ovSEN6LKqaIkNueUvwceyeYc7fY=
clipboard@^2.0.8:
version "2.0.10"
resolved "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.10.tgz#e61f6f7139ac5044c58c0484dcac9fb2a918bfd6"
integrity sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
clipboardy@^2.3.0:
version "2.3.0"
resolved "https://registry.nlark.com/clipboardy/download/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"
@@ -3315,9 +3400,9 @@ dashdash@^1.12.0:
assert-plus "^1.0.0"
dayjs@^1.10.7:
version "1.10.7"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
version "1.11.0"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805"
integrity sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
@@ -3446,6 +3531,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.nlark.com/delayed-stream/download/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.nlark.com/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -3648,17 +3738,18 @@ electron-to-chromium@^1.3.830:
integrity sha1-J6WyFGjp/vsOMooClANhfyCs7Jw=
element-plus@^1.1.0-beta.16:
version "1.1.0-beta.16"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.1.0-beta.16.tgz#4409d9e33d005693f6039f5ed1fe05e301b3170d"
integrity sha512-4BZEldnIfFZs5A/saRqaWE4PwTot4p3YZU7qsDr3ev2zp35pcCL9TtpWMLIvNTMxvxKew0HTDPTk9fAWIZFQrQ==
version "1.1.0-beta.24"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-1.1.0-beta.24.tgz#858b05932ebc0be15419d3974d15be2a4f4b696c"
integrity sha512-dmo61e/D6mwJVacMhxOMSPb5sZPt/FPsuQQfsOs1kJWkhGDmTlny/sZvgIQr1z0zh3pjlJadGAlNS+0nySPMmw==
dependencies:
"@popperjs/core" "^2.10.1"
"@element-plus/icons" "^0.0.11"
"@popperjs/core" "^2.10.2"
"@vueuse/core" "~6.1.0"
async-validator "^3.4.0"
async-validator "^4.0.3"
dayjs "^1.10.7"
lodash "^4.17.21"
memoize-one "^5.2.1"
normalize-wheel "^1.0.1"
normalize-wheel-es "^1.1.0"
resize-observer-polyfill "^1.5.1"
elliptic@^6.5.3:
@@ -3781,8 +3872,8 @@ escalade@^3.1.1:
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.nlark.com/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
@@ -4318,11 +4409,16 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.0.0, follow-redirects@^1.14.0:
follow-redirects@^1.0.0:
version "1.14.4"
resolved "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.4.tgz?cache=0&sync_timestamp=1631622206750&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
integrity sha1-g4/fSKi73XnlLuUfsclOPtmLk3k=
follow-redirects@^1.14.0:
version "1.14.9"
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.nlark.com/for-in/download/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -4618,6 +4714,13 @@ globby@^9.2.0:
pify "^4.0.1"
slash "^2.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
dependencies:
delegate "^3.1.2"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.8"
resolved "https://registry.nlark.com/graceful-fs/download/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
@@ -5904,7 +6007,7 @@ memfs@^3.1.2:
memoize-one@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
memory-fs@^0.4.1:
@@ -6305,10 +6408,10 @@ normalize-url@^3.0.0:
resolved "https://registry.nlark.com/normalize-url/download/normalize-url-3.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnormalize-url%2Fdownload%2Fnormalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha1-suHE3E98bVd0PfczpPWXjRhlBVk=
normalize-wheel@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45"
integrity sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=
normalize-wheel-es@^1.1.0:
version "1.1.2"
resolved "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.1.2.tgz#285e43676a62d687bf145e33452ea6be435162d0"
integrity sha512-scX83plWJXYH1J4+BhAuIHadROzxX0UBF3+HuZNY2Ks8BciE7tSTQ+5JhTsvzjaO0/EJdm4JBGrfObKxFf3Png==
npm-run-path@^2.0.0:
version "2.0.2"
@@ -7587,7 +7690,7 @@ requires-port@^1.0.0:
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-cwd@^2.0.0:
@@ -7768,6 +7871,11 @@ select-hose@^2.0.0:
resolved "https://registry.nlark.com/select-hose/download/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
selfsigned@^1.10.8:
version "1.10.11"
resolved "https://registry.nlark.com/selfsigned/download/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9"
@@ -8490,6 +8598,11 @@ timsort@^0.3.0:
resolved "https://registry.nlark.com/timsort/download/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.nlark.com/tmp/download/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -8961,11 +9074,11 @@ vue-loader@^15.9.2:
vue-style-loader "^4.1.0"
vue-router@^4.0.11:
version "4.0.11"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.11.tgz#cd649a0941c635281763a20965b599643ddc68ed"
integrity sha512-sha6I8fx9HWtvTrFZfxZkiQQBpqSeT+UCwauYjkdOQYRvwsGwimlQQE2ayqUwuuXGzquFpCPoXzYKWlzL4OuXg==
version "4.0.14"
resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.0.14.tgz#ce2028c1c5c33e30c7287950c973f397fce1bd65"
integrity sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw==
dependencies:
"@vue/devtools-api" "^6.0.0-beta.14"
"@vue/devtools-api" "^6.0.0"
vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
version "4.1.3"
@@ -8981,18 +9094,20 @@ vue-template-es2015-compiler@^1.9.0:
integrity sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU=
vue@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.11.tgz#6b92295048df705ddac558fd3e3ed553e55e57c8"
integrity sha512-JkI3/eIgfk4E0f/p319TD3EZgOwBQfftgnkRsXlT7OrRyyiyoyUXn6embPGZXSBxD3LoZ9SWhJoxLhFh5AleeA==
version "3.2.31"
resolved "https://registry.npmmirror.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6"
integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==
dependencies:
"@vue/compiler-dom" "3.2.11"
"@vue/runtime-dom" "3.2.11"
"@vue/shared" "3.2.11"
"@vue/compiler-dom" "3.2.31"
"@vue/compiler-sfc" "3.2.31"
"@vue/runtime-dom" "3.2.31"
"@vue/server-renderer" "3.2.31"
"@vue/shared" "3.2.31"
vuex-persistedstate@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-4.0.0.tgz#ed82f266ca98c869a2aad9cb9880c2f608c05f3a"
integrity sha512-jDs+awbV9YD2A2F6S5zgtYq1Bjd8v0YldOK6HPv1EJZzGMse0FtZTREfXvA7zlVfq9MpmSZJNmYQVylfpZ5znQ==
version "4.1.0"
resolved "https://registry.npmmirror.com/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz#127165f85f5b4534fb3170a5d3a8be9811bd2a53"
integrity sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==
dependencies:
deepmerge "^4.2.2"
shvl "^2.0.3"