# Stanley 週活動紀錄 ###### tags: `活動紀錄` *[build]: npm run build *[CSP]: Content Security Policy *[CSS]: Cascading Style Sheets *[env]: 環境變數 *[GA]: Google Analytics *[IE]: Internet Explorer *[JS]: Javascript *[migrate]: php artisan migrate ## ==20240619== - **Laravel後端驗證** 當使用 **Laravel** 的驗證時如果有需要判斷是否必填的驗證的話,可使用 **required_with** 或 **required_if** 的規則。 - ==**required_with**== 此規則表示只有當指定的其他字段(fields)存在值時,當前字段才是必填的。 以下範例當 **photo** 有傳入值時,**photo_alt** 則為必填: ```php= <php $request->validate([ 'photo' => 'string', 'photo_alt' => 'required_with:photo|string|max:255', ]); ``` - ==**required_if**== 此規則表示當另一個字段(field)等於指定值/匹配多個值時,當前字段是必填的。 以下範例當 **role** 傳入的值是 **1** 或是 **2** 時,**right** 則為必填: ```php= <php $request->validate([ 'role' => 'required|integer|in:1,2,3', 'right' => 'required_if:role,1,2|string', ]); ``` --- ## ==20240612== - **MAC筆電的 Squel Ace 資料庫時間預設值設定** 在 **HeidiSQL** 中當==手動==新增一筆資料時,只要對 **created_at** 及 **updated_at** 點擊後便會自動填入目前的時間。 而使用 **Squel Ace** 時,當==手動==點擊 **created_at** 及 **updated_at** 時,軟體並不會自動填入目前的時間,但如果自己輸入除了麻煩也會有輸入錯誤的情形發生,因此可以透過設置 **SQL** 所提供的函式 **CURRENT_TIMESTAMP()** 來協助自動填入: 1. 選擇資料庫及資料表。 ![image](https://hackmd.io/_uploads/B1h7-XeH0.png) 2. 在 **created_at** 及 **updated_at** 欄位的 **Default** 輸入 **current_timestamp()** 即可。 ![1717740320865](https://hackmd.io/_uploads/rJd2qSlBR.jpg) --- ## ==20240605== - **Windows 工作排程器 用法** 在專案上有時候需要部屬至顧客指定的伺服器,且可能會用上排程之類的功能,如果伺服器是 **Windows** 則需要靠些工具來執行,**Windows** 的「工作排程器」就能夠處理這種狀況。以備份SQL資料庫為例: 1. 開啟工作排程器,點選右側的建立工作。 ![image](https://hackmd.io/_uploads/rJZU4Td4C.png) 2. 輸入排程名稱,執行選項可選擇「只有使用者登入時才執行」或是「不論使用者登入與否均執行」。這個看是否要在登出時執行。 ![image](https://hackmd.io/_uploads/BJRcB6u4C.png) 3. 設定觸發程序 ![image](https://hackmd.io/_uploads/BkahSa_VC.png) 4. 設定動作,指令碼那邊可以將設定好要進行備份的批次檔放入,按下確定即可完成排程的設定 ![image](https://hackmd.io/_uploads/r17y8T_N0.png) --- ## ==20240529== - **createMany() 的用法** 在 **Laravel** 中,**createMany()** 是 **Eloquent ORM** 提供的一個方法,用於在關聯表中一次創建多個新的模型實例。這個方法通常在處理一對多或多對多的關聯時使用,範例如下: ```php= <?php $user = User::with('posts')->find(1); $data = [ ['title' => '公告', 'content' => 'xxxxxx'], ['title' => '錄取通知', 'content' => 'xxxx'], ]; $user->posts()->createMany($data); ``` :::warning - **該方法僅可在關聯中使用** ::: - **系統權限架構重整** 本週在進行專案時遇到==細部權限處理==的狀況,之前因為經驗、知識及時間不足,所以以往遇到細部權限處理都是土法煉鋼慢慢加上去,但這樣的方式其實非常花時間且彈性很差,因此搭配彈跳視窗的反饋進行整體的架構重整,概念如下: 1. 設置中間件(**Middleware**) 2. 中間件(**Middleware**)內判斷帳號細部權限 3. 當權限不足時回傳錯誤(**Error**)給前端 4. 前端於全域模板(**Layout**)設置監聽錯誤(**Error**) 5. 當收到錯誤(**Error**)後呈現彈跳視窗回饋後端給的錯誤訊息 --- ## ==20240522== - **defineExpose() 的用法** 在 **Vue** 中的 **Composition API** 裡的 **defineExpose()** 可以暴露組件的屬性給親層(**Parent**) 來使用,用法如下: ==子組件== ```jsx= <script setup> import { ref } from 'vue'; const msg = 'Hello World'; const num = ref(5); const sum = (a = 0, b = 0) => a + b; defineExpose({ msg, num, childFn }); </script> ``` ==親層== ```jsx= <script setup> import { ref, onMounted } from 'vue'; const child = ref(null); onMounted(() => { console.log(child.value.msg); // Hello World console.log(child.value.num); // 5 console.log(child.value.sum(6, 4)); // 10 }); </script> <template> <Child ref="child" /> </template> ``` --- ## ==20240515== - **Lazy Loading(延遲加載) 與 Eager Loading(預先加載)** 在 **Laravel** 中,可透過 **Model** 定義好的方法來調用關聯,如下 **Post** 調用 **user**: ```php= <?php $post = Post::first(); $postUserName = $post->user->name; ``` 其原理為在調用 **user** 資料時,**Laravel** 會根據你在 **Post** 定義的 **user** 方法,取得對應的關係,並再下一句 `SELECT * FROM users WHERE id = [post_user_id]` 指令取回該關係的資料。 由於是在事後補下一個 **SQL** 指令來滿足對應資料的查詢,因此這種調用方式稱為 **Lazy Loading**。 但當出現下列情形時: ```php= <?php $posts = Post::get(); $arr = []; foreach($posts as $post){ array_push($arr, $post->user->name); } ``` foreach 迴圈每經過一個元素,就會下一次 `SELECT * FROM users WHERE id = [post_user_id]` 指令到 **db** 撈資料,這種情況就是所謂的 **N+1 query**,導致 **db** 效能不彰,此時最好的解法就是 **Eager Loading**。 **Laravel** 提供 **with()** 這個方法來實現 **Eager Loading**,原理是 **with()** 方法會預先加載關聯,這樣即便是上述的迴圈,也不會產生 **N+1 query** 來影響效能,範例如下: ```php <?php $posts = Post::with('user')->get(); $arr = []; foreach($posts as $post){ array_push($arr, $post->user->name); } ``` - **Laravel 的 load() 與 loadMissing()** 呈上,當如果出現在沒有使用 **with()** 的情況下獲取了 **Model**,也可使用 **load()** 或是 **loadMissing()** 來重新變成 **Eager Loading**,範例如下: ```php= <?php $posts = Post::get(); // 預先加載user關聯 $posts->load('user'); $arr = []; foreach($posts as $post){ array_push($arr, $post->user->name); } ``` 而 **loadMissing()** 是僅預先加載還沒被加載的關聯,如下: ```php= <?php $posts = Post::with('user')->get(); // 預先加載user跟comment關聯,但因為user已被加載,所以僅會加載comment $posts->loadMissing(['user', 'comment']); $arr = []; foreach($posts as $post){ array_push($arr, $post->user->name); } ``` --- ## ==20240501== - **Nginx 調整設定** 在部屬至測試站時,發現到部分網頁在 **F5** 重新整理後,會產生 **502 bad gateway** 的錯誤,透過 **Nginx** 的 **log** 發現原因是 **Response Headers** 中包含了大量的數據,例如大量的 **Cookie** 或者非常大的 **Session** 數據。 解決的方式便是增加 **Nginx** 的 `fastcgi_buffers` 和 `fastcgi_buffer_size` 配置指令的值。這兩個指令控制 **Nginx** 用於存儲從 **FastCGI** 伺服器讀取的響應的緩衝區的數量和大小。 ```nginx= location ~ \.php$ { .... fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; } ``` 呈上範例來說,`fastcgi_buffers` 被設置為 16 16k,這意味著 **Nginx** 將使用 16 個 16 KB 的緩衝區來存儲響應。`fastcgi_buffer_size` 被設置為 32k,這是用於存儲第一部分響應(通常包含響應頭)的緩衝區的大小。 不過具體設置的值是根據伺服器記憶體狀況而定,目前還在摸索合適的設定值。 --- ## ==20240424== - **firstWhere 語法** 一般根據 **where** 條件取出一筆資料時如下 ```php= $user = User::where('name', 'Stanley')->first(); ``` 可以使用 **firstWhere** 更簡短完成 ```php= $user = User::firstWhere('name', 'Stanley'); ``` 配合多個 **where** 條件也更方便 ```php= $whereQuery = [ ['status', 1], ['id', 5], ]; $user = User::firstWhere($whereQuery); ``` --- ## ==20240417== - **defineModel** :::warning - **只能在 Vue 3.4+ 以上版本使用** ::: **defineModel()** 是 **v-model** 的進化版,能再大幅度簡化 **props** / **emit**,使用也非常方便,是官方目前最推薦的寫法,在 **Vue 3.4+** 以後都可直接使用 在 **Vue** 的設定中,只要是 **define** 開頭的,大都是 **Vue** 的巨集,不需要匯入就能直接使用,我們常用的 **defineProps()** 以及 **defineEmits()** 等都是屬於 **Vue** 的巨集。 ==父組件== ```jsx= <script setup> import { ref } from 'vue'; const counterValue = ref(999); </script> <template> <Child v-model="counterValue" /> </template> ``` ==子組件 **(Child.vue)**== ```jsx= <script setup> const modelValue = defineModel({ type: Number }) const updateValue = () => { // 使用 .value 就能直接與父組件 v-model 綁定的值(counterValue)同步 modelValue.value++ } </script> <template> <div>從父元件接收的值為 {{ modelValue }}</div> <button type="button" @click="updateValue">增加數字</button> </template> ``` --- ## ==20240410== - **CSS 陰影的介紹及使用** 利用 **CSS** 產生陰影效果的呈現方式有以下 2 種: - **box-shadow** 是做陰影效果的屬性,因為元素是矩形,所以內容不論是文字或是包含透明背景的圖片,呈現的效果都是矩形的陰影。 ```css= /* x 軸-位移 | y 軸-位移 | 顏色 */ .box { box-shadow: 5px 5px gray; } /* x 軸-位移 | y 軸-位移 | 模糊值 | 顏色 */ .box { box-shadow: 5px 5px 5px gray; } /* x 軸-位移 | y 軸-位移 | 模糊值 | 擴散值 | 顏色 */ .box { box-shadow: 5px 5px 5px 5px gray; } /* 內側 | x 軸-位移 | y 軸-位移 | 模糊值 | 擴散值 | 顏色 */ .box { box-shadow: inset 5px 5px 5px 5px gray; } ``` - **drop shadow** 是 **filter** 屬性的一種濾鏡渲染效果。使用方式與 **box-shadow** 類似,只是可以處理不規則的圖片陰影。 ```css= .box { filter: drop-shadow(5px 5px gray); } ``` ![image](https://hackmd.io/_uploads/HyIGHiQeA.png) ![image](https://hackmd.io/_uploads/HyJDHiXeR.png) --- ## ==20240403== - **好句分享** :::info **你有多自律,就有多自由** ::: - **從 Figma 輸出 WebP 檔** **WebP** 是一個種較新的檔案格式,由 Google 開發,是一款以加快網頁載入速度為目標的檔案類型。**WebP** 圖檔可以呈現高品質影像,支援透明度和動畫呈現,同時檔案大小遠低於 **JPG** 和 **PNG** 等格式,因此非常適合使用於含大量圖片的網頁中。 1. **Figma** 於右上角先切換到 **Dev** 模式,並點擊 **Plugins** 分頁 ![image](https://hackmd.io/_uploads/HJ79RzByA.png) 2. 於下方列表選擇 **WebP Exporter** 後,選擇圖片後就可輸出成 **WebP**。 ![image](https://hackmd.io/_uploads/rkFo1QSkR.png) - **Laravel + Inertia 使用 PUT 來傳送檔案** 由於 **HTML** 標準本身的限制。**HTML** 表單只支援 **GET** 和 **POST** 兩種 **HTTP** 方法。因此,當我們需要使用 **PUT**、**PATCH** 或 **DELETE** 方法時,就需要透過某種方式來模擬這些方法。 其中當要傳輸檔案時如果使用 **PUT** 方法,因為原生不支援,自然無法傳輸二進制的檔案,如果使用 **Laravel + Inertia** 時可於資料中攜帶 `_method` 參數來實行檔案傳輸: ==路由== ```php= Route::put('/new/update', [NewsController::class, 'update']); ``` ==頁面== ```jsx= import { router } from '@inertiajs/vue3'; const formData = ref({ id: 1, name: 'Jhon', _method: 'put', }); const submit = () => { router.post('/new/update', formData.value); }; ``` --- ## ==20240327== - **CSS Position Sticky** 遇到同學使用 `position:sticky` 卻無效的狀況,原因在於 HTML 結構的問題,必須將要 `position:sticky` 的元素與整體內容放在同一層才能正常使用。 - [**惜物網**](https://shwoo.gov.taipei/shwoo/newhome/newhome00/index) [**惜物網**](https://shwoo.gov.taipei/shwoo/newhome/newhome00/index)是一個提供民眾便捷的公部門二手物品網路競價拍賣交易市集。 - **會計恆等式** ==資產 = 負債 + 股東權益== 左借右貸 - **會計循環** 企業將一個會計期間內所發生的交易事項,依照==分錄、過帳、試算、調整、結帳、編表==六個會計程序處理,週而復始,循環不已,稱為「會計循環」。 ![image](https://hackmd.io/_uploads/rJ1YJD9Ra.png) - **財務報表** 財務報表(英語:Financial statements),簡稱財報,是一套會計文件,它反映一家企業過去一個財政時間段(主要是季度或年度)的財政表現,及期末狀況。它以量化的財務數字,分目表達。財務報表能幫助投資者和債權人了解企業的經營狀況,進一步幫助經濟決策。 通常分為四張表-==資產負債表、損益表、業主權益表及現金流量表==。 ![image](https://hackmd.io/_uploads/rk3fbDqA6.png) - **每股盈餘(EPS)** 每股盈餘 (Earnings per share,EPS) 在企業財報內的涵義是,企業能為每一股的股票賺進多少錢,故每股盈餘就是用來衡量企業獲利能力的指標,每股盈餘越高即代表公司為股東賺錢的能力越強。 --- ## ==20240320== - **文字反白的樣式修改** ==CSS== ```css p::selection { background-color: red; color: white; } ``` ==Tailwind== ```html <p class="selection:bg-red-300 selection:text-white"></p> ``` - **用unsignedSmallInteger()替代unsignedTinyInteger()** 在 **Laravel** 中建立資料表時有時會用上 **unsignedTinyInteger()** 來建立一個最大容納 **3** 位數的正數數字欄位: ```php $table->unsignedTinyInteger('type')->comment('最新消息類別'); ``` 但是當對這個欄位進行 **change()** 修改時,會產生錯誤,原因是當我們需要改變已經存在的欄位時,**Laravel** 會使用 **Doctrine DBAL(資料庫抽象層)** 來處理,而 **Doctrine DBAL(資料庫抽象層)** 並不支援 **tinyIntegar** 資料型態。 為了應對業務上可能對資料庫的變動,因此建議解決方式則是使用 **smallIntegar** 資料型態來取代 **tinyIntegar** 資料型態來建立一個最大容納 **5** 位數的正數數字欄位: ```php $table->unsignedSmallInteger('type')->comment('最新消息類別'); ``` --- ## ==20240313== - **Ping指令** `ping` 這個指令是一個最常用的網路檢測工具,它可以藉由發送封包,檢查自己與特定設備之間的網路是否暢通,並同時測量網路連線的來回通訊延遲時間(round-trip delay time),通常如果網路出問題時,我們都會使用 ping 這個指令來做初步的檢查。 - ==**Windows**== 基本語法 ``` ping www.google.com ``` 測試 IPv6 的位址 ``` ping -6 www.google.com ``` - ==**Linux 及 Mac OS X**== Linux 與 Mac OS X 中的 `ping` 指令用法與 Windows 有些小差異,比較明顯的不同就是直接執行 ping 時,它會持續不斷的每秒 ping 一次指定的設備,直到按下 `Ctrl + c` 終止為止。 因此可以加上 `-c` 參數,例如仿照 Windows 下預設的方式,發出 4 個封包後就停止: ``` ping -c 4 www.google.com ``` --- ## ==20240306== - **路由群組命名** 如果希望路由群組命名前綴能夠一致,可以用 name() 來加入命名前綴 ```php= <?php Route::name('news.')->controller(NewsController::class)->group(function() { Route::get('/news', 'index')->name('list'); Route::get('/news', 'detail')->name('detail'); }); ``` 呈上範例就可以使用 `route('news.list')` 及 `route('news.detail')` 對應到正確的路由。 --- ## ==20240228== - **美化 Windows 終端機** :::danger - **如果以下執行程序中出現 ".ps1 檔案無法載入,因為這個系統上已停用指令碼執行" 的提示,可參考 [PowerShell - 解決 ".ps1 檔案無法載入,因為這個系統上已停用指令碼執行" 問題](https://limitedcode.blogspot.com/2016/03/powershell-ps1.html)** ::: 1. 打開 **PowerShell** 後,前往 [**Oh My Posh**](https://ohmyposh.dev/) 2. **Get Started** -> **Introduction** -> **Windows**,按照步驟在 **PowerShell** 貼上指令安裝: ```shell winget install JanDeDobbeleer.OhMyPosh -s winget ``` 3. 安裝好後重啟 **PowerShell**,在 **PowerShell** 輸入以下指令打開設定檔記事本: ```shell notepad $PROFILE ``` :::warning - **如果輸入後打開的記事本顯示找不到檔案的話,就改輸入以下指令來創造一個新的設定檔後再打開**: ```shell New-Item -Path $PROFILE -Type File -Force notepad $PROFILE ``` ::: 4. 到 [**Oh My Posh**](https://ohmyposh.dev/) 的 **Themes** 裡選擇喜歡的終端機樣式主題,然後到設定檔記事本中貼下以下程式碼: ![1708747652621](https://hackmd.io/_uploads/rymjj1Pha.jpg) ```shell oh-my-posh init pwsh --config 'C:\Users\這裡替換成電腦使用者名稱\AppData\Local\Programs\oh-my-posh\themes\這裡放主題名稱.omp.json' | Invoke-Expression ``` 5. 然後儲存設定檔記事本後關掉,接下來在 **PowerShell** 輸入指令來重載設定檔: ```shell . $PROFILE ``` 6. 如果發現樣式上產生亂碼,需要安裝 [**Nerd Font 字型**](https://www.nerdfonts.com/) 來解決,[**Nerd Font 字型**](https://www.nerdfonts.com/) 是針對開發環境提供了大量的圖示和符號,常見於終端機或程式編輯器,可以用於表示作業系統、資料夾路徑、檔案類型、Git 版本控制狀態、特殊字元等等,選擇滿意的字型下載解壓縮後進行字型安裝: ![1708746183124](https://hackmd.io/_uploads/HJ3AByv2p.jpg) ![1708746299209](https://hackmd.io/_uploads/BJwLLJDna.jpg) ![1708746373548](https://hackmd.io/_uploads/BkInIyv3T.jpg) ![1708746397615](https://hackmd.io/_uploads/B1anLkw36.jpg) 7. 打開 **PowerShell** 設定,到 預設->外觀 進行字型的更換後儲存即可: ![螢幕擷取畫面 (30)](https://hackmd.io/_uploads/BJQatJD2T.png) ![1708747248764](https://hackmd.io/_uploads/HJvWcywn6.jpg) 8. 如果在 **VScode** 也正常顯示,則需打開 **VScode** 設定,找到 **Terminal** 的 **Font Family** 去將剛剛安裝的 **Nerd Font 字型** 寫進去即可: ![1708747391696](https://hackmd.io/_uploads/Hy8q5yP3a.jpg) 9. 完成後範例如下: ![1708747558559](https://hackmd.io/_uploads/SJ9Vi1P3p.jpg) --- ## ==20240131== - **鏤空文字CSS** 在撰寫專案時發現需要使用文字鏤空,查了一下語法可使用 **-webkit-text-stroke** 範例如下: ```css= .openwork-text { color: transparent; -webkit-text-stroke: 1px red; } ``` - **Laravel Model 的 $casts 配合 json 使用** **$casts** 是 **Model** 提供的一個類型轉換的屬性,在使用該屬性時,無論是設置資料還是獲取資料,都將會把資料轉換為指定的類型,在配合 **json** 格式使用時可更加方便存取: ```php= <?php class Test extends Model { protected $casts = [ 'data' => 'json' ] } ``` 呈上那樣設置,就能再存入跟拿取資料時自動 **json_encode** 及 **json_decode**,來提升方便性。 :::danger - **如果存入的資料不能被轉為 json 時會照樣存入,給前端時也是會照原樣給,如果前端也沒寫好判斷的話,很可能就會產生錯誤,因此存入前一定要用 validate 進行型別的驗證來保證可以轉成 json 格式。** - **如果資料是空字串,取出時會變成 null 值,記得要寫判斷(??)來整理好後再給前端。** ::: 不過如果 **json** 內有中文的話還是會被轉成 **Unicode**,這時候可在 **Model** 中覆寫轉換函數來讓存入的資料不要被轉成 **Unicode**: ```php= <?php { protected $casts = [ 'data' => 'json' ] } protected function asJson($value) { return json_encode($value, JSON_UNESCAPED_UNICODE); } ``` 呈上程式碼,加入 **asJson** 函式覆蓋原有方法後,**json** 存中文時就會正常了。 - **localeCompare的使用** **localeCompare** 函式在 **JS** 中可以用來比較兩個字串的順序,回傳 -1、1 或 0(表示之前、之後或相等)。: ```jsx= const a = '01'; const b = '02'; const sort = a.localeCompare(b); console.log(sort); // 回傳 -1 ``` ```jsx= const a = '03'; const b = '02'; const sort = a.localeCompare(b); console.log(sort); // 回傳 1 ``` ```jsx= const a = '02'; const b = '02'; const sort = a.localeCompare(b); console.log(sort); // 回傳 0 ``` --- ## ==20240124== - **Laravel Route 中 controller() 的用法** 當有一群同樣類型的路由通常我們會使用 **group** 來整個包起來進行統一控制,如下範例: ```php= <?php Route::prefix('product')->group(function () { Route::get('/', [ProductListController::class, 'index'])->name('product.index'); Route::get('/detail', [ProductListController::class, 'detail'])->name('product.detail'); Route::post('/bid', [ProductListController::class, 'bid'])->middleware(['auth', 'role.weight:2'])->name('product.bid'); }); ``` 不過也可以使用 **controller()** 將 **Controller** 拉到整個群組來進行更簡單的撰寫,範例如下: ```php= <?php Route::prefix('product')->controller(ProductListController::class)->group(function () { Route::get('/', 'index')->name('product.index'); Route::get('/detail', 'detail')->name('product.detail'); Route::post('/bid', 'bid')->middleware(['auth', 'role.weight:2'])->name('product.bid'); }); ``` - **Git 指令的使用** 通常使用 **git** 版本控制都是透過介面來進行上傳的動作,這次剛好在修正安全性漏洞時,因為要每個專案都要執行一次,每次都要切回 **GitHub Desktop** 來進行上傳是很麻煩的事,因此直接在 **VSCode** 在終端輸入指令就好,藉此把指令寫下來: ![image](https://hackmd.io/_uploads/SkQISgYYa.png) ==上傳所有的檔案到索引 (**staging area**)== ```shell git add . ``` ==**commit** 到 本地數據庫 (**Local Repository**)== ```shell git commit -m "build: 更新vite安全性漏洞" ``` ==上傳到遠端數據庫== ```shell git push ``` ==一次輸入== ```shell git add .;git commit -m "build: 更新vite安全性漏洞";git push ``` --- ## ==20240117== - **社會觀察心得及反思** 最近最夯的選舉終於告一段落,每天在網路上的紛紛擾擾也能暫時畫下休止符,可能也是後來有開始參與教育及回到校園的關係,就有一些感觸跟想法。 講結論,現在理科的訓練出來缺乏==人文史觀==,缺少同理心及感性,不重視價值只重視效率;然後文科出來的又缺乏==邏輯思維==,缺少判斷力跟理性,不重視實際狀況只談理想,似乎直接往兩個極端走。 回到自身就會反思跟警惕自己,盡量要多元的吸收知識以及平衡感性跟理性,在 **AI** 的浪潮之下,覺得這才是保持競爭力與他人做出區隔的重要性。 - **allowfullscreen 屬性** 當使用 **iframe** 元素時,假設外部的網站有點擊後開啟全螢幕檢視的功能,因為安全性的問題,預設是無法成功觸發的,如果想正常觸發就必須加入 **allowfullscreen** 屬性才能夠正常開啟。 ```jsx <iframe src="https://xxxxxx" frameborder="0" allowfullscreen title="校園美感教育季VR"></iframe> ``` - **focus-within 偽類** **focus-within** 是一個可以用來改變上層元素樣式的偽類選擇器,只要在你要套用的元素上面加上 **focus-within**,這樣裡面只要有表單元素有被 **focus**,就會吃到該樣式。 ```jsx= <form> <p>您想點哪種口味?</p> <label> <span>全名:</span> <input name="firstName" type="text" /> </label> <label> <span>味道:</span> <select name="flavor"> <option>櫻桃口味</option> <option>綠茶口味</option> <option>薄荷口味</option> </select> </label> </form> ``` ```css= label { display: block; margin-top: 1em; } label:focus-within { font-weight: bold; } ``` > 參考來源 [focus-within - MDN Web Docs](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:focus-within) - **CSS Reset 概念** **CSS Reset**( **CSS** 重置),是網頁設計中一個重要的概念,用來清除不同瀏覽器(**Chrome、Firefox、Internet Explorer** 等)之間的樣式差異。當我們在網頁設計中使用 **CSS** 來控制網頁的外觀和風格時,不同瀏覽器可能會對相同的 **HTML** 元素、**CSS**樣式有不同的解釋,這種差異可能會導致不一樣的外觀。為了解決這個問題,**CSS Reset**就變得很重要。 ==**2008年版本**== ![code](https://hackmd.io/_uploads/SyeIlz-Ya.png) ==**2024年版本**== ![image](https://hackmd.io/_uploads/rkiP1zZt6.png) 但隨著 **IE** 消失在市場後,加上目前各大廠致力於整合不同瀏覽器之間的差異,現在的 **CSS Reset** 概念逐漸從樣式的統一,逐步轉向致力於使用者體驗及網頁可訪問性,像有些 **CSS Reset** 會加入如下範例: ```css= html:focus-within { scroll-behavior: smooth; } ``` 上述 **CSS** 是加入 `scroll-behavior: smooth` 對捲軸平滑滾動,特別設置给了 `html:focus-within` 偽類,而不是直接給 **html** 捲軸平滑滾動,這樣做的目的是只對使用鍵盤 **tab** 鍵切換焦點時時,讓頁面進行平滑滾動,帶來更好的使用體驗。 --- ## ==20240110== - **臨時助教心得** 第一次以助教身分參與課程,內容上瑣事較多,基本上比較偏向保母性質,雖然不難,但也相對較無趣,因為整體節奏是跟隨講師在進行,因此比較長時間會不知道要做什麼,不過這次參與也看到一些狀況: 1. **Never**的節奏把握非常穩,在教學及練習時間平衡的很好,也非常用心的準備所有的教案。 2. 在尚未教導 **JS** 的狀況下,先教套件 **Swiper**,因此發生了同學因為 **JS** 不熟導致的練習錯誤,以及認知上的誤導,可能後續可以拿來進行檢討調整。 3. 因為專案上 **SCSS** 用到的狀況越來越少,**Never** 在進行 **SCSS** 教學時,偶爾會不小心使用 **STYLE** 或是使用 **CSS** 而沒有進行 **SCSS** 的 **watch**,有同學反應改不清楚為何要使用 **SCSS**,可能後續可以拿來進行教案的檢討調整。 - **Uiverse.io介紹** [**Uiverse.io**](https://uiverse.io/elements) 是一個致力於提供開源 UI(使用者介面)元素的網站,專門為各種專案提供豐富且美觀的自定義元素。這些元素主要是用 CSS 或 Tailwind 設計的,用戶可以自由地創建、分享,並在自己的專案中使用這些元素 。[**Uiverse.io**](https://uiverse.io/elements) 的一大特點是其社群驅動的特性,所有的 UI 元素都是由社群成員製作的,並且這些元素完全免費,無論是個人還是商業用途均可使用。 ![image](https://hackmd.io/_uploads/SJNSnXcua.png) --- ## ==20240103== - **計算你的 GitHub 帳號目前價值** 可以當作趣味分享給學生(? [Github Worth - Know Your GitHub Account Worth in Seconds.](https://github-worth.vercel.app/) ![github-worth](https://hackmd.io/_uploads/B1vbpjq_a.png) - **利用laravel-er-diagram-generator來產生ERD圖** 實體關聯圖(**Entity Relationship Diagram**,簡稱 **ERD**)是關聯式資料庫的設計圖,在實體關聯圖裡,一個實體(**entity**)會對應一個資料表(**table**)。 而可以使用 [**laravel-er-diagram-generator**](https://github.com/beyondcode/laravel-er-diagram-generator) 這個套件來自動產生實體關聯圖 :::warning - **需先安裝 [GraphViz](https://graphviz.org/) 才可以使用。** - **[GraphViz](https://graphviz.org/) 是一個由 AT&T 實驗室啟動的開源工具包,用於繪製DOT語言指令碼描述的圖形。它也提供了供其它軟體使用的函式庫。** ::: ![image](https://hackmd.io/_uploads/SJioabiPp.png) --- ## ==20231227== - **Baseline 2023** **Caniuse**、 **MDN** 新增一個狀態: **Baseline** 這是用來幫助開發者判斷一個功能是否可以使用的指標 有三種狀態: - **Widely available**: 主流瀏覽器都支援,且超過 30 個月以上 - **Newly available**:主流瀏覽器的最新版本都已經支援 - **None**:不是所有主流瀏覽器都有支援 ![image](https://hackmd.io/_uploads/H1i_Q77vp.png) ![image](https://hackmd.io/_uploads/SyHWHXmDp.png) **Baseline** 是今年 **Google IO** 提出的新指標,詳細內容見 https://web.dev/baseline - **使用「noindex」禁止Google 搜尋建立索引** 有時候我們不希望讓搜尋引擎找到我們的網站並建立索引(測試站)的話,就可以在**head**中加入以下標籤: ==防止所有支援 **noindex** 規則的搜尋引擎將網站上的網頁編入索引== ```html <meta name="robots" content="noindex"> ``` ==「專門防止 **Google** 網路檢索器」將特定網頁編入索引== ```html <meta name="googlebot" content="noindex"> ``` 而在實務上為讓同一套程式碼在測試站及正式站有不同的結果,就會使用 **env** 來進行控制 ```php= @if (env('GOOGLE_REBOT_BAN', false)) <meta name="robots" content="noindex, nofollow"> <meta name="googlebot" content="noindex"> @endif ``` - **Laravel的where另外一種用法** 在使用**where**時常有多個條件,如以下範例: ```php= <?php $keywords = $request->keywords; $date = $request->$date; $users = User::where('role', 1) ->where('status', '!=', 2) ->where('created_at', '<', $date) ->where('name', 'like', "%{$keywords}%") ->get(); ``` 上述程式碼也能用以下寫法 ```php= <?php $keywords = $request->keywords; $date = $request->$date; $$whereQuerys = [ ['role', 1], ['status', '!=', 2] ['created_at', '<', $date] ['name', 'like', "%{$keywords}%"], ]; $users = User::where($whereQuerys)->get(); ``` --- ## ==20231220== - **使用Pivot進行查詢** 當**Laravel**建立多對多的表時,中間表就被稱為**Pivot**,可以使用`pivot`屬性來訪問中間表的資料: ```php= <?php $user = App\User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at; } ``` > 參考 [**Laravel 技巧之 Pivot**](https://learnku.com/articles/3798/laravel-skills-pivot) - **利用Figma的AI插件Builder.io產生切版** - 首先選取想產生切版的區塊 ![1702952776312](https://hackmd.io/_uploads/rJDCyFA8T.jpg) - 然後選取Builder.io並生成程式碼 ![1702952799337](https://hackmd.io/_uploads/Sy4xxKCLT.jpg) - 就可以複製程式碼拿來用了 ![1702952829607](https://hackmd.io/_uploads/ryyt-K0L6.jpg) --- ## ==20231213== - **HackMD表情符號大全** 完整的表情符號列表 [在這裡](https://github.com/ikatyang/emoji-cheat-sheet)。 - **設置height: auto無法觸發transition動畫的原理及解方** 這次剛好在幫忙處理其他專案的**bug**時,看到這個**issue**,其實以前有研究過了,但沒有紀錄就又忘記該怎麼解釋了,因此透過週記來記錄起來:smiley:。 目標是選單使用手風琴方式進行收合,使用控制 **height** 來觸發 **transition** 並展開選單。 :::info :bulb: **當 height: auto 時,設置 transition 是無法觸發動畫的,CSS 需要知道具體的 height 值才能觸發 transition 效果。** ::: 因此通常採取的方式有3種:point_down: :::warning 1. 使用 **transform** 的 **scaleY** 去放大縮小。 - 缺點: - 內容會變形。 - 還是會佔據原來的內容空間。 2. 改用 **max-height** 取代 **height** 來展開。 - 缺點: - 只能固定最大高度,無法隨內容動態展開。 - **transition-timing** 因為作用在 **max-height** 上的原因,導致展開時的速度體感與內容不一致。 3. 獲取內容的實際 **height** 來動態設置。 - 缺點: - 需使用 **JS**,不是純 **CSS**。 ::: 呈上,通常實務上最優解是方法3,在 **Vue** + **Tailwind** 中設置 **height** 範例如下:point_down: ```jsx= <script setup> import { ref } from 'vue'; const list = [ ... ]; const selectedIndex = ref(null); const contentDoms = ref([]); const contentHeight = ref(0); // 收合選單 const toggleMenu = (index) => { contentHeight.value = contentDoms.value?.[index]?.offsetHeight ?? 0; if (selectedIndex.value === index) { selectedIndex.value = null; } else { selectedIndex.value = index; } }; // 動態設置父層高度 const renderHeight = (index) => { if (selectedIndex.value !== index) return 'h-0'; return `h-[${contentHeight.value}px]`; }; </script> <template> <div> <div v-for="(item, index) in list" :key="index"> <button type="button" @click="toggleMenu(index)">開關</button> <div class="overflow-hidden transition-[height] duration-700 ease-in-out" :class="renderHeight(index)" > <div ref="contentDoms">{{ item.content }}</div> </div> </div> </div> </template> ``` 但是這樣的方式選單打開了但是並沒有 **transition** 效果,要成功觸發 **transition** 效果需改使用 **style** ,修改範例如下:point_down: ```jsx= // 動態設置父層高度 const renderHeight = (index) => { if (selectedIndex.value !== index) return 0; return contentHeight.value; }; <template> <div> <div v-for="(item, index) in list" :key="index"> <button type="button" @click="toggleMenu(index)">開關</button> <div class="overflow-hidden transition-[height] duration-700 ease-in-out" :style="`height: ${renderHeight(index)}px`" > <div ref="contentDoms">{{ item.content }}</div> </div> </div> </div> </template> ``` :::info :bulb: **瀏覽器渲染架構順序如下 HTML -> CSS -> JS**。 :bulb: **Tailwind** 是採用原子化 **CSS** ::: **原理結論**:point_down: :::warning - **height** 因為是 **JS** 動態變化,如果使用 **Tailwind** 的話,**CSS** 的 **height** 的值仍舊沒有先提前寫入 **CSS** 中,因此也無法觸發 **transition** 效果。 - 而當寫在 **style** 裡面時,因為在編譯 **HTML** 階段時就已經產生 **height** 的值,當執行 **CSS** 的 **transition** 的時候,就能夠依據 **height** 的值正常的觸發動畫效果。 ::: - **使用隊列(Queues)來進行寄信** 使用寄信功能寄一封信時都會大約需要2-3秒左右的處理時間,但如果同時寄超過2封以上就會有很明顯的延遲感,甚至會導致使用者誤解是否沒發送成功而再次發送等狀況,這時候就很適合使用==隊列==的功能來同步處理。 **Laravel**官方文件有非常詳盡的[**隊列(Queues)**](https://laravel.com/docs/10.x/queues)教學,且提供多種處理隊列的方式,而在**window**上最易用的方式是使用**database**。 - **Laravel的when()方法** 當在進行查詢時,某些場合下會需要根據條件進行不同的查詢,以前會使用以下範例:point_down: ```php= <?php $status = $request->status; // 狀態有1、2、3 $user = User::query(); // 當狀態不為1時指定查詢2及3的資料,否則全拿 if ($status !== 1) { $user->where('status', $status); } ``` 但可以使用 **Laravel** 提供的 **when** 方法來進行更簡潔的寫法:point_down: ```php= <?php $status = $request->status; // 狀態有1、2、3 $user = User::when($status !== 1, fn ($query) => $query->where('status', $status)); // 或是 // $user = User::when($status !== 1, function ($query, $status) { // $query->where('status', $status); // }); ``` :::info :bulb: **when(判斷的條件, 當為true時執行的fn, 當為false時執行的fn),前2個參數為必填** ::: --- ## ==20231206== - **網頁列印的CSS細節** 之前自身比較少碰到關於網頁列印的部分,此次藉由專案的需求來補足這塊缺少的部分。 - **列印時隱藏該元素:** ==原生CSS== ```css= @media print { .no-print { display: none; } } ``` ==Tailwind== ```jsx= <div class="print:hidden"></div> ``` - **設定列印邊界及尺寸** ```css= @page { size: A5; /* 指定列印紙張大小 */ margin: 0; /* 移除預設空白邊界 */ } ``` - **專案字型細節調整** 在專案上使用了設計師指定字體「何某手寫體」,使用方式為 ```css= @font-face { font-family: nanifont; src: url(https://cdn.jsdelivr.net/gh/max32002/nanifont@1.036/webfont/NaniFont-Regular.woff2) format("woff2") , url(https://cdn.jsdelivr.net/gh/max32002/nanifont@1.036/webfont/NaniFont-Regular.woff) format("woff"); } ``` 但因為字型被用到後才開始載入,導致有用到的頁面網頁體驗上有==不順暢的字型過渡效果==。 後來使用解法的思路為在前台首頁的隱藏**div**加入該字體,強制在進入後下載字體,這樣當進入有使用到該字體的頁面後就不會出現不順暢的字型過渡效果了。 - **Google搜尋網站前綴小icon** 在使用**Google**進行網站搜尋時,列出來的網站前方會有個小**icon**,只要在**head**加入`rel="icon"`的**link**標籤就可呈現在**Google**搜尋中了。 ```jsx= <link rel="icon" href="/favicon.ico"> ``` --- ## ==20231129== - **發現[Tailwind](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state)沒用過的語法** - 當使用**peer**等於**CSS**中 ~ 指定的效果 ```jsx= <div class="peer">peer元素</div> <div class="peer-hover:bg-red-500">移到peer上時變紅色</div> ``` - **peer**也能夠透過指定名稱的方式對不同對象達到 ~ 指定的效果 ```jsx= <div class="peer/first">peer元素1號</div> <div class="peer-hover/first:bg-red-500">移到peer元素1號上時變紅色</div> <div class="peer/second">peer元素2號</div> <div class="peer-hover/second:bg-bluc-500">移到peer元素2號上時變藍色</div> ``` - 使用 **file:** 來改變上傳檔案**input**按鈕的樣式 ```jsx= <input type="file" class="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-violet-50 file:text-violet-700 hover:file:bg-violet-100" > ``` ![1701048309825](https://hackmd.io/_uploads/HkkExOWSa.jpg) - **使用[CSS Container Query(容器查詢)](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries)來進行更細緻的RWD排版** 很多時候,RWD 並不是由 Viewport 或螢幕大小來決定的,而應該是由容器的大小來決定,實作範例如下: ```jsx= <div class="container"> <h1>我是標題</h1> </div> ``` ```css=+ .container { container-type: inline-size; width: 50%; border: 1px solid black; } h1 { color: blue; } @container (width <= 300px) { h1 { color: red; } } ``` 目前主流瀏覽器已全面支援 ![1701145999558](https://hackmd.io/_uploads/SyZip17Sp.jpg) - **HTML用電子郵件分享的方法** 不填入指定信箱,只寫標題(subject)及內文(body)來讓用戶開啟信箱進行分享 ```jsx= <a href="mailto:?subject=分享網站&body=分享好文 http://www.website.com" title="電子郵件分享" > 電子郵件分享 </a> ``` > 參考 [**mailto: 點擊連結 開啟寄送 email**](https://www.letswrite.tw/mailto-link/) - **Console中更直觀地展示Ref/Reactive** 在**Vue**的**Composition API**中,當要印出**Ref**及**Reactive**的變數時,會不太直觀能看到是什麼 ![1700996740389](https://hackmd.io/_uploads/rJXo8olrp.jpg) 但可透過設定讓其顯示的更清楚: 1. 在**Chrome**中開啟**Developer Tools**,然後按**F1**進入設定頁面。 ![1700996833474](https://hackmd.io/_uploads/BJPlPolH6.jpg) 2. 前往**Preferences → Console**,勾選**Enable custom formatters**選項,然後重新整理網頁即可。 ![1700997002352](https://hackmd.io/_uploads/Hko2Djlrp.jpg) ![1700997026649](https://hackmd.io/_uploads/BykAvierp.jpg) > 參考 [**深入 Vue 開發:在 Console 中更直觀地展示 Reactive Objects**](https://serko.dev/post/vue-reactive-objects-console-display/zh-hant/) --- ## ==20231122== - **[馬斯洛](https://zh.wikipedia.org/zh-tw/%E4%BA%9A%E4%BC%AF%E6%8B%89%E7%BD%95%C2%B7%E9%A9%AC%E6%96%AF%E6%B4%9B)的需要層級理論** 需求層次理論(Maslow's hierarchy of needs)由美國猶太裔人本主義心理學家亞伯拉罕•馬斯洛(Abraham Maslow)提出,是研究組織激勵(motivation)時應用最廣泛的理論。 ![image](https://hackmd.io/_uploads/HklRsMU4a.png) - ==人們會先致力於滿足低層次的需求,才轉而追求高層次需求。== - ==需求層次理論是解釋人格的重要理論,也是解釋動機的重要理論。其提出個體成長的內在動力是動機。== - **專案客戶要求埋入AI助手** 客戶要求埋入[**voiceflow**](https://www.voiceflow.com/)的AI助手,埋入方式也很單純,在HTML加入一段指定的代碼即可(類似GA串接)。 - **Tailwind CSS元件庫** 在製作專題時因為沒有設計師,因此便開始找尋使用**Tailwind CSS**的模板或組件,首先**Tailwind CSS**官方的UI庫是需要付費的,而我找到的開源項目如下: - [**Tailblocks**](https://tailblocks.cc/) - [**Tailwind Components**](https://tailwindcomponents.com/) 以上兩個元件庫都有==檢視原始碼==及==一鍵複製==功能。 - **後續專案開發方式調整** - 使用**CSS**取代**SCSS** 隨著使用**Tailwind CSS**開發後,已經幾乎不會使用到**SCSS**,考慮到除了初始配置**SCSS**的複雜性外;以及**Tailwind CSS**與**SCSS**本身在編譯原理上的衝突,因此改使用原生的**CSS**取代**SCSS**。 - 加入[**tailwind-merge**](https://github.com/dcastil/tailwind-merge)套件 [**tailwind-merge**](https://github.com/dcastil/tailwind-merge)是一個合併**Tailwind CSS**類別並避免樣式衝突的套件: ==使用套件以前== **text-black**如果不改成 **!text-black**加上**Important**會產生衝突而蓋不過去 ```jsx= <script> import { computed } from 'vue'; const props = defineProps({ type: { type: String, default: 'submit', }, color: { type: String, default: '', }, }); const colorClass = computed(() => { let classList = ''; switch (props.color) { case 'white': classList = 'bg-white !text-black'; break; case 'blue': classList = 'bg-blue-600'; break; case 'red': classList = 'bg-red-600'; break; case 'green': classList = 'bg-green-600'; break; default: classList = 'bg-gray-800'; } return classList; }); </script> <template> <button :type="props.type" class="px-3 py-1 text-white" :class="colorClass" > <slot /> </button> </template> ``` ==使用套件以後== **text-black**產生的衝突會被處理而蓋過去 ```jsx= <script> import { computed } from 'vue'; import { twMerge } from 'tailwind-merge'; const props = defineProps({ type: { type: String, default: 'submit', }, color: { type: String, default: '', }, }); const colorClass = computed(() => { let classList = ''; switch (props.color) { case 'white': classList = 'bg-white text-black'; break; case 'blue': classList = 'bg-blue-600'; break; case 'red': classList = 'bg-red-600'; break; case 'green': classList = 'bg-green-600'; break; default: classList = 'bg-gray-800'; } return classList; }); </script> <template> <button :type="props.type" :class="twMerge('px-3 py-1 text-white', colorClass)" > <slot /> </button> </template> ``` ## ==20231115== - **使用[v-model](https://cn.vuejs.org/guide/components/v-model.html)綁定組件的父子組件傳值** 透過**Mike**及**Stan**看到了專案中使用了[**v-model**](https://cn.vuejs.org/guide/components/v-model.html)的進階用法,之前就知道此用法,但一直沒有將此方法有效導入實務中,這次直接導入實務的組件,進而減少組件使用的困難度。 ==單變數== ```jsx= // 父層 <CustomInput v-model="searchText" /> ``` ```jsx= // CustomInput組件 <script setup> defineProps(['modelValue']); defineEmits(['update:modelValue']); </script> <template> <input type="text" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" /> </template> ``` ==多變數== ```jsx= // 父層 <CustomInput v-model:first-name="firstName" v-model:last-name="lastName" /> ``` ```jsx= // CustomInput組件 <script setup> defineProps({ firstName: String, lastName: String, }); defineEmits(['update:firstName', 'update:lastName']); </script> <template> <input type="text" :value="firstName" @input="$emit('update:firstName', $event.target.value)" /> <input type="text" :value="lastName" @input="$emit('update:lastName', $event.target.value)" /> </template> ``` - **[Power Bi](https://powerbi.microsoft.com/zh-tw/)的引入** 此次與Stan因專案需求在研究[**Power Bi**](https://powerbi.microsoft.com/zh-tw/)如何引用至網頁後台,後來發現**iframe**插入的方式簡單不費力,不過未來能否實用在專案上是關於授權及付費的問題,尚待確認。 - **[MockUPhone](https://mockuphone.com/)合成網站** 此次為了前端畫面展示,找到[**MockUPhone**](https://mockuphone.com/)合成網站,可以不用登入外,且有各種尺寸的設備**Mockup**去使用。 ![mock-group](https://hackmd.io/_uploads/Bk4DrjWV6.png) - **Vue.js中DOM元素的直接操作** 如果要在**Vue**中直接控制DOM元素的話,可用[**ref**](https://cn.vuejs.org/guide/essentials/template-refs.html)這個預設**API**來操作: ==操作單個**DOM**元素== ```jsx= <script setup> import { ref, onMounted } from 'vue'; const input = ref(null); onMounted(() => { input.value.focus(); }); </script> <template> <input ref="input" /> </template> ``` ==**v-for**中操作多個**DOM元素**== ```jsx= <script setup> import { ref, onMounted } from 'vue'; const list = ref([1, 2, 3]); const itemRefs = ref([]); onMounted(() => console.log(itemRefs.value)); </script> <template> <ul> <li v-for="(item, index) in list" :key="index" ref="itemRefs"> {{ item }} </li> </ul> </template> ``` --- ## ==20231108== - **更換深拷貝使用的函式** Laravel 10 以前的版本都會預設安裝[**lodash**](https://lodash.com/)函式庫,因此當要使用深拷貝時便直接使用[**lodash**](https://lodash.com/)的[**cloneDeep()**](https://lodash.com/docs/4.17.15#cloneDeep)函式,但是從Laravel 10 開始不會預設安裝[**lodash**](https://lodash.com/)函式庫。 剛好瀏覽器的HTML DOM API的[**structuredClone()**](https://developer.mozilla.org/zh-CN/docs/Web/API/structuredClone)已被瀏覽器全面支援,因此便改使用[**structuredClone()**](https://developer.mozilla.org/zh-CN/docs/Web/API/structuredClone)取代[**lodash**](https://lodash.com/)的[**cloneDeep()**](https://lodash.com/docs/4.17.15#cloneDeep)函式來進行**JS**的深拷貝,也能藉此改用原生支援的API取代依賴的函式庫。 ![1698936049876.jpg](https://hackmd.io/_uploads/B1jfrEZ76.jpg) ![1698936066844.jpg](https://hackmd.io/_uploads/S1YQSEWmT.jpg) - **Laravel 關聯進階查詢** 在重新讀**Laravel**文檔時發現了另一種關聯進階查詢的方式,可以一對多查詢出最新或是最舊的一筆,舉例一篇**Post**(貼文)有多個**Comments**(留言),如果要找出該筆貼文的最新或是最舊的留言,可在**Post**的**Model**中加入以下關聯: ==最新== ```php= <?php public function latestComment() { return $this->hasOne(Comment::class)->latestOfMany(); } ``` ==最舊== ```php= <?php public function oldestComment() { return $this->hasOne(Comment::class)->oldestOfMany(); } ``` :::warning - **此方法最低支援Laravel 8** - **須注意的是此方法的最新最舊判斷是以Primary key為判定** ::: - **發現composer install --no-dev指令的缺點** 在部屬時發現當使用**composer install --no-dev**指令後,有個被排除的開發用套件**doctrine/dbal**,當該套件沒有被安裝時執行**migrate**時,==如果**migration**使用了**renameColumn**或**change**等函式的話==,會產生錯誤無法執行下去。 因此經整體業務及維護考量,==部屬專案時先暫時維持**composer install**指令==。 - **利用xampp及Apache架設部屬專案正式站** 這次是第一次實行外網正式站的架設部屬,將大致的過程記錄下來,後續詳細細節再慢慢補充: - [**Xampp及Apache網站部屬建置指南**](https://hackmd.io/@Stanleyei/rywme_Dma) --- ## ==20231101== - **處理專案新功能及伺服器申請行政作業** 1. A專案收到通知須協助填寫遠端連線申請表格,透過**Kaz**的協助知道該如何填寫,後續會將該填寫方式收至專案資料夾。 2. B專案臨時要增加可從後台顯示/隱藏前台的網站瀏覽人數的功能。包含呈上A專案,發現到公務單位都很喜歡在==週五==才會有消息回應,可能以後考量時程上也要將此狀況考量進去較安全。 - **最佳化後台表格關鍵字搜尋、欄位正倒序以及後台整體模板** 1. 在撰寫React時對於[**hook**](https://react.dev/reference/react-dom/hooks)/[**composable**](https://vuejs.org/guide/reusability/composables.html#mouse-tracker-example)及 **JS** 狀態有更深一層的理解,其實[**Pinia**](https://pinia.vuejs.org/)本身就是一個狀態+自定義函式的組合,也因為如此重構了後台中表格列表頁常見的==關鍵字搜尋及欄位正倒序==功能;簡單來說就是將每頁重複的變數集中,統一由一個**Store**去控制及儲存變數狀態,進而減少每頁重複程式碼以及更集中式的管理函式。 2. 同時嘗試對後台的版面模板進行再一層的組件包覆,讓後台整體的一致性更高,在調整同時也學習使用到平常沒用過的Vue函式,透過使用[**useSlots()**](https://vuejs.org/api/sfc-script-setup.html#useslots-useattrs)這個Vue函式,可以從子組件==獲取插槽的相關資訊==,進而進行判斷使用,例: ==一般插槽== ```jsx= // 子組件 import { useSlots } from 'vue'; const slots = useSlots(); <template> <button type="button"> <span v-if="!!slots.default">如果父層有在插槽放元素/文字則顯示</span> <slot /> </button> </template> ``` ==具名插槽== ```jsx= // 子組件 import { useSlots } from 'vue'; const slots = useSlots(); <template> <button type="button" class="flex gap-2"> <div> <span v-if="!!slots.left">如果父層有在left插槽放元素/文字則顯示</span> <slot name="left" /> </div> <div> <span v-if="!!slots.right">如果父層有在right插槽放元素/文字則顯示</span> <slot name="right" /> </div> </button> </template> ``` - **Code Review學生程式碼及回答學生疑問** 這次隨著參與的學生變多,我發現到一個現象,就是對於[**fetch**](https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API/Using_Fetch)的不熟悉外,還有對於整個==架構的資料流==沒有明確的概念。舉例: :::info - **功能需求:當列表有多筆資料時,點擊單筆資料會跳出該筆資料詳細資訊的彈窗** - **學生解法:有10筆資料,於是也產生10個彈窗,然後每次點擊打開彈窗時使用[**fetch**](https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API/Using_Fetch)再去拿一次資料** ::: 上述==不正確==的資料流處理及架構,也讓我感受應該指導的方向,如果能夠在網路上查詢到的語法及方法,可以讓學生自行去學習找到正確的資料,而我們更要引導學生的是網路上較少資料,或是較抽象難與融會貫通的==資料流處理及架構==。 - **分享前端相關知識文章** 之前有讀到網友整理的[**前端簡史**](https://mp.weixin.qq.com/s/ij_KzQDACyFL9oZQbYHKSg),覺得能夠在教學中穿插更多的故事分享,幫助調整教學節奏,也不會冷場。 --- ## ==20231025== - **透過影片/直播來了解程式「複雜度」** {%youtube gi7JfN3qpao %} 對於非本科的我來說,剛好藉此來補足演算法及程式複雜度的基礎。 - **處理 GA 串接問題** 這次串接 **GA** 發現到之前沒深究的問題,包含兩點: 1. 不同帳號(付費/非付費)串接時,發出的 **GA** 請求網址是不同的,這在開啟[**CSP**](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP)後,當沒有把該 **GA** 請求網址列入[**CSP**](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP)白名單,會導致請求無法發出,進而使得 **GA** 無發收到網站的資料。 2. 在目前架構下,因為Google相關串接的id或是金鑰是放在前端,如果是部屬方式是本地 **build** 完才放進去正式伺服器的話,==必須將本地跑 **build** 的 **env** 內的Google相關串接的id或是金鑰換成正式使用的再執行 **build**==,不然會編譯出沒有帶Google相關串接的id或是金鑰的 **JS** 檔,導致無法正確執行該串接功能。 - **Code Review學生未完成專案的程式碼,發現錯誤的全域變數設置** - 發現到在使用blade建構專案時,因為有全域的模板(header、footer等)需要傳入資料,而==學生使用**Providers**的方式在一建立專案就呼叫**Modal**獲取資料傳入全域==;但是這樣會造成新的專案執行`composer install`時會因為尚未先執行 **migrate** 反而會先報錯。==理論上初始設置專案不應該因為指令先後順序產生任何錯誤==,對於新人及未了解專案狀況的工程師來說會造成多餘的排查時間成本浪費。 - 呈上,較合理的解決方式是使用**Middleware**將全域變數放置其中,一起代入blade模板中。 - **分享[**LOGO AI 產生器**](https://logo.com/)** 我在執行專題時,剛好企劃本身並沒有Logo,因此我利用了[**LOGO AI 產生器**](https://logo.com/)來產生Logo: :::warning - **需先註冊帳號** - **以下輸入內容都只能用英文** ::: 1. 首先輸入商標名稱後,按下**Make a Logo**按鈕 ![](https://hackmd.io/_uploads/S1mjMWIz6.jpg) 2. 輸入標語(可跳過) ![](https://hackmd.io/_uploads/S1bFUZUGT.jpg) 3. 選擇產業(可跳過) ![](https://hackmd.io/_uploads/H1emwb8z6.jpg) 4. 選擇主色調(可跳過) ![](https://hackmd.io/_uploads/HJlEXPb8zT.jpg) 5. 選擇字體樣式(可跳過) ![](https://hackmd.io/_uploads/ByDUPbIzp.jpg) 6. 輸入最多5個關鍵字(可跳過) ![](https://hackmd.io/_uploads/r1ojP-IGT.jpg) 7. 選擇產出後喜歡的Logo ![](https://hackmd.io/_uploads/SJaxO-Iz6.jpg) 8. 按下**Save Logo**及**Download your logo**後選擇想下載的檔案類型 ![](https://hackmd.io/_uploads/By6bKbIG6.jpg) ![](https://hackmd.io/_uploads/H1sUKZLMa.jpg) ![](https://hackmd.io/_uploads/r1JstbUfp.jpg) - **持續修正整合將React融入專案** --- ## ==20231018== - **研究HackMD語法及操作功能** [**HackMD功能介紹**](https://hackmd.io/features-tw?both) - **開展讀書會分享計劃** - **讀書會分享計劃目的如下:** 1. 此為由==Kaz==提出的每週一日讀書會計畫的==先行版==;礙於程式開發組成員各自任務不同及可見面時間零散,因此將其調整為約30分鐘的活動紀錄分享會。 2. 此會分享目的為讓團隊彼此==情報交流==,相互了解彼此目前工作狀況及活動。 3. 利用HackMD記錄方式,讓團隊成員==養成寫筆記習慣==及==熟悉MD檔的使用==。 4. 透過紀錄一週活動,可以幫助團隊成員加深執行過的項目、活動的印象,來==幫助整理各自的時間管理==。 - **讀書會分享計劃執行細節如下:** 1. 參與人員為數位方塊程式開發成員。 2. 分享交流上一週各自的活動紀錄及心得(教學狀況、專案開發、Bug修復、研究技術、業界情報等與程式相關活動)。 3. 固定==每週三下午14:00==於==Discord==進行分享交流會,暫定約30分鐘,未來視情況調整,因故無法參與需提前於碼農Line群組中通知。 4. 於==HackMD==各自紀錄自身上週的活動及狀況,除文字外,可附上圖片或是超連結來幫助其他人更容易了解狀況。 5. ==須於每週會前完成上週各自的活動紀錄==,即便因故未參與,其他人也能透過筆記來了解情報及狀況。 - **進行Laravel + React + Inertia的專案技術整合** 目前進行針對[**Eslint**](https://eslint.org/)的React規則逐條進行調整,同時熟悉React在原有架構下功能該如何實現,例如傳值、全域狀態管理、搭配套件等...。 - **修正專案Bug** 因自身疏忽漏掉 **GA** 的請求被[**CSP**](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP)擋掉,且沒有進行正式站的再次驗證,導致近一個月的 **GA** 都收集不到資料;程式上調整將 **GA** 加入[**CSP**](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP)的白名單中,同時也針對自身對 **GA** 的操作及反饋不熟悉進行知識補強。 - **指導學生專題** 學生幾乎每天都有問題來詢問請教,我自己這次也發現很多自身指導上的盲點,剛好指導的2組是用不同的技術來進行專題開發,原先擔心是否直接使用框架會太快,所以是傾向先讓學生熟悉原生,但後續感受到2組同學問的問題及程式碼撰寫的差異,在框架組反而不用我擔心;反倒原生組因為沒有被框架限制,導致程式碼品質狀況較多,算是從指導者的角度感受到框架的優點了。 - **composer的--no-dev指令的使用** 拉下專案後安裝PHP套件通常使用的指令是: ```shell composer install ``` 這指令會將composer.json中所有套件都安裝,但是有些套件是本地端開發用的,例如檢查PHP程式碼的[**PHP_CodeSniffer**](https://github.com/squizlabs/PHP_CodeSniffer)、自動生成Models套件的[**reliese/laravel**](https://github.com/reliese/laravel);這些套件在上線後是沒有用的,因此在部屬時通常會使用以下指令: ```shell composer install --no-dev ``` 透過該指令便不會將那些dev開發中使用的套件裝在伺服器上了。 :::danger 注意,如果該專案中有全域被引用的開發套件,例如[**Krlove**](https://github.com/krlove/eloquent-model-generator);則部屬時應該繼續使用`composer install`來進行部屬,否則會部屬失敗。 ::: ---