vue2+vue3

相关链接:
ES6 再学习 2021-01-08
Vue 再学习 2021-01-11
.
B站视频
操作笔记 09_vue/视频1:vue2+vue3
2023-08-14 18:33:55(开始)
2023-08-23 18:47:17(结束) 9天,得益于有素材和资料
.
相关文档链接:
Vue.js 官方文档
pinia官方文档
axios 中文文档
pinia 持久化 ===> 官方文档
json-server 官方地址
vant2 官方文档
element-plus 官方文档

常用提取

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
{{}}
v-html
v-show v-if v-else-if v-else

v-on:click="count--"
@click="count--"
@click="count=count+2"
@click="fn"
@click="buy(5)"

v-model原理 :value 和 @input 组合
<input v-model="msg1" type="text"/>
<input :value="msg2" @input="msg2=$event.target.value" type="text">

main.js 加载 App.vue,渲染为 index.html

数组
this.booksList = this.booksList.filter(e => e.id !== id)
this.booksList.splice(i, 1)

this.list.reduce((sum, e) => sum + e.num, 0);

项目创建问题
npm config set registry https://registry.npm.taobao.org
npm install -g yarn
yarn global add @vue/cli
vue create vue-demo3 -m yarn
cd vue-demo3
yarn serve
yarn cache clean
yarn install

npm create vue@latest

npm install -g pnpm # npm => yarn更快 => pnpm更快
cd vue3-big-event-admin-2
pnpm install
pnpm dev

依赖
<style lang="less">
yarn add less less-loader -D # 开发依赖

yarn add vuex@3 # vue2 vuex3 router3 => vue3 vuex4 router4

yarn add vant@latest-v2 # Ctrl + F 搜索查看使用

yarn global add json-server # Ctrl + F 搜索查看使用

yarn add axios # axios 中文文档 ===> https://www.axios-http.cn/docs/instance

yarn add postcss-px-to-viewport@1.1.1 -D # 适配 px => vw

yarn add pinia # pinia官方文档 https://pinia.vuejs.org/zh/

yarn add pinia-plugin-persistedstate # pinia 持久化 ===> 官方文档 https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/

npm yarn pnpm

day01

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
资料:黑马程序员    小程序,找到 vue2+vue3   百度网盘下载资料。

整个文件夹 WebStorm 打开,

vue重点关注数据。

{{}} 差值表达式。 可以 +- 二元运算 三元运算。

控制台 app.msg='hello' 可以修改。

开发者工具 Vue.js devtools。

v-html 绑定等同于 innerHTML属性。
v-show display:none; 有元素。 (频繁隐藏/显示)
v-if 无元素。(条件渲染)

v-if v-else-if v-else 紧挨着使用。






v-on:click="count--"
@click="count++"
v-on:click="count=count+2"

@click="fn"
methods: {
fn() {
// this === app
this.ifShow = !this.ifShow;// 正确(推荐)
// app.ifShow = !app.ifShow;// 正确
// ifShow = !ifShow;// 失败
}
}

@click="buy(5)"
@click="buy(10)"
methods: {
buy(money) {
this.money -= money;
}
}


<img v-bind:src="url" v-bind:title="msg">
<img :src="url" :title="msg">


<p v-for="(e,i) in list"> {{e}} --- {{i}} </p>
<p v-for="e in list"> {{e}} </p>


数组的两种删除
del(id, i) {
// 成功
// this.booksList = this.booksList.filter(e => e.id !== id)

// 成功
this.booksList.splice(i, 1)

// delete this.booksList[0]// 失败
// this.booksList[i]=null// 失败
}


v-for使用要加 :key
<li v-for="(e,i) in booksList" :key="e.id"><!--使用要加 :key-->
<!--<li v-for="(e,i) in booksList">-->
<span>{{ e.name }}</span>
<span>{{ e.author }}</span>
<button @click="del(e.id, i)">删除</button>
</li>


v-model 双向绑定 表单
v-model="username"
v-model="password"

day02

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
    <input @keyup="fn" v-model="username" type="text">
<input @keyup.enter="enter" v-model="username" type="text">
fn(e) {
console.log(e)// 事件对象打印

if (e.key === 'Enter') {
console.log("按键回车触发了~~~ 输入了" + this.username)
}
},
enter() {
console.log('触发了回车事件')
}


姓名:<input v-model.trim="username" type="text"><br>
年纪:<input v-model.number="age" type="text"><br>

<div @click="fatherFn" class="father">
<!-- @click.stop 父元素不会弹出 -->
<div @click.stop="sonFn" class="son">儿子</div>
</div>

<!-- @click.prevent 点击超链接,不会跳转 -->
<a @click.prevent href="http://www.baidu.com">阻止默认行为</a>



添加 class
<div :class="{ pink : true}" class="box">陶攀峰</div>
<div :class="{ pink : true, big: true}" class="box">陶攀峰</div>

<div :class="['pink']" class="box">陶攀峰</div>
<div :class="['pink', 'big']" class="box">陶攀峰</div>




