Css Animation 動態

animation: name duration delay infinite linear;

口訣:animation: 名字 持續時間 延遲 無限 功能

name => @keyframes name{}

1
2
3
4
5
6
7
animation: mymove 5s 2s infinite linear;

//名字
@keyframes mymove {
from {top: 0px;}
to {top: 200px;}
}

animation-duration => 持續時間

1
2
3
4
5
6
7
//animation-duration 持續時間:預設:0
normal,reverse反向,alternate交替 ,alternate-reverse交替反向
animation-duration: 3s;
animation-duration: normal;
animation-duration: reverse; //反向
animation-duration: alternate; //交替
animation-duration: alternate-reverse ; //交替反向

animation-delay 動畫延遲

1
2
3
4
5
6
7
8
9
10
預設:0s
animation-delay:2s;

# animation-iteration-count //動畫迭代計數
預設:1
animation-iteration-cout:3 // number
animation-iteration-cout:infinite //無限
animation-iteration-coutinitial ,//初始
animation-iteration-cout :inherit //繼承

animation-timing-function 動畫定時功能

1
2
3
4
5
6
7
8
9
10
11
12
預設:ease
linear: 開始與結束相同動畫
ease:輕鬆,默認值 動畫有一個緩慢的開始,然後是快速的,然後是緩慢的結束
ease-in: 動畫啟動緩慢
ease-out:動畫結束緩慢
ease-in-out:動畫既有緩慢的開始,也有緩慢的結束
step-start:相當於步驟(1,開始)
step-end:Equivalent to steps(1, end) 相當於步驟(1,結束)
steps(int,start|end):
指定一個步進函數,有兩個參數。 第一個參數指定函數中的間隔數。 它必須是一個正整數(大於 0)。 第二個參數是可選的,是值“start”或“end”,並指定在區間內發生值變化的點。 如果省略第二個參數,則賦予其值“end”
cubic-bezier(n,n,n,n):
在貝塞爾函數中定義您自己的值 可能的值是從 0 到 1 的數值

animation-play-state 動畫播放狀態

1
2
3
4
5
6
7
8
9
10
11
12
paused 暫停 /running 跑步

注意:一定要指定animation-duration屬性,否則duration為0,永遠不會播放。

