2024年5月2日 星期四

有沒有一種可能在axios會報錯,fetch卻會成功呢

前言

今天與後端工程師對接,使用axios在url正確,method正確,且不送參數的情況下,api順利的回來了,但換另外一隻api媽的鬼故事就開始了。

由於我打login api,所以理應需要傳送username, password,竟然報錯Network error,console報CORS,一開始我一直懷疑是行火牆問題,但有什麼理由第一隻api會過第二隻不會。難道防火牆會只擋這隻api!!然後後端堅持他的設定沒問題。還傳搭打成功的截圖給我,重點是只有截圖沒有程式碼,而且我跟你ip國籍不同,變數太多拉!!

最後後端工程師提到,設定與上一個專案相同,我想到上一個專案底層是用fetch去寫的,所以我死馬當活馬醫,改用fetch去打api,竟然還真的給我打過了.....所以問題出在哪呢?經過我反覆嘗試找到了差異,以下進入程式

fetch(url, {
method: "post",
    body: JSON.stringify(data)
})
axios({
method,
url,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
      data,
})
// 簡單說fetch預設Content-Type是application/x-www-form-urlencoded,而axios預設是application/json
// 所以fetch的body需要JSON.stringify就是因為要轉成網址

上網查一下application/x-www-form-urlencoded這是一個比較古老的編碼形式,然後data還必須傳formData,所以我說到底誰還在寫這麼難用的api,真他X的


2024年4月24日 星期三

tailwindcss3換皮要怎麼做呢

前言

早在多年之前,我看過奶綠大大一篇網誌提到,tailwindcss可以做到更換語系並伴隨修改文字大小,且可以在10分鐘搞定,原理是靠檢測body上的一些property,然後對tailwindcss的ultility進行覆寫,架設text-lg是1rem,當遇到body[lang="en"]時text-lg就會變成0.75rem,這樣的效果。然而~我怎麼樣都找不到這篇文章,正確來說是找到了,但是點進去看看不到關鍵字(應該是基於某些原因部分內容被刪掉了)。因此只好自己開始摸索

原本想說只是檢測上層class應該是不難做到,結果官網看來看去也沒半點消息,只有一個darkMode的文件應該可以做到我要的需求。但這樣需要在每個要變換顏色的class加上不同膚色的前綴,例如text-primary, dark:text-primary, yellow:text-primary...,不是這樣開發效能非常不佳,因此列入備案,繼續尋找答案。

在苦思無果後上網發文,網路大神果然很熱心的回覆了,除了上述darkMode的方法,還另外得到了三個解法,記錄一下給大家參考

方法一,原生變數解法

// src/main.css
@tailwind base;
@tailwind components;
@tailwind utilities;

.main {
--theme-main: #D53737
}
.test {
--theme-main: #007712
}

// tailwind.config.js
/** @type {import('tailwindcss').Config} */

export default {
content: ["./index.html", "./src/**/*.{vue,js,ts}"],
theme: {
extend: {
colors: {
"theme-main": "var(--theme-main)",
},
spacing: {
mobile: "600px",
},
},
},
plugins: [],
}

// src/App.vue
div(class="max-w-mobile mx-auto my-0 h-full", :class="'test'") // or main
div(class="bg-theme-main") what is my bg color

第二種解法也相當容易,使用tw-colors的tailwindcss套件

// yarn add -D tw-colors
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
const { createThemes } = require("tw-colors")

export default {
content: ["./index.html", "./src/**/*.{vue,js,ts}"],
theme: {
extend: {
spacing: {
mobile: "600px",
},
},
},
plugins: [
createThemes({
main: {
"theme-main": "#D53737",
},
test: {
"theme-main": "#007712",
},
}),
],
}

架構方法類似,使用方法一樣,我就不多做介紹

最後介紹第三種,同樣是使用套件,叫daisyUI

// yarn add -D daisyui@latest
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
const { createThemes } = require("tw-colors")

export default {
daisyui: {
themes: ["light", "dark", {
"main": {
"theme-main": "#a991f7",
"primary": "#a991f7",
"secondary": "#f6d860",
"accent": "#37cdbe",
"neutral": "#3d4451",
"base-100": "#ffffff",
},
}],
},
  plugins: [require("daisyui")],
}

<div data-theme="main">
This div will always use light theme
<span class="text-theme-main">This span will always use retro theme!</span>
</div>

