add new Dashboard

This commit is contained in:
liangping 2022-08-24 21:51:45 +08:00
parent 0fb20ce4f3
commit da77476827
29 changed files with 1022 additions and 145 deletions

View File

@ -255,16 +255,20 @@ export default {
},
accounts() {
let accounts = getLocalAccounts() || {}
accounts = Object.entries(accounts).map(v => ({ wallet: v[0], address: v[1].address.find(x => x.chain === this.selected_chain.chain_name) }))
accounts = Object.entries(accounts)
.map(v => ({ wallet: v[0], address: v[1].address.find(x => x.chain === this.selected_chain.chain_name) }))
.filter(v => v.address)
if (accounts.length > 0) {
// accounts > 0 and wallet not setted, pick the first one as default
if (accounts.length > 0 && accounts.findIndex(x => x.wallet === this.walletName) < 0) {
this.updateDefaultWallet(accounts[0].wallet)
}
return accounts.filter(x => x.address)
},
},
mounted() {
if (accounts.findIndex(x => x.wallet === this.walletName) < 0 && this.walletName !== 'Wallet') {
this.updateDefaultWallet(null)
}
return accounts
},
},
methods: {
formatAddr(v) {

View File

@ -416,6 +416,10 @@ export default class ChainFetch {
return this.get('/bank/balances/'.concat(address), config).then(data => commonProcess(data))
}
async getCommunityPool(config = null) {
return this.get('/cosmos/distribution/v1beta1/community_pool', config).then(data => commonProcess(data))
}
async getAllIBCDenoms(config = null) {
const conf = config || this.getSelectedConfig()
const sdkVersion = conf.sdk_version
@ -470,6 +474,14 @@ export default class ChainFetch {
return null
}
async getCoinInfo(coin = null) {
const conf = this.getSelectedConfig()
if (conf.assets[0] && conf.assets[0].coingecko_id) {
return ChainFetch.fetch(' https://api.coingecko.com', `/api/v3/coins/${coin || conf.assets[0].coingecko_id}`)
}
return null
}
// CoinMarketCap
static async fetchCoinMarketCap(url) {
const host = 'https://price.ping.pub'

View File

@ -439,9 +439,9 @@ export function formatNumber(count, withAbbr = false, decimals = 2) {
return result
}
export function tokenFormatter(tokens, denoms = {}) {
export function tokenFormatter(tokens, denoms = {}, decimal = 2) {
if (Array.isArray(tokens)) {
return tokens.map(t => formatToken(t, denoms, 2)).join(', ')
return tokens.map(t => formatToken(t, denoms, decimal)).join(', ')
}
return formatToken(tokens, denoms, 2)
}

View File

@ -122,7 +122,7 @@ const router = new VueRouter({
path: '/:chain/',
name: 'dashboard',
alias: '/:chain',
component: () => import('@/views/Summary.vue'),
component: () => import('@/views/Dashboard.vue'),
meta: {
pageTitle: 'Home',
breadcrumb: [
@ -136,7 +136,7 @@ const router = new VueRouter({
{
path: '/:chain/parameters',
name: 'parameters',
component: () => import('@/views/Summary.vue'),
component: () => import('@/views/Parameters.vue'),
meta: {
pageTitle: 'Parameters',
breadcrumb: [
@ -400,7 +400,6 @@ router.beforeEach((to, from, next) => {
const c = to.params.chain
if (c) {
store.commit('select', { chain_name: String(c).toLowerCase() })
store.dispatch('chains/getAllIBCDenoms', Vue.prototype)
}
const config = JSON.parse(localStorage.getItem('chains'))

View File

@ -70,6 +70,8 @@ export default {
if (defaultWallet && defaultWallet.length > 0) {
localStorage.setItem('default-wallet', defaultWallet)
state.chains.defaultWallet = defaultWallet
} else {
state.chains.defaultWallet = null
}
},
setIBCDenoms(state, denoms) {

View File

@ -47,8 +47,8 @@ import { fromBase64 } from '@cosmjs/encoding'
import { decodeTxRaw } from '@cosmjs/proto-signing'
import Tx from '@/libs/data/tx'
import { abbrMessage, tokenFormatter } from '@/libs/utils'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ArrayFieldComponent from './ArrayFieldComponent.vue'
import ObjectFieldComponent from './components/ObjectFieldComponent.vue'
import ArrayFieldComponent from './components/ArrayFieldComponent.vue'
export default {
components: {

418
src/views/Dashboard.vue Normal file
View File

@ -0,0 +1,418 @@
<template>
<div>
<b-alert
variant="danger"
:show="syncing"
>
<div class="alert-body">
<span>No new blocks have been produced since <strong>{{ latestTime }}</strong> </span>
</div>
</b-alert>
<b-row>
<b-col><dashboard-price-chart-2 /></b-col>
</b-row>
<!-- Stats Card Vertical -->
<b-row class="match-height">
<b-col
xl="2"
md="4"
sm="6"
>
<dashboard-card-vertical
icon="BoxIcon"
:statistic="height"
statistic-title="Height"
color="info"
/>
</b-col>
<b-col
xl="2"
md="4"
sm="6"
>
<dashboard-card-vertical
color="warning"
icon="DollarSignIcon"
:statistic="supply"
statistic-title="Total Supply"
/>
</b-col>
<b-col
xl="2"
md="4"
sm="6"
>
<dashboard-card-vertical
color="danger"
icon="PercentIcon"
:statistic="ratio"
:statistic-title="`Bonded: ${bonded}`"
/>
</b-col>
<b-col
xl="2"
md="4"
sm="6"
>
<dashboard-card-vertical
color="primary"
icon="TrendingUpIcon"
:statistic="inflation"
statistic-title="Inflation"
/>
</b-col>
<b-col
xl="2"
md="4"
sm="6"
>
<dashboard-card-vertical
color="success"
icon="AwardIcon"
:statistic="communityPool"
statistic-title="Community Pool"
/>
</b-col>
<b-col
xl="2"
md="4"
sm="6"
>
<dashboard-card-vertical
hide-chart
color="danger"
icon="UserCheckIcon"
:statistic="validators"
statistic-title="Active Validators"
/>
</b-col>
</b-row>
<b-card no-body>
<b-card-header>
<b-card-title>Active Proposals</b-card-title>
</b-card-header>
<b-card-body>
<b-link
v-for="prop in proposals"
:key="prop.id"
:to="`./${chain}/gov/${prop.id}`"
>
<b-media
no-body
class="mb-1"
>
<b-media-aside>
<b-avatar
rounded
size="42"
variant="light-primary"
>
{{ prop.id }}
</b-avatar>
</b-media-aside>
<b-media-body class="d-flex flex-column justify-content-center">
<h6 class="transaction-title">
{{ prop.title }}
</h6>
<small>{{ formatType(prop.contents['@type']) }} {{ formatEnding(prop.voting_end_time) }}</small>
</b-media-body>
</b-media>
</b-link>
<div v-if="proposals.length === 0">
No active proposal!
<b-link :to="`./${chain}/gov`">
Browse all
</b-link>
</div>
</b-card-body>
</b-card>
<b-card
border-variant="primary"
:title="`${walletName} Assets`"
bg-variant="transparent"
class="shadow-none"
>
<b-row>
<b-col
lg="3"
sm="6"
>
<dashboard-card-horizontal
icon="DollarSignIcon"
color="success"
:statistic="walletBalances"
statistic-title="Balances"
/>
</b-col>
<b-col
lg="3"
sm="6"
>
<dashboard-card-horizontal
icon="BoxIcon"
:statistic="walletStaking"
statistic-title="Staking"
/>
</b-col>
<b-col
lg="3"
sm="6"
>
<dashboard-card-horizontal
icon="PercentIcon"
color="danger"
:statistic="walletRewards"
statistic-title="Rewards"
/>
</b-col>
<b-col
lg="3"
sm="6"
>
<dashboard-card-horizontal
icon="TrendingUpIcon"
color="warning"
:statistic="walletUnbonding"
statistic-title="Unbonding"
/>
</b-col>
</b-row>
</b-card>
<router-link to="/wallet/import">
<b-card class="addzone text-center">
<feather-icon icon="PlusIcon" />
Connect Wallet
</b-card>
</router-link>
</div>
</template>
<script>
import {
BRow, BCol, BAlert, BCard, BTable, BFormCheckbox, BCardHeader, BCardTitle, BMedia, BMediaAside, BMediaBody, BAvatar,
BCardBody, BLink,
} from 'bootstrap-vue'
import {
formatNumber, formatTokenAmount, isToken, percent, timeIn, toDay, toDuration, tokenFormatter, getLocalAccounts,
} from '@/libs/utils'
import ParametersModuleComponent from './components/parameters/ParametersModuleComponent.vue'
import DashboardCardHorizontal from './components/dashboard/DashboardCardHorizontal.vue'
import DashboardCardVertical from './components/dashboard/DashboardCardVertical.vue'
import DashboardPriceChart2 from './components/dashboard/DashboardPriceChart2.vue'
export default {
components: {
BAvatar,
BRow,
BCol,
BAlert,
BCard,
BTable,
BFormCheckbox,
BCardHeader,
BCardTitle,
BMediaBody,
BMediaAside,
BMedia,
BCardBody,
BLink,
ParametersModuleComponent,
DashboardCardHorizontal,
DashboardPriceChart2,
DashboardCardVertical,
},
data() {
return {
chain: this.$store.state.chains.selected.chain_name,
syncing: false,
latestTime: '',
marketData: null,
height: '-',
supply: '-',
bonded: '-',
validators: '-',
communityPool: '-',
ratio: '-',
inflation: '-',
proposals: [],
walletBalances: '-',
walletStaking: '-',
walletRewards: '-',
walletUnbonding: '-',
}
},
computed: {
walletName() {
const key = this.$store?.state?.chains?.defaultWallet
return key || 'Wallet'
},
},
created() {
this.$http.getLatestBlock().then(res => {
this.height = res.block.header.height
if (timeIn(res.block.header.time, 3, 'm')) {
this.syncing = true
} else {
this.syncing = false
}
this.latestTime = toDay(res.block.header.time, 'long')
this.validators = res.block.last_commit.signatures.length
})
this.$http.getStakingParameters().then(res => {
Promise.all([this.$http.getStakingPool(), this.$http.getBankTotal(res.bond_denom)])
.then(pool => {
this.supply = `${formatNumber(formatTokenAmount(pool[1].amount, 2, res.bond_denom, false), true, 2)}`
this.bonded = `${formatNumber(formatTokenAmount(pool[0].bondedToken, 2, res.bond_denom, false), true, 2)}`
this.ratio = `${percent(pool[0].bondedToken / pool[1].amount)}%`
})
})
this.$http.getCommunityPool().then(res => {
this.communityPool = this.formatToken(res.pool)
})
const conf = this.$http.getSelectedConfig()
if (conf.excludes && conf.excludes.indexOf('mint') > -1) {
this.inflation = '-'
} else {
this.$http.getMintingInflation().then(res => {
this.inflation = `${percent(res)}%`
}).catch(() => {
this.inflation = '-'
})
}
this.$http.getGovernanceListByStatus(2).then(res => {
this.proposals = res.proposals
})
const accounts = getLocalAccounts() || {}
const account = Object.entries(accounts)
.map(v => ({ wallet: v[0], address: v[1].address.find(x => x.chain === this.$store.state.chains.selected.chain_name) }))
.filter(v => v.address)
.find(x => x.wallet === this.walletName)
if (account) {
this.fetchAccount(account.address.addr)
}
},
methods: {
formatToken(tokens) {
if (Array.isArray(tokens)) {
let nativeToken = tokens.filter(x => !x.denom.toUpperCase().startsWith('IBC/'))
if (tokens.length > 1) {
const sum = {}
const reduce = nativeToken.reduce((b, a) => {
const b2 = b
if (b2[a.denom]) {
b2[a.denom] += Number(a.amount)
} else {
b2[a.denom] = Number(a.amount)
}
return b2
}, sum)
nativeToken = Object.keys(reduce).map(k => ({ denom: k, amount: reduce[k] }))
}
return tokenFormatter(nativeToken, {}, 0)
}
return '-'
},
fetchAccount(address) {
this.$http.getBankAccountBalance(address).then(bal => {
this.walletBalances = this.formatToken(bal)
})
this.$http.getStakingReward(address).then(res => {
this.walletRewards = this.formatToken(res.rewards.map(x => x.reward).flat())
})
this.$http.getStakingDelegations(address).then(res => {
const delegations = res.delegation_responses || res
this.walletStaking = this.formatToken(delegations.map(x => x.balance).flat())
})
this.$http.getStakingUnbonding(address).then(res => {
const token = this.$store.state.chains.selected.assets[0]
if (token) {
const newTokens = []
const denom = token.base
const unbonding = res.unbonding_responses || res
unbonding.forEach(x => {
x.entries.forEach(y => {
newTokens.push({
amount: y.balance,
denom,
})
})
})
this.walletUnbonding = this.formatToken(newTokens)
}
})
},
formatEnding(v) {
return toDay(v, 'from')
},
formatType(v) {
const txt = String(v).replace('Proposal', '')
const index = txt.lastIndexOf('.')
return index > 0 ? txt.substring(index + 1) : txt
},
normalize(data, title) {
if (!data) return null
const items = this.makeItems(data)
return {
title,
items,
}
},
makeItems(data) {
return Object.keys(data).map(k => {
if (isToken(data[k])) {
return { title: tokenFormatter(data[k]), subtitle: k }
}
if (typeof data[k] === 'boolean') {
return { title: data[k], subtitle: k }
}
return { title: this.convert(data[k]), subtitle: k }
})
},
convert(v) {
if (typeof v === 'object') {
const v2 = {}
Object.entries(v).forEach(e => {
const k = e[0]
const x = e[1]
v2[k] = this.convert(x)
})
return v2
}
const d = parseFloat(v)
if (d === 0) return '0'
if (d < 1.01) {
return `${percent(d)}%`
}
if (d > 1000000000) {
return `${toDuration(d / 1000000)}`
}
if (d > 0) {
return d.toFixed()
}
return v
},
},
}
</script>
<style>
.addzone {
border: 2px dashed #ced4da;
background: #fff;
border-radius: 6px;
cursor: pointer;
box-shadow: none;
}
.addzone :hover {
border: 2px dashed #7367F0;
}
</style>

View File

@ -278,7 +278,7 @@ import {
import { Proposal, Proposer } from '@/libs/data'
import dayjs from 'dayjs'
import OperationModal from '@/views/components/OperationModal/index.vue'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ObjectFieldComponent from './components/ObjectFieldComponent.vue'
// import { formatToken } from '@/libs/data/data'

View File

@ -118,7 +118,6 @@ import { formatTokenDenom } from '@/libs/utils'
import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue'
import Place from './components/KlineTrade/Place.vue'
// import Kline from './components/kline/index.vue'
import SummaryPriceChart from './SummaryPriceChart.vue'
export default {
components: {
@ -130,7 +129,6 @@ export default {
BSpinner,
Place,
BCard,
SummaryPriceChart,
FeatherIcon,
},
beforeRouteUpdate(to, from, next) {

View File

@ -10,50 +10,32 @@
</b-alert>
<b-row>
<b-col>
<summary-parmeters-component
:data="chain"
/>
</b-col>
</b-row>
<b-row v-if="marketChartData">
<b-col>
<b-card>
<summary-price-chart
:chart-data="marketChartData"
:height="150"
:min-height="150"
/>
</b-card>
<parameters-module-component :data="chain" />
</b-col>
</b-row>
<b-row>
<b-col>
<summary-assets-component />
<parameters-module-component :data="mint" />
</b-col>
</b-row>
<b-row>
<b-col>
<summary-parmeters-component :data="mint" />
</b-col>
</b-row>
<b-row>
<b-col>
<summary-parmeters-component :data="staking" />
<parameters-module-component :data="staking" />
</b-col>
</b-row>
<b-row v-if="gov.items.length > 0">
<b-col>
<summary-parmeters-component :data="gov" />
<parameters-module-component :data="gov" />
</b-col>
</b-row>
<b-row>
<b-col>
<summary-parmeters-component :data="distribution" />
<parameters-module-component :data="distribution" />
</b-col>
</b-row>
<b-row>
<b-col>
<summary-parmeters-component :data="slashing" />
<parameters-module-component :data="slashing" />
</b-col>
</b-row>
</div>
@ -64,12 +46,10 @@ import {
BRow, BCol, BAlert, BCard,
} from 'bootstrap-vue'
import {
formatNumber, formatTokenAmount, getUserCurrency, isToken, percent, timeIn, toDay, toDuration, tokenFormatter,
formatNumber, formatTokenAmount, isToken, percent, timeIn, toDay, toDuration, tokenFormatter,
} from '@/libs/utils'
import SummaryParmetersComponent from './SummaryParmetersComponent.vue'
import SummaryAssetsComponent from './SummaryAssetsComponent.vue'
import SummaryPriceChart from './SummaryPriceChart.vue'
import ParametersModuleComponent from './components/parameters/ParametersModuleComponent.vue'
export default {
components: {
@ -77,9 +57,7 @@ export default {
BCol,
BAlert,
BCard,
SummaryParmetersComponent,
SummaryAssetsComponent,
SummaryPriceChart,
ParametersModuleComponent,
},
data() {
return {
@ -118,29 +96,6 @@ export default {
},
}
},
computed: {
marketChartData() {
if (this.marketData && this.marketData.prices) {
const labels = this.marketData.prices.map(x => x[0])
const data = this.marketData.prices.map(x => x[1])
return {
labels,
datasets: [
{
label: `Price (${getUserCurrency().toUpperCase()})`,
data,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
pointStyle: 'dash',
barThickness: 15,
},
],
}
}
return null
},
},
created() {
this.$http.getLatestBlock().then(res => {
const height = this.chain.items.findIndex(x => x.subtitle === 'height')

View File

@ -204,70 +204,68 @@
</b-card-footer>
</b-card>
<!-- First Row -->
<template>
<b-row class="match-height">
<b-col
lg="4"
md="12"
>
<staking-commission-component :data="validator.commission" />
</b-col>
<b-col
lg="4"
md="12"
>
<staking-reward-component
:data="distribution"
:validator="validator.operator_address"
:address="accountAddress"
<b-row class="match-height">
<b-col
lg="4"
md="12"
>
<staking-commission-component :data="validator.commission" />
</b-col>
<b-col
lg="4"
md="12"
>
<staking-reward-component
:data="distribution"
:validator="validator.operator_address"
:address="accountAddress"
/>
</b-col>
<b-col
lg="4"
md="12"
>
<staking-address-component
:hex-address="hexAddress"
:operator-address="validator.operator_address"
:consensus-pubkey="validator.consensus_pubkey"
:account-address="accountAddress"
/>
</b-col>
</b-row>
<b-row>
<b-col>
<b-card title="Transactions">
<b-table
:items="txs"
striped
hover
responsive="sm"
stacked="sm"
>
<template #cell(height)="data">
<router-link :to="`../blocks/${data.item.height}`">
{{ data.item.height }}
</router-link>
</template>
<template #cell(txhash)="data">
<router-link :to="`../tx/${data.item.txhash}`">
{{ formatHash(data.item.txhash) }}
</router-link>
</template>
</b-table>
<b-pagination
v-if="Number(transactions.page_total) > 1"
:total-rows="transactions.total_count"
:per-page="transactions.limit"
:value="transactions.page_number"
align="center"
class="mt-1"
@change="pageload"
/>
</b-col>
<b-col
lg="4"
md="12"
>
<staking-address-component
:hex-address="hexAddress"
:operator-address="validator.operator_address"
:consensus-pubkey="validator.consensus_pubkey"
:account-address="accountAddress"
/>
</b-col>
</b-row>
<b-row>
<b-col>
<b-card title="Transactions">
<b-table
:items="txs"
striped
hover
responsive="sm"
stacked="sm"
>
<template #cell(height)="data">
<router-link :to="`../blocks/${data.item.height}`">
{{ data.item.height }}
</router-link>
</template>
<template #cell(txhash)="data">
<router-link :to="`../tx/${data.item.txhash}`">
{{ formatHash(data.item.txhash) }}
</router-link>
</template>
</b-table>
<b-pagination
v-if="Number(transactions.page_total) > 1"
:total-rows="transactions.total_count"
:per-page="transactions.limit"
:value="transactions.page_number"
align="center"
class="mt-1"
@change="pageload"
/>
</b-card>
</b-col>
</b-row>
</template>
</b-card>
</b-col>
</b-row>
<operation-modal
type="Delegate"
:validator-address="validator.operator_address"
@ -286,9 +284,9 @@ import {
} from '@/libs/utils'
import { keybase } from '@/libs/fetch'
import OperationModal from '@/views/components/OperationModal/index.vue'
import StakingAddressComponent from './StakingAddressComponent.vue'
import StakingCommissionComponent from './StakingCommissionComponent.vue'
import StakingRewardComponent from './StakingRewardComponent.vue'
import StakingAddressComponent from './components/staking/StakingAddressComponent.vue'
import StakingCommissionComponent from './components/staking/StakingCommissionComponent.vue'
import StakingRewardComponent from './components/staking/StakingRewardComponent.vue'
export default {
components: {

View File

@ -107,7 +107,7 @@ import {
BCard, BTableSimple, BTr, BTd, BBadge, BCardBody, BAlert,
} from 'bootstrap-vue'
import { toDay, tokenFormatter } from '@/libs/utils'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ObjectFieldComponent from './components/ObjectFieldComponent.vue'
export default {
components: {

View File

@ -430,8 +430,8 @@ import {
toDuration, abbrMessage, abbrAddress, getUserCurrency, getUserCurrencySign, numberWithCommas, toETHAddress,
} from '@/libs/utils'
import OperationModal from '@/views/components/OperationModal/index.vue'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ChartComponentDoughnut from './ChartComponentDoughnut.vue'
import ObjectFieldComponent from './components/ObjectFieldComponent.vue'
import ChartComponentDoughnut from './components/charts/ChartComponentDoughnut.vue'
export default {
components: {

View File

@ -251,7 +251,7 @@ import ToastificationContent from '@core/components/toastification/Toastificatio
import AppCollapse from '@core/components/app-collapse/AppCollapse.vue'
import AppCollapseItem from '@core/components/app-collapse/AppCollapseItem.vue'
import OperationModal from '@/views/components/OperationModal/index.vue'
import ChartComponentDoughnut from './ChartComponentDoughnut.vue'
import ChartComponentDoughnut from './components/charts/ChartComponentDoughnut.vue'
import EchartScatter from './components/charts/EchartScatter.vue'
export default {

View File

@ -2,7 +2,7 @@
<b-tabs content-class="mt-1">
<!-- This tabs content will always be mounted -->
<b-tab
title="Ongoing Proposals"
title="Active Proposals"
pill
>
<b-row class="match-height">
@ -158,7 +158,7 @@ import {
} from '@/libs/utils'
import dayjs from 'dayjs'
import WalletUpgradeEvents from './WalletUpgradeEvents.vue'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ObjectFieldComponent from './components/ObjectFieldComponent.vue'
export default {
components: {

View File

@ -12,7 +12,7 @@
import { percent } from '@/libs/utils'
import { $themeColors } from '@themeConfig'
import ChartjsComponentDoughnutChart from './ChartjsComponentDoughnutChart.vue'
import ChartjsComponentDoughnutChart from './ChartjsComponentDoughnutChart'
export default {
name: 'ChartDoughnut',

View File

@ -0,0 +1,51 @@
<template>
<b-card no-body>
<b-card-body class="d-flex justify-content-between align-items-center">
<div class="truncate">
<h2 class="mb-25 font-weight-bolder">
{{ statistic }}
</h2>
<span>{{ statisticTitle }}</span>
</div>
<b-avatar
:variant="`light-${color}`"
size="45"
>
<feather-icon
size="21"
:icon="icon"
/>
</b-avatar>
</b-card-body>
</b-card>
</template>
<script>
import { BCard, BCardBody, BAvatar } from 'bootstrap-vue'
export default {
components: {
BCard,
BCardBody,
BAvatar,
},
props: {
icon: {
type: String,
required: true,
},
statistic: {
type: [Number, String],
required: true,
},
statisticTitle: {
type: String,
default: '',
},
color: {
type: String,
default: 'primary',
},
},
}
</script>

View File

@ -0,0 +1,49 @@
<template>
<b-card class="text-center">
<b-avatar
class="mb-1"
:variant="`light-${color}`"
size="45"
>
<feather-icon
size="21"
:icon="icon"
/>
</b-avatar>
<div class="truncate">
<h2 class="mb-25 font-weight-bolder">
{{ statistic }}
</h2>
<span>{{ statisticTitle }}</span>
</div>
</b-card>
</template>
<script>
import { BCard, BAvatar } from 'bootstrap-vue'
export default {
components: {
BCard,
BAvatar,
},
props: {
icon: {
type: String,
required: true,
},
statistic: {
type: [Number, String],
required: true,
},
statisticTitle: {
type: String,
default: '',
},
color: {
type: String,
default: 'primary',
},
},
}
</script>

View File

@ -0,0 +1,391 @@
<template>
<b-card v-if="marketData && marketData.prices">
<b-row>
<b-col md="4">
<div class="d-flex flex-column">
<h2 class="font-weight-bold">
{{ coinInfo.name }} <span class="text-uppercase">({{ coinInfo.symbol }})</span>
</h2>
<span
v-b-tooltip.hover.v-danger
title="Coingecko Rank"
>Coingecko Rank: <b-badge variant="light-danger">
#{{ coinInfo.coingecko_rank }}
</b-badge></span>
</div>
<hr>
<div class="my-1">
<b-badge
v-for="tag in coinInfo.categories"
:key="tag"
variant="light-secondary"
style="margin: 2px;"
>
{{ tag }}
</b-badge>
</div>
<div>
<!-- buy -->
<div
class="rounded d-flex justify-content-between p-2"
:class="`bg-light-${color}`"
>
<b-dropdown
id="dropdown-3"
size="sm"
text="Pairs"
:variant="color"
>
<b-dropdown-item
v-for="(pair, i) in tickers"
:key="i"
@click="selectPair(pair)"
>
<b-row style="width:400px;">
<b-col cols="4">
{{ pair.market.name }}
</b-col>
<b-col
cols="4"
class="text-uppercase text-truncate"
>
{{ coinInfo.symbol }}/{{ pair.target }}
</b-col>
<b-col
cols="4"
class="font-weight-bold"
:class="`text-${colorMap(pair.trust_score)}`"
>
${{ pair.converted_last.usd }}
</b-col>
</b-row>
</b-dropdown-item>
</b-dropdown>
<div class="text-truncate ml-1">
<sup class="text-body">
<small>$</small>
</sup>
<h2 class="d-inline mr-25">
{{ selectedTicker.converted_last.usd }}
</h2>
<sub class="text-body"><small>/ {{ selectedTicker.target }}</small></sub>
</div>
</div>
<!--/ buy -->
<b-button
block
:variant="color"
class="mt-1"
:href="selectedTicker.trade_url"
>
Buy {{ String(coinInfo.symbol).toUpperCase() }}
</b-button>
</div>
</b-col>
<b-col md="8">
<b-card-header class="d-flex justify-content-between">
<!-- size -->
<b-button-group
size="sm"
>
<b-button
:variant="type==='prices'? 'primary': 'outline-primary'"
@click="selectChart('prices')"
>
Price
</b-button>
<b-button
:variant="type !== 'prices'? 'primary': 'outline-primary'"
@click="selectChart('total_volumes')"
>
Volumes
</b-button>
</b-button-group>
<!-- size -->
<b-button-group
size="sm"
>
<b-button
:variant="days===1? 'primary': 'outline-primary'"
@click="selectDays(1)"
>
Daily
</b-button>
<b-button
:variant="days===7? 'primary': 'outline-primary'"
@click="selectDays(7)"
>
Weekly
</b-button>
<b-button
:variant="days===30? 'primary': 'outline-primary'"
@click="selectDays(30)"
>
Monthly
</b-button>
</b-button-group>
</b-card-header>
<b-card-body class="pb-0">
<!-- apex chart -->
<vue-apex-charts
type="line"
height="240"
:options="chartOptions"
:series="series"
/>
</b-card-body>
</b-col>
</b-row>
<b-card-footer class="px-0">
<div v-if="coinInfo.description && coinInfo.description.en">
{{ coinInfo.description.en || '' }}
</div>
<div class="my-1">
LINKS:
<b-button
:href="homepage"
class="mr-1"
variant="outline-primary"
size="sm"
>
<feather-icon icon="CastIcon" /> Webwite
</b-button>
<b-button
:href="twitter"
class="mr-1"
variant="outline-primary"
size="sm"
>
<feather-icon icon="TwitterIcon" /> Twitter
</b-button>
<b-button
:href="github"
class="mr-1"
variant="outline-primary"
size="sm"
>
<feather-icon icon="GithubIcon" /> Github
</b-button>
<b-button
:href="telegram"
class="mr-1"
variant="outline-primary"
size="sm"
>
<feather-icon icon="SendIcon" /> Telegram
</b-button>
<b-dropdown
v-if="coinInfo.links && coinInfo.links.blockchain_site"
id="dropdown-2"
size="sm"
text="Explorers"
variant="outline-primary"
>
<b-dropdown-item
v-for="site in coinInfo.links.blockchain_site.filter(x => x)"
:key="site"
:href="site"
>{{ site }}</b-dropdown-item>
</b-dropdown>
</div>
</b-card-footer>
</b-card>
</template>
<script>
import {
BCard, BCardHeader, BCardTitle, BCardBody, BCardText, BFormRadio, BButton, BButtonGroup, BCol, BRow,
BCardFooter, BBadge, VBTooltip, BDropdown, BDropdownItem,
} from 'bootstrap-vue'
import VueApexCharts from 'vue-apexcharts'
import { $themeColors } from '@themeConfig'
import FeatherIcon from '../../../@core/components/feather-icon/FeatherIcon.vue'
export default {
components: {
VueApexCharts,
BCard,
BCardHeader,
BCardText,
BCardTitle,
BCardBody,
BFormRadio,
BButton,
BButtonGroup,
BCol,
BRow,
BCardFooter,
BBadge,
BDropdown,
BDropdownItem,
FeatherIcon,
},
directives: {
'b-tooltip': VBTooltip,
},
data() {
return {
colors: {
green: 'success',
yellow: 'warning',
},
coinInfo: {},
type: 'prices',
days: 30,
tickers: [],
selectedTicker: {
converted_last: {},
},
marketData: {
prices: [],
volumes: [],
},
chartOptions: {
chart: {
toolbar: { show: false },
zoom: { enabled: false },
type: 'line',
dropShadow: {
enabled: true,
top: 18,
left: 2,
blur: 5,
opacity: 0.2,
},
offsetX: -10,
},
stroke: {
curve: 'smooth',
width: 4,
},
grid: {
borderColor: '#ebe9f1',
padding: {
top: -20,
bottom: 5,
left: 20,
},
},
legend: {
show: false,
},
colors: ['#df87f2'],
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
inverseColors: false,
gradientToColors: [$themeColors.primary],
shadeIntensity: 1,
type: 'horizontal',
opacityFrom: 1,
opacityTo: 1,
stops: [0, 100, 100, 100],
},
},
markers: {
size: 0,
hover: {
size: 5,
},
},
xaxis: {
type: 'datetime',
},
yaxis: {
tickAmount: 5,
labels: {
style: {
colors: '#b9b9c3',
fontSize: '0.857rem',
},
formatter(val) {
if (val > 999999999) {
return `${(val / 1000000000).toFixed(1)}b`
}
if (val > 999999) {
return `${(val / 1000000).toFixed(1)}m`
}
return val > 999 ? `${(val / 1000).toFixed(1)}k` : val.toFixed(2)
},
},
},
tooltip: {
x: { show: false },
},
},
}
},
computed: {
series() {
return [
{
name: this.type,
data: this.marketData[this.type].map(x => ({ x: x[0], y: x[1] })),
},
]
},
color() {
return this.colorMap(this.selectedTicker.trust_score)
},
homepage() {
if (this.coinInfo.links) {
return this.coinInfo.links.homepage[0] || '#'
}
return '#'
},
twitter() {
if (this.coinInfo.links) {
return this.coinInfo.links.twitter_screen_name ? `https://twitter.com/${this.coinInfo.links.twitter_screen_name}` : '#'
}
return '#'
},
telegram() {
if (this.coinInfo.links) {
return this.coinInfo.links.telegram_channel_identifier ? `https://twitter.com/${this.coinInfo.links.telegram_channel_identifier}` : '#'
}
return '#'
},
github() {
if (this.coinInfo.links) {
return this.coinInfo.links.repos_url.github[0] || '#'
}
return '#'
},
},
created() {
this.$http.getMarketChart(this.days).then(res => {
this.marketData = res
})
this.$http.getCoinInfo().then(res => {
if (res) {
this.coinInfo = res
this.tickers = res.tickers
// eslint-disable-next-line prefer-destructuring
this.selectedTicker = this.tickers[0]
}
})
},
methods: {
colorMap(v) {
return this.colors[v] || 'secondary'
},
selectChart(v) {
this.type = v
},
selectDays(v) {
this.days = v
this.$http.getMarketChart(this.days).then(res => {
this.marketData = res
})
},
selectPair(v) {
this.selectedTicker = v
},
url(which) {
return this.coinInfo[which][0] || '#'
},
},
}
</script>

View File

@ -85,8 +85,8 @@
import {
BCard, BCardHeader, BCardTitle, BCardText, BCardBody, BRow, BCol, BMedia, BMediaAside, BAvatar, BMediaBody, BPopover, BButton,
} from 'bootstrap-vue'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ArrayFieldComponent from './ArrayFieldComponent.vue'
import ObjectFieldComponent from '../ObjectFieldComponent.vue'
import ArrayFieldComponent from '../ArrayFieldComponent.vue'
export default {
components: {