下标切换,激活不同 class
<ul>
<li v-for="(e,i) in list" :key="i" @click="activeIndex=i">
<a :class="{ active : i === activeIndex}" href="#">{{ e.name }}</a>
</li>
</ul>
activeIndex: 0,// 记录高亮下标
list: [
{id: 1, name: '京东秒杀'},
{id: 2, name: '每日特价'},
{id: 3, name: '品类秒杀'}
]


:style 绑定样式
<div class="box" :style="{ height:'100px', backgroundColor:'green' }"></div>
<div class="box" :style="{ height:'100px', 'background-color':'black' }"></div>

进度条
<div class="inner" :style="{ width: pct+'%' }">
<span>{{ pct }}%</span>
</div>
data: {
pct: 80
}



计算属性 当做属性使用,定义为函数。
<p>礼物总数:{{ total }} 个</p>
computed: {
total() {
return this.list.reduce((sum, e) => sum + e.num, 0);
}
}

computed 比 methods 多了缓存特性。
computed 拿结果 {{计算属性}}
methods 处理逻辑,也可以拿结果(无缓存) {{方法名()}}



计算属性的完整写法
data: {
firstName: '攀峰',
lastName: '陶'
},
methods: {
use() {
// 必须要有 setter 方法
this.fullName = '吕小布'
}
},
computed: {
// fullName() {
// return this.lastName + this.firstName
// }

fullName: {
get() {
return this.lastName + this.firstName
},
set(value) {
this.lastName = value.slice(0, 1)
this.firstName = value.slice(1)
}
}
}




watch方法的多种使用
watch: {
// 1. 新值 + 旧值
// words(newValue, oldValue) {
// console.log(`数据变化了 ${oldValue} ---> ${newValue}`)
// }

// 2. 仅新值
// words(newValue) {
// console.log(`数据变化了 ---> ${newValue}`)
// }

// 3. 对象属性
// async 'obj.words'(newValue) {
// // console.log(`数据变化了 ---> ${newValue}`)
//
// const res = await axios({
// url: 'https://applet-base-api-t.itheima.net/api/translate',
// params: {
// words: newValue
// }
// })
//
// // console.log(res)
// // console.log(res.data.data)// 翻译结果
// this.result = res.data.data
// }

// 4. 防抖动
// 'obj.words'(newValue) {
// // console.log(`数据变化了 ---> ${newValue}`)
//
//
// // 跟渲染无关的,无需在 data 中定义属性。
// // this.timer 相当于直接在 app.timer 属性赋值。(属性不存在会新增)
// clearTimeout(this.timer)
// this.timer = setTimeout(async () => {
// const res = await axios({
// url: 'https://applet-base-api-t.itheima.net/api/translate',
// params: {
// words: newValue
// }
// })
//
// // console.log(res)
// // console.log(res.data.data)// 翻译结果
// this.result = res.data.data
// }, 200)// 模拟200ms
// }

// 5. 监视整个对象 + 立即执行
obj: {
deep: true,// 所有属性都监听
immediate: true,// 一进入页面,handler 执行一次
handler(newValue) {
// 这里的 newValue 是变化后的 obj对象
console.log(`数据变化了 ---> ${newValue}`)

clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios({
url: 'https://applet-base-api-t.itheima.net/api/translate',
params: newValue
})

// console.log(res)
// console.log(res.data.data)// 翻译结果
this.result = res.data.data
}, 200)// 模拟200ms
}
}
}

day03-Vue2

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
created mounted updated destroyed



async await
echarts.init setOptions
getList 刷新数据 setOptions list.map



yarn 启动项目(一开始使用 npm,老是失败,后改为 yarn 就好了)
--------
npm config set registry https://registry.npm.taobao.org

npm install -g yarn

yarn global add @vue/cli

vue --version
》》》@vue/cli 5.0.8

### 设置默认 yarn 包管理器,待验证 默认提示让我修改这个文件 vi /c/Users/TaoPanfeng/.vuerc
###### vue config set packageManager yarn
### 查看 yarn 配置
###### yarn config list
### vue 配置命令
###### vue config --help


vue create vue-demo3 -m yarn

cd vue-demo3

yarn serve


yarn cache clean
yarn install
--------


main.js 加载 App.vue,渲染为 index.html


<style lang="less">
yarn add less less-loader -D # 开发依赖





局部注册:Vue 引入 Vue
import MyHeader from "@/components/MyHeader.vue";
import MyMain from "@/components/MyMain.vue";
import MyFooter from "@/components/MyFooter.vue";
export default {
// 大驼峰命名法
components: {MyFooter, MyMain, MyHeader}
}

全局注册:main.js 导入 使用。
import MyButton from "@/components/MyButton.vue";
Vue.component('MyButton', MyButton)





v-for 数字循环
<!--
<BaseBrandItem></BaseBrandItem>
<BaseBrandItem></BaseBrandItem>
<BaseBrandItem></BaseBrandItem>
-->

<!-- i [1,2,3] 遍历3次-->
<BaseBrandItem v-for="i in 3" :key="i"></BaseBrandItem>