至於還有一些奇怪招數,原生土法煉鋼法及NODE_ENV修改大法

.main {
text-theme-main: #b42222;
bg-theme-main: #b42222;
......
};
.test {
text-theme-main: #007712;
bg-theme-main: #007712;
......
}


/** @type {import('tailwindcss').Config} */
const { createThemes } = require("tw-colors")

export default {
content: ["./index.html", "./src/**/*.{vue,js,ts}"],
theme: {
extend: {
     colors: {
       "theme-main": NODE_ENV == "prod" ? "#D53737" : "#007712",
      },
spacing: {
mobile: "600px",
},
},
},
}


恩.....我就不多解釋了

最終我選擇了tw-colors套件解法,原因就是夠簡單,當然靠css原生變數那招也很吸引我,且不用另外裝套件,但唯一缺點就是要分兩個檔案維護,開發上不夠直覺。至於daisyUI,看官網這應該是個滿強大的套件,但很多東西應該都制定好了,但感覺有點重,應該是適合當後台開發使用,前台設計師應該不會讓我這麼輕易不照他給的色碼開發,所以還是選用最簡單的tw-colors,好了,又完美解決了一次事件,祝大家有個美好的一天。

好文分享,如果你docker desktop跑得很慢的話,可以考慮OrbStack

參考網站 

小記

雖然我自從換成m2 pro之後就沒有這樣的困擾了,不過還是值得記錄一下

2024年4月18日 星期四

typescript中的map,How to get types from arrays

前言

我們都知道要把自定義物件轉成型別在ts裡面要使用typeof,

const config = {
"self": {
"method": "get",
"uri": "self"
},
"login": {
"method": "post",
"uri": "backend/login"
},
"logout": {
"method": "get",
"uri": "logout"
}
}
const keys = keyof type config // "self" | "login" | "logout"

但假設今天是一個陣列,例如

const datas = [
{
"key": "collect",
"value": "Collect"
},
{
"key": "ffc",
"value": "FFC"
},{
"key": "switch",
"value": "Switch"
},
]

那我要把所有的key取出來變成類別該怎麼寫呢,總不能真的用js的map吧,實際上就是,你上網找ts map也只會找到集合的Map,所以該怎麼寫呢,我們直接看code

let key = (typeof datas)[number]["key"]

沒錯,就是這麼簡單,最後你就會發現key的類別是string.....那我幹嘛這麼大費周章,寫這麽多字只為了宣告string!!

先別急,我們要再做一點調整,不過不是類別宣告,而是資料

const datas = [
{
key: "collect",
value: "Collect",
},
{
key: "ffc",
value: "FFC",
},
{
key: "switch",
value: "Switch",
},
] as const
let key = (typeof datas)[number]["key"] // "collect" | "ffc" | "switch"

加上一個 as const資料就會被定型,在取用key時就可以取道固定的值了。

不過const資料之後,或許還是會產生其他的問題,例如realonly {...}不符合Rcord<string, any>之類的,這些問題......改天再說吧!以上,下班!

參考網站


2024年4月17日 星期三

vue要產生一個component在body要怎麼做

前言

相信很多小夥伴跟我有相同的疑問,或是使用過modal這個套件,會在body增加一個可控制的component,我一直以為是createElement相關的方法去做到的,一直到今天.......

今天在線上抗到一個component,來人我們直接上範例

<teleport to="body">
<p> 移動我到 body </p>
</teleport>

最終結果就會是

<body>
<div id="app">......</div>
<p> 移動我到 body </p>
</body>

關於teleport有兩個屬性to跟disabled,to就是要移到哪裡去,支援css選擇器,disabled就是有沒有生肖,亦或是留在原地

以上簡短分享

2024年4月16日 星期二

在vue裡面寫pug真的有這麼恐怖嗎?

前言

許久之前有開過一個vue3專案,template我使用pug來寫,基本上用得還算順利。除了一開始不會自動排版,但也靠vetur解決了(雖然vetur那時跟volar打架,但後來也還算完美落幕)。一直到這兩天我從心起了一個專案,憑藉對ts及vue的熟悉,我突然感覺到不太對勁:不光是template幾乎不會報錯、ts不會自動跳出提示,連格式化也失靈了。因此我開始上網求助

網路大神果然不會讓我失望,清一色都是叫我儘早放棄,把pug說的要多糟有多糟,支援要多差有多差。



