顯示具有 vue 標籤的文章。 顯示所有文章
顯示具有 vue 標籤的文章。 顯示所有文章

2022年11月2日 星期三

Vue心得2022-2

前言

繼 Vue心得2022 ,因為文章太長不好找內容,所以拆出來 

mixins

https://juejin.cn/post/7076340796361801759  彻底搞懂Vue中的Mixin混入(保姆级教程)


diff --git a/src/mixin/index.js b/src/mixin/index.js
new file mode 100644
index 0000000..80b67fc
--- /dev/null
+++ b/src/mixin/index.js
@@ -0,0 +1,23 @@
+export const mixins = {
+  data () {
+    return {
+      msg: '我是小猪课堂'
+    }
+  },
+  computed: {},
+  created () {
+    console.log('我是mixin中的created生命周期函数')
+  },
+  mounted () {
+    console.log('我是mixin中的mounted生命周期函数')
+  },
+  methods: {
+    clickMe () {
+      console.log('我是mixin中的点击事件')
+      this.clickMe2()
+    },
+    clickMe2 () {
+      console.log('我是mixin中的clickMe2')
+    }
+  }
+}
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 6664fb9..e690975 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -63,14 +63,15 @@
+    <button @click="clickMe">点击我</button>
   </div>
 </template>
 
@@ -80,6 +81,7 @@ import store from '@/store'
 import { mapGetters } from 'vuex'
 import _ from 'lodash'
 import ModalComponent from '@/components/ModalComponent'