# animation-fill-mode 動畫填充模式
預設:nane
none: 默認值。 動畫在執行之前或之後不會對元素應用任何樣式
forwards:轉發 元素將保留最後一個關鍵幀設置的樣式值(取決於動畫方向和動畫迭代計數)
backwards:向後獲取由第一個關鍵幀設置的樣式值(取決於動畫方向,並在動畫延遲期間保留此值
both:兩個都 動畫將遵循向前和向後的規則,在兩個方向上擴展動畫屬性
initial:最初的 將此屬性設置為其默認值。 閱讀關於初始
inherit:繼承 從其父元素繼承此屬性。 閱讀關於繼承

moment.js

1
2
3
4
5
6
npm install moment --save   # npm
yarn add moment # Yarn
Install-Package Moment.js # NuGet
spm install moment --save # spm
meteor add momentjs:moment # meteor
bower install moment --save # bower (deprecated)

Unix 轉換

format格式化
如何使用引入:import moment from ‘moment’,與時間轉換:moment(‘timestamp值’).format(‘YYYY-MM-DDTHH:mm:ss.SSS’);

1
2
3
4
5
6
7
8
9
10
11
12
//引入
import moment from 'moment';
//目前時間搓
const TimeStamp = moment().valueOf()
console.log('TimeStamp', TimeStamp);
//目前時間搓
const nowTimeStamp = Date.now();
console.log('nowTimeStamp', nowTimeStamp);
const nowTime = moment(nowTimeStamp).format('YYYY-MM-DDTHH:mm:ss.SSS');
console.log('現在時間', nowTime);
console.log('現在時間格式化',moment().format());

moment(‘要驗證的日期’).isBetween(‘起始日’, ‘截止日’);

使用情境:驗證是否有在一段時間以內或是在截止日以前

1
2
3
moment('2023-03-29').isBetween('2023-03-01', '2023-03-29'); // true

moment('2023-04-29').isBetween('2023-03-01', '2023-03-29'); // false

相對時間:

距離現在幾個月:moment(“20221031”, “YYYYMMDD”).fromNow()
moment(“20221031”, “YYYYMMDD”).fromNow();
解釋:距離現在幾個月

距離開始小時:moment().startOf(‘day’).fromNow()
moment().startOf(‘day’).fromNow();
解釋:距離今天開始時間

距離結束小時:moment().endOf(‘day’).fromNow()
moment().startOf(‘day’).fromNow();
解釋:距離今天結束時間

距離小時結束分鐘:moment().startOf(‘hour’).fromNow();
解釋:距離小時結束分鐘

1
2
3
4
5
6
7
console.log('現在時間:', moment().format());//2023-01-07T13:44:41+08:00
console.log('20221031距離現在:', moment("20221031", "YYYYMMDD").fromNow());//2 months ago
console.log('20230620距離現在:', moment("20230620", "YYYYMMDD").fromNow());//in 5 months
console.log('距離今天開始小時startOf('day'):', moment().startOf('day').fromNow());//14 hours ago
console.log('距離今天結束時間endOf('day'):', moment().endOf('day').fromNow());//in 10 hours
console.log('距離小時結束分鐘startOf('hour'):',moment().startOf('hour').fromNow())//37 minutes ago

運算

增加天數變換日期:moment().add(天數, ‘days’).calendar();

現在的日期加上七天

1
moment().add(7, 'days').calendar();//01/14/2023

減去天數變換日期:moment().subtract (天數, ‘days’).calendar();

現在的日期減去七天

1
moment().subtract(7, 'days').calendar(); //12/31/2022

###距離今天還有幾天: from & to:不會有負值 例如:-1,-2

1
2
3
4
5
const a = moment([2023, 0, 1]);
const b = moment([2023, 0, 12]);
const c = moment([2023, 0, 20]);
console.log('距離', a.from(b),'天');
console.log('距離', a.to(c), '天');

查詢

isBefore 是用來查詢是否在早於後面的時間

1
2
moment('2023-10-20').isBefore('2023-10-21') // true
moment('2023-10-20').isBefore('2023-12-31', 'year') // false

isSame 是用來查詢是否和後面的時間相等

1
2
moment('2022-10-20').isSame('2023-12-31', 'year') // false
moment('2023-10-20').isSame('2023-01-01', 'year') // true

isAfter 是用來查詢是否和晚於後面的時間

1
2
moment('2022-10-20').isAfter('2023-10-19') // true
moment('2010-10-20').isAfter('2010-01-01', 'year') // false

isSame 是用來查詢是否在設定的時間範圍內

1
2
moment('2010-10-20').isBetween('2010-10-19', '2010-10-25') // true
moment('2010-10-20').isBetween('2010-01-01', '2012-01-01', 'year') // false

更換語言

1
2
3
moment.locale('zh-tw', {
months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
}

Promise

「我承諾幫你做某件事情,能不能成功還不一定,但是我做完之後會把結果告訴你」的意思

Promise 是一個代表非同步運作的最終狀態的物件 (成功或失敗)

callback的特性:

控制的反轉=>可靠性不足=>無法控制第三方的函式會不會在正確的時間點呼叫以及呼叫正確的次數的callback函式。

Promise的特性:

可靠:Promise 回來的東西是可信任的,一旦回傳就無法被更改。
控制回歸:將執行 callback 的控制權回歸自己的程式。

pending承諾 一直沒有回應

resolve()成功兌現 或 fulfilled 被兌現 =>.then()

reject()失敗 =>.catch() 或.then 的第二個參數

1
2
3
4
5
6
var defer = new Promise(function(resolve, reject) { 
// 如果成功, resolve()
// 如果失敗, reject()
})
.then(laterOn)
.catch(printError);

Vite TypeScript 請求攔截與響應攔截封裝

新增 axios/index.ts

引入import Axios, { AxiosRequestConfig, AxiosResponse, AxiosPromise } from ‘axios’;

请求攔截 axios.interceptors.request.use((config:any) => {return config;},(error)=>{return Promise.rejest(error);})

響應攔截 axios.interceptors.response.use((response) => {return response;},(error)=>{return Promise.rejest(error);})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import Axios, { AxiosRequestConfig, AxiosResponse, AxiosPromise } from 'axios';

const server = Axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
// timeout: 15000,
// withCredentials: config.apiConfig.withCredentials
})

