2022年11月2日 星期三

CSS心得2022

Stylus

https://stackoverflow.com/a/66284049  "ERESOLVE unable to resolve dependency tree" when installing npm react-facebook-login
https://stackoverflow.com/a/65348395  Unable to resolve dependency tree error when installing npm packages


安裝

如果在 Vue心得2022  時沒有安裝Stylus,可以這樣安裝

$ npm install stylus stylus-loader --save-dev
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: @vue/eslint-config-standard@6.1.0
npm ERR! Found: eslint-plugin-vue@8.7.1
npm ERR! node_modules/eslint-plugin-vue
npm ERR!   dev eslint-plugin-vue@"^8.0.3" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer eslint-plugin-vue@"^7.0.0" from @vue/eslint-config-standard@6.1.0
npm ERR! node_modules/@vue/eslint-config-standard
npm ERR!   dev @vue/eslint-config-standard@"^6.1.0" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: eslint-plugin-vue@7.20.0
npm ERR! node_modules/eslint-plugin-vue
npm ERR!   peer eslint-plugin-vue@"^7.0.0" from @vue/eslint-config-standard@6.1.0
npm ERR!   node_modules/@vue/eslint-config-standard
npm ERR!     dev @vue/eslint-config-standard@"^6.1.0" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

這時候要加 --legacy-peer-deps(用nvm升級npm到 8.19.2 無效)

$ npm install stylus stylus-loader --save-dev --legacy-peer-deps
added 5 packages, changed 1 package, and audited 992 packages in 4s

126 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

npm audit

如果npm install --save 遇到類似錯誤(npm ERR! code ERESOLVE)也可以這樣處理
$ npm install --save --legacy-peer-deps
added 986 packages, and audited 987 packages in 47s

124 packages are looking for funding
  run `npm fund` for details

high severity vulnerability

To address all issues, run:
  npm audit fix

Run `npm audit` for details.

處理完發現有vulnerability,按照提示處理

$ npm audit
# npm audit report

terser  5.0.0 - 5.14.1
Severity: high
Terser insecure use of regular expressions before v4.8.1 and v5.14.2 leads to ReDoS - https://github.com/advisories/GHSA-4wf5-vphf-c2xc
fix available via `npm audit fix`
node_modules/terser

high severity vulnerability

To address all issues, run:
  npm audit fix

$ npm audit fix --legacy-peer-deps
changed 1 package, and audited 987 packages in 4s

124 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

npm audit fix 時也會遇到 code ERESOLVE 錯誤,一樣加 --legacy-peer-deps 處理

使用

diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 47f0bc6..b2504c3 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -63,6 +63,14 @@
         <div class="two"></div>
       </article>
     </section>
+    <div id="container">
+      <div id="header">Header contents</div>
+    </div>
+    <div id="wrapper-1">
+      <div id="container">
+        <div id="header">Header contents</div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -152,7 +160,7 @@ export default {
 }
 </script>
 
-<style scoped>
+<style scoped lang="stylus">
 #outer {
   background: aqua;
   text-align: left;
@@ -278,4 +286,15 @@ article {
   height: 30px;
 }
 
+#container {
+  #header {
+    background: salmon
+  }
+}
+
+#wrapper-1
+  #container
+    #header
+      background: burlywood
+
 </style>

s

在 <style> 加 lang="stylus",括號可有可無,加了才能在WebStorm - Move Caret to Matching Brace (Ctrl + M)。Stylus的擴展名是.styl


讓float的div和父div一樣高

https://stackoverflow.com/a/31528903  How to make a floated div 100% height of its parent?
子div:不要讓他float ,加 display: table-cell; 
父元素:加 display: table; 

#outer{
    display: table;
}
#inner {
    display: table-cell;
    float: none;
}
s

position: absolute 的父div高度

https://stackoverflow.com/a/13546011  Position: absolute and parent height?
https://stackoverflow.com/a/12071017  Make absolute positioned div expand parent div height

