2022年6月30日 星期四

ScreenToGif

安裝

Microsoft Store 直接安裝

使用

打開應用程式後

螢幕錄影

調整大小,開始錄影,停止


編輯

播放,減少影格數(2次),刪除前面所有影格,刪除後面所有影格

圖片

多選影格,效果(像素化),形狀

保存(ctrl+s)

可輸出gif或影片


結果


輸出影片需設定FFmpeg路徑


下載FFmpeg


解壓縮,設定環境變量(Path),然後就能輸出影片(mp4)














2022年6月13日 星期一

Vue心得2022

前言

寫了 Vue心得 之後,在這年又開始寫Vue了

版本

$ node -v
v16.13.1

$ npm -v
8.1.2

$ vue --version
@vue/cli 5.0.4

創建一個新項目

$ vue create vue2022

手動選擇

? Please pick a preset:
  Default ([Vue 3] babel, eslint) 
  Default ([Vue 2] babel, eslint) 
> Manually select features

選擇需要的feature

? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
>(*) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

選擇vue版本

? Choose a version of Vue.js that you want to start the project with 
  3.x
> 2.x

使用history 模式

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

選擇linter / formatter配置

? Pick a linter / formatter config: 
  ESLint with error prevention only 
  ESLint + Airbnb config
> ESLint + Standard config
  ESLint + Prettier


? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
>(*) Lint on save
 ( ) Lint and fix on commit

配置文件

? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)
> In dedicated config files
  In package.json


新建完專案後,會自動提交一個init的git commit


同組件路由切換時不重新渲染組件

如/about2 和 /about 都使用 views/AboutView.vue 組件:

diff --git a/src/App.vue b/src/App.vue
index 240acf4..2995013 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,7 +2,8 @@
   <div id="app">
     <nav>
       <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
+      <router-link to="/about">About</router-link> |
+      <router-link to="/about2">About2</router-link>
     </nav>
     <router-view/>
   </div>
diff --git a/src/router/index.js b/src/router/index.js
index a395a1f..5d09db5 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -17,6 +17,11 @@ const routes = [
     // this generates a separate chunk (about.[hash].js) for this route
     // which is lazy-loaded when the route is visited.
     component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+  },
+  {
+    path: '/about2',
+    name: 'about2',
+    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
   }
 ]
 
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
index 3fa2807..16bb285 100644
--- a/src/views/AboutView.vue
+++ b/src/views/AboutView.vue
@@ -1,5 +1,17 @@
 <template>
   <div class="about">
-    <h1>This is an about page</h1>
+    <h1>This is an {{$route.name}} page</h1>
   </div>
 </template>
+
+<script>
+export default {
+  name: 'AboutView',
+  created () {
+    console.log('AboutView.vue')
+    console.log(this.$router)
+    console.log(this.$route)
+    console.log(this.$route.path)
+  }
+}
+</script>
s
切換路由時,組件的created不被呼叫

自動更新 - :key="$route.fullPath"

https://stackoverflow.com/a/51170320  Do we have router.reload in vue-router?
https://stackoverflow.com/a/49646063  Vue-router reload components
在組件上新增 :key="$route.fullPath"
diff --git a/src/App.vue b/src/App.vue
index 69ddd1a..4f3e76c 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -8,6 +8,7 @@
     <router-view
+      :key="$route.fullPath"/>
   </div>
 </template>
s

手動更新 - :key="componentKey"

https://stackoverflow.com/a/54367510  Can you force Vue.js to reload/re-render?

diff --git a/src/App.vue b/src/App.vue
index 4f3e76c..e4e7a4a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -8,7 +8,8 @@
     <router-view
       :parentString="string"
       @reloadComponentEvent="emitReloadComponentEvent($event)"
+      :key="componentKey"
+      @forceRerenderEvent="emitForceRerenderEvent($event)"
       v-if="isRouterAlive"/>
   </div>
 </template>
@@ -18,7 +19,8 @@ export default {
   data () {
     return {
       isRouterAlive: true,
+      componentKey: 0
     }
   },
   methods: {
@@ -28,6 +30,14 @@ export default {
     },
     emitReloadComponentEvent () {
       this.reload()
+    },
+    forceRerender () {
+      this.componentKey += 1
+    },
+    emitForceRerenderEvent (name) {
+      console.log('emitForceRerenderEvent')
+      console.log(name)
+      this.forceRerender()
     }
   }
 }
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
index fafb0f5..7678eb2 100644
--- a/src/views/AboutView.vue
+++ b/src/views/AboutView.vue
@@ -1,6 +1,7 @@
 <template>
   <div class="about">
     <h1 @click="reloadComponent">This is an {{$route.name}} page:{{parentString}}</h1>