洨編連團隊的道歉信都寫好了,準備要把所有template改回html,最終看到圖片中那則留言,想說斯馬當活馬醫吧。再試一回,結果真讓我試出來了。以下來記錄一下

備註一下,以下小編都是使用vscode及相關套件進行處理

首先是format的部分(prettiers extension請先安裝好)

yarn add -D prettier @prettier/plugin-pug
// .prettierrc
{
"semi": false,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 150,
"tabWidth": 2,
"pugClassNotation": "attribute",
"pugExplicitDiv": true,
"pugSortAttributes": "asc",
"pugSortAttributesBeginning": [],
"pugSortAttributesEnd": [
"v-for",
"^:",
"^@",
"^v-"
],
"plugins": ["@prettier/plugin-pug"]
}

這樣就可以使用prettier進行format了(我還以為prettier什麼都支援,原來需要外掛),後面那些設定大家可以自行調整,洨編是依照個人喜好設定

再來是vscode typescript compiler的部分

yarn add -D @vue/language-plugin-pug
// tsconfig.json
{
  "vueCompilerOptions": {
"plugins": ["@vue/language-plugin-pug"]
}
}

這樣類別及型別錯誤什麼的就可以正常辨識了。

不過網路大神講得這麼恐怖,或許其中還有什麼未知的問題等待我去挖掘,總之我提出的三個問題暫時是解決了,至於未來.....的事交給未來的我去解決吧!

又過了驚險的一天,收工下班!

2024年3月19日 星期二

HD鬼故事N+1集

在許久許久之前,有位異常懶惰的工程師,正躺絕對不坐,能做絕對不站。如果能夠自動化的事情我就絕對不會親自動手,所以凡舉布版打包一類的瑣事,這位工程師通通交由腳本去執行。但事情往往不會這麼順利

在他的服務單位有一個傳統,正式機需由後端或維運來佈版,至於背後的原因,那可能又是另外一則鬼故事了。

測試機在沒人管的前提下,當然由這位工程師說了算,直接從github actions ci走腳本一路打包布版版到機器,全程無尿點,舒服!至於正式機......總之我秉持你愛怎樣我配合的想法,工程師就寫了個腳本打包,然後傳壓縮檔給後端讓他們自己去發揮。多日來倒也算相安無事,但鬼故事總是發生得措手不及那才叫做鬼故事啊!

這專案再一次大更版後,正式環境出現了不明原因的錯誤,且多日debug無果,老闆開始檢討起開發流程,覺得在測試與正式間,應該再多一個uat環境。這想法原則上沒問題,問題在後面。老闆覺得換皮站應該每個皮的系統是獨立的,防止換B皮結果A皮壞掉.....從技術面向來看,這位工程師已經不知道要從何吐槽起了。總之還是那句,你愛怎樣怎樣。

老闆最終再提一個想法,應該要加快布版速度,防止用戶有不良體驗。

看到這裡大家應該覺得,那不就測試機的佈版腳本,就改一下環境變數直接上到正式機就好。工程師原本也是這樣想。但很可惜關於佈版前端工程師並沒有話語權。接下來主管一系列指令更是殺得工程師手足無措

關於快速佈版,主管提出使用cicd。這乍看下沒有問題,問題在工程師以為他也是github actions腳本寫一寫佈版,大錯特錯!他把佈版腳本寫進寶塔裡面,使用人工點擊腳本佈版

然後關於uat,工程師原本以為他會叫工程師開一個uat分支,多一個環境變數檔,當uat push時觸發佈版。結果完全不是我想得這麼回事!每當程式要上uat,會叫工程師把開發分支merge回master。(先別噓),然後他再點“按鈕”進行uat佈版,等測試完成後再點按鈕上正式。

哎.......就請問如果uat測試沒過,要改東西,這時有hotfix趕著上版怎麼辦!算了~只是一個小小前端工程師~如果能保一切順利老闆開心,這位工程師願意陪你們演猴戲,但好巧不巧事情就這麼發生了......

因為這套系統在打所有api前,會先讀取本地ip.txt,把裡面的網址進行解析測試,看哪個通就走哪個,但當初也不知道怎麼規劃的ip.txt放在根目錄,有開發過vue vite的小夥伴一定知道如果打包完要在根目錄,那檔案開發時要放在/public裡面,但事實是,ip.txt不歸前端管,是歸維運管,所以不應該放到前端專案中,等於打包時不應該打包到ip.txt這隻檔案,但最終打包出來卻要有這個檔案。