day04

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
scoped   默认 data-v-hash值   例如:data-v-2e9b3b8a
当前模版所有节点,全部加属性 data-v-hash值
div[data-v-2e9b3b8a] css 都被加上属性选择器。

vue---父传子,子传父
父传子 属性绑定子对象,子props接收
子传父 this.$emit 发射 k v,父监听 @k=fn 监听 fn(v) 接收 v

父传子 可多个属性。 props:['属性1', '属性2', ...]
类型校验。 props:{属性1:Number, 属性2:String, ...} Number String Boolean Object Array Function

props 属性不能修改,要想修改,需要借助函数 this.$emit(k,v) 子传父来修改。父再 @k=k k(v) this.v=v



08-事件总线-扩展
// 发事件
Bus.$emit('sendMsg', '今天天气不错,适合旅游')
// 监听事件
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})


09-provide和inject-扩展
// 提供
provide() {
return {
// 简单类型 是非响应式的
color: this.color,// String 非响应式
// 复杂类型 是响应式的
userInfo: this.userInfo,// Object 响应式
}
},

// 接收
inject: ['color', 'userInfo'],



v-model原理 :value 和 @input 组合
<input v-model="msg1" type="text"/>
<input :value="msg2" @input="msg2=$event.target.value" type="text">




父传子的 v-model
// 父
<!-- <BaseSelect :value="selectId" @input="selectId = $event"></BaseSelect> -->
<BaseSelect v-model="selectId"></BaseSelect>
// 子
<select :value="value" @change="change">
props: {
value: String
},
methods: {
change(e) {
this.$emit('input', e.target.value)
}
}




.sync 修饰符
// 父
<!--<BaseDialog :ifShow="ifShow" @update:ifShow="ifShow=$event"></BaseDialog>-->
<BaseDialog :ifShow.sync="ifShow"></BaseDialog>

// 子
<div v-show="ifShow">
<button @click="close">x</button>
</div>
props: {
ifShow: Boolean
},
methods: {
close() {
this.$emit('update:ifShow', false)
}
}



ref 和 $refs dom对象
<div ref="myChart" class="base-chart-box">子组件</div>
// querySelector 查询范围 -> 整个页面
// const myChart = echarts.init(document.querySelector('.base-chart-box'))
const myChart = echarts.init(this.$refs.myChart)


ref 和 $refs 组件对象
<BaseForm ref="baseForm"></BaseForm>
this.$refs.baseForm.get()




$nextTick 异步更新
<input type="text" v-model="editValue" ref="inp"/>
edit() {
this.isShowEdit = true

// this.$refs.inp.focus()// 此时 dom 还未更新完成

this.$nextTick(() => {
this.$refs.inp.focus()// dom 已经更新完成
})
}

vue---父传子,子传父

day05-自定义指令-slot

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# 指令定义
<input v-focus type="text">

// 全局指令 main.js
Vue.directive('focus', {
inserted(el){
el.focus()
}
})

// 局部注册
directives: {
focus: {
inserted(el) {
el.focus()
}
}
}




# 带有值的指令
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
export default {
data() {
return {
color1: 'red',
color2: 'blue'
}
},
directives: {
color: {
inserted(el, binding) {
el.style.color = binding.value// 元素渲染时触发
}, update(el, binding) {
el.style.color = binding.value// 值变化时,dom 更新
}
}
}
}


06-自定义指令-封装v-loading指令
蒙层封装 v-loading






# 插槽 slot
<MyDialog>111</MyDialog>
<MyDialog>222</MyDialog>
<MyDialog><div>你好</div></MyDialog>
<MyDialog><div>你好</div><div>你好</div></MyDialog>

<div class="dialog-content">
<!--你确认要退出本系统么?-->
<slot></slot>
</div>

# 插槽 slot 默认值
<MyDialog></MyDialog><!--显示默认-->
<MyDialog>1</MyDialog>
<MyDialog>2</MyDialog>

<div class="dialog-content">
<slot>0</slot>
</div>

# 具名插槽
<MyDialog>
<!--<template v-slot:s1>内容1</template>-->
<template #s1>内容1</template>
<template v-slot:s2>内容2</template>
<template v-slot:s3>内容3</template>
</MyDialog>

<slot name="s1"></slot>
<slot name="s2"></slot>
<slot name="s3"></slot>


# 插槽传值
// 父
<MyTable :list="list">
<!--<button>删除</button>-->

<!-- 不解构 写法 -->
<!--<template #default="e">-->
<!-- <button @click="del(e.i)">删除</button>-->
<!--</template>-->

<!-- 解构 写法 -->
<template #default="{i}">
<button @click="del(i)">删除</button>
</template>
</MyTable>
<MyTable :list="list2">
<!--<button>查看</button>-->
<template #default="{ e }">
<button @click="show(e.name, e.age)">查看</button>
</template>
</MyTable>
methods: {
del(i) {
this.list.splice(i, 1)
},
show(name, age) {
alert(`name=${name}, age=${age}`)
}
}
// 子
<tr v-for="(e,i) in list" :key="i">
<td>{{ i + 1 }}</td>
<td>{{ e.name }}</td>
<td>{{ e.age }}</td>
<td>
<!--<button>删除</button>-->
<slot :i="i" :e="e"></slot>
</td>
</tr>
props: {
list: Array,
},