+    <button @click="clickForceRerender">forceRerender</button>
   </div>
 </template>
 
@@ -17,6 +18,10 @@ export default {
     reloadComponent () {
       console.log('reloadComponent')
       this.$emit('reloadComponentEvent', this.$route.name)
+    },
+    clickForceRerender () {
+      console.log('forceRerender')
+      this.$emit('forceRerenderEvent', this.$route.name)
     }
   },
   props: {
s

手動更新 - this.$nextTick()

https://www.zhihu.com/question/49863095/answer/289157209  请问vue组件如何reload或者说vue-router如何刷新当前的route??

diff --git a/src/App.vue b/src/App.vue
index 2995013..3eb8189 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,10 +5,31 @@
       <router-link to="/about">About</router-link> |
       <router-link to="/about2">About2</router-link>
     </nav>
-    <router-view/>
+    <router-view
+      @reloadComponentEvent="emitReloadComponentEvent($event)"
+      v-if="isRouterAlive"/>
   </div>
 </template>
 
+<script>
+export default {
+  data () {
+    return {
+      isRouterAlive: true
+    }
+  },
+  methods: {
+    reload () {
+      this.isRouterAlive = false
+      this.$nextTick(() => (this.isRouterAlive = true))
+    },
+    emitReloadComponentEvent () {
+      this.reload()
+    }
+  }
+}
+</script>
+
 <style>
 #app {
   font-family: Avenir, Helvetica, Arial, sans-serif;
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
index 16bb285..545956a 100644
--- a/src/views/AboutView.vue
+++ b/src/views/AboutView.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="about">
-    <h1>This is an {{$route.name}} page</h1>
+    <h1 @click="reloadComponent">This is an {{$route.name}} page</h1>
   </div>
 </template>
 
@@ -12,6 +12,12 @@ export default {
     console.log(this.$router)
     console.log(this.$route)
     console.log(this.$route.path)
+  },
+  methods: {
+    reloadComponent () {
+      console.log('reloadComponent')
+      this.$emit('reloadComponentEvent', this.$route.name)
+    }
   }
 }
 </script>
s

特定路由被緩存

https://www.jianshu.com/p/0b0222954483  vue-router 之 keep-alive
指定首頁(home)緩存 => 即不重新渲染,HomeView.vue不重複呼叫created()
about、about2不緩存 => about、about2需用不同組件(component)

diff --git a/src/App.vue b/src/App.vue
index eda18d3..588bafb 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,12 +5,14 @@
       <router-link to="/about">About</router-link> |
       <router-link to="/about2">About2</router-link>
     </nav>
+    <keep-alive>
+      <router-view v-if="$route.meta.keepAlive">
+        <!-- 这里是会被缓存的视图组件,比如 home -->
+      </router-view>
+    </keep-alive>
+    <router-view v-if="!$route.meta.keepAlive">
+      <!-- 这里是不被缓存的视图组件,比如 about、about2 -->
+    </router-view>
   </div>
 </template>
 
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
index 1c544cb..f59096c 100644
--- a/src/components/HelloWorld.vue
+++ b/src/components/HelloWorld.vue
@@ -37,6 +37,9 @@ export default {
   name: 'HelloWorld',
   props: {
     msg: String
+  },
+  created () {
+    console.log('HelloWorld.vue')
   }
 }
 </script>