這聽起來很饒口,但實際上就是這樣。那應該怎麼做到呢?很簡單,測試機腳本是先下指令清光ip.txt以外的所有檔案,再把壓縮包直接壓進來,這樣就可以ip.txt不會被覆蓋及刪除。

當然主管也不笨,他在package.json中使用指令,在打包完把指令路徑的ip.txt複製到指定目錄。這樣原本上也沒什麼問題,但問題出在寶塔,例如yarn build && cp ../ip.txt ./dist/,這樣理論上會先打包在複製檔案,但不知道為什麼如果是按按鈕下指令,最終會看不到ip.txt這隻檔案,或許是寶塔優化了腳本變成多執行序執行??導致一開始就複製進去,在build完後一並被刪除,不知道,這位工程師並不想了解。總之在改成這樣上版後,正式機癱瘓了,原因是找不到ip.txt。死活也找不到為什麼,最終只好一遍又一遍人工把ip.txt複製進去,可喜可賀......個頭啊!所以當初導入cicid的意義在哪,還是要人工啊!

後記

過程中工程師無論明著說,暗著說,告訴他們已經有寫好腳本。然後把專案管理權全數交數去,主管明明就可以看著腳本知道過程中到底都發生些什麼事,卻一行都不看,偏偏選用這麼....不自動的方式去打包,唉~槽點滿滿。真的搞不清楚主管到底是強還是弱了~

最終這位工程師將何去何從,下場又是如何,請靜待下回分曉


3/21鬼故事更新

果然就在發文兩天後就生了塞車,事後工程師主動請纓才讓事件有了完美的帷幕,至於日後還會發生怎樣有趣的事呢,讓我們拭目以待

2024年3月18日 星期一

在前端寫後端?如何寫出不會被快取清掉的資料

前言

前陣子接到一個需求,要前端產出一個uuid,傳給後端當身份驗正。先別噴,身份驗證坐在前端前所未聞,但任務確實就是這樣,開發過前端的都知道,前端存資料選項沒幾個,就算我把資料存local storage,一但遇到清除快取也會消失。一開始PM那邊當然是說沒關係,我們盡量,但背後還是不斷給壓力說看有沒有辦法解決......這擺明就是後端的工作,我實在想不明白為什麼必須得前端做想,他們困難點到底在哪,相當然爾我兩手一攤說沒有辦法,結果主管有天突然就跳出來了說他想到更好的方法了!!

我們先來解釋一下他的想法,簡單說把產生uuid的做成單獨的一個站,然後除了存入local storage之外,還要用postMessage的方式將產生的uuid回傳,看到這裡聰明的小夥伴應該猜到,在原專案用iframe將上面這個站嵌入就可以了,變且addEventListener("message", (e) => { e.data })去監聽回傳內容就好。

這樣除非對方真的很專業開啟了產生uuid的站並清除快取,不然在原本的站怎麼清uuid都會保持不變,我附上code