单页 vs 多页
单页:性能高
多页:SEO强




路由(1. add 2. import 3. use 4. new 5. 引入)

# vue2 router3 vuex3
# vue3 router4 vuex4
yarn add vue-router@3.6.5

import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter()
new Vue({
render: h => h(App),
router
}).$mount('#app')


路由配置后,URL路径显示多了 #/
http://localhost:8080/
http://localhost:8080/#/




// 定义路由规则
import FindMusic from "@/views/FindMusic.vue";
import MyMusic from "@/views/MyMusic.vue";
import MyFriend from "@/views/MyFriend.vue";

const router = new VueRouter({
routes: [
{path: '/find', component: FindMusic},
{path: '/my', component: MyMusic},
{path: '/friend', component: MyFriend},
]
})

// 使用
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/friend">朋友</a>
</div>
<div class="top">
<router-view></router-view>
</div>
</div>






组件分类维护
views 页面组件 配合路由使用
components 复用组件

@ 取代 . @指 src目标

单页 vs 多页

day06-router

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# router-link
<!--<a href="#/find">发现音乐</a>-->
<!--<a href="#/my">我的音乐</a>-->
<!--<a href="#/friend">朋友</a>-->

<!-- <a href="#/find" class="router-link-exact-active router-link-active" aria-current="page">发现音乐</a> -->
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
.footer_wrap a.router-link-active {
background-color: purple;
}


# 模糊/精确、自定义
router-link-exact-active to="/my" 可以匹配 /my /my/a /my/b ...
router-link-active to="/my" 仅匹配 /my

const router = new VueRouter({
routes: [...],
linkActiveClass: 'a',
linkExactActiveClass: 'e-a',
})
<!-- <a href="#/find" class="e-a a" aria-current="page">发现音乐</a> -->
<router-link to="/find">发现音乐</router-link>


# 查询参数 传参(多个参数)
{path: '/search', component: Search}
<router-link to="/search?key=前端培训">前端培训</router-link>

<p>搜索关键字: {{ $route.query.key }} </p>
created() {
console.log(this.$route.query.key)
}

# 动态路由 传参(单个参数)
{path: '/search/:words', component: Search}
<router-link to="/search/前端培训">前端培训</router-link>

<p>搜索关键字: {{ $route.params.words }} </p>
created() {
console.log(this.$route.params.words)
}


# 可选参数
{path: '/search/:words?', component: Search}
<router-link to="/search/">不传参数</router-link>


# 重定向
{path: '/', redirect: '/search'},
{path: '/home', component: Home},
访问1 会重定向到 2
1. http://localhost:8080/#/
2. http://localhost:8080/#/search


# 404
import NotFound from '@/views/NotFound.vue'
{path: '*', component: NotFound}// 放最下面,上面都匹配不到,匹配到 * 任意路径

# 路由模式 去除 #/
const router = new VueRouter({
mode: 'history',// 去除 #/
routes: [
...
{path: '*', component: NotFound}
]
})
http://localhost:8080/#/home # 默认
http://localhost:8080/home # mode: 'history',// 去除 #/


# 跳转传参 path name query params
<button @click="search">搜索一下</button>
methods: {
search() {
// 路径跳转
// this.$router.push('/search')
// this.$router.push({
// path: '/search'
// })

// 路径跳转 传参
// 使用 $route.query.key
// this.$router.push(`/search?key=${this.inpValue}`)
// this.$router.push({
// path: `/search?key=${this.inpValue}`
// })
// this.$router.push({
// path: '/search',
// query: {
// key: this.inpValue
// }
// })

// 动态路由
// 使用 $route.params.words
// this.$router.push(`/search/${this.inpValue}`)
// this.$router.push({
// path: `/search/${this.inpValue}`
// })

// 命名跳转 适合长路径
// {name: 'search', path: '/search/:words?', component: Search},
// this.$router.push({
// name: 'search'
// })
this.$router.push({
name: 'search',
// 跳转地址 http://localhost:8080/#/search/words?q1=q1&q2=q2
// 使用 $route.query 或 $route.params
query: {
q1: 'q1',
q2: 'q2'
},
params: {
p1: 'p1',
p2: 'p2',
words: 'words'
}
})
}
}



# 嵌套路由 children
const router = new VueRouter({
routes: [
{path: '/', redirect: '/Article'},
{path: '/Layout', redirect: '/Article'},
{
path: '/Layout',
component: Layout,
children: [
{path: '/Article', component: Article},
{path: '/Collect', component: Collect},
{path: '/Like', component: Like},
{path: '/User', component: User},
]
},
// {path: '/ArticleDetail', component: ArticleDetail},
{path: '/ArticleDetail/:id', component: ArticleDetail},
]
})

<!-- created 拿到数据后,再显示 -->
<div v-if="e.id">