問題

position: absolute 的父div高度沒有height

diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index b1c9159..f7d07e1 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -35,12 +35,34 @@
+    <section id="foo">
+      <header>Foo</header>
+      <article>
+        <div class="one"></div>
+        <div class="two"></div>
+      </article>
+    </section>
   </div>
 </template>
 
@@ -154,4 +176,106 @@ export default {
 .clear {
   clear: both;
 }
+article {
+  background: #0f0;
+  /*question*/
+  position: relative;
+}
+
+.one {
+  /*question*/
+  position: absolute;
+  top: 10px;
+  left: 10px;
+  background: red;
+  width: 30px;
+  height: 30px;
+}
+
+.two {
+  /*question*/
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  background: blue;
+  width: 30px;
+  height: 30px;
+}
+
 </style>

s

10年前的解法(original)

diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index b1c9159..f7d07e1 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -35,12 +35,34 @@
+    <section id="foo">
+      <header>Foo</header>
+      <article>
+        <div class="one"></div>
+        <div class="two"></div>
+      </article>
+    </section>
   </div>
 </template>
 
@@ -154,4 +176,106 @@ export default {
 .clear {
   clear: both;
 }
+article {
+  background: #0f0;
+  /*original*/
+  position: relative;
+  overflow: hidden;
+}
+
+.one {
+  /*original*/
+  position: relative;
+  float: left;
+  margin-top: 10px;
+  margin-left: 10px;
+  background: red;
+  width: 30px;
+  height: 30px;
+}
+
+.two {
+  /*original*/
+  position: relative;
+  float: right;
+  margin-top: 10px;
+  margin-right: 10px;
+  background: blue;
+  width: 30px;
+  height: 30px;
+}
+
 </style>

s

grid


因為10年前瀏覽器CSS佈局沒有grid和flex,我們現在可以用grid和flex
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index b1c9159..f7d07e1 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -35,12 +35,34 @@
+    <section id="foo">
+      <header>Foo</header>
+      <article>
+        <div class="one"></div>
+        <div class="two"></div>
+      </article>
+    </section>
   </div>
 </template>
 
@@ -154,4 +176,106 @@ export default {
 .clear {
   clear: both;
 }
+article {
+  background: #0f0;
+  /*grid*/
+  display: grid;
+  grid-template-columns: 30px auto 30px;
+  padding: 10px 10px 0;
+}
+
+.one {
+  /*grid*/
+  height: 30px;
+  background: red;
+  grid-column: 1/2;
+}
+
+.two {
+  /*grid*/
+  height: 30px;
+  background: blue;
+  grid-column: 3/4;
+}
+
 </style>

s


display: grid;  讓容器變成grid
grid-template-columns: 30px auto 30px;  行佈局3個grid,第一個寬度30px,第二個自動填充剩餘寬度,第三個寬度30px。必須在這設定grid寬度,在子div的item設置無效
padding: 10px 10px 0;  因為gap不含外圍,所以在容器上設padding
grid-column: 1/2; 設定.one從第1個位置開始到第2個位置結束
grid-column: 3/4; 設定.two從第3個位置開始到第4個位置結束(跳過2/3)


flex(推薦)

https://stackoverflow.com/a/35578723  Giving wrapped flexbox items vertical spacing
https://stackoverflow.com/a/31006659  Align an element to bottom with flexbox
https://stackoverflow.com/a/41440249  Flexbox Two Rows Two Columns
https://stackoverflow.com/a/19027949  Flexbox: center horizontally and vertically
https://stackoverflow.com/a/33049392  How to vertically align text inside a flexbox?


diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index b1c9159..f7d07e1 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -35,12 +35,34 @@
+    <section id="foo">
+      <header>Foo</header>
+      <article>
+        <div class="one"></div>
+        <div class="two"></div>
+      </article>
+    </section>
   </div>
 </template>
 
@@ -154,4 +176,106 @@ export default {
 .clear {
   clear: both;
 }
+article {
+  background: #0f0;
+  /*flex*/
+  display: flex;
+  justify-content: space-between;
+}
+
+.one {
+  /*flex*/
+  margin-top: 10px;
+  margin-left: 10px;
+  background: red;
+  width: 30px;
+  height: 30px;
+}
+
+.two {
+  /*flex*/
+  margin-top: 10px;
+  margin-right: 10px;
+  background: blue;
+  width: 30px;
+  height: 30px;
+}
+
 </style>

s



display: flex;  讓容器變成flex
justify-content: space-between;  可以直接點chrome的Open flexbox editor去調








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年10月4日 星期二

chrome插件:Highlight This

目的

在網頁上多個特定單詞用不同顏色標記出來
https://chrome.google.com/webstore/detail/highlight-this-finds-and/fgmbnmjmbjenlhbefngfibmjkpbcljaj   Highlight This: finds and marks words

設定群組和單詞



設定顏色


設定作用的域名白名單


結果

yahoo.com (Test Group和Test Group 2皆生效)


google.com (Test Group和Test Group 2皆生效)


bing.com (只有Test Group 2生效)










2022年8月9日 星期二

Yii 2心得

環境

Windows 10
PHP 8
nginx
MySQL 8.0.18
yiisoft/yii2 2.0.45
dmstr/yii2-adminlte-asset 2.6.2
mdmsoft/yii2-admin 2.12


安裝 Yii 2 Advanced Project Template



使用composer 安裝

$ composer2 create-project --prefer-dist yiisoft/yii2-app-advanced yii-application


準備

初始化

在 yii-application/ 目錄中執行
$ php init
Yii Application Initialization Tool v1.0

Which environment do you want the application to be initialized in?

  [0] Development
  [1] Production

  Your choice [0-1, or "q" to quit] 0


  Initialize the application under 'Development' environment? [yes|no] yes

  Start initialization ...

   generate backend/config/codeception-local.php
   generate backend/config/main-local.php
   generate backend/config/params-local.php
   generate backend/config/test-local.php
   generate backend/web/index-test.php
   generate backend/web/index.php
   generate backend/web/robots.txt
   generate common/config/codeception-local.php
   generate common/config/main-local.php
   generate common/config/params-local.php
   generate common/config/test-local.php
   generate console/config/main-local.php
   generate console/config/params-local.php
   generate console/config/test-local.php
   generate frontend/config/codeception-local.php
   generate frontend/config/main-local.php
   generate frontend/config/params-local.php
   generate frontend/config/test-local.php
   generate frontend/web/index-test.php
   generate frontend/web/index.php
   generate frontend/web/robots.txt
   generate yii
   generate yii_test
   generate yii_test.bat
   generate cookie validation key in backend/config/main-local.php
   generate cookie validation key in common/config/codeception-local.php
   generate cookie validation key in frontend/config/main-local.php
      chmod 0777 backend/runtime
      chmod 0777 backend/web/assets
      chmod 0777 console/runtime
      chmod 0777 frontend/runtime
      chmod 0777 frontend/web/assets
      chmod 0755 yii
      chmod 0755 yii_test

  ... initialization completed.

配置DB

common/config/main-local.php 

migration

$ php yii migrate
Yii Migration Tool (based on Yii v2.0.45)

Creating migration history table "migration"...Done.
Total 2 new migrations to be applied:
        m130524_201442_init
        m190124_110200_add_verification_token_column_to_user_table

Apply the above migrations? (yes|no) [no]:yes
*** applying m130524_201442_init
    > create table {{%user}} ... done (time: 0.064s)
*** applied m130524_201442_init (time: 0.097s)

*** applying m190124_110200_add_verification_token_column_to_user_table
    > add column verification_token string NULL DEFAULT NULL to table {{%user}} ... done (time: 0.040s)
*** applied m190124_110200_add_verification_token_column_to_user_table (time: 0.064s)


2 migrations were applied.

Migrated up successfully.

nginx

前端目錄 root /path/to/yii-application/frontend/web/ 
後端目錄 root /path/to/yii-application/backend/web/

偽靜態

location / {
    # Redirect everything that isn't a real file to index.php
    try_files $uri $uri/ /index.php$is_args$args;
}

避免存取assets/目錄下的php文件

# deny accessing php files for the /assets directory
location ~ ^/assets/.*\.php$ {
    deny all;
}

hosts


寶塔open_basedir報錯

Warning: require(): open_basedir restriction in effect. File(C:\path\yii-application\vendor\autoload.php) is not within the allowed path(s): (C:/path/yii-application/backend/web/;C:/Windows/Temp/;C:/Temp/;C:/BtSoft/temp/session/) in C:\path\yii-application\backend\web\index.php on line 6

Warning: require(C:\path\yii-application\vendor\autoload.php): Failed to open stream: Operation not permitted in C:\path\yii-application\backend\web\index.php on line 6

Fatal error: Uncaught Error: Failed opening required 'C:\path\yii-application\backend\web/../../vendor/autoload.php' (include_path='.;C:\php\pear') in C:\path\yii-application\backend\web\index.php:6 Stack trace: #0 {main} thrown in C:\path\yii-application\backend\web\index.php on line 6

寶塔面板 => 網站 => 設置 => 網站目錄 => 取消勾選 防跨站攻击(open_basedir)

測試用戶

backend/tests/_data/login_data.php 中有測試的user資料

return [
    [
        'username' => 'erau',
        'auth_key' => 'tUu1qHcde0diwUol3xeI-18MuHkkprQI',
        // password_0
        'password_hash' => '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne',
        'password_reset_token' => 'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490',
        'created_at' => '1392559490',
        'updated_at' => '1392559490',
        'email' => 'sfriesen@jenkins.info',
    ],
];
把他換成SQL

INSERT INTO `user` ( `username`, `auth_key`, `password_hash`, `password_reset_token`, `email`, `created_at`, `updated_at` )
VALUES
    (
        'erau',
        'tUu1qHcde0diwUol3xeI-18MuHkkprQI',
        '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne',
        'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490',
        'sfriesen@jenkins.info',
    1392559490,
    1392559490);
s
然後就能用用戶 erau (密碼password_0)登錄了,
或是登錄時斷點在 User->validatePassword() 時打印 Yii::$app->security->generatePasswordHash("password") 設置你要的密碼

安裝AdminLTE



$ composer2 require dmstr/yii2-adminlte-asset "^2.1"


複製 vendor/dmstr/yii2-adminlte-asset/example-views/yiisoft/yii2-app/backend/views/  

不要使用bootstrap4



因為 AdminLTE 2.4.13 使用 bootstrap 3 ,但是 yii2-app-advanced 2.0.43 以後的 backend/assets/AppAsset.php 改用了 bootstrap4 ,會造成樣式衝突
所以改回bootstrap 3

diff --git a/backend/assets/AppAsset.php b/backend/assets/AppAsset.php
index a93f5c3..ee9d874 100644
--- a/backend/assets/AppAsset.php
+++ b/backend/assets/AppAsset.php
@@ -18,6 +18,7 @@ class AppAsset extends AssetBundle
     ];
     public $depends = [
         'yii\web\YiiAsset',
-        'yii\bootstrap4\BootstrapAsset',
+        // 'yii\bootstrap4\BootstrapAsset',
+        'yii\bootstrap\BootstrapAsset',
     ];
 }
s

安裝yii2-admin

http://static.kancloud.cn/curder/yii/247758  Yii2中使用yii2-admin搭建RBAC权限控制
http://static.kancloud.cn/curder/yii/247759  Yii2项目后台整合yii2-admin模块


$ composer2 require mdmsoft/yii2-admin "~2.0"

Migration

新增menu菜單表 

$ php yii migrate --migrationPath=@mdm/admin/migrations
Yii Migration Tool (based on Yii v2.0.45)

Total 2 new migrations to be applied:
        m140602_111327_create_menu_table
        m160312_050000_create_user

Apply the above migrations? (yes|no) [no]:yes
*** applying m140602_111327_create_menu_table
    > create table {{%menu}} ... done (time: 0.197s)
*** applied m140602_111327_create_menu_table (time: 0.414s)

*** applying m160312_050000_create_user
*** applied m160312_050000_create_user (time: 0.037s)

2 migrations were applied.

Migrated up successfully.

新增 rbac 相關表(auth_rule、auth_item、auth_item_child、auth_assignment)

$ php yii migrate --migrationPath=@yii/rbac/migrations
Yii Migration Tool (based on Yii v2.0.45)

Total 4 new migrations to be applied:
        m140506_102106_rbac_init
        m170907_052038_rbac_add_index_on_auth_assignment_user_id
        m180523_151638_rbac_updates_indexes_without_prefix
        m200409_110543_rbac_update_mssql_trigger

Apply the above migrations? (yes|no) [no]:yes
*** applying m140506_102106_rbac_init
    > create table {{%auth_rule}} ... done (time: 0.058s)
    > create table {{%auth_item}} ... done (time: 0.059s)
    > create index idx-auth_item-type on {{%auth_item}} (type) ... done (time: 0.039s)
    > create table {{%auth_item_child}} ... done (time: 0.060s)
    > create table {{%auth_assignment}} ... done (time: 0.042s)
*** applied m140506_102106_rbac_init (time: 0.445s)

*** applying m170907_052038_rbac_add_index_on_auth_assignment_user_id
    > create index auth_assignment_user_id_idx on {{%auth_assignment}} (user_id) ... done (time: 0.037s)
*** applied m170907_052038_rbac_add_index_on_auth_assignment_user_id (time: 0.091s)

*** applying m180523_151638_rbac_updates_indexes_without_prefix
    > drop index auth_assignment_user_id_idx on {{%auth_assignment}} ... done (time: 0.018s)
    > create index {{%idx-auth_assignment-user_id}} on {{%auth_assignment}} (user_id) ... done (time: 0.021s)
    > drop index idx-auth_item-type on {{%auth_item}} ... done (time: 0.046s)
    > create index {{%idx-auth_item-type}} on {{%auth_item}} (type) ... done (time: 0.034s)
*** applied m180523_151638_rbac_updates_indexes_without_prefix (time: 0.185s)

*** applying m200409_110543_rbac_update_mssql_trigger
*** applied m200409_110543_rbac_update_mssql_trigger (time: 0.049s)


4 migrations were applied.

Migrated up successfully.


配置


diff --git a/backend/config/main.php b/backend/config/main.php
index 6e2fe57..1483c5c 100644
--- a/backend/config/main.php
+++ b/backend/config/main.php
@@ -11,7 +11,12 @@
     'basePath' => dirname(__DIR__),
     'controllerNamespace' => 'backend\controllers',
     'bootstrap' => ['log'],
-    'modules' => [],
+    'modules' => [
+        'admin' => [
+            'class' => 'mdm\admin\Module',
+            // 'layout' => 'left-menu',//yii2-admin的导航菜单
+        ]
+    ],
     'components' => [
         'request' => [
             'csrfParam' => '_csrf-backend',
@@ -37,14 +42,34 @@
         'errorHandler' => [
             'errorAction' => 'site/error',
         ],
-        /*
+        // 開啟enablePrettyUrl
         'urlManager' => [
             'enablePrettyUrl' => true,
             'showScriptName' => false,
             'rules' => [
             ],
         ],
-        */
+
+        'authManager' => [
+            // 'class' => 'yii\rbac\PhpManager', // or use 'yii\rbac\DbManager'
+            'class' => 'yii\rbac\DbManager', // 使用DB做RBAC
+        ],
     ],
     'params' => $params,
+    'as access' => [
+        'class' => 'mdm\admin\components\AccessControl',
+        'allowActions' => [
+            'site/*',
+            'admin/*', // 臨時開啟admin 路由
+            'gii/*',  // 臨時開啟gii
+            'debug/*',  // debug bar
+            // '*',  // 臨時開啟所有權限
+            'some-controller/some-action',
+            // The actions listed here will be allowed to everyone including guests.
+            // So, 'admin/*' should not appear here in the production, of course.
+            // But in the earlier stages of your development, you may probably want to
+            // add a lot of actions here until you finally completed setting up rbac,
+            // otherwise you may not even take a first step.
+        ]
+    ],
 ];

s
開啟enablePrettyUrl前的訪問地址是: /index.php?r=admin  

使用

Users(用戶)


Routes(路由)



把左邊可用的路由(會自動抓)放到右邊去,右邊的路由才會在permissionrole時出現

Permissions(權限)


分配路由

創建permission後,可以在view裡面對該permission設置多個route

Roles(角色)


分配路由和權限

創建role後,可以在view裡面對該role設置多個route、permission和role

Assignments(分配)


分配角色和權限

根據現有的user,可以在view裡面對該user設置多個role和permission

無權限訪問顯示403

如果沒對該用戶分配有delete權限的role和delete的permission 



Rules(規則)

要和代碼配合,可以不用

Menus(菜單)


設置父菜單


獲取有權限的菜單


diff --git a/backend/views/layouts/left.php b/backend/views/layouts/left.php
index 53b2cb3..f2b64a1 100644
--- a/backend/views/layouts/left.php
+++ b/backend/views/layouts/left.php
@@ -1,3 +1,8 @@
+<?php
+
+use mdm\admin\components\MenuHelper;
+
+?>
 <aside class="main-sidebar">
 
     <section class="sidebar">
@@ -25,42 +30,57 @@
             </div>
         </form>
         <!-- /.search form -->
+        <?php
+        $items = MenuHelper::getAssignedMenu(Yii::$app->user->id);
+        ?>
 
         <?= dmstr\widgets\Menu::widget(
             [
                 'options' => ['class' => 'sidebar-menu tree', 'data-widget'=> 'tree'],
+                'items' => $items,
             ]
         ) ?>
s

設置父菜單js錯誤

Uncaught TypeError: elem.getClientRects is not a function

因為 jQuery 是v3.6.0 (vendor/bower-asset/jquery/dist/jquery.js)而 jQuery UI 是 v1.11.4 (vendor/mdmsoft/yii2-admin/assets/jquery-ui.js)

升級jQuery UI(未使用)

https://github.com/mdmsoft/yii2-admin/issues/386  jquery-ui.js need to update !!!
https://github.com/mdmsoft/yii2-admin/pull/363  Fix menu. Update jquery-ui.


加入 jquery-migrate

https://stackoverflow.com/a/38314852  jQuery UI error - f.getClientRects is not a function
加入 jquery-migrate-3.0.0.js 讓jquery 3+ 兼容低版本jquery ui

diff --git a/backend/assets/AppAsset.php b/backend/assets/AppAsset.php
index ee9d874..ac1f620 100644
--- a/backend/assets/AppAsset.php
+++ b/backend/assets/AppAsset.php
@@ -15,6 +15,7 @@ class AppAsset extends AssetBundle
         'css/site.css',
     ];
     public $js = [
+        'js/jquery-migrate-3.0.0.js',
     ];
     public $depends = [
         'yii\web\YiiAsset',
diff --git a/backend/web/js/jquery-migrate-3.0.0.js b/backend/web/js/jquery-migrate-3.0.0.js
new file mode 100644
index 0000000..350b799
--- /dev/null
+++ b/backend/web/js/jquery-migrate-3.0.0.js
@@ -0,0 +1,540 @@
+/*!
+ * jQuery Migrate - v3.0.0 - 2016-06-09
...
s

Gii

https://www.yiichina.com/tutorial/695  gii的命令行用法 [ 2.0 版本 ]


先建表,以水果表為例:

CREATE TABLE `fruits` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
  `created_at` datetime NOT NULL COMMENT '創建時間',
  `updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
s

網頁生成CRUD代碼

Table Name 可以下拉用選的,選完後 Model Class Name 會自動填入


命令行生成CRUD代碼

為了和前面區分,這裡model和controller用單數的Fruit
$ php yii gii/model --ns=common\\models --tableName=fruits --modelClass=Fruit
Running 'Model Generator'...

The following files will be generated:
        [new] C:\path\yii-application\common\models\Fruit.php

Ready to generate the selected files? (yes|no) [yes]:

Files were generated successfully!
Generating code using template "C:\path\yii-application\vendor\yiisoft\yii2-gii\src\generators\model/default"...
 generated C:\path\yii-application\common\models\Fruit.php
done!

--ns=common\\models - 指定Model的路徑
--tableName=fruits - 表名用複數
--modelClass=Fruit - Model名用單數

$ php yii gii/crud --modelClass=common\\models\\Fruit --controllerClass=backend\\controllers\\FruitController --viewPath=@backend/views/fruit --searchModelClass=common\\models\\FruitSearch
Running 'CRUD Generator'...

The following files will be generated:
        [new] C:\path\yii-application\backend\controllers\FruitController.php
        [new] C:\path\yii-application\common\models\FruitSearch.php
        [new] C:\path\yii-application\backend\views\fruit\_form.php
        [new] C:\path\yii-application\backend\views\fruit\_search.php
        [new] C:\path\yii-application\backend\views\fruit\create.php
        [new] C:\path\yii-application\backend\views\fruit\index.php
        [new] C:\path\yii-application\backend\views\fruit\update.php
        [new] C:\path\yii-application\backend\views\fruit\view.php

Ready to generate the selected files? (yes|no) [yes]:

Files were generated successfully!
Generating code using template "C:\path\yii-application\vendor\yiisoft\yii2-gii\src\generators\crud/default"...
 generated C:\path\yii-application\backend\controllers\FruitController.php
 generated C:\path\yii-application\common\models\FruitSearch.php
 generated C:\path\yii-application\backend\views\fruit\_form.php
 generated C:\path\yii-application\backend\views\fruit\_search.php
 generated C:\path\yii-application\backend\views\fruit\create.php
 generated C:\path\yii-application\backend\views\fruit\index.php
 generated C:\path\yii-application\backend\views\fruit\update.php
 generated C:\path\yii-application\backend\views\fruit\view.php
done!

--modelClass=common\\models\\Fruit - 指定所使用的model路徑
--controllerClass=backend\\controllers\\FruitController - 設置控制器路徑
--viewPath=@backend/views/fruit - 設置視圖路徑,fruit用單數
--searchModelClass=common\\models\\FruitSearch= 設定搜索模型

如何知道命令行有哪些參數可用

如 searchModelClass 
可在開發者工具裡的表單查看




即可有水果表(fruits)的CRUD

debugbar

內建debugbar,laravel則需要另外安裝
路由




CVE



https://packagist.org/packages/yiisoft/yii2/advisories?version=4339826  yiisoft/yii2 Security Advisories for 2.0.37
2.0.37之前的版本有 遠程代碼執行(remote code execution)漏洞

總結

和laravel相比,官方更新沒有那麼快搭建後台系統比較麻煩(dmstr/yii2-adminlte-asset + mdmsoft/yii2-admin),不像laravel-admin直接搞定,網上的文檔也比較舊


其他

update

https://stackoverflow.com/a/38692364  Yii2: update field with query builder

\Yii::$app->db->createCommand()
    ->update('fruits', ['status' => 1], 'id > 1')
    ->execute();
=> UPDATE `fruits` SET `status`=1 WHERE id > 1










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