// uuid page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSO Page</title>
</head>
<body>
<script>
function generateUUID() {
var d = new Date().getTime();
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
d += performance.now(); // 使用性能計時器來增加隨機性
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
var uuid = localStorage.getItem('uuid');
if(!uuid){
uuid = generateUUID();
localStorage.setItem('uuid', uuid);
}
// 發送 local storage 中的數據給父頁面
window.parent.postMessage(uuid, document.referrer);
</script>
</body>
</html>


// parent page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parent Page</title>
</head>
<body>
<iframe src="https://uuid.com" width="1" height="1" style="display:none"></iframe>
<script>
// 接收來自子 iframe 的消息
window.addEventListener('message', function(event) {
  console.log(event.data) // uuid
}, false);
</script>
</body>
</html>

後記

我雖然滿佩服主管可以想到這種奧步的,且這招不光是只能用來存資料,也可以拿來做些偷雞摸狗的事情,例如我第一分工作就看到老闆使用iframe更新主頁面的畫面.......某種意義上來說也是ajax拉,把部分後端的工作移到前端來做。不考量安全性的話......天啊,我好不想想起當時那個畫面==大家還是考量一下安全性,珍惜生命,少用iframe吧

2024年3月15日 星期五

要改變component的樣式為什麼不是用v-deep

前言

在與前同事隨意尬聊的狀況下,前團是提出要我幫他看code的需求,他說他一直無法改變component的樣式,我想說不過就是v-deep的麽簡單的事情,所以想也不想就答應了,殊不知最終我搞了兩個小時的故事....

問題起源於他使用了套件vue-advanced-chat來製作聊天視窗,正確來說是前同事使用,所以他也只是背鍋而已。然後公司需求很多,然後這套件很多功能其實都無法達成需求。這故事告訴我們,選元件要慎選,如果客製化需求太多,自己造輪子不一定是不好,至少彈性足夠。

接下來要說說這次遇到什麼問題,就是audio的樣式在某種解析度會跑版,需要用css去修正,但無論用v-deep或是global寫css,死活都是吃不到,正一籌莫展之際,我看到了幾個關鍵字。

首先第一個為什麼這個套件的tag還可以保持原樣<vue-advanced-chat>,然後在tag下面出現#shadow-root(open)的字樣,下面可以展開,上次我看到類似的景樣是在iframe裡面,如果這狀況跟iframe真的是類似,那確實css selector無法吃到裡面也很正常。我上網查了關鍵字發現確實是類似的情況shadow dom,簡單說被web component包裝成一個原生tag了。

我原本想說有了關鍵字剩下就簡單了,所以去查要怎麼樣才能讓shadow dom吃到css,查了半天都說有:host之類的選擇器可以用,但每個都是過了一樣沒有效果,但看到這網站說明後才大致明瞭這東西必須在寫web component當時,也就是內部撰寫,所以也還是沒辦法達成我要的效果,且裡面也明確表示css是無法從外層直接影響內層的樣式。

正當我想放棄時我想到,既然css無法做到,能否使用javascript去強制修改呢?結果還真讓我找到解法,要分成兩個步驟來說,首先你要可以選擇到shadow dom,做法也不難,就是你要在選擇到web component之後,直接.shadowRoot就可以選到dom,之後再創造一個style並把內容寫好寫滿,之後appendChild進去就好,我們直接來看code

// 寫一個可以選擇到shadow dome的selector
const querySelectorAll = (node,selector) => {
const nodes = [...node.querySelectorAll(selector)],
nodeIterator = document.createNodeIterator(node, Node.ELEMENT_NODE);
let currentNode;
while (currentNode = nodeIterator.nextNode()) {
if(currentNode.shadowRoot) {
nodes.push(...querySelectorAll(currentNode.shadowRoot,selector));
}
}
return nodes;
}

// inject css
const style = createElement("style")
style.innerHTML = `
.class { ...... }
......
`

document.querySelector("custom-web-component").shadowRoot.appendChild(style)

這樣就大功告成拉。

後記

我就說好好的vue component為什麼要包成web component,應該是考量到要讓各種框架使用,然後你包沒有關係,為什麼要包成shadow dom,然後你包成shadow dom沒有關係,為什麼還要有bug!!好在大爺我幾十歲了,不怕你!雖然已經一點多了,下班

2024年3月11日 星期一

vscode常常壞掉該怎麼處理

前言

如果有寫typescript的人應該再複製貼上或使用snippets來輔助輸入的人應該很常發現貼上後莫名畫面中出現很多紅線,然後vscode的輔助開發就壞掉了,需要關掉重啟才可以

今天我心血來潮,嘗試使用指令reload window去重啟vscode,這樣確實可以達到不用關閉就可以修復編輯器的效果,但每次都要按command+shift+p 然後再輸入reload...實在有點繁瑣,心想應該可以設定個command+r的快捷鍵讓他自動重啟吧

所以我打開快捷設定,我這邊用英文版,所以我不知道中文版叫什麼名字,可以參考圖片


打開之後找到reload window發現原來已經有快捷了,而且還正好是command+r,但為什麼怎麼按都沒反應呢?後來上網查在知道原來when的這個欄位也要設定,要設定觸發情境

所以在reload window上面點右鍵選擇修改上面點右鍵選擇修改when,然後輸入editorTextFocus

因為壞掉一般都在程式編輯過程吧。設定完之後就大功告成了,再遇到壞掉直接敲快捷就搞定了。

祝大家之後都可以準時下班~啾咪~

2024年3月10日 星期日

bilibili內嵌設定

在祖國你別想說要用youtube絕對封得你不要不要,如果直接把影片放s3也不見得會比bilibili穩定,所以難免會使用到影片嵌入。

但直接使用官方給你的內遷網址範例如下

<iframe src="//player.bilibili.com/player.html?aid=10335022&bvid=BV1nx411m7bV&cid=17072810&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

然後如果你要加上自動播放等其他的功能,autoplay=1,之類的功能,你會發現根本不屌你,你上網查基本上也查不太到有用的資料。如果只是參數沒用那也就算了,safari網頁開啟竟然沒有聲音....我你個去你們家測試工程師會不會太混了一點!

終於找到這篇說明,原來bilibili有寫一個播放器,所以只要把網址稍微改一下

<iframe src="//www.bilibili.com/blackboard/html5mobileplayer.html?aid=10335022&bvid=BV1nx411m7bV&cid=17072810&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

就是//player.bilibili.com/player.html換成了//www.bilibili.com/blackboard/html5mobileplayer.html

之後其他網站叫的參數基本上就可以生效了,safari功能也正常了,交差去吧~


2024年2月27日 星期二

檢查是否有出現在畫面中可以使用IntersectionObserver

 直接附上程式碼,本程式以vue3寫法為範例


<template>
    <div>
        <div> ... </div>
        <div v-if="!checkFinished" :style="{ height: footerRef?.clientHeight + 'px' }"></div>
    <div ref="heightCheckerRef" :style="{ height: marginBottom + 'px' }" />
        <div ref="footerRef" class="px-3 py-4 fixed">
        <button class="bg-gray-500 w-full text-white rounded py-2">返回网站</button>
    </div>
    </div>
</template>

<script setup>
const footerRef = ref(null)
const heightCheckerRef = ref(null)
const checkFinished = ref(false)
const marginBottom = ref(0)

onMounted(async () => {
  // 在資料齊全後
const observer = new IntersectionObserver(([checker]) => {
if (!checker.isIntersecting) { // 是否有在畫面上
marginBottom.value = footerRef.value.offsetHeight
}
    observer.unobserve(heightCheckerRef.value) // 取消觀察
checkFinished.value = true
})
observer.observe(heightCheckerRef.value) // 放入要觀察的DOM
})
</scirpt>


畫面中情境為footer是fixed,如果有內容被footer擋住,那勢必要設個margin-bottom,可以用類似的方法去做檢查

同理,如果有scroll的情境,拉致最底需要加載資料,可以不用在那邊算高度了,也可以使用IntersectionObserver去檢查最底下區塊是否有出現在畫面中,一旦出現就加載,直到最後一頁

HD鬼故事第N集

 HD鬼故事第N集:

在公元2xxx年,一日雲端設備有些故障,某位年薪據說超過千萬的高級工程師,一頓操作猛如虎,下了重開機指令,頓時風雲變色,機器再啟之際已無過往跡象,不僅程式碼刪光,連資料庫也不見蹤跡,詢問原A場員工才得知,當初啟用機器使用的是暫存碟,也就是重啟資料就會刪得精光,且是破壞式格式化,一旦重啟神仙難救。


公司上下馬上盤點各種可能補救措施,力求把多年客戶資料給救回來,有人提出備份的資料庫(在原來硬碟一起被格式化了,至於為什麼備份的資料庫要跟資料庫放同一個硬碟,這又是另一則鬼故事了),也有人想從request的log試圖把資料補救一點回來,可惜最終依然無力回天。昨日種種譬如昨日死。


沒人知道這年薪千萬的工程師最終下場如何,只知道,做人最好不要太自負,不熟要懂得請教專業人員。


最終,今年才剛開始,希望人人都能領到下個月薪水過個好龍龍year~


免責聲明:以上都是我在翻閱九陽神功時,不小心撇見旁邊的註解文,也不知道什麼意思就轉錄出來了,如有雷同絕對是巧合

2024年2月14日 星期三

Tauri 濃縮版的Electron.js

前言

老實說我還沒開始學這塊,因為也沒什麼場景可以使用,但網路上看起來這玩意兒挺有趣的,相比起來也更輕量,未來或許有機會用到,先做一下紀錄

附上參考連結

2024年1月31日 星期三

雖然有點久了,但還是來玩一下Github Pages

前言

在小珍珠生日這天,志清哥找我討論一個案子,說教師部需要,大神都開口了當然是不能收錢拉,但我首先跟他提出一個最根本的問題,伺服器呢!!沒有伺服器我能怎麼辦呢?我還真的能怎麼辦,後來思考既然需求並不包含資料庫跟後端,那來玩一下出了很久一直沒機會碰的Github pages吧

使用Github pages很容易,開啟一個新repository,然後到setting > pages頁面 > 找到Branch這個欄位,然後選擇分支,在選擇資料夾即可(資料夾只支援/跟/docs兩個)

如果是一般前端專案,根目錄要放專案,所以可以把build出來的檔案放到docs,這邊我以vue及vite為範例

export default defineConfig({
 // 因為最後專案網址是https://{github_account}.github.io/{repository}, 不是在根目錄,所以base也要調整
base: "/{repository_name}",
build: {
target: "es6",
outDir: "docs", // <- 改這裡
},
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
})

這樣就每次更新,然後build完一起把檔案commit就好,一切就大功告成了。

But,把build完的檔案一起commit這看起來是件很愚蠢的事,所以我開始想看有沒有機會使用action解決,在奶綠茶大大多年前的文章中找到解法,有人已經把套件做了,我只要使用就好,但因為年代久遠,現在已經是疫情後的2024年,有些畫面已經不一樣了還是要摸索一下,要使用的是peaceiris/actions-gh-pages@v3這套

step1.
簡單說明一下首先你要到帳戶的settings(右上角頭貼) > Developer Setting > Person Access Token > Tokens(classic) > 產生一組token供action使用(產生完記得複製起來,不然頁面關掉就看不到了)

step2.
接著到repository的Settings > Secrets and variables > actions > secrets > 新增一個secrets(開頭不能是GITHUB_,其他可以隨便取,我這邊取ACTIONS_TOKEN)

step3.
最後在專案中新增/.github/workflows/main.yml,這裡也有許多參考範例

name: GitHub Pages

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- uses: actions/checkout@v3

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: "20"

- name: build
run: |
yarn
yarn build

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.ACTIONS_TOKEN }}
publish_dir: ./docs