// 请求攔截
axios.interceptors.request.use((config:any) => {

//如果localStorage.token有數據,參閱知識點
if ( localStorage.token ) {
//解釋token的使用方式
config.headers.Authorization=localStorage.token;

}
return config;
},
(error) => {
return Promise.reject(error)
})

//響應攔截
axios.interceptors.response.use((response) => {
// 這裡是對響應進行統一處理
return response
},
(error) => {
// 可以對錯誤進行統一處理
if (error.response.status === 401) {
localStorage.removeItem('token');
}
// console.log(error.response.status)
return Promise.reject(error)
})

/**
* 封装post请求,在api模塊直接調用
* -url 請求地址
* -params 參數
* -isQs 是否將參數序列化
*/
export const postApi = (url: string, params?: any, isQs?: boolean): AxiosPromise<any> => {
return server({
url,
method: 'post',
data: isQs ? stringify(params) : params
})
}
/**
* 封装get请求,在api模塊直接調用
* -url 請求地址
* -params 參數
*/

export const getApi = (url: string, query: any): AxiosPromise<any> => {
return server({
url,
method: 'get',
params: query
})
}

/**
* 封装put请求,在api模塊直接調用
* -url 請求地址
* -params 參數
*/

export const editApi = (url: string, params?: any, isQs?: boolean): AxiosPromise<any> => {
return server({
url,
method: 'put',
data: isQs ? stringify(params) : params
})
}

/**
* 封装delete请求,在api模塊直接調用
* -url 請求地址
* -id 參數
*/

export const removeApi = (url: string, id?: any, isQs?: boolean): AxiosPromise<any> => {
return server({
url,
method: 'delete',
data: isQs ? stringify(id) : id
})
}

知識點

HTTP Token 使用方式: Basic Token v.s Bearer Token

使用Token的方式

1.使用Authorization HEADER:
Ex: Authorization: Bearer QQxhZGRpbjpvcGVuIHNlc2FtQQ==
2.使用Form Body:
不建議將Token加入Body,使用時 ,並使用GET 之外的 HTTP verbs (POST、DELETE、PUT)
HEADER: Content-Type: “application/x-www-form-urlencoded”
3.直接使用在URI :
一般來說不會使用這種方法,除非是Authorization header不支援,或者protect resource 支援該方法。
Ex: GET /resource?access_token=mF_9.B5f-4.1JqM HTTP/1.1

Token Response HTTP Response Status code 為200時代表成功

Basic Token: 當授權為「不」通過時,回傳401 code並告知哪個 Protect Scope 錯誤

Bearer Token: 當授權為「不」通過時,回傳400、401、403 code,並根據狀況回傳對應的錯誤。

創建api模塊管理所有的api接口

1
2
3
4
5
import { postApi } from '@/axios/index'

// 登入
export const Login = (params: any) => postApi('/auth/login', params, true)
export const Register = (params: any) => postApi('/auth/register', params, true)
github ts axios 封裝

Vuex

安裝Vuex

1
2
3
npm install vuex@next --save
yarn add vuex@next --save

main.js引入vuex

1
2
3
4
5
6
7
8
9
import Vuex from 'vuex'
Vue.use(Vuex)

export const app = new Vue({
el: '#app',
store,
components: { App },
template: '<App/>'
})

新增store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
state: {
//如同data
msg: 'Vuex',
} ,
actions: {
//呼叫mutations處理分同步,第一個參數:context,第二個參數:payload
}
mutations: {
如同methods,只有mutations才能操作state資料狀態
第一個參數:state,第二個參數:payload
},
getters: {
//狀態延伸
}
})

單一狀態樹,頁面調用msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<h1>{{msg}}</h1>
</div>
</template>

<script>
import { app } from '../main'
import Vue from 'vue'
import { mapState } from 'vuex'
export default {

computed: {
...mapState({
msg: state => state.msg
})
}
}
</script>

modules模組概念
新增store/modules/values.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import axios from "axios"