+import { mixins } from '@/mixin'
 
 export default {
   name: 'TestView',
@@ -87,6 +89,7 @@ export default {
     ChildComponent,
     ModalComponent
   },
+  mixins: [mixins],
   data () {
     return {
       singleObj: {
@@ -119,12 +122,19 @@ export default {
   },
+  created () {
+    console.log('我是组件的created调用mixin数据', this.msg)
+  },
+  mounted () {
+    console.log('我是组件的mounted生命周期函数')
   },
   methods: {
+    clickMe2 () {
+      console.log('我是组件的clickMe2')
+    },
     callMe (count) {
       console.log(count)
       this.parentCount = count

s

結果

我是mixin中的created生命周期函数  - 先呼叫mixin的created
我是组件的created调用mixin数据 我是小猪课堂
我是mixin中的mounted生命周期函数  - 先呼叫mounted的created
我是组件的mounted生命周期函数
我是mixin中的点击事件
我是组件的clickMe2  - 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。







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

























2020年10月28日 星期三

laravel 6 Mix前端指南

環境

框架:laravel 6.6

安裝 laravel/ui 

$ composer require laravel/ui:^1.0 --dev
如果報錯:

In PackageManifest.php line 122:

  Undefined index: name


Script @php artisan package:discover --ansi handling the post-autoload-dump event returned with error code 1

Installation failed, reverting ./composer.json and ./composer.lock to their original content.
原因:composer 2.0的問題,把composer 版本回退之前版本
# composer self-update
Updating to version 2.0.2 (stable channel).
   Downloading (100%)
Use composer self-update --rollback to return to version 1.8.4
# composer self-update --rollback
Rolling back to version 2019-02-11_10-52-10-1.8.4.
ps.  laravel 6必須安裝 laravel/ui:^1.0 --dev 的版本


生成vue基本腳手架

$ php artisan ui vue
Vue scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.
這個操作新增了:
resources/js/components/ExampleComponent.vue
resources/sass/_variables.scss
resources/sass/app.scss
修改了:
package.json
resources/js/app.js
resources/js/bootstrap.js
註冊vue和vue組件
+++ b/resources/js/app.js
+window.Vue = require('vue');
...
+Vue.component('example-component', require('./components/ExampleComponent.vue').default);
...
+const app = new Vue({
+    el: '#app',
+});

$ npm install
$ npm run dev
...
 ERROR  Failed to compile with 2 errors

 error  in ./resources/sass/app.scss

Module build failed (from ./node_modules/css-loader/index.js):
ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
ValidationError: Invalid options object. Sass Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'outputStyle'. These properties are valid:
   object { implementation?, sassOptions?, prependData?, sourceMap?, webpackImporter? }
    at validate (/var/www/html/project-z/coolapp/node_modules/schema-utils/dist/validate.js:98:11)
    at Object.loader (/var/www/html/project-z/coolapp/node_modules/sass-loader/dist/index.js:36:28)
    at /var/www/html/project-z/coolapp/node_modules/webpack/lib/NormalModule.js:316:20
    at /var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:367:11
    at /var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:233:18
    at runSyncOrAsync (/var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:143:3)
    at iterateNormalLoaders (/var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
    at /var/www/html/project-z/coolapp/node_modules/loader-runner/lib/LoaderRunner.js:205:4
    at /var/www/html/project-z/coolapp/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:85:15
    at processTicksAndRejections (internal/process/task_queues.js:79:11)

 error  in ./resources/sass/app.scss

Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
ValidationError: Invalid options object. Sass Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'outputStyle'. These properties are valid:
   object { implementation?, sassOptions?, prependData?, sourceMap?, webpackImporter? }
    at validate (/var/www/html/project-z/coolapp/node_modules/schema-utils/dist/validate.js:98:11)
    at Object.loader (/var/www/html/project-z/coolapp/node_modules/sass-loader/dist/index.js:36:28)

 @ ./resources/sass/app.scss 2:14-253

原因:

 php artisan ui vue  改了 package.json ,使用了 "sass-loader": "^8.0.0", ,使得下一個操作 npm install 安裝了sass-loader 8.0.0

解法:

移除sass-loader 8.0.0 改裝7.1.0
$ npm uninstall --save-dev sass-loader
$ npm install --save-dev sass-loader@7.1.0

再執行
$ npm run dev

生成 登錄/註冊 腳手架

$ php artisan ui vue --auth
這個操作新增了:
app/Http/Controllers/HomeController.php
resources/views/auth/login.blade.php
resources/views/auth/passwords/confirm.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/register.blade.php
resources/views/auth/verify.blade.php
resources/views/home.blade.php
resources/views/layouts/app.blade.php
修改了:
routes/web.php

登錄頁

http://app.test:5566/login  使用users 表的 email 和password 登錄(passport API也是這個方式登錄的)

歡迎頁

http://app.test:5566/  resources/views/welcome.blade.php:68 上方新增了Login和Register按鈕

編寫Vue 組件

diff --git a/resources/js/components/ExampleComponent.vue b/resources/js/components/ExampleComponent.vue
index 3fb9f9aa..944c6973 100644
--- a/resources/js/components/ExampleComponent.vue
+++ b/resources/js/components/ExampleComponent.vue
@@ -3,7 +3,7 @@
         <div class="row justify-content-center">
             <div class="col-md-8">
                 <div class="card">
-                    <div class="card-header">Example Component</div>
+                    <div class="card-header">Example Component Bear</div>

                     <div class="card-body">
                         I'm an example component.
diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php
index 05dfca92..73075388 100644
--- a/resources/views/home.blade.php
+++ b/resources/views/home.blade.php
@@ -1,6 +1,7 @@
 @extends('layouts.app')

 @section('content')
+<example-component></example-component>
 <div class="container">
     <div class="row justify-content-center">
         <div class="col-md-8">

監視和自動重編譯發生變化的組件

在hyper-v 上執行,不要在windows 10 上執行,PHPStorm Deployment 要忽略 node_modules 目錄上傳
$ npm run watch-poll 

新增組件後,需要重新編譯 npm run dev , 或重新執行 npm run watch-poll  


參考資料

https://stackoverflow.com/a/61197256  Laravel PackageManifest.php: Undefined index: name
https://stackoverflow.com/a/60513876  Sass Loader Error: Invalid options object that does not match the API schema







2020年4月16日 星期四

Vue心得

安裝nodejs和npm

https://tecadmin.net/install-latest-nodejs-and-npm-on-centos/  How To Install Latest Nodejs on CentOS/RHEL 7

新增Node.js Yum Repository - Stable 版本(12.x)

# yum install -y gcc-c++ make
# curl -sL https://rpm.nodesource.com/setup_12.x | sudo -E bash -

安裝Node.js

# yum install nodejs

檢查 Node.js 和 NPM 版本

# node -v
v12.16.2
# npm -v
6.14.4

npm命令自動完成

$ npm completion >> ~/.bashrc
$ . ~/.bashrc


升級npm


npm i 就是npm install,只是簡寫
https://stackoverflow.com/questions/6237295/how-can-i-update-nodejs-and-npm-to-the-next-versions  How can I update NodeJS and NPM to the next versions?
# npm install -g npm

安裝/升級Vue Cli

https://cli.vuejs.org/zh/guide/installation.html
# npm install -g @vue/cli
...
-g : 同--global ,global模式安裝,將會安裝到 /usr/lib/node_modules/@vue/cli 下

檢查版本

# vue --version
@vue/cli 4.3.1

npm install的--save是什麼意思

https://stackoverflow.com/a/19578808  What is the --save option for npm install?
npm 5.0.0 後預設將modules裝在dependency,所以 --save 已經不再需要了。另外--save-dev將套件存在devDependencies、--save-optional將套件存在optionalDependencies


創建一個新項目

https://ithelp.ithome.com.tw/articles/10222966  Day28 vue.js - Vue cli 3.0 環境建置

$ vue create hello-world

手動選取要 preset (預先裝置) 的特性

? Please pick a preset:
  default (babel, eslint)
❯ Manually select features

選取要安裝的特性 (用space多選)

預設選了Babel、Linter / Formatter
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

Babel : JavaScript 編譯器、轉譯器。
Router : Vue 的路由器
Vuex vuex(vue的狀態管理模式)
CSS Pre-processors : CSS 前處理器(如:less、sass)
Linter / Formatter : 程式碼風格檢查和格式化(如:ESlint)

是否使用 Router 歷史記錄模式

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y
history 模式,這種模式充分利用 -history.pushState API 來完成 URL 跳轉而無須重新加載頁面。

css預先處理器

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
❯ Sass/SCSS (with dart-sass)
  Sass/SCSS (with node-sass)
  Less
  Stylus

https://stackoverflow.com/a/56422541  Vue CLI CSS pre-processor option: dart-sass VS node-sass?  
簡單說選 dart-sass而不是node-sass 的原因:官方推薦、比較快

ESLint 協助讓你寫的程式符合規範的輔助工具,區分嚴謹程度

? 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)
❯◉ 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

是否將上述配置儲存到 preset 的 default

? Save this as a preset for future projects? (y/N) N

運行專案項目(開發用)

$ cd hello-world
$ npm run serve

運營專案指定端口

https://cli.vuejs.org/zh/config/#devserver
新增 vue.config.js
所有 webpack-dev-server 的选项 都支持。 vue.config.js 加入:
module.exports = {
  devServer: {
    port: 8100
  }
}
s
然後就能以  http://192.168.1.x:8100/ 訪問專案(當然server 8100端口防火牆要記得開)

關閉ESLint

https://stackoverflow.com/questions/49121110/how-to-disable-eslint-on-vue-cli-3  How to disable eslint on vue-cli 3?
vue.config.js 新增
module.exports = {
    chainWebpack: config => {
        config.module.rules.delete('eslint');
    }
}
這樣 npm run serve 時就不會做 eslint 檢查
WebStorm => Settings => Languages & Frameworks => Javascript => Code Quality Tools => ESLint => 勾選 Disable ESLint

用vue-native-websocket做長連結

https://juejin.im/post/5dd4ebdff265da47c8603e02   9012年末,带你快速上手vue-native-websocket

使用Mint-ui

https://zhuanlan.zhihu.com/p/61403630  2019最受欢迎的前端7个UI框架
https://juejin.im/post/5d674d87e51d4561fa2ec0a6  基于Vue CLI3 搭建五脏俱全的移动端H5应用


安裝

$ npm i mint-ui

使用

src/views/Test.vue:
import Vue from 'vue'
import Mint from 'mint-ui'
import 'mint-ui/lib/style.css'

Vue.use(Mint)
Mint.Toast('提示信息2')
s

import Vue from 'vue'
import Toast from 'mint-ui/lib/toast'
import 'mint-ui/lib/toast/style.css'

Vue.component(Toast.name, Toast)
Toast('提示信息4')
s
或安裝 babel-plugin-component ,可自動加載css
$ npm i babel-plugin-component -D
然後修改 babel.config.js
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  "plugins": [
    ["component", {
      "libraryName": "mint-ui",
      "style": true
    }]
  ]
}

需重新 npm run serve ,就可以這樣使用
import Vue from 'vue'
import { Cell, Checklist, Toast } from 'mint-ui'

Vue.component(Toast.name, Toast)
Toast('提示信息3')
s

打包項目到dist目錄

$ npm run build

如果在Windows上報錯

'vue-cli-service' 不是内部或外部命令,也不是可运行的程序

原因

node_modules/ 是在 CentOS上npm install的,專案在Windows卻不能 npm run serve、build

解法

https://github.com/vuejs/vue-cli/issues/1119#issuecomment-382105533  vue-cli-service does not run in cmd.exe or Powershell
在Windows 上刪除 node_modules/ 後重新 npm install

使用Vant

因為 mint-ui 已經2年以上沒更新了,改用還有在更新的vant
https://youzan.github.io/vant/#/zh-CN/quickstart  快速上手
https://github.com/youzan/vant  youzan/vant

安裝Vant

$ npm i vant -S

檢查已安裝插件的版本

$ npm list vant
hello-world@0.1.0 C:\Users\user\WebstormProjects\hello-world
`-- vant@2.6.3

使用

diff --git a/src/views/About.vue b/src/views/About.vue
 <template>
   <div class="about">
     <h1>This is an about page</h1>
+    <van-button type="primary">主要按钮2</van-button>
   </div>
 </template>
+<script>
+import Vue from 'vue'
+import Button from 'vant/lib/button'
+import 'vant/lib/button/style'
+Vue.component(Button.name, Button)
+export default {
+}
+</script>

Vue.component(Button.name, Button) - 沒引入會報錯 [Vue warn]: Unknown custom element: <van-button> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
https://github.com/youzan/vant/issues/359#issuecomment-347491408  全局注册的组件在单文件中不识别 #359
chenjiahan: Vue.use 是注册插件用的,注册组件请使用 Vue.component
export default {} - 沒寫會報錯 TypeError: Cannot set property 'render' of undefined
https://stackoverflow.com/a/51021896  Cannot set property 'render' of undefined

自動引入style

安裝babel-plugin-import 插件
$ npm i babel-plugin-import -D

babel 7 在 babel.config.js 配置
diff --git a/babel.config.js b/babel.config.js
   plugins: [
     ['component', {
       libraryName: 'mint-ui',
       style: true
     }],
+    ['import', {
+      libraryName: 'vant',
+      libraryDirectory: 'es',
+      style: true
+    }, 'vant']
   ]
 }
一定要用 babel-plugin-import,用 babel-plugin-component 會報錯
Module not found: Error: Can't resolve 'vant/lib/toast/style.css' in

import Vue from 'vue'
import { Button } from 'vant'
Vue.component(Button.name, Button)
export default {
}
就是少寫import css那一行

ps. https://www.npmjs.com/package/@vue/cli-plugin-babel  @vue/cli-plugin-babel
@vue/cli-plugin-babel 使用 Babel 7

修正Cordova中字體文件路徑錯誤

環境: vant@2.6.3cordova@9.0.0
https://github.com/youzan/vant/issues/2366  file://协议下加载字体文件路径错误 #2366
https://stackoverflow.com/q/14575208  Using css font-face in a Phonegap Windows Phone 8 app

Tabbar 上的圖是用 vant-icon-db1de1.woff2 畫出來的,但在cordova中一樣因為 file:// 協議的關係,使得字型缺失
vant-icon-db1de1.woff2 是在 node_modules/vant/es/icon/index.css 的 @font-face 中請求 https://img.yzcdn.cn/vant/vant-icon-db1de1.woff2 來的

解法

將 vant-icon-db1de1.woff2 下載到本地  src/assets/vant-icon-db1de1.woff2 。 src/App.vue 加入( 從node_modules/vant/es/icon/index.css 的 @font-face 粘過來修改)
@font-face {
  font-weight: 400;
  font-family: vant-icon;
  font-style: normal;
  font-display: auto;
  src: url("assets/vant-icon-db1de1.woff2") format('woff2')
}
即可