# keep-alive 返回时,不会重新加载(默认缓存所有)
# 优点:会把 dom 不移除,放内存中,提升性能
# include 组件名数组 匹配的组件才缓存
# exclude 组件名数组 匹配的组件不缓存
# max 最大缓存多少
<!-- 缓存所有组件。文件列表 && 文章详情 都会被缓存 -->
<keep-alive>
<router-view></router-view>
</keep-alive>

<!-- 只缓存 文章详情 -->
<!--<keep-alive :include="['LayoutPage']">-->
<keep-alive :include="keepArr">
<router-view></router-view>
</keep-alive>
data() {
return {
keepArr: ['LayoutPage']
}
}
// Layout.vue
export default {
name: 'LayoutPage',
// 被 keep-alive 缓存了,再次进入不会执行
created() {
console.log('created')
},
mounted() {
console.log('mounted')
},
destroyed() {
console.log('destroyed')
},

// 被 keep-alive 缓存, 进入会激活,离开会失活
activated() {
console.log('activated')
},
deactivated() {
console.log('deactivated')
}
}


# 自定义创建项目
# .../IDEA/Project/other/vue/Vue2+3入门到实战-配套资料/01-随堂代码&素材/day05/准备代码/15-day06/diy01
vue create diy01 -m yarn
cd diy01
yarn serve

