2022年12月6日 星期二

判斷瀏覽器執行環境為手機或平板

 function isMobile() {
    return /iPhone|Android/i.test(navigator.userAgent);
}

function isTablet() {
    return /(ipad|tablet|(android(?!.*mobile))|(windows(?!.*phone)(.*touch))|kindle|playbook|silk|(puffin(?!.*(IP|AP|WP))))/.test(navigator.userAgent.toLowerCase());
}

2022年11月15日 星期二

epplus 設定儲存格為行動電話格式

  public static void 設定行動電話(this ExcelRange range, string 電話)
        {
            range.Value = 電話.ToInt64();
            range.Style.Numberformat.Format = "[>99999999]0000-000-000;000-000-000";
        }

 ws.Cells[rowindex, colindex].設定行動電話("0911222333"); => excel 顯示為 0911-222-333

2022年11月1日 星期二

javascript 走訪物件屬性

for (const prop in obj)
    if (Object.prototype.hasOwnProperty.call(obj, prop)) 
        console.log(obj[prop]);

2022年10月28日 星期五

swiper (幻燈片) 學習心得

左右箭頭自定義且不要重疊

html
<swiper :modules="modules" slides-per-view="auto" :navigation="{ nextEl: '.swiper-button-next',prevEl: '.swiper-button-prev',}">
        <div class="swiper-button-next"><i class="bi bi-caret-right-fill" style="font-size:1.5em"></i></div>
        <div class="swiper-button-prev"><i class="bi bi-caret-left-fill" style="font-size:1.5em"></i></div>

css
<style>
    .swiper-button-prev:after, .swiper-button-next:after {
        content: '';
    }
    .swiper {
        padding: 0 1.75em;
    }
    .swiper-button-next {
        right: 0
    }
    .swiper-button-prev {
        left: 0
    }
    .swiper-button-next, .swiper-button-prev {
        background-color:white;
        top:0;
        margin-top:unset;
        height:100%;
    }
</style>

官網

2022年10月17日 星期一

開發環境網站遇到 SSL 憑證無效

錯誤訊息: AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: NotTimeValid
確認是否 localhost 憑證有效 : 設定 > 網際網路選項 > 內容 > 憑證,或直接點擊網址列左側不安全文字查看憑證

解法1: 
刪除 c:\Users\[username]\AppData\Roaming\ASP.NET\https\ 裡面所有檔案,然後重啟專案,若有配合 vue 則連同 vue 的 npm 一起重啟,此時檔案會重新產生

解法2:
於 visual studio 中開啟套件管理主控台並輸入下面指令
dotnet dev-certs https --clean
dotnet dev-certs https --trust

2022年9月29日 星期四

Vue3 with composition api 學習心得

import 相關物件

import { ref, readonly, computed, watch, watchEffect, onMounted, provide} from 'vue';
import * as common from "@/utils/common"; // @ = clientapp/src


基本用法

setup() {
  const itemRefs = []; // 定義內部使用物件
  const orders = ref([]); // 定義HTML可使用物件
  const obj = readonly(ref({value:0,text:'123'})); // 定義唯讀物件
  const no = computed(() => `$No.{obj.value}`); // 定義計算物件
  const f1= (p1, p2) => { // 定義函數
    orders.value=xxx; // 設定物件值
  }
  onMounted(() => { // html rendered
  ...
  })
  ... // 預先執行程式
  return {orders, f1} // 回傳HTML可存取的物件和函數
}

等待 dom 更新完畢

import { nextTick } from 'vue'
用法1:
const xxx = async () => {
  await nextTick(); // 等待 dom 更新完畢
  ...
}
用法2:
const xxx = () => {
  nextTick(() => ...);  // 設定 dom 更新完畢後要執行的程式
}

使用 props 和 emits

