This commit is contained in:
2021-09-25 15:12:02 +08:00
parent 943c07051c
commit 24aa6d50dd
25 changed files with 840 additions and 24 deletions

View File

@@ -1,12 +1,18 @@
<template>
<router-view v-slot="{ Component }">
<keep-alive :include="includeList">
<component :is="Component"></component>
<div id="layout">
<Header/>
<component :is="Component"></component>
<Footer/>
</div>
</keep-alive>
</router-view>
</template>
<script lang="ts" setup>
import Footer from '@/components/Footer'
import Header from '@/components/Header'
import { ref, watch } from 'vue'
import { RouteLocationNormalizedLoaded, useRoute } from 'vue-router'
@@ -21,7 +27,29 @@ watch(route, (to: RouteLocationNormalizedLoaded) => {
</script>
<style lang="less">
* {
padding: 0;
margin: 0;
}
body {
background: #f7f8fa;
font-size: 14px;
}
#layout {
.container {
width: 1200px;
margin: 0 auto;
min-height: calc(100vh - 164px);
}
}
.wrap {
width: 1200px;
margin: 0 auto;
}
.breadcrumb {
padding: 32px 0;
}
</style>

View File

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

View File

@@ -0,0 +1,3 @@
import Chain33Rpc from '@33cn/chain33-rpc-api'
export default new Chain33Rpc('http://explorer.cnskl.com/api', null)

BIN
src/assets/ball.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

222
src/components/Banner.vue Normal file
View File

@@ -0,0 +1,222 @@
<template>
<div class="banner">
<div class="left-trans">
<!-- <div class="animation-area flex-between-stright">-->
<!-- <img src="../../../assets/img/computer/henxian-zuo.gif" class="line-left" alt="">-->
<!-- <img src="../../../assets/img/computer/box.png" class="box-left" alt="">-->
<!-- <div class="circle-area">-->
<!-- <img src="../../../assets/img/computer/cicle-out.png" class="left-out-cicle" alt="">-->
<!-- <img src="../../../assets/img/computer/cicle-inner.png" class="left-inner-cicle" alt="">-->
<!-- </div>-->
<!-- </div>-->
</div>
<div class="middle">
<div class="search-box">
<h1>区块链浏览器</h1>
<div class="search-component">
<input
type="text"
class="search-input"
placeholder="地址/哈希/区块高度"
v-model="searchKey"
@keyup.enter="onSearch"
>
<div class="search-btn" @click="onSearch">搜索</div>
</div>
</div>
<!-- <chain-info></chain-info> -->
</div>
<div class="right-trans">
<!-- <div class="animation-area flex-between-stright">-->
<!-- <div class="circle-area">-->
<!-- <img src="../../../assets/img/computer/cicle-out.png" class="right-out-cicle" alt="">-->
<!-- <img src="../../../assets/img/computer/cicle-inner.png" class="right-inner-cicle" alt="">-->
<!-- </div>-->
<!-- <img src="../../../assets/img/computer/box.png" class="box-right" alt="">-->
<!-- <img src="../../../assets/img/computer/henxian-you.gif" class="line-right" alt="">-->
<!-- </div>-->
</div>
</div>
</template>
<script lang="ts" setup>
import { block } from '@/api'
import router from '@/router'
import { useStore } from '@/store'
import { ElMessage } from 'element-plus'
import { computed, ref } from 'vue'
const store = useStore()
const searchKey = ref<string>('')
const maxHeight = computed(() => store.getters.maxHeight)
const onSearch = () => {
searchKey.value = searchKey.value.trim()
if (searchKey.value === '') {
return ElMessage({
message: '请输入 地址/哈希/区块高度',
type: 'warning',
offset: 300
})
}
const reg = /^[0-9]*[1-9][0-9]*$/
if (reg.test(searchKey.value)) {
let h = Number(searchKey.value)
if (h > 0 && h <= maxHeight.value) {
block.getBlockHash(h).then(res => {
if (res.error == null) {
router.push({ name: 'BlockDetail', params: { hash: res.result.hash } })
} else {
return ElMessage({
message: res.error,
type: 'warning',
offset: 300
})
}
}).catch(err => {
return ElMessage({
message: err.message,
type: 'warning',
offset: 300
})
}).finally(() => {
searchKey.value = ''
})
} else {
ElMessage({
message: '输入的区块高度有误',
type: 'warning',
offset: 300
})
}
} else if (searchKey.value.length == 66 || searchKey.value.length == 64) {
let value = searchKey.value.length === 64 ? '0x' + searchKey.value : searchKey.value
block.queryTransaction(value).then(res => {
if (res.error == null) {
console.log(res)
router.push({ name: 'TradeDetail', params: { hash: res.result.tx.hash } })
} else {
block.getBlockOverview(value).then(data => {
if (data.error == null) {
router.push({ name: 'BlockDetail', params: { hash: data.result.head.hash } })
} else {
return ElMessage({
message: res.error,
type: 'warning',
offset: 300
})
}
})
}
}).catch(err => {
return ElMessage({
message: err.message,
type: 'warning',
offset: 300
})
}).finally(() => {
searchKey.value = ''
})
} else {
block.getAddrOverview(searchKey.value).then(res => {
if (res.error == null) {
router.push({ name: 'Address', params: { address: searchKey.value } })
} else {
return ElMessage({
message: res.error,
type: 'warning',
offset: 300
})
}
}).catch(err => {
return ElMessage({
message: err.message,
type: 'warning',
offset: 300
})
}).finally(() => {
searchKey.value = ''
})
}
}
</script>
<style scoped lang="less">
.banner {
height: 556px;
background: #2458cd;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
.middle {
height: 556px;
position: relative;
width: 1000px;
flex-shrink: 0;
background: url("/assets/images/ball.png") center center no-repeat;
.search-box {
width: 700px;
margin: 220px auto 0;
text-align: center;
h1 {
font-size: 56px;
font-weight: 500;
color: #FFFFFF;
line-height: 74px;
}
.search-component {
margin-top: 41px;
position: relative;
.search-input {
box-sizing: border-box;
width: 700px;
height: 50px;
background: #FFFFFF;
border-radius: 39px;
border: none;
font-size: 16px;
padding: 0 150px 0 22px;
color: #838b9e;
&:focus {
outline: none;
}
}
.search-btn {
position: absolute;
right: 3px;
top: 3px;
width: 132px;
height: 44px;
line-height: 44px;
background: linear-gradient(90deg, #3892f4, #1a59e5);
border-radius: 39px;
font-size: 16px;
font-weight: 400;
color: #FFFFFF;
cursor: pointer;
transition: 0.3s;
}
.search-btn:hover {
opacity: 0.9;
}
.search-btn:active {
opacity: 1;
}
}
}
}
}
</style>

23
src/components/Footer.vue Normal file
View File

@@ -0,0 +1,23 @@
<template>
<div class="footer ">
<div class="wrap">
@2021 域展科技 All Rights Reserved.Powered by 哈尔滨域展科技有限公司
黑ICP备19007143号-2
</div>
</div>
</template>
<script lang="ts" setup>
</script>
<style scoped lang="less">
.footer {
height: 100px;
background-color: #409eff;
color: #FFFFFF;
text-align: center;
display: flex;
align-items: center;
}
</style>

46
src/components/Header.vue Normal file
View File

@@ -0,0 +1,46 @@
<template>
<div class="header">
<div class="wrap">
<div class="logo" @click="router.push({name: 'Home'})">
LOGO
</div>
<Nav/>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import Nav from './Nav'
const router = useRouter()
</script>
<style scoped lang="less">
.header {
background-color: #2055ca;
z-index: 100;
position: relative;
color: #FFF;
display: flex;
border-bottom: 1px solid #6886d2;
.wrap {
display: flex;
justify-content: space-around;
.nav {
width: 600px;
}
.logo {
align-items: center;
display: flex;
margin-right: 32px;
height: 64px;
cursor: pointer;
}
}
}
</style>

85
src/components/Nav.vue Normal file
View File

@@ -0,0 +1,85 @@
<template>
<ul class="nav">
<li v-for="(item,index) in navList" :key="index" class="flex-center-stright">
<div class="nav-trans" @click="linkTo(item.route)">
<span :data-title="item.title">{{ item.title }}</span>
</div>
</li>
</ul>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// region 导航
const navList = ref([
{
title: '首页',
route: 'Home'
},
{
title: '查看区块',
route: 'Block'
},
{
title: '查看数据',
route: 'Trade'
},
{
title: '解析数据',
route: 'Analytical'
},
{
title: '广播数据',
route: 'Broadcast'
}
])
const linkTo = (name) => {
router.push({ name })
}
// endregion
</script>
<style scoped lang="less">
ul.nav {
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
list-style: none;
height: 64px;
li {
.nav-trans {
overflow: hidden;
position: relative;
height: 30px;
}
span {
height: 30px;
line-height: 30px;
transition: all ease-out 0.3s;
display: inline-block;
}
span:after {
display: inline-flex;
position: absolute;
left: 0;
content: attr(data-title);
transform: translateY(100%);
}
}
li:hover {
span {
color: #EDCB17;
transform: translateY(-100%);
}
}
}
</style>

View File

@@ -1,3 +1,5 @@
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
@@ -5,6 +7,7 @@ import store, { key } from './store'
const app = createApp(App)
app.use(ElementPlus)
app.use(store, key)
app.use(router)
app.mount('#app')

View File

@@ -2,6 +2,7 @@ import vuex from '@/store'
import type { MyRouteMeta, MyRouteRecordRaw } from '@/types/router'
import { createRouter, createWebHistory } from 'vue-router'
import auth from './routers/auth'
import block from './routers/block'
import user from './routers/user'
/**
@@ -19,6 +20,7 @@ const routes: MyRouteRecordRaw[] = [
component: () => import(/* webpackChunkName: "home" */ '@/views/Home/index.vue')
},
...auth,
...block,
...user,
{
path: '/404',
@@ -48,12 +50,12 @@ const router = createRouter({
* 路由守卫
*/
router.beforeEach((to, from, next) => {
typeof to.meta !== 'undefined' && setDocumentTitle(<MyRouteMeta>to.meta)
typeof to.meta !== 'undefined' && setDocumentTitle(<MyRouteMeta>to.meta!)
const isAuthenticated: string = vuex.getters.isLogin
if (to.name !== 'AuthLogin' && to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'AuthLogin', query: { to: to.path }})
next({ name: 'AuthLogin', query: { to: to.path } })
} else if (isAuthenticated && (to.name == 'AuthLogin' || to.name == 'AuthRegister')) {
next({ name: 'User' })
} else {
@@ -65,8 +67,8 @@ router.beforeEach((to, from, next) => {
* 设置文档标题
* @param meta MyRouteMeta
*/
function setDocumentTitle(meta: MyRouteMeta) {
document.title = meta?.title
function setDocumentTitle (meta: MyRouteMeta) {
document.title = process.env.VUE_APP_TITLE + ' ' + meta?.title
const ua = navigator.userAgent
const regex = /\bMicroMessenger\/([\d.]+)/
if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) {

View File

@@ -0,0 +1,82 @@
import type { MyRouteRecordRaw } from '@/types/router'
export default [
{
path: '/blocks',
name: 'Block',
meta: {
title: '查看区块',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Block/index.vue')
},
{
path: '/blocks/:hash',
name: 'BlockDetail',
meta: {
title: '区块详情',
keepAlive: false,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Block/detail.vue')
},
{
path: '/trades',
name: 'Trade',
meta: {
title: '查看数据',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Trade/index.vue')
},
{
path: '/trades/:hash',
name: 'TradeDetail',
meta: {
title: '数据详情',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Trade/detail.vue')
},
{
path: '/analytical',
name: 'Analytical',
meta: {
title: '解析数据',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Other/analytical.vue')
},
{
path: '/broadcast',
name: 'Broadcast',
meta: {
title: '广播数据',
keepAlive: true,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Other/broadcast.vue')
},
{
path: '/address/:address',
name: 'Address',
meta: {
title: '地址信息',
keepAlive: false,
requiresAuth: false,
showTabBar: true
},
component: () => import(/* webpackChunkName: "auth" */ '@/views/Address/index.vue')
}
] as MyRouteRecordRaw[]

View File

@@ -16,6 +16,7 @@ export interface State {
tokenType: string
openId: string
loginAt: number
maxHeight: number
user: BaseInfo
auth?: AuthState
refresh?: RefreshState
@@ -29,6 +30,7 @@ export default createStore<State>({
tokenType: '',
openId: '',
loginAt: 0,
maxHeight:110,
user: {} as BaseInfo
},
getters: {
@@ -40,6 +42,9 @@ export default createStore<State>({
},
userInfo: (state: State): BaseInfo => {
return state.user
},
maxHeight: (state: State): number => {
return state.maxHeight
}
},
mutations: {
@@ -63,7 +68,10 @@ export default createStore<State>({
},
cleanUserInfo: (state: State): void => {
state.user = {} as BaseInfo
}
},
setMaxHeight: (state: State, height: number): void => {
state.maxHeight = height
},
},
actions: {
setUserInfo: ({ commit }, info: BaseInfo): void => {
@@ -71,6 +79,9 @@ export default createStore<State>({
},
setOpenId: ({ commit }, openId: string): void => {
commit('setOpenId', openId)
},
setMaxHeight: ({ commit }, height: number): void => {
commit('setMaxHeight', height)
}
},
modules: {

View File

@@ -45,7 +45,7 @@ export default {
auth.logout().then(() => {
commit('cleanAccessToken', null, { root: true })
commit('cleanUserInfo', null, { root: true })
localStorage.removeItem(PERSISTED_KEY)
localStorage.removeItem(PERSISTED_KEY as string)
resolve(true)
}).catch(err => {
reject(err.message)

View File

@@ -0,0 +1,18 @@
<template>
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>地址信息</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,21 @@
<template>
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="{ name: 'Block' }">全部区块</el-breadcrumb-item>
<el-breadcrumb-item>区块详情</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const blocks = ref([])
</script>
<style scoped lang="less">
</style>

65
src/views/Block/index.vue Normal file
View File

@@ -0,0 +1,65 @@
<template>
<div class="container">
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>全部区块</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="page-header">
<div class="title">
全部区块28个区块
</div>
<el-pagination
background
v-model:currentPage="currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="20"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
<el-table :data="blocks" stripe class="table">
<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>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const blocks = ref([])
</script>
<style scoped lang="less">
.page-header {
display: flex;
justify-content: space-between;
.title {
font-size: 16px;
font-weight: 500;
color: #53657a;
}
}
.table {
margin-top: 20px;
box-shadow: 0 0.5rem 1.2rem rgba(189, 197, 209, 0.2);
border-radius: 2px;
font-size: 12px;
}
</style>

View File

@@ -1,20 +1,11 @@
<template>
<div class="home">
我是首页
</div>
<Banner/>
</template>
<script lang="ts" setup>
import { useStore } from '@/store'
import { BaseInfo } from '@/types/user'
import { computed } from 'vue'
const store = useStore()
const info = computed<BaseInfo>(() => {
return store.getters.userInfo
})
</script>
<style scoped>
import Banner from '@/components/Banner'</script>
<style scoped lang="less">
.home {
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>解析数据</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const blocks = ref([])
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>广播数据</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const blocks = ref([])
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,21 @@
<template>
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="{ name: 'Trade' }">全部交易</el-breadcrumb-item>
<el-breadcrumb-item>交易详情</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const blocks = ref([])
</script>
<style scoped lang="less">
</style>

63
src/views/Trade/index.vue Normal file
View File

@@ -0,0 +1,63 @@
<template>
<div class="breadcrumb">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ name: 'Home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>全部区块</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="page-header">
<div class="title">
全部数据65条数据
</div>
<el-pagination
background
v-model:currentPage="currentPage"
:page-sizes="[10, 20, 30, 40]"
:page-size="20"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</el-pagination>
</div>
<el-table :data="blocks" stripe class="table">
<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>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const blocks = ref([])
</script>
<style scoped lang="less">
.page-header {
display: flex;
justify-content: space-between;
.title {
font-size: 16px;
font-weight: 500;
color: #53657a;
}
}
.table {
margin-top: 20px;
box-shadow: 0 0.5rem 1.2rem rgba(189, 197, 209, 0.2);
border-radius: 2px;
font-size: 12px;
}
</style>