js 當地時鐘

Html 寫入一個 canvas,js 綁定

1
2
3
4
5
<div class="container">
<div class="content">
<canvas class="canvas" width="500" height="500" ></canvas>
</div>
</div>

js 綁定標籤 canvas,使用其getContext()方法

檢查支援

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
var canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
//檢查支援
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
console.log('radius1',radius)
ctx.translate(radius, radius);
radius = radius * 0.90
console.log('radius2',radius * 0.90)
//每一秒執行一次函式
setInterval(drawClock, 1000);
}


function drawClock() {
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawTime(ctx, radius);
}

//塞入時鐘圖案
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2*Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
ctx.strokeStyle = '#00bcd4'; //外框顏色
ctx.lineWidth = radius*0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius*0.1, 0, 2*Math.PI);
ctx.fillStyle = '#00bcd4';//中心點顏色
ctx.fill();
}

//塞入1~12的字樣
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius*0.15 + "px arial";
ctx.textBaseline="middle";
ctx.textAlign="center";
for(num = 1; num < 13; num++){
ang = num * Math.PI / 6;
ctx.rotate(ang);
ctx.fillStyle = '#3333';//小時的顏色
ctx.translate(0, -radius*0.85);
ctx.rotate(-ang);
ctx.fillText(num.toString(), 0, 0);
ctx.rotate(ang);
ctx.translate(0, radius*0.85);
ctx.rotate(-ang);
}
}


function drawTime(ctx, radius){
var now = new Date();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
//hour
hour=hour%12;
hour=(hour*Math.PI/6)+
(minute*Math.PI/(6*60))+
(second*Math.PI/(360*60));
drawHand(ctx, hour, radius*0.5, radius*0.07);
//minute
minute=(minute*Math.PI/30)+(second*Math.PI/(30*60));
drawHand(ctx, minute, radius*0.8, radius*0.07);
// second
second=(second*Math.PI/30);
drawHand(ctx, second, radius*0.9, radius*0.02);
}


function drawHand(ctx, pos, length, width) {
ctx.beginPath();
ctx.strokeStyle = '#3f51b5';//指針顏色
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.moveTo(0,0);
ctx.rotate(pos);
ctx.lineTo(0, -length);
ctx.stroke();
ctx.rotate(-pos);
}

Canvas Clock Face (畫布時鐘圖案)
Canvas Clock Numbers(畫布時鐘數字)
畫布啟動時鐘

js 點擊鍵盤發出聲音

綁定class叫做 key 的值

1
2
const keys = document.querySelectorAll('.key')
console.log(keys.length) //9

綁定keydown 呼叫函式playsound

document.addEventListener(’keydown’, function(e){ … }, false);
點擊呼叫該函數addEventListener 三考

1
window.addEventListener('keydown', playsound)

keys迴圈

1
2
3
keys.forEach((key) => {
key.addEventListener('click', removeTransition)
})

data attributes 自訂屬性

透過幫html加上data-*自訂屬性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//函數playsound
function playsound(e) {
console.log('e',e)
console.log('e.code',e.code) //"e.code" "KeyF"
//透過幫html加上data-*自訂屬性
const key = document.querySelector(`.key[data-key="${e.code}"]`)
console.log('`.key[data-key="${e.code}"]`',`.key[data-key="${e.code}"]`)//".key[data-key='KeyD']"
const audio = document.querySelector(`audio[data-key="${e.code}"]`)
key.classList.add('play')
//
audio.currentTime = 0
audio.play()
}

function removeTransition(e) {
console.log('e.propertyName',e.propertyName)
if (e.propertyName !== 'transform') return
e.target.classList.remove('play')
}

點擊呼叫該函數addEventListener
addEventListener(transitioned,執行的函式)
audio.currentTime
javascript使用add()、remove()、replace()和toggle()方法來改變元素的外觀
classList add()方法來改變元素的外觀
classList remove()方法來改變元素的外觀
classList toggle()方法來改變元素的外觀
Css顯示與transform scale()

Vite vue-i18n TypeScript elementPlus 的坑

安裝版本的選擇

1
npm install vue-i18n@next --save

實踐上必須版本9以上

獨自拆成一個檔案 i18n.ts