props: ['id'], 
emits: ['done'],
setup(props, context) {
  console.log(props.id) // 取得 props
  context.emit('done'); // 回拋事件

若要修改 props 物件內容,需要另外建立參考物件
const objref = ref(props.obj)
objref.name='123'

使用 provide 和 inject

上層元件(例如 app.vue)
provide("userName", userName) // 設定子元件可讀取寫入的物件
provide('book', readonly(book)) // 設定子元件可讀取的物件
provide("hasRight", { hasRight }) // 設定子元件可呼叫的函數

子元件
const userName = inject("userName"); // 注入上層元件的物件
const { hasRight } = inject("hasRight"); // 注入上層元件的函數


設定watch

watch(id, (newValue, oldValue) => { // 監聽變數
});
watch(()=>obj.id, (newValue, oldValue) => { // 監聽物件屬性
});
watch(()=>obj, (newValue, oldValue) => { // 監聽物件所有屬性
},{deep:true});
watchEffect(() => { // 自動監控使用到的變數
   fetchData(); // 只要 props.id 有變化就會自動呼叫 fetchData(),但若 props.id 包在 setTimeout 裡面則不會自動監控
})
const fetchData = () => {
if (props.id)
axios.get(...)
.then(response => {
...
});
}


v-for 搭配 ref

<div v-for="order in orders" :key="order.id" :ref="setItemRef">
let itemRefs = [];
const setItemRef = el => {
if (el) {
itemRefs.push(el) // 將 v-for 每次產生出來的元件放入陣列
}
if (itemRefs.length == orders.value.length) { // 當元件全部產生後執行
const index = orders.value.findIndex(a => a.id == props.id); // 取得特定元件
itemRefs[index].scrollIntoView(); // 讓特定元件顯示在最上面
}
}
onBeforeUpdate(() => {
                itemRefs = []
            })

避免 import .vue 出現 typescript 編譯錯誤

修改 src/shims-vue.d.ts
declare module '*.vue' {
    import Vue from 'vue'
    export default Vue
}

異步載入元件

import { defineAsyncComponent } from 'vue';
export default {
components: {
xxx: defineAsyncComponent(() =>
import('./xxx.vue')
),
},

DOM出現於視野內時觸發執行動作

import { useIntersectionObserver } from '@vueuse/core'
export default {
        setup(props) {
            const targetEl = ref();
const { stop } = useIntersectionObserver(
                targetEl,
                ([{ isIntersecting }]) => {
                    if (isIntersecting) {                        
                        ...
                        stop(); // 避免重複觸發
                    }
                }
            );
    return { targetEl }
        },
    };
<template>
    <div ref="targetEl"></div>
</template>

強制更新元件內容

例如屬性值是動態判斷且會參考 props,但 props 改變時並不會更新屬性值
設定元件的 key 並更新其值會強制更新元件內容
html
====
<WaitSchedule :key="updatekey"></WaitSchedule>
javascript
====
import WaitSchedule from './xxx.vue';
    export default {
        components: {
            WaitSchedule
        },
        setup() {
            const updatekey=ref(1)
            watch(() => xxx, () => {
                updatekey.value++;
            });


設定日期可選最大值為今天

<input class="form-control" type="date" :max="(new Date()).toISOString().split('T')[0]">

import self

須設定元件名稱否則會跳警告且無法正確載入第二個自己
export default {
    name: 'name1',

動態css

綁定 css 的值為某個屬性,以達到動態設定目的
<style>
    .cv-week {
        min-height: v-bind(weekheight);
    }
</style>
 export default {
     setup() {
        const maxevents=ref()
        const weekheight = computed(() => `${1 + maxevents.value * 2}em`)
        return {weekheight }

※單檔元件才有作用 (javascript 寫在同一個 .vue)


※vue3 v-on:change & v-on:input 會抓到舊值,寫法可改為 @change="xxx($event)",$event 放的是新值
※作為 component 必須有唯一的root element否則會有異常狀況


讓容器內的元件出現在容器可見區域最上方

xxx.scrollIntoView(); 

使用情境 : 容器設定 overflow-y: auto 時,內部有多個元件且超過可見區域範圍而出現垂直捲軸,指定讓某個元件顯示在容器可見區域最上方

參考來源

2022年9月23日 星期五

導入 ESLint 學習心得

若要允許 html 使用全形空白及 import xxx.vue 檔名使用中文命名,修改 package.json 如下
"eslintConfig": {
  "rules": {
    "no-irregular-whitespace": 0 <= 為了使用全形空白
    "vue/multi-word-component-names": 0 <= 為了使用中文命名
  }
}

2022年9月13日 星期二

例外處理機制

目的 : 讓前端呼叫者可以顯示易懂的錯誤訊息

步驟 :
1. 自訂 ActionResult
public class CustomError : IActionResult
{
private readonly HttpStatusCode _status;
private readonly string _errorMessage;

public CustomError(HttpStatusCode status, string errorMessage)
{
_status = status;
_errorMessage = errorMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(new
{
errorMessage = _errorMessage
})
{
StatusCode = (int)_status,
};
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = _errorMessage;
await objectResult.ExecuteResultAsync(context);
}
}

2. 自訂 ExceptionFilter
public class BaseExceptionFilter : IExceptionFilter
{
private readonly IHostEnvironment _hostEnvironment;

public BaseExceptionFilter(IHostEnvironment hostEnvironment) =>
_hostEnvironment = hostEnvironment;

public void OnException(ExceptionContext context)
{
if (_hostEnvironment.IsDevelopment())
{
// 顯示英文訊息方便google
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
}

string controllerName = context.RouteData.Values["controller"].ToString();
string actionName = context.RouteData.Values["action"].ToString();
var request = context.HttpContext.Request;
var requestUrl = request.Scheme + "://" + request.Host + request.Path;
                var username = context.HttpContext.User?.Identity.Name;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("xxx發生未預期錯誤");
sb.AppendLine($"錯誤訊息: {context.Exception.詳細訊息()}");
sb.AppendLine($"堆疊追蹤: {context.Exception.StackTrace.Replace("<","<").Replace(">",">")}"); // 若使用skype 通知則不接受<和>
sb.AppendLine($"網址: {requestUrl}");
         sb.AppendLine($"系統使用者: {username}");
sb.AppendLine($"Controller: {controllerName}");
sb.AppendLine($"Action: {actionName}");
                if (_hostEnvironment.IsDevelopment()) {
                    // 直接顯示除錯訊息
                    context.Result = new CustomError(HttpStatusCode.InternalServerError, sb.ToString());
                }
                else {
                    // todo: 使用 skype、line、email 等方式發送除錯訊息給資訊人員
                     context.Result =  new CustomError(HttpStatusCode.InternalServerError, "發生未預期錯誤,請洽資訊部");
                }
}
}

3.1. 建立頂層 Controller,遇到未攔截的錯誤時透過自訂 ExceptionFilter 回傳自訂 ActionResult
[TypeFilter(typeof(BaseExceptionFilter))]
public class BaseController : ControllerBase

3.2. Controller Action 若要回傳錯誤則透過自訂ActionResult
ex. return new CustomError(HttpStatusCode.BadRequest, "請輸入收貨地址");

4. 前端顯示錯誤訊息
ex.
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
console.error(error);
if (error.response.status == 401) {
alert('登入逾時');
setTimeout(() => {
window.location.reload();
}, 2000);
}
else {
if (error.response.data.errorMessage) alert(error.response.data.errorMessage);
else alert(error.message);
}
return Promise.reject(error);
});

非 core 版後端寫法

public class BaseExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// 顯示英文訊息方便google
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");

string controllerName = context.RouteData.Values["controller"].ToString();
string actionName = context.RouteData.Values["action"].ToString();
var requestUrl = context.HttpContext.Request.Url.AbsoluteUri;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("xxx發生未預期錯誤");
sb.AppendLine($"錯誤訊息: {context.Exception.詳細訊息()}");
sb.AppendLine($"堆疊追蹤: {context.Exception.StackTrace.Replace("<", "<").Replace(">", ">")}");
sb.AppendLine($"網址: {requestUrl}");
sb.AppendLine($"系統使用者: {xxx}");
sb.AppendLine($"Controller: {controllerName}");
sb.AppendLine($"Action: {actionName}");
// todo: 使用 skype、line、email 等方式發送除錯訊息給資訊人員
context.ExceptionHandled = true;
context.HttpContext.Response.TrySkipIisCustomErrors = true;
context.HttpContext.Response.Write("發生未預期錯誤,請洽資訊部");
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}

頂層Controller
[BaseExceptionFilter]
public class BaseController : Controller
{
  
}
※若要回傳可預期錯誤給使用者看,則用以下方式
async public Task<IHttpActionResult> xxxAsync()
{
if (xxx)
{
HttpContext.Current.Response.Write(xxx);
HttpContext.Current.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
return default;
}

2022年8月26日 星期五

vue 使用中文注意事項

v-for 走訪物件名稱不要用中文,否則會造成事件函數永遠抓到第一個物件,舉例:
<div v-for="訂單 in 訂單s" :key="訂單.訂單編號">
<i class="bi bi-chevron-double-down" style="position:absolute;bottom:0.25em;right:0.25em;" @click="訂單.展開=true" ></i> // "訂單"抓到的是第一個物件而非當下走訪的物件
</div>

產生二維條碼

後端

安裝nuget套件 ZXing.Net

BarcodeWriter bw = new BarcodeWriter();
bw.Format = BarcodeFormat.QR_CODE;
bw.Options.Hints.Add(EncodeHintType.MARGIN, 2); // 避免內距太寬造成圖片過小
bw.Options.Height = bw.Options.Width = 80;
Bitmap bitmap = bw.Write("123");
var picture = sheet.Drawings.AddPicture("123", bitmap);
picture.SetPosition(0, 0, 0, 0); // zero base

前端

2022年8月21日 星期日

ass 雙語字幕隱藏英文

通常第二字幕為英文,且位置在下黑框處,透過修改顏色為黑色以便隱藏英文,例如:
Dialogue: 0,0:21:13.41,0:21:15.77,Default,,0,0,0,,格温\N{\fn微软雅黑\fs14}Gwen.
調整如下
Dialogue: 0,0:21:13.41,0:21:15.77,Default,,0,0,0,,格温\N{\fn微软雅黑\fs14\c&H000000}Gwen.

2022年7月22日 星期五

輸入方塊設定唯讀但可接受輸入資料

<b-form-input readonly id="輸入" v-on:keyup="輸入($event)" v-model="單號"></b-form-input>

輸入(event) {
            if (this.單號==null) this.單號 = '';
             if (event.key == 'Backspace') this.單號 = this.單號.left(this.單號.length - 1);
            else if (event.key != 'Enter') this.單號 += event.key;
},

先設定焦點,然後鍵盤輸入時可接收輸入資料,於手機上可避免跳出輸入鍵盤,適用於搭配相機或透過電腦接上掃描器掃條碼的情境

2022年7月20日 星期三

mouseenter 和 mouseover 的差別

mouseover: 當鼠標移入元素或其子元素都會觸發 (會造成重複觸發),對應的移除事件是 mouseout

mouseenter: 當鼠標移入元素本身(不包含子元素)會觸發,對應的移除事件是 mouseleave

2022年7月19日 星期二

取得資料庫表格欄位寬度

public static int? GetMaxLength(this System.Data.Entity.DbContext context, string tableName, string propertyName)
        {
            var oc = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)context).ObjectContext;

            return oc.MetadataWorkspace.GetItems(System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace).OfType<System.Data.Entity.Core.Metadata.Edm.EntityType>()
                     .Where(et => et.Name == tableName)
                     .SelectMany(et => et.Properties.Where(p => p.Name == propertyName))
                     .Select(p => p.MaxLength)
                     .FirstOrDefault();
        }

2022年7月7日 星期四

c# 例外錯誤訊息顯示為英文

System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");

2022年6月23日 星期四

設定焦點直到成功

考慮有些元件是後來才會動態出現,或一開始被設為禁用之後才動態改為啟用,透過 retry 直到成功設定為止
通常是搭配 vue.js 這類透過資料綁定控制顯示隱藏或禁用的方式需要以這種方式設定焦點

function autofocus(elem, id) {
    setTimeout(function () {
        if (id) elem = document.getElementById(id);
        let isFocused = (document.activeElement === elem)
        if (!isFocused) {
            $(elem).focus();
            autofocus(elem);
        }
    }, 100);
}

2022年6月20日 星期一

html5 內建表單驗證用法

html
====
<form id="form1"> 
 <input required .../> // 檢查是否有輸入資料
 <input type="email" ... /> // 檢查是否符合email 格式
 <input type="number" min="1" max="10" .../> // 檢查數值是否落在指定範圍
 <input pattern="..." .../> // 用正規表示式檢查輸入資料  
</form>

javascript
====
需要驗證表單則呼叫 checkValidity()
ex. $('#form1')[0].checkValidity()
需要顯示錯誤訊息則呼叫 reportValidity() (自動呼叫checkValidity())
ex. $('#form1')[0].reportValidity()

2022年6月17日 星期五

列舉 轉為 IEnumerable

public enum colors { red,blue,green }

var list=Enum.GetValues(typeof(colors)).Cast<colors>().Select(a => new { value = (int)a, text = a.ToString() });

2022年6月16日 星期四

淺層複製物件 by 走訪屬性

 public static T1 Clone<T1>(this object a, T1 target = default)
        {
            var obj = target == null ? (T1)Activator.CreateInstance(typeof(T1)) : target;
            foreach (var pbase in a.GetType().GetProperties())
            {
                var p = obj.GetType().GetProperties().Where(b => b.CanWrite && pbase.Name == b.Name && (pbase.PropertyType == b.PropertyType || pbase.PropertyType.IsAssignableFrom(b.PropertyType) || b.PropertyType.IsAssignableFrom(pbase.PropertyType))).SingleOrDefault(); // 考慮同型態但nullable 不同也進行複製(如 datetime? 和 datetime)
                if (p != null) p.SetValue(obj, pbase.GetValue(a, null), null);
            }
            return obj;
        }

用法:
a.Clone(b)
var b = a.Clone<xxx>()

讓網站色彩轉為深色模式 (和 windows 色彩設定連動)


<script src="https://cdn.jsdelivr.net/npm/darkmode-js"></script>
<script>
    function addDarkmodeWidget() {
        new Darkmode();
    }
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        var head = document.getElementsByTagName('head')[0];
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = '@Url.Content("~/Content/深色主題.css?"+DateTime.Now.ToShortTimeString())';
        link.media = 'all';
        head.appendChild(link);
        window.addEventListener('load', addDarkmodeWidget);
    }
</script> 

深色主題.css // 手動優化部分顏色配置
option {
    color: rgb(200,200,200);
    background-color: #333;
}

2022年6月13日 星期一

判斷IP是否為內部IP

 public static class IPAddressExtensions
    {
        /// <summary>
        /// Returns true if the IP address is in a private range.<br/>
        /// IPv4: Loopback, link local ("169.254.x.x"), class A ("10.x.x.x"), class B ("172.16.x.x" to "172.31.x.x") and class C ("192.168.x.x").<br/>
        /// IPv6: Loopback, link local, site local, unique local and private IPv4 mapped to IPv6.<br/>
        /// </summary>
        /// <param name="ip">The IP address.</param>
        /// <returns>True if the IP address was in a private range.</returns>
        /// <example><code>bool isPrivate = IPAddress.Parse("127.0.0.1").IsPrivate();</code></example>
        public static bool IsPrivate(this System.Net.IPAddress ip)
        {
            // Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
            if (ip.IsIPv4MappedToIPv6)
                ip = ip.MapToIPv4();

            // Checks loopback ranges for both IPv4 and IPv6.
            if (System.Net.IPAddress.IsLoopback(ip)) return true;

            // IPv4
            if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                return IsPrivateIPv4(ip.GetAddressBytes());

            // IPv6
            if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
            {
                return ip.IsIPv6LinkLocal ||
#if NET6_0
                       ip.IsIPv6UniqueLocal ||
#endif
                       ip.IsIPv6SiteLocal;
            }

            throw new NotSupportedException(
                    $"IP address family {ip.AddressFamily} is not supported, expected only IPv4 (InterNetwork) or IPv6 (InterNetworkV6).");
        }

        private static bool IsPrivateIPv4(byte[] ipv4Bytes)
        {
            // Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
            bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;

            // Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
            bool IsClassA() => ipv4Bytes[0] == 10;

            // Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
            bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;

            // Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
            bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;

            return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
        }
    }

舉例用法 : 檢查函數呼叫者是否來自內部IP
public static string xxx() {
var clientIP = System.Net.IPAddress.Parse(HttpContext.Current.Request.UserHostAddress);
if (!clientIP.IsPrivate()) throw new Exception("非法呼叫!!");
}

2022年5月14日 星期六

madvr + HDR 影片變灰

madvr 設定問題,參考如下 






就算螢幕實際只有8bit,但還是要選 auto,不能用預設的8bit,否則就會有問題

2022年5月5日 星期四

dll 專案使用內建連線字串

不要依賴組態設定檔,因為參考的專案不會使用到,直接於程式內處理
雖然可以在參考專案的組態設定檔加入相同設定,但缺乏獨立性且麻煩,也會造成 .net 6.0 專案無法使用 .net 4.8 的dll,因為組態設定檔架構不同

1. 擴充 DbContext class,設定連線字串,並提供產生物件的函數
 public partial class xxx : System.Data.Entity.DbContext
    {
        static string ConnectionString = "metadata=xxx;provider=System.Data.SqlClient;provider connection string=\"xxx\"";
        private xxx(string connectionString)
       : base(connectionString)
        {
        }

        public static xxx CreateDbContext()
        {            
            return new xxx(ConnectionString);
        }
    }

2. 使用連線物件
using (var db = xxx.CreateDbContext()) {
}

entity framework 改為搭配 microsoft.data.sqlclient

nuget 安裝 : Microsoft.Data.SqlClient、ErikEJ.EntityFramework.SqlServer

連線字串加入 TrustServerCertificate=True 避免遇到錯誤訊息 : A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - 此憑證鏈結是由不受信任的授權單位發出的
例: Server=xxx; Database=xxx;TrustServerCertificate=True;

繼承 dbcontext 的class加入attribute
[System.Data.Entity.DbConfigurationType(typeof(System.Data.Entity.SqlServer.MicrosoftSqlDbConfiguration))]
public partial class xxxContext : DbContext

2022年4月19日 星期二

Vue-multiselect 學習心得

基本用法

html
====
<vue-multiselect style="width:7em;" v-model="居住城市" :options="居住城市s" :multiple="true" :close-on-select="false" :clear-on-select="false" placeholder="居住地區" label="text" track-by="value" select-label="" selected-label="" deselect-label="" limit="1" :limit-text="limitText" :title="已選擇居住城市">                  
</vue-multiselect>      

javascript
====
已選擇居住城市() {
  let text = "";
  if (this.居住城市) this.居住城市.forEach(a => text += "、" + a.text);
  return text.substring(1);
},
limitText(count) {
  return `已選擇 ${count+1}`;
},  

css
====
.multiselect {
    display: inline-block;
    min-height: unset;
    color: unset;
}

.multiselect--disabled {
    opacity: unset;
    
}
    .multiselect--disabled .multiselect__select {
        background-color: #fff;
    }
    .multiselect input, .multiselect__single {
        font-size: inherit;
    }

.multiselect__single {
    margin-top: 2px;
    margin-bottom: 3px;
    padding-left: 0;
}

.multiselect__input {
    width: 5em !important;
    padding-left: 0;
}

.multiselect__option {
    padding: 5px;
    min-height: unset;
}

.multiselect__option--selected {
    background-color: yellow;
}

.multiselect__option:after {
    padding-right: 5px;
    line-height: unset;
    top: 5px;
    font-size: unset;
}

.multiselect__select {
    width: 20px;
    height: 35px;
    padding: unset;
}

.multiselect__tags {
    padding: 5px 20px 0 5px;
    min-height: 35px;
}
.multiselect__input {
    padding: 2px 0 0 0;
    margin-bottom:0;
}

.multiselect__tag {
    margin: 0;
    padding-left: 3px;
    padding-right: 15px;
}

.multiselect__tag-icon {
    width: 15px;
}

.multiselect__strong {
    margin-bottom: unset;
}

.multiselect__content-wrapper, .multiselect--active {
    z-index: 6 !important; // 避免下拉選單被遮住,但一樣會被 b-table sticky column 遮住且無解!
}

.multiselect__content-wrapper {
    width: unset;
    min-width: 100%;
}

輸入關鍵字即時呼叫後端取得資料

html
====
<vue-multiselect :ref="xxx" :id="xxx" v-model="item" label="text" track-by="value" placeholder="輸入姓名" open-direction="bottom" :options="items" :searchable="true" select-label="" selected-label="" deselect-label="" :internal-search="false" :options-limit="30" v-on:search-change="findSender($event,xxx)">
    <span slot="noResult">查無資料</span>
</vue-multiselect>

javascript
====
findSender(searchtext, id) {
            if (searchtext.length < 2) return;
            // 延遲且單一處理(丟棄尚未啟動的呼叫),避免輸入太快造成先發後至錯亂問
            clearTimeout(this.$_timeout);
            this.$_timeout = setTimeout(() => { 題
                this.items = [];
                this.item = null;
                let $vue = this;
                axios.post(..., { text: searchtext})
                    .then(response => {
                        if (response.data.length > 0) {
                            $vue.items = response.data.map(a => ({ value: a.value, text: a.name }));
                        }
                    }).catch(function (error) {
                        console.error(error);
                        $vue.$bvToast.toast(error.response.data, { variant: 'danger', solid: true, noAutoHide: true });
                    });
            }, 500); // set this value to your preferred debounce timeout
        },

取得對應的dom 進行其他操作
$(this.$refs.xxx.$el).closest('tr').find('[name=...]').focus();

關閉選單
this.$refs.xxx.deactivate();

2022年3月30日 星期三

應用程式集區自動停止原因分析

從事件檢視器 > windows 紀錄 > 應用程式 找到錯誤訊息

失敗的應用程式名稱: w3wp.exe,版本: 8.5.9600.16384,時間戳記: 0x5215df96
失敗的模組名稱: clr.dll,版本: 4.8.4311.0,時間戳記: 0x5fbdcb6d
例外狀況代碼: 0xc00000fd <= stack overflow
錯誤位移: 0x00000000001217af
失敗的處理程序識別碼: 0x371c
失敗的應用程式開始時間: 0x01d843e17dd264a3
失敗的應用程式路徑: c:\windows\system32\inetsrv\w3wp.exe
失敗的模組路徑: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
報告識別碼: c2df370a-afd4-11ec-80c7-00155d011300

需要找到哪段程式造成此問題
※只有懷疑某個功能異常時才開啟此程式進行除錯,平常不要啟動,因為很佔cpu 資源!

2022年3月25日 星期五

背景模糊效果

.背景模糊 {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    background: rgba(0,0,0,0.2);
    filter: blur(5px);

2022年3月11日 星期五

2022年3月2日 星期三

2022年2月27日 星期日

用 edge 看 netflix 開啟 hdr 及 5.1聲道

支援 5.1 聲道: 安裝擴充套件 Netflix_Extension,選項勾選第一個即可 (第三個別勾不然會報錯)
開啟 HDR: 網址列輸入 edge://flags,Force Color Profile 選擇 sRGB  (目前版本應該不需要了)

2022年2月22日 星期二

避免初始化頁面時暫時性會顯示dom物件或出現 {{...}}

搭配 class: ng-cloak

<div class="ng-cloak"  ng-show="showInvoiceConfirm"></div>

<span class="ng-cloak">{{aaa}}</span>

2022年2月16日 星期三

LINE加入好友行動條碼圖片

網址 : https://developers.line.biz/console/

路徑 : provider > 選擇 channel > Messaging API > QR code


若要透過點選網址加入

網址 : https://manager.line.biz/

路徑 : 選擇帳號 > 增加好友人數 > 加入好友指南 > 建立網址

2022年2月9日 星期三

input、textarea 遇到有類似 html 文字的資料無法 postback,例如 &#、

web.config 加入以下設定
<system.web>
  <pages validateRequest="false" 
或 .aspx 加入以下設定
<%@ Page ValidateRequest="false" 

.aspx 若有使用ScriptManager 則加入以下設定
<asp:ScriptManager  ValidateRequestMode="Disabled"

2022年1月25日 星期二

發佈專案遇到錯誤 : project.assets.json' 沒有 'netcoreapp3.1' 的目標

可能是專案從 3.1 升級到 6.0 會遇到此問題

請確認是否已安裝 nuget 目標與建置工作,並確認專案檔內容是否相關套件都升級到6.0



input 連結 datalist 用程式控制彈出選項

範例: nextTick(() => document.querySelector('input').showPicker());  ※僅支援現代瀏覽器