2021年3月19日 星期五

ECMAScript 6 學習筆記

Template Literals 字串樣版
====
let name='弘弘';
alert(`我叫${name},你好嗎?` );

Arrow Functions 箭頭函數
====
setInterval(() => console.log(aa),1000); // 無參數,單一指令
.then(result=>{ // 有參數,多個指令
console.log('looped');        
alert('looped');
});

走訪陣列
====
let arr = [1, 2, 3];
for (let a of arr) {
    console.log(a);
}

2021年3月17日 星期三

zoom 學習筆記

預設姓名 (免除進入會議時還要手動輸入姓名)
會議連結帶入參數 : &uname=姜大弘%20(弘弘)

LINE強制用外部瀏覽器開啟會議連結 (iphone尚未安裝zoom app 才能正確跳轉到下載頁面)
會議連結帶入參數 : &openExternalBrowser=1

事件處理 : 進入會議室自動呼叫網址
一、設定 webhook
1. 登入 app maketplace https://marketplace.zoom.us/
2. 下拉選單選擇 build app
3. Webhook Only > create
4. Feature > Event Subscriptions > 新增
Event notification endpoint URL : 輸入要呼叫的網址
Event types : 新增 meeting > Participant/Host joined meeting
二、呼叫網址端程式寫法
if (Request.Headers["authorization"] != [對應 feature 的 Verification Token]) return; // 確認來源合法
      string documentContents = ""; // request body
      using (Stream receiveStream = Request.InputStream)
        using (StreamReader readStream = new StreamReader(receiveStream, System.Text.Encoding.UTF8))
          documentContents = readStream.ReadToEnd();
      var json = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(documentContents);
      var name = json.Value<Newtonsoft.Json.Linq.JObject>("payload").Value<Newtonsoft.Json.Linq.JObject>("object").Value<Newtonsoft.Json.Linq.JObject>("participant").Value<string>("user_name"); // 進入會議室的姓名
      var 會議id = json.Value<Newtonsoft.Json.Linq.JObject>("payload").Value<Newtonsoft.Json.Linq.JObject>("object").Value<string>("id");


2021年3月12日 星期五

清單效果

<dl class="row">
    <dd class="d-flex justify-content-end col-4 col-sm-5 col-md-6">活動:</dd>
    <dd class="col-8 col-sm-7 col-md-6">大溪一日遊</dd>
    <dd class="d-flex justify-content-end col-4 col-sm-5 col-md-6">日期:</dd>
    <dd class="col-8 col-sm-7 col-md-6">2021-12-31</dd>