引入要用的語系zh,en 為json的檔案
引入要用的語系zh,en 為json的檔案
*要把 legacy 設為 false,才可以使用 Composition API
在 src/ 目錄底下,新增 plugins/i18n.ts,設定語系並在main.ts引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createI18n } from 'vue-i18n'
import zh from '../lang/zh-TW.json'
import en from '../lang/en-US.json'

type MessageSchema = typeof zh

const i18n = createI18n<[MessageSchema], 'zh-TW' | 'en-US'>({
legacy: false, // 要把 legacy 設為 false,才可以使用 Composition API
locale: 'zh-TW', //
fallbackLocale: 'zh-TW',
globalInjection: true,//全域注入
messages: {
'zh-TW': zh,
'en-US': en
}
})
//輸出
export default i18n

新增zh-TW.json,en-US.json

在 src/ lang資料夾,新增 zh-TW.json 以及 en-US.json 兩個檔案

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
// zh-TW.json
{
"nav_menu":{
"home":"Home",
"login":"Login",
"register":"Register"
},
"form_validate":{
"PleaseConfirmPassword":"Please confirm password!",
"PleaseEnterPassword":"Please Enter Password!",
"PasswordCannotBeEmpty":"Password cannot be empty.!",
}
}

// zh-TW.json
{
"nav_menu":{
"home":"首頁",
"login":"登入",
"register":"註冊"
},
"form_validate":{
"PleaseConfirmPassword":"請再輸入一次密碼!",
"PleaseEnterPassword":"請輸入電子信箱!",
"PasswordCannotBeEmpty":"密碼不能為空!"
}
}

在 TypeScript 中使用 ESLint

#在 TypeScript 中使用 ESLint

安裝 ESLint

1
npm install eslint --save-dev

安裝 typescript-eslint-parser

由於 ESLint 預設使用 Espree 進行語法解析,無法識別 TypeScript 的一些語法,故我們需要安裝 typescript-eslint-parser,替代掉預設的解析器,別忘了同時安裝 typescript:

1
npm install typescript typescript-eslint-parser --save-dev

由於 typescript-eslint-parser 對一部分 ESLint 規則支援性不好,故我們需要安裝 eslint-plugin-typescript,彌補一些支援性不好的規則。

1
npm install eslint-plugin-typescript --save-dev

建立配置檔案

ESLint 需要一個配置檔案來決定對哪些規則進行檢查,配置檔案的名稱一般是 .eslintrc.js 或 .eslintrc.json。
當執行 ESLint 的時候檢查一個檔案的時候,它會首先嚐試讀取該檔案的目錄下的配置檔案,然後再一級一級往上查詢,將所找到的配置合併起來,作為當前被檢查檔案的配置。
我們在專案的根目錄下建立一個 .eslintrc.js,內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
parser: 'typescript-eslint-parser',
plugins: [
'typescript'
],
rules: {
// @fixable 必須使用 === 或 !==,禁止使用 == 或 !=,與 null 比較時除外
'eqeqeq': [
'error',
'always',
{
null: 'ignore'
}
],
// 類別和介面的命名必須遵守帕斯卡命名法,比如 PersianCat
'typescript/class-name-casing': 'error'
}
}

以上配置中,我們指定了兩個規則,其中 eqeqeq 是 ESLint 原生的規則(它要求必須使用 === 或 !==,禁止使用 == 或 !=,與 null 比較時除外),typescript/class-name-casing 是 eslint-plugin-typescript 為 ESLint 增加的規則(它要求類別和介面的命名必須遵守帕斯卡命名法,比如 PersianCat)。
規則的取值一般是一個數組(上例中的 eqeqeq),其中第一項是 off、warn 或 error 中的一個,表示關閉、警告和報錯。後面的項都是該規則的其他配置。
如果沒有其他配置的話,則可以將規則的取值簡寫為陣列中的第一項(上例中的 typescript/class-name-casing)。

關閉、警告和報錯的含義如下:
  • 關閉:禁用此規則
  • 警告:程式碼檢查時輸出錯誤資訊,但是不會影響到 exit code
  • 報錯:發現錯誤時,不僅會輸出錯誤資訊,而且 exit code 將被設為 1(一般 exit code 不為 0 則表示執行出現錯誤)

檢查整個專案的 ts 檔案