diff --git a/src/router/index.js b/src/router/index.js
index 5d09db5..1b097c7 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -8,7 +8,10 @@ const routes = [
   {
     path: '/',
     name: 'home',
+    component: HomeView,
+    meta: {
+      keepAlive: true // 需要被缓存
+    }
   },
   {
     path: '/about',
@@ -16,12 +19,18 @@ const routes = [
   {
     path: '/about',
     name: 'about',
-    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
+    meta: {
+      keepAlive: false // 不需要被缓存
+    }
   },
   {
     path: '/about2',
     name: 'about2',
-    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+    component: () => import(/* webpackChunkName: "about" */ '../views/About2View.vue'),
+    meta: {
+      keepAlive: false // 不需要被缓存
+    }
   }
 ]
 
diff --git a/src/views/About2View.vue b/src/views/About2View.vue
new file mode 100644
index 0000000..28f3639
--- /dev/null
+++ b/src/views/About2View.vue
@@ -0,0 +1,18 @@
+<template>
+  <div class="about">
+    <h1>This is an {{$route.name}} page:</h1>
+    <button>forceRerender</button>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'About2View',
+  created () {
+    console.log('About2View.vue')
+    console.log(this.$router)
+    console.log(this.$route)
+    console.log(this.$route.path)
+  }
+}
+</script>
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index e8d96d7..2eca718 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -13,6 +13,9 @@ export default {
   name: 'HomeView',
   components: {
     HelloWorld
+  },
+  created () {
+    console.log('HomeView.vue')
   }
 }
 </script>
s

組件間溝通


父傳子(props)

diff --git a/src/components/ChildComponent.vue b/src/components/ChildComponent.vue
index e47d61b..7e2b2fe 100644
--- a/src/components/ChildComponent.vue
+++ b/src/components/ChildComponent.vue
@@ -1,12 +1,22 @@
 <template>
   <div class="child">
     ChildComponent
+    <div>{{obj.title}}</div>
+    <div>{{obj.content}}</div>
   </div>
 </template>
 
 <script>
 export default {
-  name: 'ChildComponent'
+  name: 'ChildComponent',
+  props: {
+    obj: {
+      type: Object,
+      default: function () {
+        return {}
+      }
+    }
+  }
 }
 </script>
 
diff --git a/src/views/ParentView.vue b/src/views/ParentView.vue
index adb9021..304371a 100644
--- a/src/views/ParentView.vue
+++ b/src/views/ParentView.vue
@@ -6,7 +6,9 @@
       <div id="right">right<br>right line 2</div>
       <div class="clear"></div>
     </div>
-    <child-component></child-component>
+    <child-component
+      :obj="singleObj"
+    ></child-component>
   </div>
 </template>
 
@@ -16,6 +18,14 @@ import ChildComponent from '@/components/ChildComponent'
 export default {
   name: 'ParentView',
   components: { ChildComponent },
+  data () {
+    return {
+      singleObj: {
+        title: 'props',
+        content: '父傳子'
+      }
+    }
+  },
   created () {
     console.log('ParentView.vue')
   }
s

子傳父(event emitter)


diff --git a/src/components/ChildComponent.vue b/src/components/ChildComponent.vue
index 7e2b2fe..45920ee 100644
--- a/src/components/ChildComponent.vue
+++ b/src/components/ChildComponent.vue
@@ -3,12 +3,18 @@
     ChildComponent
     <div>{{obj.title}}</div>
     <div>{{obj.content}}</div>
+    <button @click="callParent">子傳父</button>
   </div>
 </template>
 
 <script>
 export default {
   name: 'ChildComponent',
+  data () {
+    return {
+      count: 0
+    }
+  },
   props: {
     obj: {
       type: Object,
@@ -16,6 +22,12 @@ export default {
         return {}
       }
     }
+  },
+  methods: {
+    callParent () {
+      this.count++
+      this.$emit('callParentEvent', this.count)
+    }
   }
 }
 </script>
diff --git a/src/views/ParentView.vue b/src/views/ParentView.vue
index 304371a..0b2cfdd 100644
--- a/src/views/ParentView.vue
+++ b/src/views/ParentView.vue
@@ -8,7 +8,9 @@
     </div>
     <child-component
       :obj="singleObj"
+      @callParentEvent="callMe($event)"
     ></child-component>
+    <div>{{parentCount}}</div>
   </div>
 </template>
 
@@ -23,11 +25,18 @@ export default {
       singleObj: {
         title: 'props',
         content: '父傳子'
-      }
+      },
+      parentCount: null
     }
   },
   created () {
     console.log('ParentView.vue')
+  },
+  methods: {
+    callMe (count) {
+      console.log(count)
+      this.parentCount = count
+    }
   }
 }
 </script>
s

<div>{{parentCount}}</div> 

https://stackoverflow.com/a/43858500  Why doesn't the data get updated in Vue Dev Tools?
為什麼Vue Dev Tools裡面的data不更新?
因為頁面沒變,所以加入 <div>{{parentCount}}</div>  把畫面更新,Vue Dev Tools裡面的data才會更新

:key

https://stackoverflow.com/a/51541950  What does the colon represent inside a VueJS/Vuetify/HTML component tag
:key 是 v-bind:key 的簡寫