</dl> 
<style>
  dl dd:nth-child(odd) { // 左邊文字強制不換列,超過則裁切
    padding-left: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow:clip;
  }
  dl dd:nth-child(even) { // 右邊文字強制不換列,超過則出現...
    padding-right: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
</style>

2021年3月10日 星期三

Vue2 學習筆記

引入相關 .js 和 .css

搭配 asp.net mvc razor page 架構,直接引入相關 .js 和 .css,不使用webpack import 方式
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script> @*目前版本是2.x*@
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue-icons.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> @*http 請求*@
<script src="~/Scripts/moment-with-locales.min.js"></script> @*date 格式化*@

html

定義app範圍
<div id="app"> 
...
</div>

顯示data
{{plaintext}} 

顯示html文字
<span v-html="htmltext"></span>

input綁定屬性值,.number 表示限制只能輸入數字
<input v-model.number="unitprice" type="number" /> 

.trim 表示自動刪除前後空白字元
<input type="text" v-model.trim="trimMsg" /> 

可綁定陣列屬性
<label><input type="checkbox" value="Jack" v-model="checkedNames">Jack</label> 
<label><input type="checkbox" value="John" v-model="checkedNames">John</label>

href 替換變數bbb
<a v-bind:href='`...?a=${bbb}`' target="_blank">{{ccc}}</a> 

select 綁定用法,v-bind:value="option" 透過 v-bind: 讓屬性 綁定變數
<select v-model="selected"> 
        <option disabled value="">请选择</option>
        <option v-for="option in options" v-bind:value="option.value" v-bind:key="option.value"> @*v-bind:key 指定識別物件的屬性*@
            {{ option.text }}
        </option>
</select>

select 複選綁定用法,搭配ctrl or shift 選擇多筆
<select multiple v-model="multiselected"> 
        <option disabled value="">请选择</option>
        <option v-for="option in options" v-bind:value="option.value">
            {{ option.text }}
        </option>
</select>

v-bind:xxx 對應 attribute:xxx 綁定屬性值,v-on:xxx 對應事件,.stop 表示事件觸發不往上層拋,.prevent 表示不觸發預設行為
<button v-bind:disabled="loading" v-bind:class="{ 'error': 按鈕禁用 }" v-bind:style="按鈕樣式" v-on:click.stop.prevent="下單($event)">下單</button> 

v-on:keyup.enter 表示按enter 後執行
<input type="text" placeholder="輸入任意文字後按下 enter 鍵" v-model.trim="trimMsg"
 v-on:keyup.enter="下單()"> 
v-on:keyup.enter.exact 表示若有搭配其他按鍵則不要觸發 (例如 shift+enter)

依據條件控制是否顯示
<span v-show="按鈕禁用">按鈕禁用</span> 
※若搭配 class:d-flex 會造成 v-show 無法正確隱藏,必須改用 v-if

強制更新頁面區塊,每當componentKey 更新時就會更新此div (重新判斷v-show)
<div v-show="new Date()>=時間 && !已預約" v-bind:key="componentKey">很抱歉,活動已開始,無法預約囉</span>

依據屬性值控制顯示忙碌中圖案,例如呼叫後端尚未完成時,內部元素會暫時無法互動,預設位置是外部容器的正中間
<b-overlay :show="loading" opacity="0.8" variant="light" spinner-variant="primary">
...
</b-overlay>
若要保持捲動瀏覽器視窗時位置不會跑掉,外面必須再包一層div
<div style="position: fixed; top: 2em; width: 100%; z-index: 10;"><b-overlay :show="loading" opacity="0.8" variant="light" spinner-variant="primary"></b-overlay></div>
或單獨使用 b-spinner 顯示忙碌中圖案,各按鈕單獨控制是否暫時disable
<div style="position: fixed;top:1em; left: 50%; transform: translateX(-50%)"  v-show="loading"><b-spinner label="Loading..."  variant="primary"></b-spinner></div>

使用 Bootstrap Icon

<b-icon-[Bootstrap Icon name]></b-icon-[Bootstrap Icon name]>
<b-icon-clipboard></b-icon-clipboard> 
針對 arrow-90deg-left 順時針旋轉90度
<b-icon-arrow-90deg-left style="transform: rotate(90deg)"></b-icon-arrow-90deg-left> 

格式化輸出

{{ xxx | dateFormat('HH:mm')}}
{{ xxx | numberFormat(0,false)  }}

javascript (放在 </html> 後面才能正確載入)
====
設定 moment 為繁體中文
moment.locale('zh-tw'); 

加入filter

// 數值格式化
Vue.filter('numberFormat', function (value, 最大小數位, 千分位符號) {// 小數位、千分位符號
        return Intl.NumberFormat('en-US', { maximumFractionDigits: 最大小數位, useGrouping: 千分位符號 }).format(value);
    });
// 日期格式化
Vue.filter('dateFormat', function (value, myFormat) {
    return moment(value).format(myFormat);
});

載入 BootstrapVue 套件,提供多種美化功能
Vue.use(BootstrapVue) 

建立 vue app

const app = new Vue({ 
        el: '#app', // 綁定 html element
        data() { // 定義屬性及預設值,需要先定義才能使用 v-model
            return {
                plaintext: '123',
                htmltext: '<i>123</i>',
                trimMsg: null,
                loading: false, // 控制顯示忙碌中圖案
            }
        },
        methods: { // 定義函數
            取得參數字串() {
                return this.參數字串;
            },
            下單() {
                this.$bvToast.toast('已下單', {variant: 'success',solid: true, noCloseButton:true}); // 顯示訊息視窗,variant 決定顏色,sold:true 表示不要透明處理,noCloseButton:true 搭配不指定 title 才不會顯示 title bar
            },
            getdata() {
                let $vue = this;
                axios.post('./get', { 參數: '@Model' }) // 呼叫後端
                    .then(response => {
                    this.resultdata = response.data;
                    }).catch(function (error) { // 请求失败处理
                        console.log(error);
                        $vue.$bvToast.toast(error.response.data, { variant: 'danger', solid: true, noCloseButton: true }); // catch 中的 this 是window,需要在之前先用變數儲存 vue 物件
                    });
            },
            並行呼叫() {
                axios.all([axios.post('./get', { 參數: '@Model' }), axios.post('./testpost')]) // 同時呼叫多個後端,全部完成後才進行下一個動作時使用此方法
                    .then(axios.spread((response1, response2) => {
                        this.resultdata1 = response1.data;
                        this.resultdata2 = response2.data;
                    }));
            },
        },
        computed: { // 計算型屬性,html使用時不需要加括號
            金額() {
                return this.unitprice * this.number;
            },            
        },
        watch: { // 監控屬性變化
            trimMsg(val, oldValue) { // 當trimMsg更新時觸發
                console.log(`新的 msg: ${val}`);
                console.log(`舊的 msg: ${oldValue}`);
            }
        },
        created: function () { // app 建立後觸發
            let $vue = this; // 預先儲存 app 以供函數使用
            axios.interceptors.request.use( // 呼叫後端前觸發
                function (config) {
                    $vue.loading = true; // 搭配b-spinner顯示忙碌中圖案,且透過 v-bind:disabled 避免重複執行click
                    return config;
                },
                function (error) {
                    $vue.loading = false; // 呼叫失敗時隱藏忙碌中圖案
                    return Promise.reject(error);
                }
            );
            axios.interceptors.response.use(function (response) { // 呼叫後端完成後觸發
                $vue.loading = false; // 呼叫完成後隱藏忙碌中圖案
                return response;
            }, function (error) {
                $vue.loading = false; // 呼叫完成但失敗時隱藏忙碌中圖案
                return Promise.reject(error);
            });
        },
        mounted: function () {
            this.$nextTick(function () {
                // 放置頁面載入後要執行的初始動作,類似 jquery $(document).ready(function() {})
                // 若要立刻呼叫 axios.post 必須先等候一下,否則 b-overlay 無法正確顯示
                // ex. setTimeout(() => this.查詢訂單(), 100);
            })
        },
    });

避免中文字出現亂碼

1. <head></head> 加入 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
2. 檔案編碼必須為"具有BOM 的 UTF-8",若無則用記事本另存,編碼選擇 具有BOM 的 UTF-8 (visual studio create view 預設此編碼)

混入 (可放共用函數)

import { commonMixin } from '@Url.Content("~/scripts/commonVue.js")'
Vue.mixin(commonMixin); // 放在 Vue.filter 之後避免 filter 失效
commonVue.js :
export const commonMixin = {
    methods: {       
        onerror: function (error) {
            console.error(error);
            if (error.response.status == 401) {
                alert('登入逾時');
                location.reload();
            }
            else
            this.$bvToast.toast(error.response.data, { variant: 'danger', solid: true, noAutoHide: true });
    },
  }
}

※若要動態新增物件屬性,必須在綁定到 vue 之前完成,否則會喪失DOM雙向綁定機制
const rows = [{value:1},{value:2},];
rows.forEach(a => {
a.text = '...';
});                                        
this.rows = rows;


相關連結

Entity Framework 建立新物件並儲存後馬上取得關聯資料

使用 CreateProxy 建立物件,不要直接 new var newmodel = _contextXXX.CreateProxy<yyy>(); ... _contextXXX.yyy.Add(newmodel); await _contextXXX.SaveC...