我們的專案原始檔一般是放在 src 目錄下,所以需要將 package.json 中的 eslint 指令碼改為對一個目錄進行檢查。由於 eslint 預設不會檢查 .ts 字尾的檔案,所以需要加上引數 –ext .ts:

1
2
3
4
5
{
"scripts": {
"eslint": "eslint src --ext .ts"
}
}

在 VSCode 中整合 ESLint 檢查

在編輯器中整合 ESLint 檢查,可以在開發過程中就發現錯誤,極大的增加了開發效率。
要在 VSCode 中整合 ESLint 檢查,我們需要先安裝 ESLint 外掛,點選「擴充套件」按鈕,搜尋 ESLint,然後安裝即可。
VSCode 中的 ESLint 外掛預設是不會檢查 .ts 字尾的,需要在「檔案 => 首選項 => 設定」中,新增以下配置:

1
2
3
4
5
6
7
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript"
]
}

使用 AlloyTeam 的 ESLint 配置

ESLint 原生的規則和 eslint-plugin-typescript 的規則太多了,而且原生的規則有一些在 TypeScript 中支援的不好,需要禁用掉。
這裡我推薦使用 AlloyTeam ESLint 規則中的 TypeScript 版本,它已經為我們提供了一套完善的配置規則。

1
npm install --save-dev eslint typescript typescript-eslint-parser eslint-plugin-typescript eslint-config-alloy

在 TypeScript 中使用 TSLint

TSLint 的使用比較簡單,參考官網的步驟安裝到本地即可:

1
npm install --save-dev tslint

建立配置檔案 tslint.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"rules": {
// 必須使用 === 或 !==,禁止使用 == 或 !=,與 null 比較時除外
"triple-equals": [
true,
"allow-null-check"
]
},
"linterOptions": {
"exclude": [
"**/node_modules/**"
]
}
}

為 package.json 新增 tslint 指令碼

1
2
3
4
5
{
"scripts": {
"tslint": "tslint --project . src/**/*.ts src/**/*.tsx",
}
}

其中 –project . 會要求 tslint 使用當前目錄的 tsconfig.json 配置來獲取型別資訊,很多規則需要型別資訊才能生效。
此時執行 npm run tslint 即可檢查整個專案。

使用 AlloyTeam 的 TSLint 配置
AlloyTeam 為 TSLint 也打造了一套配置 tslint-config-alloy

1
npm install --save-dev tslint-config-alloy

為什麼 ESLint 無法檢查出使用了未定義的變數(no-undef 規則為什麼被關閉了)?
因為 typescript-eslint-parser 無法支援 no-undef 規則。它針對正確的介面定義會報錯。
所以我們一般會關閉 no-undef 規則。
為什麼有些定義了的變數(比如使用 enum 定義的變數)未使用,ESLint 卻沒有報錯?
因為無法支援這種變數定義的檢查。建議在 tsconfig.json 中新增以下配置,使 tsc 編譯過程能夠檢查出定義了未使用的變數:

1
2
3
4
5
6
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
參考
在 TypeScript 中使用 ESLint

Git 錯誤訊息處理

git 出現 fatal: not a git repository (or any of the parent directories)問題

錯誤訊息為:fatal: not a git repository (or any of the parent directories): .git
中譯:找不到 .git 這樣的目錄。

使用 git init 指令

1
git init

remote: Support for password authentication was removed on August 13, 2021.

1
2
3
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/gpg-team/lottery-front.git/'

處理方式:生成令牌

登入git

Settings

選擇 Develop Setting

選擇 Tokens 點選Generate token

全新版本,克隆的时候也在​​​github.com​​前面加个令牌

1
git clone https://(TOKEN)@github.com/網址.git 

全新版本,克隆的时候也在​​​github.com​​前面加个令牌

1
git clone -b <分支名稱> https://<TOKEN>@github.com/網址.git 

Vue3 TelePort

Teleport Alert

父元件建立

  • 建立一個isOpenAlert=ref(true) =>放置到 return
  • openAlert與closeAlert函式 =>放置到 return
  • template 內 Teleport 下放置 to="#modal" `` **@closeFunction=> $emit('closeFunction')
  • 到App.vue放置 `` 對應 to="#modal"