---------
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
( ) Vuex
>(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
-----------
3.x
> 2.x
-----------
Use history mode for router? No
-----------
Sass/SCSS (with dart-sass)
> Less
Stylus
-----------
ESLint with error prevention only
ESLint + Airbnb config
> ESLint + Standard config (无分号,标准化)
ESLint + Prettier
-----------
>(*) Lint on save(保存时校验)
( ) Lint and fix on commit
-----------
> In dedicated config files(单独文件)
In package.json
-----------
Save this as a preset for future projects? (y/N) n(不保存默认设置)
-----------

? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, CSS Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n




# ESLint 设置自动修复
WebStorm 设置搜索 ESLint
勾选 Automatic ESLint configuration
勾选 Run eslint --fix on save

# Ctrl + S 自动格式化代码
WebStorm 设置搜索 Save
勾选 Reformat code
勾选 Run eslint --fix(上面:ESLint 设置自动修复)

ESLint 设置自动修复 与 自动化格式化代码

day07-vuex

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# Vuex 创建
vue create vuex-demo -m yarn
yarn add vuex@3

// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store()
export default store

// src/main.js
import store from '@/store/index.js'
new Vue({
render: h => h(App),
store
}).$mount('#app')

# Vuex 使用定义的数据
// src/store/index.js
const store = new Vuex.Store({
state: {
title: '我是标题',
count: 100
}
})
// 任意组件 都可以直接使用
<p>{{ $store.state.title }}</p>
<p>{{ $store.state.count }}</p>

# Vuex 计算属性
// 简写
<p>{{ title }}</p>
<p>{{ count }}</p>

import { mapState } from 'vuex'
// computed: {
// title () {
// return this.$store.state.title
// },
// count () {
// return this.$store.state.count
// }
// }

// computed: mapState(['title', 'count'])

computed: {
...mapState(['title', 'count'])
}


# 严格模式
changeCount (i) {
// this.$store.state.count = this.count + i
// this.$store.commit('countAdd1')
// this.$store.commit('changeCount', i)
this.$store.commit('changeCount', { count: i })
}

// src/store/
const store = new Vuex.Store({
strict: true, // 严格模式,修改时控制台会报错
state: {
title: '我是标题',
count: 100
},
mutations: {
countAdd1 (state) {
state.count++
},
countSub1 (state) {
state.count--
},
// changeCount (state, count) {
// state.count += count
// }
changeCount (state, e) {
state.count += e.count
}
}
})




# mapMutations
// src/store/index.js
mutations: {
addCount1 (state) {
state.count++
},
addCount (state, count) {
state.count += count
}
}

// Son1.vue
<button @click="addCount1">值 + 1</button>
<button @click="addCount(5)">值 + 5</button>
<button @click="addCount(10)">值 + 10</button>
import { mapState, mapMutations } from 'vuex'
methods: {
...mapMutations(['addCount1', 'addCount'])// 与下面二者等同
// addCount1 () {
// this.$store.commit('addCount1')
// },
// addCount (i) {
// this.$store.commit('addCount', i)
// }
}





# actions 与 mapActions
// src/store/index.js
mutations: {
changeCount (state, count) {
state.count = count
}
},
actions: {
// context 暂时当成 store 仓库
changeCountAsync (context, count) {
setTimeout(() => {
context.commit('changeCount', count)// setTimeout 模拟异步
}, 1000)
}
}

// Son1.vue
<button @click="changeCountAsync(777)">1秒后修改为777</button>
import { mapState, mapMutations, mapActions } from 'vuex'
methods: {
...mapActions(['changeCountAsync'])// 与下面等同
// changeCountAsync (count) {
// this.$store.dispatch('changeCountAsync', count)
// }
}


# getters 与 mapGetters
// src/store/index.js
state: {
list: [1, 2, 3, 4, 5]
},
getters: {
sum (state) {
return state.list.reduce((sum, e) => sum + e, 0)
}
}

<!--<div>{{ $store.state.list }} 和 {{ $store.getters.sum }}</div>-->
<div>{{ list }} 和 {{ sum }}</div>
import { mapState, mapGetters } from 'vuex'
computed: {
...mapState(['list']),
...mapGetters(['sum'])
},



# 模块化
// src/store/modules/user.js
const state = {
userInfo: {
name: '大牛',
age: 18
},
score: 80
}
const mutations = {}
const actions = {}
const getters = {}

export default {
state,
mutations,
actions,
getters
}

// src/store/index.js
import user from './modules/user'
modules: {
user
}

# 模块化 state
// src/store/modules/user.js
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
// Son1.vue
<div>{{ $store.state.user.userInfo.name }}</div>
<div>{{ user.userInfo.name }}</div>
<div>{{ userInfo.name }}</div>
<div>{{ score }}</div>
import { mapState } from 'vuex'
computed: {
...mapState(['user']),
...mapState('user', ['userInfo', 'score'])
},



# 模块化 getters
// src/store/modules/user.js
const getters = {
upperCaseName (state) {
return state.userInfo.name.toUpperCase()
}
}

// Son1.vue
<div>{{ $store.getters["user/upperCaseName"] }}</div>
<div>{{ upperCaseName }}</div>
import { mapGetters } from 'vuex'
computed: {
...mapGetters('user', ['upperCaseName'])
},


# 模块化 mutations
// src/store/modules/user.js
const mutations = {
changeUserInfo (state, userInfo) {
state.userInfo = userInfo
}
}

// Son1.vue
<button @click="changeUserInfo({name:'kevin', age:5})">更新个人信息</button>
import { mapMutations } from 'vuex'
methods: {
...mapMutations('user', ['changeUserInfo'])// 与下面等同
// changeUserInfo (userInfo) {
// this.$store.commit('user/changeUserInfo', userInfo)
// }
}



# 模块化 actions
// src/store/modules/user.js
const actions = {
changeUserInfoAsync (context, userInfo) {
setTimeout(() => {
context.commit('changeUserInfo', userInfo)
}, 1000)
}
}

// Son1.vue
<button @click="changeUserInfoAsync({name:'kevin2', age:555})">1s更新个人信息</button>
import { mapActions } from 'vuex'
methods: {
...mapActions('user', ['changeUserInfoAsync']) // 与下面等同
// changeUserInfoAsync (userInfo) {
// this.$store.dispatch('user/changeUserInfoAsync', userInfo)
// }
}


# vuex-cart 购物车
# vue/Vue2+3入门到实战-配套资料/01-随堂代码&素材/day05/准备代码/17-day07-vuex 购物车/vuex-cart
# json-server
------
# json-server 官方地址 https://www.npmjs.com/package/json-server
yarn global add json-server

# json-server 命令找不到
# 环境变量 PATH 配置 C:\Users\TaoPanfeng\AppData\Local\Yarn\bin

# 查看帮助命令
json-server --help

json-server -w index.json

# index.json 内容如下
{
"cart": [
{"id":100001,"price":128,"count":1}
],
"friends": [
{"id":1,"name":"大牛","age":18},
{"id":2,"name":"二蛋"}
]
}


// yarn add axios
import axios from 'axios'
const res = await axios.get('http://localhost:3000/cart')
context.commit('changeList', res.data)

// 更新 db.json
await axios.patch(`http://localhost:3000/cart/${e.id}`, {
count: e.count
})
------

vuex modules

day10-智慧商城项目

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
ve


vue create taopanfeng-shopping -m yarn
? Manually select features
? Babel, Router, Vuex, CSS Pre-processors, Linter
? 2.x
? history router? No
? Less
? Standard
? Lint on save
? In dedicated config files
? Save No

cd taopanfeng-shopping
yarn serve

# 清空 assets components views
# 搞干净 router/index.js App.vue
# 建包 api utils

vant 组件库
官方文档 https://vant-contrib.gitee.io/vant/v2/#/zh-CN/
vue2 vant2
vue3 vant3/vant4

其他组件库
PC端 element-ui(element-plus)(饿了么) ant-design-vue(阿里)
移动端 vant-ui


# vant2文档 快速上手
全部
yarn add vant@latest-v2
// main.js
import Vant from 'vant'
import 'vant/lib/index.css'
Vue.use(Vant)// 插件安装初始化,vant所有组件导入
// App.vue
<van-button type="primary">主要按钮</van-button>

按需
yarn add babel-plugin-import -D
// babel.config.js
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
// utils/vant-ui.js
import Vue from 'vue'
import { Button, Switch, Rate } from 'vant'

Vue.use(Button)
Vue.use(Switch)
Vue.use(Rate)
// main.js
import '@/utils/vant-ui'
// App.vue
<van-button type="danger">危险按钮</van-button>
<van-switch v-model="checked"/>
<van-rate v-model="value"/>



# 适配 px => vw
yarn add postcss-px-to-viewport@1.1.1 -D
// postcss.config.js
module.exports = {
plugins: {
'postcss-px-to-viewport': {
// vw适配的标准屏的宽度 iphoneX
// 设计图 750,调成1倍 => 适配375标准屏幕
// 设计图 640,调成1倍 => 适配320标准屏幕
viewportWidth: 375
}
}
}

// App.vue
<div class="box"></div>
.box {
width: 300px; /* (300 / 375) * 100 = 80vw */
height: 300px;
background-color: pink;
}






# 一级路由
新建 view/layout/index.vue
// router/index.js
// import layout from '@/views/layout/index.vue'
import layout from '@/views/layout'// 简写(两种写法都可以)
const router = new VueRouter({
routes: [
{ path: '/', component: layout }
]
})


# Tabbar 标签栏 > 自定义颜色 ===> https://vant-contrib.gitee.io/vant/v2/#/zh-CN/tabbar
# Icon 图标 > 线框风格 ===> https://vant-contrib.gitee.io/vant/v2/#/zh-CN/icon
// utils/vant-ui.js
import { Tabbar, TabbarItem } from 'vant'
Vue.use(Tabbar)
Vue.use(TabbarItem)

<van-tabbar active-color="#ee0a24" inactive-color="#000">
<van-tabbar-item icon="wap-home-o">首页</van-tabbar-item>
<van-tabbar-item icon="apps-o">分类页</van-tabbar-item>
<van-tabbar-item icon="shopping-cart-o">购物车</van-tabbar-item>
<van-tabbar-item icon="user-o">我的</van-tabbar-item>
</van-tabbar>


# 二级路由
# Tabbar 标签栏 > 路由模式 ===> https://vant-contrib.gitee.io/vant/v2/#/zh-CN/tabbar
import home from '@/views/layout/home.vue'
{
path: '/layout',
component: layout,
children: [
{ path: '/home', component: home }
]
},

<router-view/>
<van-tabbar route>
<van-tabbar-item to="/home" icon="wap-home-o">首页</van-tabbar-item>
</van-tabbar>










axios 中文文档 ===> https://www.axios-http.cn/docs/instance




# Toast 轻提示 ===> https://vant-contrib.gitee.io/vant/v2/#/zh-CN/toast

// 1. 导入调用 ( 组件内 或 非组件中均可 )
import { Toast } from 'vant';
Toast('提示内容');

// 2. 通过this直接调用 ( 组件内)
import { Toast } from 'vant';
Vue.use(Toast)
this.$toast('提示内容')





待整理
gitee 上传 taopanfeng-shopping + pdf + md

day11-Vue3

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
vue create    webpack

create vue vite(比 webpack 更快)






npm init vue@latest
cd vue3-demo-2
yarn install
yarn dev

npm install
npm run dev


vue3
组件导入,可直接使用
组件可多个根元素

ref template 无需.value



# 父传子
// App.vue
const age = ref(18)
const incr = () => {
age.value++
}
<button @click="incr">age +1</button>
<MySon name="陶攀峰" :age="age"></MySon>

// MySon.vue
const props = defineProps({
name: String,
age: Number
})
// 要使用 props.
console.log(props.name)

<!-- template 可直接使用 -->
<div class="son">我是子组件 - {{ name }} {{ age }}</div>


# 子传父
// MySon.vue
const $emit = defineEmits(['changeAge'])
const changeAge = (i) => {
$emit('changeAge', i)
}
<button @click="$emit('changeAge', -1)">age -1</button>
<!--<button @click="changeAge(-1)">age -1</button>-->

// App.vue
const changeAge = (i) => {
age.value += i
}
<MySon @changeAge="changeAge"></MySon>



# ref引用
// App.vue
import MyExpose from "./components/MyExpose.vue";
const inp = ref(null);
console.log(inp.value);// null 还未渲染完
onMounted(() => {
console.log(inp.value);// <input type="text">
inp.value.focus();
})
const clickFn = () => {
inp.value.focus();
}
<input ref="inp" type="text">
<button @click="clickFn">聚集</button>


const refMyExpose = ref(null);
const myExpose = () => {
console.log(refMyExpose.value.count);
refMyExpose.value.sayHi();
}
<button @click="myExpose">MyExpose</button>
<MyExpose ref="refMyExpose"></MyExpose>

// MyExpose.vue
const count = 0
const sayHi = () => {
console.log('hi')
}
defineExpose({
count,
sayHi
})
<div>
count: {{ count }}
</div>



# 跨层级传递 provide 和 inject

// App.vue
<script setup>
import MyInner1 from './components/MyInner1.vue'
import {provide, ref} from "vue";

provide('name', '陶攀峰')

const count = ref(1)
provide('count', count)
provide('changeCount', (i) => {
count.value += i
})
</script>

<template>
<pre>
// App.vue
// --- MyInner1.vue
// ------ MyInner11.vue
</pre>
<div>
App.vue
<button @click="count++">count++</button>
<MyInner1></MyInner1>
</div>
</template>


// MyInner1.vue
<script setup>
import MyInner11 from './MyInner11.vue'
</script>

<template>
<div>
MyInner1.vue
<MyInner11></MyInner11>
</div>

</template>

// MyInner11.vue
<script setup>
import {inject} from "vue";

const name = inject('name')
const count = inject('count')
const changeCount = inject('changeCount')
</script>

<template>
<div>
MyInner11.vue --- {{ name }} --- {{ count }}
<button @click="changeCount(-1)">count--</button>
</div>
</template>


# defineOptions vue3.3
// views/login/index.vue
<!--<script>
export default {
name: 'loginIndex'
}
</script>-->

<script setup>
defineOptions({
name: 'loginIndex'
})
</script>



# defineModel vue3.3
// App.vue
<script setup>
import MyInput from "@/components/MyInput.vue";
import MyInput2 from "@/components/MyInput2.vue";
import {ref} from "vue";

const txt = ref("hello");
</script>

<template>
<div>
父: {{ txt }}
<MyInput v-model="txt"></MyInput><!-- 二者等同 -->
<!--<MyInput :modelValue="txt" @update:modelValue="txt=$event"></MyInput>-->

<MyInput2 v-model="txt"></MyInput2>
</div>
</template>


// MyInput.vue
<script setup>
defineProps({
modelValue: String
})
</script>

<template>
<div>
子1: <input type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</div>
</template>


// MyInput2.vue 需要配置下面的 vite.config.js
<script setup>
import {defineModel} from "vue";

const modelValue = defineModel()
</script>

<template>
<div>
子2: <input type="text"
:value="modelValue"
@input="modelValue= $event.target.value"
>
</div>
</template>


// vite.config.js
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true
}
})
]
})

