OSDN Git Service

feat: add lock page
authorj <coffce404@gmail.com>
Wed, 24 Feb 2021 09:54:00 +0000 (17:54 +0800)
committerj <coffce404@gmail.com>
Wed, 24 Feb 2021 09:54:00 +0000 (17:54 +0800)
13 files changed:
src/assets/img/locked.png [new file with mode: 0644]
src/assets/language/cn.js
src/assets/language/en.js
src/components/MenubarComponent.vue
src/components/toast/index.js
src/models/lock.js [new file with mode: 0644]
src/popup.js
src/prompt.js
src/router.js
src/views/lock.vue [new file with mode: 0644]
src/views/welcome/creation.vue
src/views/welcome/import.vue
src/views/welcome/password/setting.vue

diff --git a/src/assets/img/locked.png b/src/assets/img/locked.png
new file mode 100644 (file)
index 0000000..1f8f173
Binary files /dev/null and b/src/assets/img/locked.png differ
index 5a1ddf7..f9a61ff 100644 (file)
@@ -329,6 +329,15 @@ const cn = {
     mnemonicHint:'请抄写助记词,并妥善保管',
     ok:'已完成'
   },
+  lock: {
+    title: '安全锁定',
+    subtitle: '请输入钱包密码以解锁',
+    placeholder: '请输入密码',
+    forget: '忘记密码?',
+    import: '重新导入钱包',
+    passwordEmpty: '请输入密码',
+    passwordError: '密码错误'
+  },
   protocol: {
     title: '服务协议',
     content: `尊敬的用户:
index 847c0e9..6496714 100644 (file)
@@ -327,6 +327,15 @@ const en = {
     mnemonicHint:'Please copy the mnemonic and keep it.',
     ok:'Completed'
   },
+  lock: {
+    title: 'Lock',
+    subtitle: 'Please enter the password',
+    placeholder: 'password',
+    forget: 'forget password?',
+    import: 'import wallet',
+    passwordEmpty: 'Please enter the password',
+    passwordError: 'Password is wrong'
+  },
   protocol: {
     title: 'Service Agreement',
     content:`Dear users:
index 18ae91e..6516202 100644 (file)
                   case RouteNames.ENABLE:
                   case RouteNames.BAPP_PROMPT:
                   case RouteNames.PRIVACY:
-                  case RouteNames.PROTOCOL: return false;
-                  default: return true;
+                  case RouteNames.PROTOCOL: 
+                  case RouteNames.LOCK: 
+                    return false;
+                  default: 
+                    return true;
               }
             },
 
index 15c98fb..87de8df 100644 (file)
@@ -1,13 +1,15 @@
 import Api from './js/api.js';
 import Vue from 'vue';
-import toast from './js/Component.vue';
+import Toast from './js/Component.vue';
 