父元件

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>
<a @click="">打開Alert</a>
<!---template 內 放置 -->
<Teleport to="#modal">
<Alert v-if="isOpenAlert" @closeFunction="closeAlert" />
</Teleport>
</div>
</template>


<script>
import { ref } from 'vue'

// 引入子元件
import Alert from './Alert'

export default {
setup () {
const isOpenAlert = ref(false)
const openAlert = () => {
isOpenAlert.value = true
}
const closeAlert = () => {
isOpenAlert.value = false
}
return{
isOpenAlert,
openAlert,
closeAlert,
}
}
}
</script>

App.vue 這裡是跳出框的跳出位置 id與父元件的 #id對應

1
2
3
4
<template>
<div id="modal"></div>
</template>

子元件

  • 使用 @click="$emit('closeFunction')" 關閉必須與父元件的@closeFunction對應
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
<template>
<div
class="deposit-pop__wrap"
:class="tw([
'absolute',
'z-30',
'top-1/2',
'left-1/2',
'-translate-x-1/2',
'-translate-y-1/2',
'w-428px',
'max-w-full',
])"
>
<div
class="deposit-pop__container"
:class="tw([
'bg-depositBg',
'px-10px',
'pb-30px',
'mx-18px',
'md:mx-0',
'rounded-20px',
])"
>
<div
class="close__icon"
:class="tw([
'ml-auto',
'w-60px',
'h-60px',
'bg-white',
])"
:style="{
'-webkit-mask': setStyleMaskImage(require('./images/i_pad_menu_close.svg')),
'mask': setStyleMaskImage(require('./images/i_pad_menu_close.svg')),
}"
@click="$emit('closeFunction')"
>
</div>
<div>
Content
</div>
</div>
</div>
</template>


<script>
import { ref } from 'vue'
export default {
setup () {
const setStyleMaskImage = (url) => {
return `url(${url}) no-repeat center /contain`
}

return{
setStyleMaskImage,
}
}
}
</script>

參考資料
使用 Vue3 Teleport 製作對話框(Modal)
ithome Teleport
Teleport

第一種寫法

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
<template>

<button class="el-button el-button--primary"
@click="open = true">Open Modal
</button>

<!--Teleport 寫法-->
<Teleport to="body">
<div v-if="open" class="modal">
<div class="modal-main">
<div class="modal-header">
<a @click="open = false">
<i class="icon-cross"></i>
</a>
</div>
<div class="modal-body">
<p>Hello from the modal!</p>
</div>
</div>
</div>
</Teleport>
</template>

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

const open = ref<boolean>(false);
const close = () => {
open.value=false
}
</script>

<style lang="scss">
.modal {
position: fixed;
z-index: 999;
top: 0%;
left: 0%;
width: 100vw;
margin-left: 0px;
height: 100vh;
background-color: #1610108a;
.modal-main{
width: 550px;
background-color: white;
margin: 100px auto;
border-radius: 8px;
padding: 25px;
.modal-header{
display: flex;
justify-content: flex-end;
i{
display: flex;
justify-content: flex-end;
}
}
}
}
@media (width<=767px) {
.modal {
.modal-main{
width: 90%;
max-width: 90%;
padding: 4%;
}
}
}
@media (width<=360px) {
.modal {
.modal-main{
width: 85%;
max-width: 85%;
}
}
}
</style>

父子間的傳值
父組件

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<template>
<button class="el-button el-button--primary"
@click="open = true"
>Open Modal</button>

<Teleport to="body">
<Alert :open="open" :lists="lists" :dynamicValidateForm="dynamicValidateForm"
@sendOpen="close" :formRef="formRef"
@sendRemoveDomain="removeDomain" @sendSubmitForm="submitForm"
@sendResetForm="resetForm" @sendAddDomain="addDomain"/>
</Teleport>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import Alert from './Alert.vue';

const open = ref<boolean>(false);

const close = () => {
open.value=false
}
const lists = ref<any[]>([
{ 'subject': 'one', id: 123 },
{ 'subject': 'two', id: 221 }
]);
import type { FormInstance } from 'element-plus'

const formRef = ref<FormInstance>()
const dynamicValidateForm = reactive<{
domains: DomainItem[]
email: string
}>({
domains: [
{
key: 1,
value: '',
},
],
email: '',
})

interface DomainItem {
key: number
value: string
}

