# 前端讀書會
# 浮水印 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>
```
浮水印生成的畫面

滾軸右移時,頁面不會有浮水印

要浮水印滿版,可以增加CSS style
```html
<template>
<el-watermark :font="font" style="position: absolute;">
<div style="height: 100vh" />
</el-watermark>
</template>
```
浮水印滿版畫面

### 二、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 一樣,浮水印只有生成在視窗可見的範圍內,如果拉動滾軸,就無法生成浮水印,但是正常沒跑版的頁面,是可以整個頁面生成浮水印,想要滿版的方式,和第一個做法一樣。另外,不知道為何浮水印尾端有被裁減。

在終端機安裝時有跳出漏洞警示,使用時要考慮安全疑慮,以及功能是否失效。

### 三、自建元件
監控螢幕長、寬,並除以浮水印的長、寬,再使用迴圈印出每個浮水印,達到滿版的效果。
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>
```
實際生成畫面

:::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 相關的檔案

想要即時打包,可在package.json 的build 設定 watch,只要檔案有異動,就會即刻打包
```javascript
"scripts": {
"build": "vite build --watch",
}
```
(二)、啟動專案
要運行打包的檔案,要執行`$npm run preview`

#### 三、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")
)
```
完成畫面

>[!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)的安全機制。這個機制是防止其他來源隨意向伺服器發送請求,造成伺服器因為頻繁地回應而過載,或是伺服器內的機密資訊外流。
瀏覽器報錯畫面


[圖片來源](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標頭

請求標頭

回應標頭

### 前端的作法(參考,可以不設定)
以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 裡
下圖為回傳的物件內容

---
# 邏輯炸彈(Logic Booms)
程式設計師在系統中寫入一段程式,當他的人事資料從公司的人事系統中被刪除時,造成公司的檔案或程式毀損。
---
# MySQL 的觸發器(trigger) 設定
1. 點選畫面左側的資料庫後,在右上方選擇觸發器

2. 新增一個觸發器

3. 選擇好資料表,要再確定觸發器是要在事件發生前或後,以及什麼事件時(新增、修改、刪除)觸發

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

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>
所以原先放入字元符號的做法就不能用了

#### 2. 解決方法
圖片的作法,以VUE 為例:
* 路徑的用法:
在vue 使用<img/>標籤,通常只要綁定,再加上路徑,就可以了

* 圖片格式的用法:
但是如果src 不是路徑,是圖片編碼的base64,就要用下列的方法了

----
### 免費網頁素材:
* 免費素材
[かわいいフリー素材集 いらすとや](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。
### 二、傳統開發網頁的內容:
如果沒有使用具有套版的工具開發,必須重複在不頁面放同樣的程式
首頁

其他頁面

------
前後端並存在同一個頁面

------
### 三、laravel 架構簡易說明
MVC 架構示意圖

laravel專案目錄架構

Model 畫面

Controller 畫面

View 畫面

JS 區塊