step4.
接著把所有改動都commit & push進去


你就會發現github actions開始動拉,deploy完就會發現一個新的分支gh-pages,裡面就會是編譯完的檔案,並且github actions也都幫你設定好了,等於上面那些步驟也不用自己來,可以說非常懶人及完美啊!

後記


我再到其他專案開啟pages頁面結果顯示這樣,點進去發現要付費,原來免費只讓你啟用一個,果然天下沒白吃的午餐啊~

幫女兒切完蛋糕接到大哥驗收完成電話,大哥的笑聲像中了樂透彩,應該是有免費的伺服器跟碼農內心竊喜吧~

2024年1月16日 星期二

如何再遮罩上挖個洞

前言

相信大家都有用過Modal,都有遇過背後那半透明的遮照,但如果要做到以下效果,在下方按鈕上挖一個洞,我的第一直覺應該會用圖片去做處理


實際上這是我接手同事專案後他做出來的效果,我仔細研究了一下他的程式碼,發現想法還挺有趣的直得學習一下。
因為這部分程式碼不好呈現,所以今天就用截圖代替吧,絕對不是因為我比較懶


簡單解釋,mask-info一樣左上對齊,設成fixed。
這裡使用
const { x, y, height, width } = element.getBoundingClientRect()
去取得按鈕(目標元素)的座標及大小,放到類元素:before去瞄準絕對位置跟尺寸,最後使用box-shadow讓陰影往外延伸並且設上透明度(照片中往外延伸3000px,以手機版來說也很夠用了)。
打完收工。

後記
跟以往我總是從座標0,0開始思考有點不一樣,沒想到類元素還可以這樣使用。只能說世界還是很大啊~(煙

<div class="mask-info" :style="clipPath">
...
</div>
const clipPath = computed(() => {
const { x, y, height, width } = props.data // element.getBoundingClientRect() 進來的值
return {
"--top": `${y}px`,
"--left": `${x}px`,
"--width": `${width}px`,
"--height": `${height}px`,
}
})
.mask-info {
@apply fixed left-0 right-0 top-0 bottom-0 z-[100];

&:before {
@apply absolute block rounded;
content: "";
left: var(--left);
top: var(--top);
width: var(--width);
height: var(--height);
box-shadow: 0 0 0 3000px rgba(0, 0, 0, 0.8);
}
}

另外紀錄一個點,同事使用var(--left)去取得主元素的style left值,這點也挺特別的,沒有使用vue3原生的v-bind去取
如果沒意外應該會寫成這樣
left: v-bind("clipPath['--left']")