day12-Pinia

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# Pinia
官方推荐
比 vuex 更简单
vuex ===> state mutations actions getters modules
pinia ===> state actions getters



npm create vue@latest


vue官方文档 https://cn.vuejs.org/
pinia官方文档 https://pinia.vuejs.org/zh/

yarn add pinia


// main.js
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import App from './App.vue'

// pinia 开始 ===> https://pinia.vuejs.org/zh/getting-started.html
const pinia = createPinia()

const app = createApp(App)
app.use(pinia)// pinia 插件安装
app.mount('#app')


// store/counter.js
import {defineStore} from "pinia";
import {computed, ref} from "vue";

export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const changeCount = i => count.value += i
const count2 = computed(() => {
return count.value * 2
})
return {count, changeCount, count2}
})


// App.vue
import {useCounterStore} from "@/store/counter";
import {storeToRefs} from "pinia";

const counterStore = useCounterStore();

// const {count, count2} = counterStore;// 非响应式
const {count, count2} = storeToRefs(counterStore);// 响应式

<h3>App.vue {{ count }} --- {{ count2 }}</h3>
<h3>App.vue {{ counterStore.count }} --- {{ counterStore.count2 }}</h3>









# pinia 持久化
官方文档 https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/

yarn add pinia-plugin-persistedstate