const removeDomain = (item: DomainItem) => {
const index = dynamicValidateForm.domains.indexOf(item)
if (index !== -1) {
dynamicValidateForm.domains.splice(index, 1)
}
}

const addDomain = () => {
dynamicValidateForm.domains.push({
key: Date.now(),
value: '',
})
}

const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
return false
}
})
}

const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
</script>

<style lang="scss">
.modal {
position: fixed;
z-index: 999;
top: 0%;
left: 0%;
width: 100vw;
margin-left: 0px;
height: 100vh;
background-color: #1610108a;
.modal-main{
width: 550px;
background-color: white;
margin: 100px auto;
border-radius: 8px;
padding: 25px;
.modal-header{
display: flex;
justify-content: flex-end;
i{
display: flex;
justify-content: flex-end;
}
}
}
}
@media (width<=767px) {
.modal {
.modal-main{
width: 90%;
max-width: 90%;
padding: 4%;
}
}
}
@media (width<=360px) {
.modal {
.modal-main{
width: 85%;
max-width: 85%;
}
}
}
</style>

Alert.vue
子組件

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
<template>
<div v-if="open" class="modal">
<div class="modal-main">
<div class="modal-header">
<a @click="sendOpen"><i class="icon-cross"></i></a>
</div>
<div class="modal-body">
<el-form
ref="formRef"
:model="dynamicValidateForm"
label-width="120px"
class="demo-dynamic"
>
<el-form-item
prop="email"
label="Email"
:rules="[
{
required: true,
message: 'Please input email address',
trigger: 'blur',
},
{
type: 'email',
message: 'Please input correct email address',
trigger: ['blur', 'change'],
},
]"
>
<el-input v-model="dynamicValidateForm.email" />
</el-form-item>
<el-form-item
v-for="(domain, index) in dynamicValidateForm.domains"
:key="domain.key"
:label="'Domain' + index"
:prop="'domains.' + index + '.value'"
:rules="{
required: true,
message: 'domain can not be null',
trigger: 'blur',
}"
>
<el-input v-model="domain.value" />
<el-button class="mt-2" @click.prevent="sendRemoveDomain(domain)"
>Delete</el-button
>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="sendSubmitForm(formRef)">Submit</el-button>
<el-button @click="sendAddDomain">New domain</el-button>
<el-button @click="sendResetForm(formRef)">Reset</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { defineProps } from 'vue'

const props = defineProps({
open: { type: Boolean },
lists: { type: Object },
dynamicValidateForm: { type: Object },
formRef: { type: Object },
})

const emits = defineEmits(['sendOpen', 'sendRemoveDomain', 'sendSubmitForm','sendResetForm', 'sendAddDomain']);
const sendOpen = () => {
emits('sendOpen', props.open=false)
}
const sendRemoveDomain = (domain:any) => {
emits('sendRemoveDomain', domain)
}
const sendSubmitForm = (formRef: any) => {
emits('sendSubmitForm', formRef)
}
const sendResetForm = (formRef: any) => {
emits('sendResetForm', formRef)
}
const sendAddDomain = () => {
emits('sendAddDomain')
}
</script>

github Teleport說明

json-server 模擬Api

json-server

  • json-server安裝
  • 查看json-server版本號
  • 建立db.json
  • 啟動 json-server db.json
  • 更改 Port 號:json-server --watch db.json --port 3004

根目錄 安裝 json-server

1
npm install -g json-server

查看 json-server版本號

1
json-server -v

啟動 json-server(資料檔案名 db.json)

新增一個json檔案類型

1
json-server (資料檔案名 db.json)
1
2
3
4
5
6
Last login: Wed Jul 12 20:00:18 on console
larahuang@larahuangde-MacBook-Pro db % json-server -v
0.17.3
larahuang@larahuangde-MacBook-Pro db % json-server db.json


啟動以後的結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

\{^_^}/ hi!

Loading db.json
Done

Resources
http://localhost:3000/Data
http://localhost:3000/Status

Home
http://localhost:3000

Type s + enter at any time to create a snapshot of the database

更改 Port 號

1
json-server --watch db.json --port 3004

Vite ts error

moduleResolution

Cannot find module 'vue'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?Vetur(2792)

處理方式: tsconfig.json 的moduleResolution配置原來為bundler改為node,"moduleResolution": "node",