@符號

https://stackoverflow.com/a/58313986  What does the @ symbol do in Vue.js?
標籤裡的@符號是 v-on 的簡寫


VUEX

版本

"vue": "^2.6.14",
"vuex": "^3.6.2"

設定state


diff --git a/src/store/index.js b/src/store/index.js
index ceffa8e..c0deccd 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,17 +1,26 @@
+import { COUNT_MUTATION } from '@/store/mutation-types'
 
 Vue.use(Vuex)
 
 export default new Vuex.Store({
   state: {
+    count: 0
   },
   getters: {
   },
   mutations: {
+    [COUNT_MUTATION] (state, n) {
+      state.count += n
+    }
   },
   actions: {
   },
diff --git a/src/store/mutation-types.js b/src/store/mutation-types.js
new file mode 100644
index 0000000..492a305
--- /dev/null
+++ b/src/store/mutation-types.js
@@ -0,0 +1,2 @@
+export const COUNT_MUTATION = 'COUNT_MUTATION'
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 0b2cfdd..e3e3708 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -1,21 +1,27 @@
 <template>
   <div>
     <div>TestView.vue</div>
+    <div @click="increase(1)">increase 1</div>
+    <div @click="increase(2)">increase 2</div>
   </div>
 </template>
 
 <script>
 import ChildComponent from '@/components/ChildComponent'
+import store from '@/store'
 
 export default {
   name: 'TestView',
@@ -36,7 +42,19 @@ export default {
     callMe (count) {
       console.log(count)
       this.parentCount = count
+    },
+    increase (n) {
+      store.commit('COUNT_MUTATION', n)
+    }
   }
 }
 </script>

s

獲取state

通過屬性訪問(store.getters.countInGetters)或mapGetters輔助函數

diff --git a/src/store/index.js b/src/store/index.js
index a0bfcb7..24dff6e 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -8,6 +8,9 @@ export default new Vuex.Store({
     count: 0
   },
   getters: {
+    countInGetters (state) {
+      return state.count
+    }
   },
   mutations: {
     increment (state, n) {
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 3b4e0b9..3e8272f 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -14,12 +14,14 @@
     <div>{{parentCount}}</div>
     <div @click="increase(1)">increase 1</div>
     <div @click="increase(2)">increase 2</div>
+    <div @click="testGetter()">test</div>
   </div>
 </template>
 
 <script>
 import ChildComponent from '@/components/ChildComponent'
 import store from '@/store'
+import { mapGetters } from 'vuex'
 
 export default {
   name: 'TestView',
@@ -43,7 +45,16 @@ export default {
     },
     increase (n) {
       store.commit('increment', n)
+    },
+    testGetter () {
+      console.log(store.getters.countInGetters)
+      console.log(this.countInGetters)
     }
+  },
+  computed: {
+    ...mapGetters([
+      'countInGetters'
+    ])
   }
 }
 </script>

s

createLogger 插件

diff --git a/src/store/index.js b/src/store/index.js
index 24dff6e..c856f10 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,5 +1,5 @@
 import Vue from 'vue'
-import Vuex from 'vuex'
+import Vuex, { createLogger } from 'vuex'
 
 Vue.use(Vuex)
 
@@ -20,5 +20,6 @@ export default new Vuex.Store({
   actions: {
   },
   modules: {
-  }
+  },
+  plugins: [createLogger()]
 })

s

lodash

檢查object有沒有key

https://stackoverflow.com/a/43096892  Checking if a key exists in a JavaScript object?
vue 2 內建lodash,直接import

diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index e3e3708..79024b7 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -22,6 +22,7 @@
 import ChildComponent from '@/components/ChildComponent'
 import store from '@/store'
 import { mapGetters } from 'vuex'
+import _ from 'lodash'
 
 export default {
   name: 'TestView',
@@ -32,11 +33,26 @@ export default {
         title: 'props',
         content: '父傳子'
       },
-      parentCount: null
+      parentCount: null,
+      todos: [
+        {
+          id: 1,
+          text: 'text 1',
+          done: true
+        },
+        {
+          id: 2,
+          text: 'text 2',
+          done: false
+        }
+      ]
     }
   },
   created () {
     console.log('TestView.vue')
+    console.log(_.get(this.todos, '0.text')) // text 1
+    console.log(_.get(this.todos, '1.id')) // 2
+    console.log(_.get(this.todos, '2.id')) // undefined
   },
   methods: {
     callMe (count) {

s

彈窗modal

使用到的特性:組件,prop 傳遞,插槽 (slot),過渡 (transitions)
https://vuejs.org/examples/#modal  Modal with Transitions

或是直接用現成插件 vue2-simplert-plugin 或 mint-ui的Message box


ModalComponent.vue

<template>
  <Transition name="modal">
    <div v-if="show" class="modal-mask">
      <div class="modal-wrapper">
        <div class="modal-container">
          <div class="modal-header">
            <slot name="header">default header</slot>
          </div>

          <div class="modal-body">
            <slot name="body">default body</slot>
          </div>

          <div class="modal-footer">
            <slot name="footer">
              default footer
              <button
                class="modal-default-button"
                @click="$emit('close')"
              >OK
              </button>
            </slot>
          </div>
        </div>
      </div>
    </div>
  </Transition>
</template>

<script>
export default {
  name: 'ModalComponent',
  props: {
    show: Boolean
  }
}
</script>

<style scoped>
.modal-mask {
  position: fixed;
  z-index: 9998;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: table;
  transition: opacity 0.3s ease;
}

.modal-wrapper {
  display: table-cell;
  vertical-align: middle;
}

.modal-container {
  width: 300px;
  margin: 0px auto;
  padding: 20px 30px;
  background-color: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  transition: all 0.3s ease;
}

.modal-header h3 {
  margin-top: 0;
  color: #42b983;
}

.modal-body {
  margin: 20px 0;
}

.modal-default-button {
  float: right;
}

/*
 * The following styles are auto-applied to elements with
 * transition="modal" when their visibility is toggled
 * by Vue.js.
 *
 * You can easily play with the modal transition by editing
 * these styles.
 */

.modal-enter-from {
  opacity: 0;
}

.modal-leave-to {
  opacity: 0;
}

.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
  -webkit-transform: scale(1.1);
  transform: scale(1.1);
}
</style>

s
name: 'ModalComponent',  組件名字必須是多個單字,否則要在 .eslintrc.js 加入  'vue/multi-word-component-names': 'off'
https://stackoverflow.com/a/72144014  Component name "Temp" should always be multi-word vue/multi-word-component-names
<slot name="header">default header</slot>  組件內的具名插槽



TestView.vue

diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index d9c5d70..b79c879 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -12,9 +12,28 @@
       @callParentEvent="callMe($event)"
     ></child-component>
     <div>{{parentCount}}</div>
+    <button id="show-modal" @click="showModal = true">Show Modal</button>
+    <!-- use the modal component, pass in the prop -->
+    <modal-component :show="showModal" @close="showModal = false">
+      <template v-slot:header>
+        <h3>custom header1,  {{ slotProps.user.firstName }}</h3>
+      </template>
+    </modal-component>
   </div>
 </template>
 
@@ -23,10 +42,14 @@ import ChildComponent from '@/components/ChildComponent'
 import store from '@/store'
 import { mapGetters } from 'vuex'
 import _ from 'lodash'
+import ModalComponent from '@/components/ModalComponent'
 
 export default {
   name: 'TestView',
+  components: {
+    ModalComponent
+  },
   data () {
     return {
       singleObj: {
@@ -45,7 +68,15 @@ export default {
           text: 'text 2',
           done: false
         }
-      ]
+      ],
+      showModal: false
     }
   },
   created () {

s
具名插槽 v-slot:header 可簡寫成 #header (WebStorm都會自動提示插槽名)。slot="header"是2.6.0 起被廢棄的寫法


.eslintrc.js

允許空白行

   rules: {
+    'no-trailing-spaces': 'off',


其他語法

v-if和v-else不要html標籤

https://stackoverflow.com/a/49218055  How to use v-if and v-else without any html tag or else
<template v-if="condition">
</template>
<template v-else>
</template>

倒計時

https://stackoverflow.com/a/59923858  How do I create a simple 10 seconds countdown in Vue.js
<template>
    {{ timerCount }}
</template>
<script>
    export default {
        data() {
            return {
                timerCount: 30
            }
        },
        watch: {
            timerCount: {
                handler(value) {
                    if (value > 0) {
                        setTimeout(() => {
                            this.timerCount--;
                        }, 1000);
                    }
                },
                immediate: true // This ensures the watcher is triggered upon creation
            }
        }
    }
</script>
s