// main.js
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'

// pinia 开始 ===> https://pinia.vuejs.org/zh/getting-started.html
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const app = createApp(App)
app.use(pinia)// pinia 插件安装
app.mount('#app')


该插件的默认配置如下:
+ 使用 localStorage 进行存储
+ store.$id 作为 storage 默认的 key
+ 使用 JSON.stringify/JSON.parse 进行序列化/反序列化
+ 整个 state 默认将被持久化

// store/counter.js
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
...
},
// {persist: true}// k=counter, v={"count":7}
// {persist: {key: 'taopanfeng-counter'}}// k=taopanfeng-counter, v={"count":7}
{persist: {key: 'taopanfeng-counter', paths: ['count']}}// 只存储 count
)


























npm => yarn更快 => pnpm更快

npm install -g pnpm

![npm yarn pnpm](https://img-blog.csdnimg.cn/a65185cdb5e74bd0b1c47ed6534e7302.png)


pnpm create vue
√ Project name: ... vue3-big-event-admin-2
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No / Yes
√ Add Prettier for code formatting? ... No / Yes



cd vue3-big-event-admin-2
pnpm install
pnpm dev


ESLint 代码规范
Prettier 试图把 ESLint 变得更屌
// .eslintrc.cjs
rules: {
// 提前设置 ===> WebStorm prettier 禁用
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 80, // 每行宽度至多80字符(左边开发,右边演示)
trailingComma: 'none', // 不加对象|数组最后逗号(去除最后的逗号)
endOfLine: 'auto' // 换行符号不限制(win mac 不一致)
}
],
// 'vue/multi-word-component-names': 'off', // 关闭 vue 组件名称多单词组成
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index'] // vue组件名称多单词组成(忽略index.vue)
}
],
'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
// 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
}




# husky
git 提交之前的狗子

git init
pnpm dlx husky-init && pnpm install
// .husky/pre-commit
pnpm lint # 所有做校验


只对当前提交的内容做校验
pnpm i lint-staged -D

// package.json
{
...
"scripts": {
...
"lint-staged": "lint-staged"
},
"dependencies": { ... },
"devDependencies": { ... },
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix"
]
}
}

// .husky/pre-commit
pnpm lint-staged # 暂存区做校验