export default {
// actions, mutations, getters 是屬於區域變數
namespaced: true,
state: {

},
actions: {
getDayObjects(context) {
const api = `${process.env.APIPATH}/api/Teacher/LoadTeacherOpenClass`;
axios.post(api, raw, {
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${process.env.TOKEN}` }
})
.then((res) => {
if (res.data.IsSuccess) {
context.commit('DAYOBJECTS', res.data.DayObjects);
}
})
.catch((error) => {
console.log(error);
})
}
},
mutations: {
DAYOBJECTS(state, payload) {
state.DayObjects = payload;
},
}
getters: {
DayObjects: state => state.DayObjects,
}
}

新增store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import dayobjectsModules from './modules/dayobjects'
import modalModules from './modules/modal'
export default new Vuex.Store({
state: {
//如同data
msg: 'Vuex',
} ,
actions: {
//呼叫mutations處理分同步,第一個參數:context,第二個參數:payload
}
mutations: {
如同methods,只有mutations才能操作state資料狀態
第一個參數:state,第二個參數:payload
},
getters: {
//狀態延伸
},
modules: {
dayobjectsModules,
},
})

單一狀態樹,頁面調用msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div>
<h1>{{msg}}</h1>
</div>
</template>

<script>
import { app } from '../main'
import Vue from 'vue'
import { mapState,mapActions,mapGetters } from 'vuex'
export default {
created(){
this.getDayObjects();
},
methods: {
...mapActions('dayobjectsModules', ['getDayObjects']),
},
computed: {
...mapState({
msg: state => state.msg
}),
...mapGetters('dayobjectsModules', ['DayObjects']),
}
}
</script>

Emailjs 寄信

申請Emailjs帳號

Emailjs 官網

Email Service獲取到一組 Service Id =>import.meta.env.VITE_SENDMAIL_SERVICE

選擇Gmail

Email Templates 這邊建立樣板,獲取到一組 Templates Id =>import.meta.env.VITE_SENDMAIL_TEMPLATE

Account =>API keys=>Public Key,獲取到一組 Public Key => import.meta.env.VITE_SENDMAIL_USERID

Vite使用方式參考官網
Vite使用方式參考官網

安裝emailjs-com

emailjs 安裝

1
npm install emailjs-com --save

綁定表單的name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div>
<el-form @submit.prevent="sendMail">
<el-form-item label="姓名" prop="name">
<el-input name="form_name" v-model="Forms.name" placeholder="請輸入你的姓名" type="test" />
</el-form-item>
<el-form-item label="email" prop="email">
<el-input name="form_email" v-model="Forms.email" placeholder="請輸入你的email" type="email" />
</el-form-item>
<el-form-item label="行動電話" prop="phone">
<el-input name="form_phone" v-model="Forms.phone" placeholder="請輸入你的行動電話" type="text" />
</el-form-item>
</el-form>
</div>
</template>

<script setup lang="ts">
import emailjs from 'emailjs-com';
const form_name = ref<string | null>('');
const form_email = ref<string | null>('');
const form_phone = ref<string | null>('');


//送出的表單
const sendMail = (e: { target: string | HTMLFormElement; }) => {
try {
const templateParams = {

form_name: form_name,
form_email: form_email,
form_phone: form_phone,

}
// console.log(templateParams)

emailjs.sendForm(import.meta.env.VITE_SENDMAIL_SERVICE, import.meta.env.VITE_SENDMAIL_TEMPLATE, e.target,
import.meta.env.VITE_SENDMAIL_USERID, templateParams)

} catch (error) {
console.log({ error })
}
}


</script>

Pinia Composition API(defineStore,storeToRefs)

安裝pinia

1
npm install --save pinia

在main.ts引入pinia

1
2
3
4
5
6
7
8

import { createPinia } from 'pinia'

const pinia = createPinia()

const app = createApp(App)
app.use(pinia)
app.mount('#app')

Vite Ssr 專案 main.ts引入方法

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createSSRApp } from 'vue'
+ import { createPinia } from 'pinia'
import App from './App.vue'
+ const pinia = createPinia()

// SSR requires a fresh app instance per request, therefore we export a function
// that creates a fresh app instance. If using Vuex, we'd also be creating a
// fresh store here.
export function createApp() {
const app = createSSRApp(App);
+ app.use(pinia)
return { app }
}

官方寫法:

stores/counter.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//引入defineStore
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
//state如同data
state: () => {
return {
count: 0,
cardLists:[],
forms: {
email: '',
password:'',
}
}
},
//如同computed
getters: {
doubleCount: (state) => state.count * 2,
},
//如同method=>

actions: {
addCount() {
this.count++
},
async fetchApi() {
try {
const api = 'https://api.coindesk.com/v1/bpi/currentprice.json';
/* let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
// Authentication: 'secret'
// "Authorization": `Bearer ${token}`,
}*/
const res = await fetch(api, { headers: headers, method: "GET" });
// this方法
this.cardLists = await res.json();
console.log('lists.value', this.cardLists)

} catch (error) {
console.log(error)
}

},
async loginIn () {
try {

const api = `${import.meta.env.VITE_API_DEFAULT_URL}/auth/login`

let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
// "Authorization": `Bearer ${token}`,
}
//以下是API文件中提及必寫的主體参數
let body = {
email: this.forms.email,
password:this.forms.password,
}
fetch(api, {
method: "POST",
headers: headers,
body: JSON.stringify(body)//****重點 */
})
.then(res => res.json())
.then(json => {
console.log('login',json)
});
}
catch (error) {
console.log(error)
}
}
},
})

在頁面使用

  • 引入counter的useCounterStore
  • 宣告useCounterStore()

引入

宣告

解構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<button @click="addCount" class="add_btn">add count</button>
<div>{{counter.count}}</div>
{{ counter.cardLists }}
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
//引入Store
import { useCounterStore } from '../stores/counter'
//宣告
const counter = useCounterStore();
const { addCount,fetchApi } =counter;
onMounted(() => {
fetchApi();
})
</script>

pinia 選項寫法 gitHub
pinia 選項寫法 gitHub

改為 Composition API

*重點:
1.引入vue Composition API
2.改為Function 寫法與Composition API
3.必須 return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { defineStore } from 'pinia';
//引入vue Composition API
import { ref,computed } from 'vue';
//改為Function 寫法與Composition API
export const useCounterStore = defineStore('counter', ()=>
{
const count = ref(0);

//computed
const doubleCount = computed(() =>
{
return count.value * 2
})

const addCount = () =>
{
count.value ++
}
const cardLists=ref([])
const fetchApi = async() =>
{
try {
const res = await axios.get('https://api.coindesk.com/v1/bpi/currentprice.json');
cardLists.value = res.data;
} catch (error) {
console.log(error)
}

}
//必須輸出 return
return {
count,addCount,doubleCount,
cardLists,fetchApi
}
})

在頁面使用

  • 引入counter的useCounterStore
  • 宣告useCounterStore()

html 加入 store.命名

使用 store action時 store.addCount()

*重點:
1.store.名稱
2.store.function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<p>{{store.count}}</p>
<pre>
{{ store.cardLists }}
</pre>
<button @click="clickAdd">Add</button>
<button @click="store.fetchApi">fetchApi</button>
</div>
</template>

<script setup lang="ts">
//引入Store
import { useCounterStore } from '@/stores/counter.js';

//宣告store = useCounterStore()
const store = useCounterStore();

const clickAdd = () => {
//使用 store action時 store.addCount()
store.addCount()
}
</script>

counter pinia寫法引入Store 方式寫法

解構方式引入

1.引入 import {storeToRefs } from ‘pinia’
2.//解構store,數據取出counter, cardLists
const { counter, cardLists } = storeToRefs(store);
3.function 解構 const { fetchApi } = store;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div>
<button @click="clickAdd">Add</button>
<h1>{{ counter }}</h1>

<div> {{ cardLists }}</div>
<pre>
{{ cardLists }}
</pre>
</div>
</template>

<script setup lang="ts">
import { ref,onMounted } from 'vue';

//引入 import {storeToRefs } from 'pinia'
import {storeToRefs } from 'pinia';
// @ts-ignore
import { useCounterStore } from '../stores/counter.js';
//宣告store = useCounterStore()
const store = useCounterStore();
//解構store,數據取出counter, cardLists
const { counter, cardLists } = storeToRefs(store);
const { fetchApi, addCount } = store;

const clickAdd = () => {
addCount()
}
onMounted(() => {
fetchApi();
});
</script>


Pinia Composition API

範例

stores/user.ts

  • acceptHMRUpdatePinia 支援熱模組替換,因此您可以直接在您的應用程式中編輯您的商店並與它們進行交互,而無需重新加載頁面,從而允許您保留現有狀態、添加甚至刪除狀態、操作和 getter。
  • 使用$patch修改state 數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// @ts-check 引入pinia的defineStore與acceptHMRUpdate
import { defineStore, acceptHMRUpdate } from 'pinia'

/**模擬登入*/
function apiLogin(a: string, p: string) {
if (a === 'ed' && p === 'ed') return Promise.resolve({ isAdmin: true })
if (p === 'ed') return Promise.resolve({ isAdmin: false })
return Promise.reject(new Error('invalid credentials'))
}

export const useUserStore = defineStore({
id: 'user',
state: () => ({
name: 'Eduardo',
isAdmin: true,
}),

actions: {
logout() {
// 登出以後 =>使用$patch修改state 數據
this.$patch({
name: '',
isAdmin: false,
})

// 我們可以做其他事情,例如重定向用戶
},

/**
* 嘗試登入用戶
*/
async login(user: string, password: string) {
const userData = await apiLogin(user, password)
// 登入以後 =>使用$patch修改state 數據
this.$patch({
name: user,
...userData,

})
console.log(userData,)
},
},
})

if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}


stores/cart.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// @ts-check 引入pinia的defineStore與acceptHMRUpdate
import { defineStore, acceptHMRUpdate } from 'pinia'
//引入useUserStore
import { useUserStore } from './user'

export const useCartStore = defineStore({
id: 'cart',
state: () => ({
rawItems: [] as string[],
}),
getters: {
items: (state): Array<{ name: string; amount: number }> =>
//https://medium.com/@davelin18yufan/js%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3reduce%E7%9A%84%E7%94%A8%E6%B3%95-95738c3f9114
state.rawItems.reduce((items, item) => {
//尋找
const existingItem = items.find((it) => it.name === item)
//console.log('現有項目existingItem',existingItem)
if (!existingItem) {
//console.log('19',existingItem)
items.push({ name: item, amount: 1 })
} else {
//console.log('22',existingItem.amount++)
existingItem.amount++
}
//console.log('items',items)
return items
}, [] as Array<{ name: string; amount: number }>),
},
actions: {
//新增Item
addItem(name: string) {
this.rawItems.push(name)
},
//刪除
removeItem(name: string) {
//lastIndexOf搜尋最後一次出現的
//https://www.w3schools.com/jsref/jsref_lastindexof.asp
const i = this.rawItems.lastIndexOf(name)
//splice() 方法可以藉由刪除既有元素並
if (i > -1) this.rawItems.splice(i, 1)
},

async purchaseItems() {
const user = useUserStore()
if (!user.name) return

console.log('購買', this.items)
const n = this.items.length
this.rawItems = []

return n
},
},
})

if (import.meta.hot) {
console.log('import.meta.hot', import.meta.hot);
import.meta.hot.accept(acceptHMRUpdate(useCartStore, import.meta.hot))
}

頁面引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<script setup lang="ts">
//引入Store
import { useCartStore } from '@/stores/cart'
import { useUserStore } from '@/stores/user'
//宣告
const cart = useCartStore();
const user = useUserStore();
const itemName = ref<string>('');

// 新增Item到購物車
const addItemToCart =()=>{
if (!itemName.value) return
cart.addItem(itemName.value)
itemName.value = ''
}
// 清空購物車
const clearCart =()=>{
if (window.confirm('您確定要清空購物車嗎?')) {
cart.rawItems = []
}
}
// 購買
const buy =async()=>{
const n = await cart.purchaseItems()
console.log(`買 ${n} items`)
cart.rawItems = []
}
</script>

<template>
<div>
<input type="text" v-model="itemName" />
<button class="add_btn" @click="addItemToCart">Add</button>
<!--新增的Item-->
<ul>
<li v-for="item in cart.items" :key="item">
{{item.name}}
<!--刪除的按鈕-->
<button class="delete_btn" @click="cart.removeItem(item.name)"
type="button">X</button>
</li>
</ul>
<!--購買按鈕-->
<button class="buy_btn" :disabled="!user.name">Buy</button>
<!--清除購物車按鈕-->
<button
class="delete_btn"
:disabled="!cart.items.length"
@click="clearCart"
type="button"
data-testid="clear"
>Clear the cart</button>
</div>

</template>

github

改Composition API寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// @ts-check 引入pinia的defineStore與acceptHMRUpdate
import { defineStore, acceptHMRUpdate } from 'pinia'
//引入vue Composition API
import { ref,computed } from 'vue';
//引入useUserStore
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {

const rawItems = ref<any[]>([]);
const items = computed(() =>{
rawItems.value.reduce((items: any[], item: any) => {
const existingItem = items.find((i:any) => i.name === item)
if (!existingItem) {
items.push({ name: item, amount: 1 })
} else {

existingItem.amount++
}
return items
},[] as Array<{ }>)
})

const addItem = (name: string) => {
console.log('新增的input 數據:', name);
rawItems.value.push(name)
}
const removeItem = (name: string) => {
console.log('刪除:',name)
const i = rawItems.value.indexOf(name)
//splice() 方法可以藉由刪除既有元素並
if (i > -1) rawItems.value.splice(i, 1)
}
const purchaseItems = () => {
const user = useUserStore()
if (!user.name) return

console.log('購買', items)
// const n = items.length
rawItems.value = []

//return n
}
return {
rawItems,items,addItem ,removeItem,purchaseItems
}
})

頁面使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
<div>
{{title}}
<input type="text" v-model="itemName" />
<button class="add_btn" @click="addItemToCart">Add</button>
<ul>
<li v-for="item in items" :key="item" v-show="items.length!=0">
{{item.name}}
<button class="delete_btn" @click="removeItem(item.name)"
type="button">X</button>
</li>
</ul>
<button class="buy_btn"
@click="buy"
:disabled="!name">Buy</button>
<button
class="delete_btn"
@click="clearCart"
type="button"
data-testid="clear"
>Clear the cart</button>
</div>
</template>

<route>
{
path: '/aboutus',
name: 'AboutUs',
meta: {
layout: 'frontLayout',
}
}
</route>

<script setup lang="ts">
//引入pinia
import {storeToRefs} from 'pinia';
import {ref,onMounted} from 'vue'

//引入Store
import { useCartStore } from '../stores/cartTest'
import { useUserStore } from '../stores/userTest'
const title =ref<string>('關於我們');
//宣告
const cart = useCartStore();
const user = useUserStore();
//解構store
const { rawItems,items } = storeToRefs(cart)
const { addItem ,removeItem,purchaseItems } = cart;
const { name,isAdmin } = storeToRefs(user);
// const { logout,login } = user;

const itemName = ref<string>('');
const addItemToCart =()=>{
if (!itemName.value)
return
cart.addItem(itemName.value)
itemName.value = ''

}
const clearCart =()=>{
if (window.confirm('您確定要清空購物車嗎?')) {
rawItems.value = []
}
}
const buy =async()=>{
const n = await purchaseItems()
console.log(`買 ${n} items`)
rawItems.value = []
}
onMounted(() => {
// getItems()
});
</script>

組合式Api寫法

Cookie &localStorage & sessionStorage

存入資料setItem(‘資料名稱’:資料)

1
localStorage.setItem(‘Name',Lara)

在 DevTools Application 中查看 localStrong

1
sessionStorage.setItem(‘Name',Lara)

在 DevTools Application 中查看 sessionStrong

取出資料getItem(‘資料名稱’)

1
2
localStorage.getItem(‘Name')
sessionStorage.getItem(‘name')

移除資料removeItem(‘資料名稱’)

1
2
localStorage.removeItem(‘Name')
sessionStorage.removeItem(‘Name')

移除全部 clear()

1
2
localStorage.clear();
sessionStorage.clear();

存入localStorage(sessionStorage):必須將陣列轉字串=>JSON.stringify();localStorage.setItem(‘User’, JSON.stringify(數據))

錯誤示範:存入[object,object]

正確必須轉換成:JSON.stringify()才存入setItem

1
2
3
4
5
6
7
8
let user = [
{ name: 'Lara',
phone: '0911123456',
birthday:'2022-12-19'}
]
var userString = JSON.stringify(user)
localStorage.setItem('User', userString);
sessionStorage.setItem('User', userString);

將localStorage(sessionStorage)取出,將資料轉換回原本的格式 JSON.parse()

1
2
JSON.parse(localStorage.getItem('User'))
JSON.parse(sessionStorage.getItem('User'))

localStorage&sessionStorage 資料儲存的格式:皆需要為字串

localStorage:跨瀏覽器分頁做使用,資料無期效限制,資料將永久被保留

使用場景:帳號登入、購物車、遊戲分數,或任何其他伺服器應該記住的資訊
缺點:
1.約 5MB 的容量空間
2.資料儲存的格式 key 和 value 都只能接受「字串 」

sessionStorage:生命週期較短每次分頁或瀏覽器關閉,資料將被清空。

使用場景:判斷用戶是否登錄

安全考慮

並非所有數據都適合儲存在這些其中。
因為網站中存有XSS 風險,打開控制台,就可以隨意修改值,就能對你的localStorage 肆意妄為。
所以不要將敏感數據儲存在這其中。

Firebase 語法整理

ref路徑

1
2
import firebase from "@/config/firebaseConfig.js";
firebase.database().ref('schedules/');

child

讀取 on,once

1
2
3
4
5
6
const teacherLists = ref<any>({})
const getTeachers = () => {
teachers.once('value', (snapshot: { val: () => any; }) => {
teacherLists.value = snapshot.val();
})
}

讀取 on:程式會在背景待命,一旦資料變動的時候,就會重新印出新的結果,即時更新訊息的聊天室

1
2
3
4
myData.on('value', (snapshot)=>{
var data = snapshot.val()
console.log(data)
})

新增 set:直接取代當前路徑中的所有資料(具有破壞性)

1
2
3
4
5
let query = {
id: Math.floor(Date.now() / 1000),
title: scheduleForm.value.title,
}
firebase.database().ref('schedules/' + query.id).set(query);

新增 push:產生了一組隨機key值

1
2
var todos = db.ref('todos');
todos.push({content:'去看電影'});

update:只修改相關資料,不會直接取代路徑中的所有資料

1
2
3
4
5
6
7
8
9
10
11
var Data = {
name: name,
mail:mail,
phone:phone
}
// 分別將資料寫進資料庫'user/'底下
// 也會同時在'uses/'下開啟一個name的子節點,並且寫入相同的data
var updates = {};
updates['/users/' + newPostKey] = postData;
updates['/users/' + name + '/' + newPostKey] = postData;
return firebase.database().ref().update(updates);

remove

1
2
3
const toDelete = (item: any) => {
teachers.child(key).remove();
}

orderByChild 排序

orderByChild()依據資料某一個特定子節點來做排序
orderByKey 排序:不需要帶參數,資料少的排前面,資料大排後面
orderByValue 排序:不需要帶參數,升冪值進行排序

過濾條件:startAt, endAt, equalTo

startAt:比如說我們想要過濾掉,體重60公斤以下的人,可以使用startAt(60)。

1
2
3
4
5
userRef.orderByChild("weight").startAt(60).once("value",  (snapshot)=> {
snapshot.forEach( (item)=> {
console.log(item.val());
});
});

過濾條件endAt:過濾掉,體重80公斤以上的人,可以使用startAt(50)。

1
2
3
4
5
userRef.orderByChild("weight").endAt(80).once("value", (snapshot)=> {
snapshot.forEach(function (item) {
console.log(item.val());
});
});

過濾條件equalTo:等於:體重等於40公斤的人時,就要使用equalTo(50),想要知道是誰的話,可以加上key (item.key)

1
2
3
4
5
6
userRef.orderByChild("weight")
.equalTo(40).once("value", (snapshot)=> {
snapshot.forEach( (item)=> {
console.log(item.val(), item.key);
});
});

limitToFirst, limitToLast 等與限制筆數

limitToFirst:只需要其中第一筆資料,體重70大於公斤的會員第一筆資料

1
2
3
4
5
userRef.orderByChild("weight").startAt(50).limitToFirst(1).once("value",  (snapshot)=> {
snapshot.forEach( (item)=> {
console.log(item.val());
});
});

limitToLast:從最尾端起算的幾筆