# 前端讀書會 # 浮水印 watermark ##### (2024.11.29) 在網頁生成浮水印,可以用CSS,JS達到效果。Vue 可以使用Element Plus 提共的浮水印元件,或使用外掛,如watermarkify/vue-watermark,也可以自建元件。 製作浮水印須考量大小、位置、距離、圖層顯示的上下位置,如果是文字,還可以設定字型、顏色等效果。 ## 介紹不同的浮水印的生成方法 ### 一、Element Plus 浮水印元件 el-watermark [Element Plus 官網說明](https://element-plus.org/en-US/component/watermark.html#watermark) 專案如果未安裝Element Plus,要先安裝,程式部分引用自官網,設定如下: ```javascript= <script setup lang="ts"> import { reactive, watch } from 'vue' import { isDark } from '~/composables/dark' const font = reactive({ color: 'rgba(0, 0, 0, .15)', }) watch( isDark, () => { font.color = isDark.value ? 'rgba(255, 255, 255, .15)' : 'rgba(0, 0, 0, .15)' }, { immediate: true, } ) </script> <template> <el-watermark :font="font"> <div style="height: 500px" /> </el-watermark> </template> ``` 浮水印生成的畫面 ![image](https://hackmd.io/_uploads/r1cj5Hrmyl.png) 滾軸右移時,頁面不會有浮水印 ![image](https://hackmd.io/_uploads/S1rTqSBXye.png) 要浮水印滿版,可以增加CSS style ```html <template> <el-watermark :font="font" style="position: absolute;"> <div style="height: 100vh" /> </el-watermark> </template> ``` 浮水印滿版畫面 ![image](https://hackmd.io/_uploads/HybRjSH71g.png) ### 二、watermarkify/vue-watermark * [watermarkify/vue-watermark 使用手冊](https://github.com/watermarkify/vue-watermark) * [功能展示連結](https://watermarkify.github.io/vue-watermark/),從畫面實際操作,可以了解每個屬性設定的功能 程式設定步驟: 1. 在終端機安裝套件,以NPM為例,輸入`npm install @watermarkify/vue-watermark` 2. 設定程式: ```javascript= <script setup lang="ts"> // step 1: import Watermark from @watermarkify/vue-watermark import { Watermark } from '@watermarkify/vue-watermark' // step 2: define watermark options // see https://github.com/watermarkify/vue-watermark#options const watermarkOptions = ref({ content: 'watermark', gap: [20, 20], offset: [10, 10], zIndex: 5, rotate: -20, }) </script> <Watermark :options="watermarkOptions"> <div>插入要加上浮水印的資料</div> </Watermark> ``` 實際生成的畫面,遇到跑版的還是會像Element Plus 一樣,浮水印只有生成在視窗可見的範圍內,如果拉動滾軸,就無法生成浮水印,但是正常沒跑版的頁面,是可以整個頁面生成浮水印,想要滿版的方式,和第一個做法一樣。另外,不知道為何浮水印尾端有被裁減。 ![image](https://hackmd.io/_uploads/SJvHlPSmkg.png) 在終端機安裝時有跳出漏洞警示,使用時要考慮安全疑慮,以及功能是否失效。 ![image](https://hackmd.io/_uploads/Sy7lCSrXye.png) ### 三、自建元件 監控螢幕長、寬,並除以浮水印的長、寬,再使用迴圈印出每個浮水印,達到滿版的效果。 1. 建立元件 :::spoiler ```javascript= <template> <div class="watermark-container"> <div v-for="(item, index) in watermarks" :key="index" class="watermark-item" :style="getWatermarkStyle(item.x, item.y)" > {{ props.text }} </div> </div> </template> <script setup> import { ref, onMounted, onBeforeUnmount, watch } from 'vue'; // 定義 props const props = defineProps({ text: { type: String, default: 'Confidential', }, opacity: { type: Number, default: 0.1, }, rotate: { type: Number, default: -30, }, fontSize: { type: Number, default: 20, }, color: { type: String, default: '#000000', }, zIndex: { type: Number, default: 9999, }, }); const watermarks = ref([]); let observer = null; // 初始化浮水印 const initWatermark = () => { const screenWidth = window.innerWidth; const screenHeight = window.innerHeight; const spacing = { x: 250, y: 150, }; const columns = Math.ceil(screenWidth / spacing.x); const rows = Math.ceil(screenHeight / spacing.y); const newWatermarks = []; for (let i = 0; i < rows; i++) { for (let j = 0; j < columns; j++) { newWatermarks.push({ x: j * spacing.x, y: i * spacing.y, }); } } watermarks.value = newWatermarks; }; // 獲取浮水印樣式 const getWatermarkStyle = (x, y) => { return { left: `${x}px`, top: `${y}px`, opacity: props.opacity, transform: `rotate(${props.rotate}deg)`, fontSize: `${props.fontSize}px`, color: props.color, }; }; // 處理視窗大小變化 const handleResize = () => { initWatermark(); }; // 處理 DOM 變化 const handleDomChange = (mutations) => { const needsUpdate = mutations.some( (mutation) => mutation.type === 'attributes' || mutation.type === 'childList' ); if (needsUpdate) { initWatermark(); } }; watch(props, () => { initWatermark(); }); onMounted(() => { initWatermark(); window.addEventListener('resize', handleResize); // 設置 MutationObserver observer = new MutationObserver(handleDomChange); observer.observe(document.body, { attributes: true, childList: true, subtree: true, }); }); onBeforeUnmount(() => { window.removeEventListener('resize', handleResize); if (observer) { observer.disconnect(); } }); </script> <style scoped> .watermark-container { position: fixed; top: 0px; left: -40px; width: 100%; height: 100%; pointer-events: none; overflow: hidden; } .watermark-item { position: absolute; display: flex; justify-content: center; align-items: center; width: 300px; height: 100px; user-select: none; white-space: nowrap; } </style> ``` ::: 2. 引入欲渲染的頁面 ```html <template> <div>這裡是資料...</div> <useWaterMark :text=waterMark :opacity="0.1" :rotate="-30" :fontSize="20" color="#000000" :zIndex="9999" /> </template> ``` 實際生成畫面 ![image](https://hackmd.io/_uploads/ByaUS8Bm1x.png) :::danger 須注意,以上三種由前端生成的浮水印,如果沒有轉成圖片檔或是設定防止刪除的機制,是可以從開發者模式刪除浮水印。 ::: --- # 非同步元件defineAsyncComponent 與 vite-plugin-federation 當有些客製化的功能,比如CSS樣式或是自訂的input 物件,除了自己專案用到,也可發布給其他專案使用時,就可以用到「非同步元件」[(defineAsyncComponent)](https://zh-hk.vuejs.org/guide/components/async.html)與 [vite-plugin-federation](https://github.com/originjs/vite-plugin-federation) ### 使用方式 #### 一、安裝套件 remote 和 host 都要安裝 `npm install @originjs/vite-plugin-federation --save-dev` or `yarn add @originjs/vite-plugin-federation --dev` #### 二、remote 端設定 1. vite.config.ts 在`exposes`設定要輸出的元件以及元件的路徑(exposes), `./Watermark`是 host 要設定的元件名稱,`./src/components/Watermark.vue` 是這個元件在remote 專案的路徑 ```javascript= import federation from '@originjs/vite-plugin-federation' export default { plugins: [ federation({ name: 'remote-app', filename: 'remoteEntry.js', // Modules to expose exposes: { './Watermark': './src/components/Watermark.vue', }, shared: ['vue'] }) ] } ``` 2. 注意瀏覽器版本限制 如果發生`ERROR: Top-level await is not available in the configured target environment` 錯誤,要設定build ``` build: { target: "esnext" } ``` 或 ``` build: { target: ["chrome89", "edge89", "firefox89", "safari15"] } ``` 3. 設定好要記得打包並讓專案運作 (一)、打包 `$npm run build` 這樣引用的host 端才能接受到remote 端的元件 打包後會多了很多federation 相關的檔案 ![image](https://hackmd.io/_uploads/Byz8gMLGJl.png) 想要即時打包,可在package.json 的build 設定 watch,只要檔案有異動,就會即刻打包 ```javascript "scripts": { "build": "vite build --watch", } ``` (二)、啟動專案 要運行打包的檔案,要執行`$npm run preview` ![image](https://hackmd.io/_uploads/H1sdNfIGJl.png) #### 三、host 端設定 1. 設定remote_app 連接的domin name ```javascript= import federation from '@originjs/vite-plugin-federation' export default { plugins: [ federation({ name: 'host-app', remotes: { remote_app: "http://localhost:5001/assets/remoteEntry.js", }, shared: ['vue'] }) ] } ``` 2. 在要使用async元件的檔案,引入remote 的元件 ```javascript= import { defineAsyncComponent } from 'vue' const Watermark = defineAsyncComponent(() => => import("remote_app/Watermark") ) ``` 完成畫面 ![image](https://hackmd.io/_uploads/HJCVXhHQye.png) >[!Note] 如果host 端沒有出現remote 端的元件,可能需要重新打包(build) :::danger 如果remote 掛掉,其他使用的也會跟著掛掉,所以要思考如何維護。 ::: 參考:[[ 想入門,我陪你 ] Vue 3 宅家輕鬆玩|Day 11:非同步組件與 vite federation 試玩](https://www.youtube.com/watch?v=KwopYEEPDgg&list=PLEfh-m_KG4dbjf0YCJ7i0FFGK3FtQpanL&index=14&ab_channel=Alex%E5%AE%85%E5%B9%B9%E5%98%9B) --- # CORS(跨來源資源共用,Cross-Origin Resource Sharing) ## 一、前言: 網頁有分靜態網頁與動態網頁兩種。靜態網頁的內容是寫死在html 檔裡,而動態網頁的內容,是客戶端(Client)向伺服器(Server)發送請求(Request),得到伺服器回應(Respones)的資料後,將資料渲染在網頁上。 當請求與回應的來源都是同一個來源(也可以說是同一個網址URL)時,瀏覽器就不會攔截伺服器回應的訊息,反之,瀏覽器就會報錯,這是瀏覽器**同源政策**(Same-origin policy)的安全機制。這個機制是防止其他來源隨意向伺服器發送請求,造成伺服器因為頻繁地回應而過載,或是伺服器內的機密資訊外流。 瀏覽器報錯畫面 ![image](https://hackmd.io/_uploads/Hyd5JzYCC.png) ![image](https://hackmd.io/_uploads/HJBMOMK00.png) [圖片來源](https://www.youtube.com/watch?v=4KHiSt0oLJ0) ## 二、介紹同源與不同源 同源是指同樣的通訊協定(Protocol,又稱Scheme),網域(Domain Name),埠號(Port),以下舉例: ### 1. 同源 範例一 * `http://example.com/index.html` * `http://example.com/product.html` 範例二 * `https://example.com:443` * `https://example.com` :::info http 預設埠號是80 https 預設埠號是443 ::: ### 2. 不同源 範例一,不同的通訊協定 * `http://example.com/index.html` * `https://example.com/product.html` 範例二,不同的網域 * `http://globe.example.com` * `http://example.com` 範例三,不同的埠號 * `https://example.com:5000` * `https://example.com` ## 三、CORS 設定 為了解決跨來源請求的問題,就必須設定CORS。而這個設定須由<font style="color:#f00">**後端**</font>處理,請後端回應時,在header,加入Access-Control-Allow-Origin的來源,通常來源設定有兩種: 1. 加上與伺服器同樣的來源 `Access-Control-Allow-Origin: https://example.com` 2. 設定為允許任何來源 `Access-Control-Allow-Origin: *` ### 後端的作法,使用UseCors()----以 Asp.Net CORE 6為例 ```csharp= if (!WebSiteEnvironment.IsProduction && !WebSiteEnvironment.IsSIT && !WebSiteEnvironment.IsUAT) { app.UseCors(x => { x.AllowAnyOrigin(). AllowAnyHeader(). AllowAnyMethod(). SetPreflightMaxAge(new TimeSpan(1, 0, 0)); }); } ``` HTTP標頭 ![Header](https://hackmd.io/_uploads/rJO-8dKCR.png) 請求標頭 ![image](https://hackmd.io/_uploads/S1qyBOtRC.png) 回應標頭 ![image](https://hackmd.io/_uploads/HJOMH_FA0.png) ### 前端的作法(參考,可以不設定) 以axios 為例: ```javascript= const http = axios.create({ baseURL: baseUrl + '/', headers: {}, }); http.interceptors.request.use( (config: InternalAxiosRequestConfig) => { config.headers['Access-Control-Allow-Origin'] = '*'; config.headers['Content-Type'] = 'application/json;charset=utf-8'; return config; } ); ``` ### CORS外掛: * [CORS unblock](https://chromewebstore.google.com/detail/cors-unblock/lfhmikememgdcahcdlaciloancbhjino) * [Allow CORS](https://chromewebstore.google.com/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf) * [CORS anywhere](https://github.com/Rob--W/cors-anywhere) :::danger `Access-Control-Allow-Origin: *` 的設定可能會導致安全漏洞 ::: <br/> --- # 地理定位(Geolocation) 是一個HTML5 的功能,透過Geolocation API 可以取得地理定位的資訊 請用瀏覽器開啟右側網 https://alonso1331.github.io/Geolocation/ ```javascript= const compass = document.querySelector('svg'); const speed = document.querySelector('.speed'); if ('geolocation' in navigator){ // watchPosition() 觀看者目前位置 navigator.geolocation.watchPosition((position) => { // 緯度, 經度 position.coords.latitude, position.coords.longitude; console.log(position); // 移動角度 deg const deg = position.coords.heading; compass.style.transform = `rotate(${deg}deg)`; // 移動速度 m/s 轉換成習慣的用法 km/h const speeds = (position.coords.speed * 3.6).toFixed(2); speed.innerHTML = `${speeds}`; }); } else { // 取消觀察者位置 navigator.geolocation.clearWatch(); } ``` <font style="color:#f00">watchPosition()</font>是觀察者目前的位置 使用navigator.geolocation.watchPosition(),會回傳經度、緯度、速度,移動的角度等資訊,將所需的資料寫到html 裡 下圖為回傳的物件內容 ![image](https://hackmd.io/_uploads/HJHJQsV2R.png) --- # 邏輯炸彈(Logic Booms) 程式設計師在系統中寫入一段程式,當他的人事資料從公司的人事系統中被刪除時,造成公司的檔案或程式毀損。 --- # MySQL 的觸發器(trigger) 設定 1. 點選畫面左側的資料庫後,在右上方選擇觸發器 ![trigger](https://hackmd.io/_uploads/SJgxlq42C.png) 2. 新增一個觸發器 ![create new trigger](https://hackmd.io/_uploads/rJALg9N3R.png) 3. 選擇好資料表,要再確定觸發器是要在事件發生前或後,以及什麼事件時(新增、修改、刪除)觸發 ![image](https://hackmd.io/_uploads/ryYC0K43C.png) 4. 定義者設定 就是設定這個觸發器是由哪位管理者設置的,有些會設定為最高權限的管理者,如下圖所示 :::info root@localhost ::: --- # Base64 的應用 ### 一、動機 在此次專案開發中,有一項聯徵報送的功能,玉山向聯合徵信中心調查債務人信用資料,聯徵中心會回傳HTML格式資料給玉山,後端則將回傳的資料存放在資料庫裡。後端將HTML格式用Base64編碼傳送到前端,然後前端將接到的Base64解碼為文字,並渲染在前端畫面。 突然有一天這個功能失效了,究竟是甚麼原因? --- ### 二、Base64 簡介 Base64是一種二進位的編碼,因為編碼的索引表總共有64個,所以被稱為Base64。通常用於資料的傳輸與儲存。 [維基百科 Base64](https://zh.wikipedia.org/wiki/Base64) 前端應用Base64 的時機在於傳遞或儲存檔案的URL 或是圖片。Base64解碼URL 後,通常以字元符號的方式呈現,而圖片因為其特性,解碼後的字元則不同於URL 的形式。 文字的編碼 :::info Hello World => SGVsbG8gV29ybGQ= ::: 圖片的編碼 :::info ![image](https://hackmd.io/_uploads/r14F_18_A.png) data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/7QB4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAD8cAVoAAxslRxwCAAACAAIcAj8ABjAwMjAyORwCPgAIMjAyMzAzMTkcAjcACDIwMjMwMzE5HAI8AAYwMDIwMjkAOEJJTQQlAAAAAAAQHD8NJrYj4BjmLvQVHPbdp//bAEMAAgEBAgEBAgICAgICAgIDBQMDAwMDBgQEAwUHBgcHB 中間略 wct/8AHaKKA2P/2Q== ::: 線上編碼轉換: * https://products.aspose.app/imaging/zh-hant/conversion/base64-to-image * 轉換純文字 https://zh-tw.rakko.tools/tools/24/ * 轉換圖片 https://www.toolnb.com/tools-lang-zh-TW/ImageToBase64.html --- ### 三、DEBUG 過程 #### 1. 找出問題 先把資料庫的HTML格式貼到HTML檔,看格式是否有誤? 結果 :heavy_check_mark: 檢查前端的編譯程式,是否解碼出錯了? 結果 :heavy_check_mark: :::success :information_source: 額外說明 JS有2個函別編碼與解碼Base64的方法,參見[W3School](https://www.w3schools.com/jsref/met_win_atob.asp) * 編碼 window.atob() * 解碼 window.btoa() atob 的意思就是ASCII to Binary,反之就是Binary to ASCII ::: 把後端給的Base64編碼放到可解碼的網站,看是否可以解出文字?結果 :x: #### <font style="color:#f00">問題就出在後端回傳的不是文字,而是圖片。</font> 所以原先放入字元符號的做法就不能用了 ![image](https://hackmd.io/_uploads/rk8kqyLd0.png) #### 2. 解決方法 圖片的作法,以VUE 為例: * 路徑的用法: 在vue 使用<img/>標籤,通常只要綁定,再加上路徑,就可以了 ![image](https://hackmd.io/_uploads/SkX3jyIO0.png) * 圖片格式的用法: 但是如果src 不是路徑,是圖片編碼的base64,就要用下列的方法了 ![image](https://hackmd.io/_uploads/HJDENcD8C.png) ---- ### 免費網頁素材: * 免費素材 [かわいいフリー素材集 いらすとや](https://www.irasutoya.com/search/label/%E4%BC%9A%E7%A4%BE?updated-max=2020-11-21T13:00:00%2B09:00&max-results=20&start=20&by-date=false) * 調色盤 [Adobe](https://color.adobe.com/zh/) [colourcontrast.cc](https://colourcontrast.cc/?background=3b36a2&foreground=352222) * 圖表 [Chart.js](https://www.chartjs.org/docs/latest/) * 特效 [simpleparallax](https://simpleparallax.com/) [transition style](https://www.transition.style/) --- # MVC 架構 ---- 以Laravel為例 ### 一、MVC 架構概述: #### 以往的程式開發是將使用者操作的畫面,業務邏輯和連接資料庫的設定,放在同一個檔案。 #### 為了能有效管理上述三個類別,於是產生了MVC、MVP、MVVM等不同的框架。 #### 而MVC是其中較為普遍的框架。 * Model 負責聯繫資料庫的部分,有資料寫入資料庫時,要把關什麼可以寫入什麼不行。 * View 是指使用者操作的畫面。也是前端將後端的資料渲染的所在。 * Controller 是接收前端使用者的CRUD等業務邏輯,並傳給Model,以便Model向資料庫取得資料。接到Model回傳資料庫的資料後,再拋回到View。 ### 二、傳統開發網頁的內容: 如果沒有使用具有套版的工具開發,必須重複在不頁面放同樣的程式 首頁 ![image](https://hackmd.io/_uploads/rJAKTx-IA.png) 其他頁面 ![image](https://hackmd.io/_uploads/r140pxWLC.png) ------ 前後端並存在同一個頁面 ![image](https://hackmd.io/_uploads/rJg91W-UA.png) ------ ### 三、laravel 架構簡易說明 MVC 架構示意圖 ![image](https://hackmd.io/_uploads/H1o4Wfk80.png) laravel專案目錄架構 ![laravel專案目錄架構](https://hackmd.io/_uploads/Bkk0iTarR.png) Model 畫面 ![image](https://hackmd.io/_uploads/ry9jzMyIA.png) Controller 畫面 ![image](https://hackmd.io/_uploads/S1kPffJU0.png) View 畫面 ![image](https://hackmd.io/_uploads/B1DOmzkUC.png) JS 區塊 ![image](https://hackmd.io/_uploads/BJISAj-I0.png)