New
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
> 1%
|
> 1%
|
||||||
last 2 versions
|
last 2 versions
|
||||||
not ie <= 8
|
not ie <= 10
|
||||||
|
|||||||
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dist/*
|
||||||
|
node_modules/*
|
||||||
@@ -2,6 +2,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
|
node: true,
|
||||||
},
|
},
|
||||||
'extends': [
|
'extends': [
|
||||||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
'presets': [
|
||||||
'@vue/app',
|
[
|
||||||
|
'@vue/app',
|
||||||
|
{
|
||||||
|
'useBuiltIns': 'entry',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'plugins': [
|
||||||
|
[
|
||||||
|
'transform-imports',
|
||||||
|
{
|
||||||
|
'vuetify': {
|
||||||
|
'transform': 'vuetify/es5/components/${member}', // eslint-disable-line no-template-curly-in-string
|
||||||
|
'preventFullImport': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -9,8 +9,15 @@
|
|||||||
"test:unit": "vue-cli-service test:unit"
|
"test:unit": "vue-cli-service test:unit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.18.0",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"material-design-icons-iconfont": "^3.0.3",
|
||||||
|
"tic.common": "^0.1.4",
|
||||||
"vue": "^2.5.17",
|
"vue": "^2.5.17",
|
||||||
|
"vue-axios": "^2.1.3",
|
||||||
|
"vue-i18n": "^8.1.0",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
|
"vuetify": "^1.2.5",
|
||||||
"vuex": "^3.0.1"
|
"vuex": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -22,8 +29,12 @@
|
|||||||
"@vue/test-utils": "^1.0.0-beta.20",
|
"@vue/test-utils": "^1.0.0-beta.20",
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"babel-jest": "^23.0.1",
|
"babel-jest": "^23.0.1",
|
||||||
|
"babel-plugin-transform-imports": "^1.5.1",
|
||||||
"node-sass": "^4.9.0",
|
"node-sass": "^4.9.0",
|
||||||
"sass-loader": "^7.0.1",
|
"sass-loader": "^7.0.1",
|
||||||
"vue-template-compiler": "^2.5.17"
|
"stylus": "^0.54.5",
|
||||||
|
"stylus-loader": "^3.0.2",
|
||||||
|
"vue-template-compiler": "^2.5.17",
|
||||||
|
"workerize-loader": "^1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/App.vue
23
src/App.vue
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<v-app id="app">
|
||||||
<div id="nav">
|
<router-view />
|
||||||
<router-link to="/">Home</router-link> |
|
</v-app>
|
||||||
<router-link to="/about">About</router-link>
|
|
||||||
</div>
|
|
||||||
<router-view/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "./styles/share";
|
||||||
#app {
|
#app {
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -16,14 +13,8 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
#nav {
|
|
||||||
padding: 30px;
|
html, body, #app {
|
||||||
a {
|
@include full-page;
|
||||||
font-weight: bold;
|
|
||||||
color: #2c3e50;
|
|
||||||
&.router-link-exact-active {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
BIN
src/assets/images/bg.jpg
Normal file
BIN
src/assets/images/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 277 KiB |
BIN
src/assets/images/logo.png
Normal file
BIN
src/assets/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
15
src/i18n/en.js
Normal file
15
src/i18n/en.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
login: {
|
||||||
|
title: 'TIC Node Console',
|
||||||
|
inputLabel: '请输入密语,例如:Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eos, molestias, quisquam?',
|
||||||
|
loginBtn: 'Sign in',
|
||||||
|
errorMnemonic: 'Invalid secret word',
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
titleOwner: 'Owner',
|
||||||
|
titleChain: 'Chain',
|
||||||
|
titleNode: 'Node',
|
||||||
|
titleNetwork: 'Network',
|
||||||
|
titleMarket: 'Market',
|
||||||
|
},
|
||||||
|
}
|
||||||
39
src/i18n/index.js
Normal file
39
src/i18n/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import VueI18n from 'vue-i18n'
|
||||||
|
import { storage } from '../services'
|
||||||
|
import en from './en'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
Vue.use(VueI18n)
|
||||||
|
|
||||||
|
const loadedLanguages = [ 'en' ]
|
||||||
|
|
||||||
|
export const i18n = new VueI18n({
|
||||||
|
locale: 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages: { en },
|
||||||
|
})
|
||||||
|
|
||||||
|
function setI18nLanguage(lang) {
|
||||||
|
i18n.locale = lang
|
||||||
|
axios.defaults.headers.common['Accept-Language'] = lang
|
||||||
|
document.querySelector('html').setAttribute('lang', lang)
|
||||||
|
storage.currentLanguage = lang
|
||||||
|
return lang
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadLanguageAsync(lang) {
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
if (i18n.locale !== lang) {
|
||||||
|
if (!loadedLanguages.includes(lang)) {
|
||||||
|
const langRes = await import(`@/i18n/${lang}`)
|
||||||
|
loadedLanguages.push(lang)
|
||||||
|
i18n.setLocaleMessage(lang, langRes.default)
|
||||||
|
return setI18nLanguage(lang)
|
||||||
|
}
|
||||||
|
return setI18nLanguage(lang)
|
||||||
|
}
|
||||||
|
return lang
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLanguageAsync(storage.currentLanguage)
|
||||||
15
src/i18n/zh-CN.js
Normal file
15
src/i18n/zh-CN.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
login: {
|
||||||
|
title: 'TIC Node 节点控制台',
|
||||||
|
inputLabel: '请输入密语,例如:Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eos, molestias, quisquam?',
|
||||||
|
loginBtn: '登录',
|
||||||
|
errorMnemonic: '输入的密语无效',
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
titleOwner: '个人中心',
|
||||||
|
titleChain: '区块/交易',
|
||||||
|
titleNode: '当前节点',
|
||||||
|
titleNetwork: '网络链接',
|
||||||
|
titleMarket: '应用商城',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
|
||||||
import { app } from './services'
|
import { app } from './services'
|
||||||
|
import { i18n } from './i18n'
|
||||||
|
import store from './store'
|
||||||
|
import './services/ui'
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
Vue.config.devtools = app.dev
|
Vue.config.devtools = app.dev
|
||||||
@@ -11,5 +13,6 @@ Vue.config.performance = !app.dev
|
|||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
|
i18n,
|
||||||
render: h => h(App),
|
render: h => h(App),
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
|||||||
39
src/plugins/vuetify.js
Normal file
39
src/plugins/vuetify.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import {
|
||||||
|
Vuetify,
|
||||||
|
VApp,
|
||||||
|
VNavigationDrawer,
|
||||||
|
VFooter,
|
||||||
|
VList,
|
||||||
|
VBtn,
|
||||||
|
VIcon,
|
||||||
|
VGrid,
|
||||||
|
VToolbar,
|
||||||
|
transitions
|
||||||
|
} from 'vuetify'
|
||||||
|
import 'vuetify/src/stylus/app.styl'
|
||||||
|
|
||||||
|
Vue.use(Vuetify, {
|
||||||
|
components: {
|
||||||
|
VApp,
|
||||||
|
VNavigationDrawer,
|
||||||
|
VFooter,
|
||||||
|
VList,
|
||||||
|
VBtn,
|
||||||
|
VIcon,
|
||||||
|
VGrid,
|
||||||
|
VToolbar,
|
||||||
|
transitions
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
primary: '#ee44aa',
|
||||||
|
secondary: '#424242',
|
||||||
|
accent: '#82B1FF',
|
||||||
|
error: '#FF5252',
|
||||||
|
info: '#2196F3',
|
||||||
|
success: '#4CAF50',
|
||||||
|
warning: '#FFC107'
|
||||||
|
},
|
||||||
|
customProperties: true,
|
||||||
|
iconfont: 'md',
|
||||||
|
})
|
||||||
@@ -1,23 +1,68 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Router from 'vue-router'
|
import Router from 'vue-router'
|
||||||
import Home from './views/Home.vue'
|
import store from './store'
|
||||||
|
|
||||||
|
import Home from './views/Home'
|
||||||
|
import Login from './views/Login'
|
||||||
|
import Owner from './views/Owner'
|
||||||
|
import Chain from './views/Chain'
|
||||||
|
import Node from './views/Node'
|
||||||
|
import Network from './views/Network'
|
||||||
|
import Market from './views/Market'
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
|
|
||||||
export default new Router({
|
const router = new Router({
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
|
||||||
component: Home,
|
component: Home,
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: 'login',
|
||||||
name: 'about',
|
component: Login,
|
||||||
// route level code-splitting
|
},
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
{
|
||||||
// which is lazy-loaded when the route is visited.
|
path: 'owner',
|
||||||
component: () => import('./views/About.vue'),
|
component: Owner,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'chain',
|
||||||
|
component: Chain,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'node',
|
||||||
|
component: Node,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'network',
|
||||||
|
component: Network,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'market',
|
||||||
|
component: Market,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
redirect: 'owner',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, _, next) => {
|
||||||
|
const loginPage = to.path === '/login'
|
||||||
|
const signed = store.getters.isLogin
|
||||||
|
if (!loginPage && !signed) {
|
||||||
|
next('/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (loginPage && signed) {
|
||||||
|
next('/owner')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
|
|||||||
1
src/services/api/index.js
Normal file
1
src/services/api/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as ticApi } from './tic'
|
||||||
6
src/services/api/tic-bg.js
Normal file
6
src/services/api/tic-bg.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { Crypto as ticCrypto } from 'tic.common'
|
||||||
|
|
||||||
|
export const secword2PubKey = secword => ticCrypto.seckey2pubkey(secword)
|
||||||
|
export const secword2Address = secword => ticCrypto.secword2address(secword)
|
||||||
|
export const isAddress = address => ticCrypto.isAddress(address)
|
||||||
|
export const secword2Account = secword => ticCrypto.secword2account(secword)
|
||||||
11
src/services/api/tic.js
Normal file
11
src/services/api/tic.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// eslint-disable-next-line
|
||||||
|
import * as worker from 'workerize-loader!./tic-bg'
|
||||||
|
|
||||||
|
const instance = worker()
|
||||||
|
|
||||||
|
export default {
|
||||||
|
secword2PubKey: async secword => instance.secword2PubKey(secword),
|
||||||
|
secword2Address: async secword => instance.secword2Address(secword),
|
||||||
|
isAddress: async address => instance.isAddress(address),
|
||||||
|
secword2Account: async secword => instance.secword2Account(secword),
|
||||||
|
}
|
||||||
@@ -1 +1,3 @@
|
|||||||
export { default as app } from './app'
|
export { default as app } from './app'
|
||||||
|
export { default as storage } from './storage'
|
||||||
|
export * from './api'
|
||||||
|
|||||||
40
src/services/storage.js
Normal file
40
src/services/storage.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const KEY_SETTING_STORAGE = 'KEY_SETTING_STORAGE'
|
||||||
|
|
||||||
|
const retrieveSettings = () => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(sessionStorage.getItem(KEY_SETTING_STORAGE)) || {}
|
||||||
|
} catch (e) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const restoreSettings =
|
||||||
|
settings => sessionStorage.setItem(KEY_SETTING_STORAGE, JSON.stringify(settings))
|
||||||
|
|
||||||
|
const supportLangs = [
|
||||||
|
{
|
||||||
|
code: 'en',
|
||||||
|
label: 'English',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'zh-CN',
|
||||||
|
label: '简体中文',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
get currentLanguage() {
|
||||||
|
const settings = retrieveSettings()
|
||||||
|
let { lang } = settings
|
||||||
|
if (!lang) {
|
||||||
|
settings.lang = navigator.language
|
||||||
|
lang = settings.lang
|
||||||
|
}
|
||||||
|
return lang
|
||||||
|
},
|
||||||
|
set currentLanguage(lang) {
|
||||||
|
const settings = retrieveSettings()
|
||||||
|
settings.lang = lang
|
||||||
|
restoreSettings(settings)
|
||||||
|
},
|
||||||
|
supportLangs,
|
||||||
|
}
|
||||||
30
src/services/ui.js
Normal file
30
src/services/ui.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import { Vuetify } from 'vuetify'
|
||||||
|
import {
|
||||||
|
VApp,
|
||||||
|
VBtn,
|
||||||
|
VMenu,
|
||||||
|
VIcon,
|
||||||
|
VSelect,
|
||||||
|
VTextField,
|
||||||
|
} from 'vuetify/es5/components'
|
||||||
|
import { Ripple } from 'vuetify/es5/directives'
|
||||||
|
import 'vuetify/dist/vuetify.min.css'
|
||||||
|
import 'material-design-icons-iconfont/dist/material-design-icons.css'
|
||||||
|
|
||||||
|
Vue.use(Vuetify, {
|
||||||
|
theme: {
|
||||||
|
primary: '#4666b9',
|
||||||
|
secondary: '#dfa24e',
|
||||||
|
accent: '#FFCDD2',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
Vue.component('VApp', VApp)
|
||||||
|
Vue.component('VBtn', VBtn)
|
||||||
|
Vue.component('VMenu', VMenu)
|
||||||
|
Vue.component('VIcon', VIcon)
|
||||||
|
Vue.component('VSelect', VSelect)
|
||||||
|
Vue.component('VTextField', VTextField)
|
||||||
|
|
||||||
|
Vue.directive('Ripple', Ripple)
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import Vuex from 'vuex'
|
|
||||||
|
|
||||||
export default new Vuex.Store({
|
|
||||||
state: {},
|
|
||||||
mutations: {},
|
|
||||||
actions: {},
|
|
||||||
getters: {},
|
|
||||||
})
|
|
||||||
@@ -1,17 +1,27 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import chain from './chain'
|
import * as modules from './modules'
|
||||||
import market from './market'
|
import { app } from '../services'
|
||||||
import network from './network'
|
import rootStore from './rootStore'
|
||||||
import owner from './owner'
|
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
/**
|
||||||
modules: {
|
* @type {Store}
|
||||||
chain,
|
*/
|
||||||
owner,
|
const store = new Vuex.Store({
|
||||||
market,
|
modules: { ...modules },
|
||||||
network,
|
strict: app.dev,
|
||||||
},
|
...rootStore,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept(['./modules'], () => {
|
||||||
|
const newModules = require('./modules')
|
||||||
|
store.hotUpdate({
|
||||||
|
modules: { ...newModules },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default store
|
||||||
|
|||||||
8
src/store/modules/chain.js
Normal file
8
src/store/modules/chain.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
getters: {},
|
||||||
|
}
|
||||||
6
src/store/modules/index.js
Normal file
6
src/store/modules/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export { default as chain } from './chain'
|
||||||
|
export { default as market } from './market'
|
||||||
|
export { default as network } from './network'
|
||||||
|
export { default as node } from './node'
|
||||||
|
export { default as owner } from './owner'
|
||||||
|
export { default as login } from './login'
|
||||||
50
src/store/modules/login.js
Normal file
50
src/store/modules/login.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { ticApi } from '../../services'
|
||||||
|
import router from '../../router'
|
||||||
|
import { i18n } from '../../i18n'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
|
||||||
|
state: {
|
||||||
|
mnemonic: '',
|
||||||
|
errorMnemonic: '',
|
||||||
|
signing: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
inputError: state => !!state.errorMnemonic,
|
||||||
|
},
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
updateMnemonic(state, mnemonic) {
|
||||||
|
if (state.errorMnemonic) {
|
||||||
|
state.errorMnemonic = ''
|
||||||
|
}
|
||||||
|
state.mnemonic = mnemonic
|
||||||
|
},
|
||||||
|
|
||||||
|
updateError(state, error) {
|
||||||
|
state.errorMnemonic = error
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSign(state, signing) {
|
||||||
|
state.signing = signing
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async login({ state, commit }) {
|
||||||
|
commit('updateSign', true)
|
||||||
|
const account = (await ticApi.secword2Account(state.mnemonic)) || {}
|
||||||
|
console.log(account)
|
||||||
|
commit('updateSign', false)
|
||||||
|
if (await ticApi.isAddress(account.address)) {
|
||||||
|
commit('updateAccount', _.omit(account, 'seckey'), { root: true })
|
||||||
|
router.push('/owner')
|
||||||
|
} else {
|
||||||
|
commit('updateError', i18n.t('login.errorMnemonic'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import Vuex from 'vuex'
|
export default {
|
||||||
|
namespaced: true,
|
||||||
export default new Vuex.Store({
|
|
||||||
state: {},
|
state: {},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
actions: {},
|
actions: {},
|
||||||
getters: {},
|
getters: {},
|
||||||
})
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import Vuex from 'vuex'
|
export default {
|
||||||
|
namespaced: true,
|
||||||
export default new Vuex.Store({
|
|
||||||
state: {},
|
state: {},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
actions: {},
|
actions: {},
|
||||||
getters: {},
|
getters: {},
|
||||||
})
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import Vuex from 'vuex'
|
export default {
|
||||||
|
namespaced: true,
|
||||||
export default new Vuex.Store({
|
|
||||||
state: {},
|
state: {},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
actions: {},
|
actions: {},
|
||||||
getters: {},
|
getters: {},
|
||||||
})
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import Vuex from 'vuex'
|
export default {
|
||||||
|
namespaced: true,
|
||||||
export default new Vuex.Store({
|
|
||||||
state: {},
|
state: {},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
actions: {},
|
actions: {},
|
||||||
getters: {},
|
getters: {},
|
||||||
})
|
}
|
||||||
96
src/store/rootStore.js
Normal file
96
src/store/rootStore.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { storage } from '../services'
|
||||||
|
import { loadLanguageAsync } from '../i18n'
|
||||||
|
import router from '../router'
|
||||||
|
|
||||||
|
const KEY_ACCOUNT = 'KEY_ACCOUNT'
|
||||||
|
|
||||||
|
function retrieveAccount() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(sessionStorage.getItem(KEY_ACCOUNT))
|
||||||
|
} catch (e) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeAccount(account) {
|
||||||
|
sessionStorage.setItem(KEY_ACCOUNT, JSON.stringify(account))
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAccount() {
|
||||||
|
sessionStorage.removeItem(KEY_ACCOUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sideMenus = [
|
||||||
|
{
|
||||||
|
title: 'home.titleOwner',
|
||||||
|
icon: 'group',
|
||||||
|
path: '/owner',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'home.titleChain',
|
||||||
|
icon: 'public',
|
||||||
|
path: '/chain',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'home.titleNode',
|
||||||
|
icon: 'all_inclusive',
|
||||||
|
path: '/node',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'home.titleNetwork',
|
||||||
|
icon: 'language',
|
||||||
|
path: '/network',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'home.titleMarket',
|
||||||
|
icon: 'apps',
|
||||||
|
path: '/market',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
state: {
|
||||||
|
account: retrieveAccount() || {},
|
||||||
|
supportLangs: storage.supportLangs,
|
||||||
|
currentLang: storage.currentLanguage,
|
||||||
|
sideMenus,
|
||||||
|
sideCollapsed: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
isLogin: state => state.account.pubkey,
|
||||||
|
|
||||||
|
currentPath: () => router.currentRoute.path,
|
||||||
|
},
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
updateCurrentLang(state, lang) {
|
||||||
|
state.currentLang = lang
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAccount(state, account) {
|
||||||
|
state.account = account || {}
|
||||||
|
storeAccount(state.account)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRoute(_, destination) {
|
||||||
|
router.push(destination)
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleCollapse(state) {
|
||||||
|
state.sideCollapsed = !state.sideCollapsed
|
||||||
|
},
|
||||||
|
|
||||||
|
logout(state) {
|
||||||
|
state.account = {}
|
||||||
|
clearAccount()
|
||||||
|
router.push('/login')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async updateCurrentLang({ commit }, lang) {
|
||||||
|
commit('updateCurrentLang', await loadLanguageAsync(lang))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
12
src/styles/_share.scss
Normal file
12
src/styles/_share.scss
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
$primaryColor: #4666b9;
|
||||||
|
$secondaryColor: #dfa24e;
|
||||||
|
$accentColor: #FFCDD2;
|
||||||
|
|
||||||
|
@mixin full-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-icon {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
16
src/views/Chain.vue
Normal file
16
src/views/Chain.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chain-page">
|
||||||
|
Chain
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Chain',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.chain-page {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,255 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<img alt="Vue logo" src="../assets/logo.png" />
|
<div class="side-menus" :class="{ collapsed: sideCollapsed || !isLogin }">
|
||||||
|
<div class="side-title">
|
||||||
|
<img src="../assets/images/logo.png" />
|
||||||
|
<span class="white--text">TIME IN CHAIN</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="side-menu-item"
|
||||||
|
v-for="menu in sideMenus"
|
||||||
|
@click="updateRoute(menu.path)"
|
||||||
|
:class="{ active: sideSelected === menu.path }"
|
||||||
|
:key="menu.path">
|
||||||
|
<v-icon dark x-large>{{ menu.icon }}</v-icon>
|
||||||
|
<div class="title white--text">{{ $t(menu.title) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="app-body">
|
||||||
|
<div class="app-header" :class="{ collapsed: !isLogin }">
|
||||||
|
<div class="indicator" @click="toggleCollapse" :class="{ collapsed: sideCollapsed }">
|
||||||
|
<v-icon class="right">chevron_right</v-icon>
|
||||||
|
<v-icon class="left">chevron_left</v-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="title primary--text">{{ $t('login.title') }}</div>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
color="secondary"
|
||||||
|
prepend-icon="language"
|
||||||
|
:value="currentLang"
|
||||||
|
@input="updateCurrentLang"
|
||||||
|
:items="supportLangs"
|
||||||
|
item-value="code"
|
||||||
|
item-text="label"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
<v-icon @click="logout">power_settings_new</v-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-view class="app-container" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sideSelected: this.$route.path,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState([
|
||||||
|
'sideMenus',
|
||||||
|
'sideCollapsed',
|
||||||
|
'supportLangs',
|
||||||
|
'currentLang',
|
||||||
|
]),
|
||||||
|
...mapGetters([
|
||||||
|
'isLogin',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapMutations([
|
||||||
|
'updateRoute',
|
||||||
|
'toggleCollapse',
|
||||||
|
'logout',
|
||||||
|
]),
|
||||||
|
...mapActions([
|
||||||
|
'updateCurrentLang',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'$route.path'(to) {
|
||||||
|
this.sideSelected = to
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../styles/share";
|
||||||
|
.home {
|
||||||
|
&, .app-body {
|
||||||
|
@include full-page;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
$sideWidth: 260px;
|
||||||
|
$headerHeight: 64px;
|
||||||
|
.side-menus {
|
||||||
|
&.collapsed {
|
||||||
|
margin-left: -$sideWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
width: $sideWidth;
|
||||||
|
background: url('../assets/images/bg.jpg') center;
|
||||||
|
background-size: cover;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
box-shadow: 1px 0 2px #ddd;
|
||||||
|
transition: margin-left .4s;
|
||||||
|
|
||||||
|
.side-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
& > img {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-menu-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 40px 0;
|
||||||
|
|
||||||
|
transition: background-color .4s;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.v-icon {
|
||||||
|
font-size: 48px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 1.2em!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($secondaryColor, .15);
|
||||||
|
}
|
||||||
|
|
||||||
|
$indicatorSize: 24px;
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: $indicatorSize;
|
||||||
|
height: $indicatorSize;
|
||||||
|
background-color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
right: -$indicatorSize*2;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) rotate(45deg);
|
||||||
|
transition: right .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: rgba(white, .25);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
right: -$indicatorSize/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
&.collapsed {
|
||||||
|
margin-top: -$headerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
transition: margin-top .4s;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: $headerHeight;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 1px 2px #ddd;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
position: relative;
|
||||||
|
$iconSize: 28px;
|
||||||
|
width: $iconSize;
|
||||||
|
align-self: stretch;
|
||||||
|
margin: 0 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.v-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: 0;
|
||||||
|
font-size: $iconSize!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
left: 45%;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
left: -15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
.left {
|
||||||
|
left: -15%;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
left: 45%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-select {
|
||||||
|
width: 160px;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
align-self: stretch;
|
||||||
|
margin: 20px;
|
||||||
|
width: 1px;
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .v-icon {
|
||||||
|
margin: 0 40px 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
106
src/views/Login.vue
Normal file
106
src/views/Login.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-page">
|
||||||
|
<img src="../assets/images/logo.png">
|
||||||
|
|
||||||
|
<h1 class="white--text">{{ $t('login.title') }}</h1>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
color="secondary"
|
||||||
|
dark autofocus
|
||||||
|
single-line
|
||||||
|
:error="inputError"
|
||||||
|
:error-messages="errorMnemonic"
|
||||||
|
:value="mnemonic"
|
||||||
|
:loading="signing"
|
||||||
|
@input="updateMnemonic"
|
||||||
|
:label="$t('login.inputLabel')" />
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
color="secondary" dark
|
||||||
|
prepend-icon="language"
|
||||||
|
:value="currentLang"
|
||||||
|
@input="updateCurrentLang"
|
||||||
|
:items="supportLangs"
|
||||||
|
item-value="code"
|
||||||
|
item-text="label"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-btn :loading="signing" dark large color="transparent" @click="login">
|
||||||
|
{{ $t('login.loginBtn') }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
createNamespacedHelpers,
|
||||||
|
mapState as mapRootState,
|
||||||
|
mapActions as mapRootActions,
|
||||||
|
} from 'vuex'
|
||||||
|
|
||||||
|
const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers('login')
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Login',
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState([
|
||||||
|
'mnemonic',
|
||||||
|
'errorMnemonic',
|
||||||
|
'signing',
|
||||||
|
]),
|
||||||
|
...mapGetters([
|
||||||
|
'inputError',
|
||||||
|
]),
|
||||||
|
...mapRootState([
|
||||||
|
'supportLangs',
|
||||||
|
'currentLang',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapMutations([
|
||||||
|
'updateMnemonic',
|
||||||
|
]),
|
||||||
|
...mapActions([
|
||||||
|
'login',
|
||||||
|
]),
|
||||||
|
...mapRootActions([
|
||||||
|
'updateCurrentLang',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.login-page {
|
||||||
|
background: url('../assets/images/bg.jpg') center;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 24%;
|
||||||
|
|
||||||
|
& > img {
|
||||||
|
width: 120px;
|
||||||
|
margin-top: -10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > h1 {
|
||||||
|
margin: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& /deep/ .v-text-field {
|
||||||
|
align-self: stretch;
|
||||||
|
flex-grow: 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& /deep/ .v-btn {
|
||||||
|
align-self: stretch;
|
||||||
|
box-shadow: 0 4px 14px #111 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
src/views/Market.vue
Normal file
16
src/views/Market.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="market-page">
|
||||||
|
Market
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Market',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.market-page {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
src/views/Network.vue
Normal file
16
src/views/Network.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="network-page">
|
||||||
|
Network
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Network',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.network-page {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
src/views/Node.vue
Normal file
16
src/views/Node.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="node-page">
|
||||||
|
Node
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Node',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.node-page {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
src/views/Owner.vue
Normal file
16
src/views/Owner.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div class="owner-page">
|
||||||
|
Owner
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Owner',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.owner-page {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user