+export let toast
 const toastInstance = {
   i18n: null,
   install: function (Vue, options) {
-    Vue.component('v-toast', toast);
+    Vue.component('v-toast', Toast);
     let methods = Api(Vue, options);
     Vue.$toast = methods;
+    toast = methods;
     Vue.prototype.$toast = methods;
     this.i18n = options;
   },
diff --git a/src/models/lock.js b/src/models/lock.js
new file mode 100644 (file)
index 0000000..1b20554
--- /dev/null
@@ -0,0 +1,11 @@
+const STORGE_KEY = 'LOCK_TIME'
+let lockTime = localStorage.getItem(STORGE_KEY)
+
+export function updateLockTime () {
+  lockTime = Date.now()
+  localStorage.setItem('LOCK_TIME', lockTime)
+}
+export function isNeedLock () {
+  const oneHour = 1000 * 60 * 60
+  return !lockTime || Date.now() - lockTime > oneHour
+}
index e02e82c..d96c01c 100644 (file)
@@ -36,6 +36,8 @@ import { Vue as VueIntegration } from "@sentry/integrations";
 import { Integrations } from '@sentry/tracing';
 import BytomObj from "./utils/Bytom";
 
+import { updateLockTime, isNeedLock } from '@/models/lock'
+
 store.dispatch(Actions.LOAD_BYTOM).then(() => {
   Vue.use(VueI18n)
   const i18n = new VueI18n({
@@ -135,6 +137,13 @@ store.dispatch(Actions.LOAD_BYTOM).then(() => {
   router.beforeEach((to, from, next) => {
     // wallet init
 
+    if (!to.meta.nolock) {
+      if (store.getters.currentAccount && isNeedLock()) {
+        next({ name: 'lock', query: { to: to.name } })
+        return
+      } 
+      updateLockTime()
+    }
     if (!(store.getters.currentAccount) && to.name == 'home') {
       next({ name: 'welcome' })
       let newURL = `${apis.runtime.getURL('pages/prompt.html')}#/welcome`;
index 1d58225..8c1a8f3 100644 (file)
@@ -34,6 +34,8 @@ import { Vue as VueIntegration } from "@sentry/integrations";
 import { Integrations } from '@sentry/tracing';
 import BytomObj from "./utils/Bytom";
 
+import { updateLockTime, isNeedLock } from '@/models/lock'
+
 store.dispatch(Actions.LOAD_BYTOM).then(() => {
   Vue.use(VueI18n)
   const i18n = new VueI18n({
@@ -135,12 +137,20 @@ store.dispatch(Actions.LOAD_BYTOM).then(() => {
   router.beforeEach((to, from, next) => {
     // wallet init
 
+    if (!to.meta.nolock) {
+      if (store.getters.currentAccount && isNeedLock()) {
+        next({ name: 'lock', query: { to: to.name } })
+        return
+      } 
+      updateLockTime()
+    }
     if (!(store.getters.currentAccount) && to.name == 'home') {
       next({ name: 'welcome' })
       let newURL = `${apis.runtime.getURL('pages/prompt.html')}#/welcome`;
       chrome.tabs.create({ url: newURL });
       return
-    }else if (!(store.getters.currentAccount && store.getters.vMnemonic)  && to.name == 'home') {
+    }
+    if (!(store.getters.currentAccount && store.getters.vMnemonic)  && to.name == 'home') {
       next({ name: 'welcome-verify-mnemonic' })
       let newURL = `${apis.runtime.getURL('pages/prompt.html')}#/mnemonic`;
       chrome.tabs.create({ url: newURL });
index 76548c0..1eac706 100644 (file)
@@ -28,6 +28,8 @@ export const RouteNames = {
   WALLETS:'wallets',
   BAPP:'bapp',
   BAPP_PROMPT:'bapp-prompt',
+
+  LOCK: 'lock'
 };
 
 const routers = [
@@ -85,7 +87,7 @@ const routers = [
   {
     path: '/enable',
     name: RouteNames.ENABLE,
-    meta: { title: '授权' },
+    meta: { title: '授权', nolock: true },
     component: resolve => {
       require(['@/views/prompts/authentication.vue'], resolve)
     }
@@ -93,7 +95,7 @@ const routers = [
   {
     path: '/bappPrompt',
     name: RouteNames.BAPP_PROMPT,
-    meta: { title: 'Bapp请求' },
+    meta: { title: 'Bapp请求', nolock: true },
     component: resolve => {
       require(['@/views/prompts/bappPrompt.vue'], resolve)
     }
@@ -176,14 +178,14 @@ const routers = [
   {
     path: '/protocol',
     name: RouteNames.PROTOCOL,
-    meta: { title: '用户协议' },
+    meta: { title: '用户协议', nolock: true },
     component: resolve => {
       require(['@/views/welcome/protocol.vue'], resolve)
     }
   },{
     path: '/privacy',
     name: RouteNames.PRIVACY,
-    meta: { title: '隐私服务' },
+    meta: { title: '隐私服务', nolock: true },
     component: resolve => {
       require(['@/views/welcome/privacy.vue'], resolve)
     }
@@ -191,7 +193,7 @@ const routers = [
   {
     path: '/welcome',
     name: RouteNames.ENTRY,
-    meta: { title: '创建账户' },
+    meta: { title: '创建账户', nolock: true },
     component: resolve => {
       require(['@/views/welcome/welcome.vue'], resolve)
     }
@@ -199,7 +201,7 @@ const routers = [
   {
     path: '/creation',
     name: RouteNames.CREATE_ACCOUNT,
-    meta: { title: '创建账户' },
+    meta: { title: '创建账户', nolock: true },
     component: resolve => {
       require(['@/views/welcome/creation.vue'], resolve)
     }
@@ -215,7 +217,7 @@ const routers = [
   {
     path: '/verify-mnemonic',
     name: RouteNames.VERIFY_MNEMONIC,
-    meta: { title: '验证助记词' },
+    meta: { title: '验证助记词', nolock: true },
     component: resolve => {
       require(['@/views/welcome/verifyMnemonic.vue'], resolve)
     }
@@ -223,7 +225,7 @@ const routers = [
   {
     path: '/import',
     name: RouteNames.RESTORE_ACCOUNT,
-    meta: { title: '创建账户' },
+    meta: { title: '创建账户', nolock: true },
     component: resolve => {
       require(['@/views/welcome/import.vue'], resolve)
     }
@@ -231,10 +233,18 @@ const routers = [
   {
     path: '/restore-password',
     name: RouteNames.SETTING_PASSWORD,
-    meta: { title: '密码' },
+    meta: { title: '密码', nolock: true },
     component: resolve => {
       require(['@/views/welcome/password/setting.vue'], resolve)
     }
+  },
+  {
+    path: '/lock',
+    name: RouteNames.LOCK,
+    meta: { title: '锁屏', nolock: true },
+    component: resolve => {
+      require(['@/views/lock.vue'], resolve)
+    }
   }
 ]
 export default routers
diff --git a/src/views/lock.vue b/src/views/lock.vue
new file mode 100644 (file)
index 0000000..2c20248
--- /dev/null
@@ -0,0 +1,127 @@
+<template>
+  <div class="lock-page">
+    <div class="main">
+      <img class="img-lock" src="@/assets/img/locked.png" alt="">
+      <div class="title">{{ $t('lock.title') }}</div>
+      <div class="subtitle">{{ $t('lock.subtitle') }}</div> 
+      <input 
+        class="input" 
+        type="password" 
+        v-model="password"
+        :class="{ error: inputError }"
+        :placeholder="$t('lock.placeholder')" 
+        @input="inputError = false"
+        @keyup.enter="onSubmit"
+      />
+      <div class="tip">
+        {{ $t('lock.forget') }} <a @click="$router.push({ name: 'welcome-import' })">{{ $t('lock.import') }}</a>
+      </div>
+      <div class="btn-submit btn btn-primary btn-round float-right" @click="onSubmit">
+        <i class="iconfont icon-right-arrow"></i>
+      </div>
+    </div>
+    <Footer />
+  </div>
+</template>
+
+<script>
+import { store } from '@/store/store'
+import { toast } from '@/components/toast'
+import account from "@/models/account"
+import { updateLockTime } from '@/models/lock'
+import { log } from 'util'
+
+export default {
+  data () {
+    return {
+      password: '',
+      inputError: false
+    }
+  },
+  computed: {
+    currentAccount: () => store.state.bytom.currentAccount
+  },
+  methods: {
+    onSubmit () {
+      if (!this.password) {
+        this.inputError = true
+        toast.error(this.$t('lock.passwordEmpty'))
+        return
+      }
+      if (!account.isValidPassword(this.currentAccount.keystore, this.password)) {
+        this.inputError = true
+        toast.error(this.$t('lock.passwordError'))
+        return
+      }
+      
+      // success
+      updateLockTime()
+      this.$router.replace({ name: this.$route.query.to })
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.main {
+  margin: 0 auto;
+  padding-top: 80px;
+  height: 600px;
+  width: 60%;
+  min-width: 320px;
+  max-width: 600px;
+  text-align: center;
+}
+.img-lock {
+  margin-bottom: 16px;
+  width: 72px;
+  height: 72px;
+}
+.title {
+  margin-bottom: 4px;
+  line-height: 40px;
+  font-weight: 600;
+  font-size: 28px;
+  color: rgba(0, 0, 0, 0.88);
+}
+.subtitle {
+  margin-bottom: 32px;
+  color: rgba(0, 0, 0, 0.24);
+  line-height: 24px;
+}
+.input {
+  box-sizing: border-box;
+  padding: 0 20px;
+  height: 56px;
+  width: 100%;
+  line-height: 56px;
+  outline: none;
+  border: 1px solid #EBEBEB;
+  border-radius: 8px;
+
+  &:focus {
+    border-color: #D6D6D6;
+  }
+  &::placeholder {
+    color: rgba(0, 0, 0, 0.24);
+  }
+  &.error {
+    border-color: #DF1E1E;
+  }
+}
+.tip {
+  margin-top: 8px;
+  font-size: 14px;
+  color: rgba(0, 0, 0, 0.88);
+  text-align: left;
+
+  > a {
+    color: #004EE4;
+  }
+}
+.btn-submit {
+  margin-top: 16px;
+  padding: 0;
+  line-height: 54px;
+}
+</style>
index b932382..7d6b274 100644 (file)
@@ -123,6 +123,7 @@ import getLang from "../../assets/language/sdk";
 import { mapActions, mapGetters, mapState } from 'vuex'
 import * as Actions from '@/store/constants';
 import { required, sameAs } from "vuelidate/lib/validators";
+import { updateLockTime } from '@/models/lock';
 
 
 let testNet = null;
@@ -260,6 +261,7 @@ export default {
 
             account.createKey(this.formItem.accAlias, null, this.formItem.passwd1, this).then(() => {
                 loader.hide();
+                updateLockTime();
                 this.formItem = {};
                 this.$router.push('/mnemonic');
             }).catch(error => {
index ebcd6cb..8c8edb5 100644 (file)
   <div>
     <div class="warp bg-white">
       <div class="header color-black">
-        <BackButton des="welcome"/>
+        <BackButton />
         <h1>
           <div class="welcome-title">{{ $t('restore.title')}}</div>
         </h1>
index fa64f29..a70f234 100644 (file)
   import { mapActions, mapGetters, mapState } from 'vuex'
   import * as Actions from '@/store/constants';
   import { required, sameAs } from "vuelidate/lib/validators";
+import { updateLockTime } from '@/models/lock';
 
 
   let testNet = null;
             //restore by mnemonic
             account.restoreByMnemonic(accountAlias, mnemonic, this.formItem.passwd1, this).then(currentAccount => {
               loader.hide();
+              updateLockTime();
               this.formItem = {};
               this[Actions.CLEAR_DATA]()
               this[Actions.PUSH_ALERT](this.$t("successMsg.restoreWallet"))
             //restore by keystore
             account.restoreByKeystore(accountAlias, keystore, this.formItem.passwd1, this).then(currentAccount => {
               loader.hide();
+              updateLockTime();
               this.formItem = {};
               this[Actions.CLEAR_DATA]()
               this[Actions.PUSH_ALERT](this.$t("successMsg.restoreWallet"))