技术栈
开发工具:VSCode
代码管理:Git
前端框架:Vue3
构建工具:Vite
路由:vue-router 4x
状态管理:vuex 4x
AJAX:axios
UI库:element-plus
数据模拟:mockjs
代码规范:eslint
代码格式化:Prettier
css预处理:sass
开始构建
1. 初始化项目
[ 安装yarn ]
安装vite:
1
2
3
| npm init vite@latest [ProjectName]
or
yarn create vite [ProjectName]
|
安装完成后vite会引导我们创建一个项目,输入项目名称,package名称,然后选择项目使用的框架,这里有多个选项,选择Vue:

之后提示选择vue还是vue-ts,这里我们选择vue-ts(如果不用ts就直接选vue)

项目创建成功,打开项目并初始化:
1
2
3
| cd vite-cloud-admin
npm install
[or yarn]
|
成功后运行项目:
1
2
| npm run dev
[or yarn dev]
|
一个Vue3+Vite+TS的项目就创建成功了:

配置所需依赖(用于处理别名不生效问题):
npm install @types/node --D
[or yarn add @types/node --D]
修改vite.config.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
| import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
base: './', //不加打包后白屏
server: {
//解决“vite use `--host` to expose”
host: '0.0.0.0',
// port: 8080,
open: true,
proxy: { // 代理
'/api': {
target: '真实接口服务地址',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') // 注意代理地址的重写
},
// 可配置多个...
}
},
resolve:{
//别名配置,引用src路径下的东西可以通过@如:import Layout from '@/layout/index.vue'
alias:[
{
find:'@',
replacement:resolve(__dirname,'src')
}
]
}
})
|
配置tsconfig.json文件
- 这一步是用来解决 “报错:找不到模块“xxx”或其相应的类型声明” 的
- 配置 “baseUrl 和 paths” 项
- paths 里的内容根据别名来进行相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| {
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"baseUrl": ".", // 这块
"paths": { // 这块
"@/*":["src/*"],
// "components":["src/components/*"],
// "pinia/*":["src/pinia/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
|
2. 代码校验
首先安装eslint:
1
2
| npm i eslint
[or yarn add eslint]
|
初始化eslint
1
2
| npx eslint --init
[or yarn eslint --init]
|
然后会问我们如何使用eslint,选择第三项,检查语法、发现问题并强制执行代码样式

什么样子的模块引入方式,这里选择第一项,import/export

然后问我们用什么框架,这里选择Vue.js

是否使用TS,YES

代码运行在哪里,选择浏览器

然后问我们使用什么代码格式,这里我们选择流行代码格式中的Standard


选择eslintrc文件的格式,这里选择JavaScript

立即初始化,YES

这样我们的eslint就安装完成了,不过由于vue3的语法规则和vue2不同,有些情况下我们的正常开发也会报错,所以需要在.eslintrc.cjs文件的rules里面添加如下配置:
1
2
3
4
5
6
7
8
9
| rules: {
'vue/no-multiple-template-root': 0,
'no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' }
]
}
|
第一项是因为vue3允许template下面有多个标签,第二个是script setup标签下,定义的变量或方法如果未使用会报错,但其实这些方法和变量可以直接在template中使用的。
在overrides中可以指定具体目录下的规则
1
2
3
4
5
6
7
8
| overrides: [
{
files: ["src/views/**/*.vue"],
rules: {
"vue/multi-word-component-names": 0
}
}
],
|
“off” or 0 - 关闭(禁用)规则
“warn” or 1 - 将规则视为一个警告(并不会导致检查不通过)
“error” or 2 - 将规则视为一个错误 (退出码为1,检查不通过)
修改配置文件后记得重启服务器 才会生效
3.代码格式化
安装prettier:
1
2
| npm i prettier
[or yarn add prettier]
|
然后在根目录创建.prettierrc文件,配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| {
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"bracketSpacing": true,
"jsxBracketSameLine": true,
"useEditorConfig": true,
"useTabs": false,
"vueIndentScriptAndStyle": true,
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": ".prettierrc"
}
]
}
|
配置完成后可以在vscode安装Prettier插件,实现保存自动格式化文件。
完成后保存文件发现报错了,这是因为Prettier格式化后的代码与eslint规范冲突,这里我们使用eslint-config-prettier
这个插件解决这个问题,安装插件:
1
2
| npm i eslint-config-prettier -D
[or yarn add eslint-config-prettier -D]
|
安装完成后还需要在.eslintrc.cjs
文件中加上一段配置才能生效,这里就直接把整个.eslintrc.cjs
拷上来了:
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
| module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'plugin:vue/essential',
'standard',
'prettier' // 就是这段配置
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: [
'vue',
'@typescript-eslint'
],
rules: {
'vue/no-multiple-template-root': 0,
'no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' }
]
}
}
|
至此,代码格式化及校验就完成了。
4.配置路由
直接安装vue-router
1
2
| npm install vue-router@4
[or yarn add vue-router@4]
|
在src文件夹下新建router目录,并在目录下新建index.ts文件,并做如下配置:
1
2
3
4
5
6
7
8
9
10
| // index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import HelloWorld from '../components/HelloWorld.vue'
const routes = [{ path: '/', component: HelloWorld }]
export default createRouter({
history: createWebHashHistory(),
routes
})
|
在main.ts中引入该文件:
1
2
3
4
5
6
7
8
| // main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
|
在App.vue里添加router-view标签:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // App.vue
<template>
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
|
启动下,看下是否生效:
因为我们在App.vue中去掉了HelloWorld组件的引入,改用router的形式,如果界面还能显示出来,就说明配置成功了。
5.配置状态管理器
vuex
首先安装vuex,默认的还是3x版本,vue3是不支持的,这里需要这样安装:
1
2
| npm install vuex@next -S
[yarn add vuex@next -S]
|
安装完成后在src文件夹下新建store文件夹,然后新建index.ts文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // store/index.ts
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
count: 0
}
},
mutations: {
increment(state: any) {
state.count++
}
}
})
export default store
|
在main.ts文件中引入store:
1
2
3
4
5
6
7
8
9
10
| // main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
|
引入完成后,我们还需要测试下有没有生效,改写下HelloWorld组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <!--HelloWorld.vue-->
<template>
<button type="button" @click="onClick">count is: {{ count }}</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const count = computed(() => {
return store.state.count
})
const onClick = () => {
store.commit('increment')
}
</script>
|
HelloWorld中引入了store,并且将store中的count挂载到页面上,点击按钮向store发送事件完成count的累加,实测没有问题,vuex安装成功。
pinia
安装
1
2
| npm install pinia
[or yarn ad]
|
安装完之后,在src下建立pinia文件夹,新建index.ts文件
1
2
3
| import { createPinia } from 'pinia'
const store = createPinia()
export default store
|
在main.js中引入
1
2
3
4
5
6
7
| import { createApp } from 'vue'
import App from './App.vue'
import pinia from './pinia'
const app = createApp(App)
app.use(pinia)
app.mount('#app')
|
使用,创建一个user.ts文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: "Eduardo",
isAdmin: true,
};
},
})
|
使用store,我们正在 定义 一个 store,因为在 setup()
中调用 useStore()
之前不会创建 store:
1
2
3
4
5
6
7
8
9
10
11
| import { useStore } from '@/pinia/user'
export default {
setup() {
const store = useStore()
return {
// 您可以返回整个 store 实例以在模板中使用它
store,
}
},
}
|
结构store
store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构
为了从 Store 中提取属性同时保持其响应式,需要使用storeToRefs()。 它将为任何响应式属性创建 refs。 当您仅使用 store 中的状态但不调用任何操作时,这很有用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useStore()
// `name` 和 `doubleCount` 是响应式引用
// 这也会为插件添加的属性创建引用
// 但跳过任何 action 或 非响应式(不是 ref/reactive)的属性
const { name, doubleCount } = storeToRefs(store)
return {
name,
doubleCount
}
},
})
|
state,大多数时候,state是store的核新部分
1
2
3
4
5
6
7
8
9
10
11
12
13
| import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
})
|
访问state
1
2
3
| const store = useStore()
store.counter++
|
重置状态
可以通过调用 store 上的 $reset()
方法将状态 重置 到其初始值:
1
2
3
| const store = useStore()
store.$reset()
|
改变状态
1
2
3
4
| store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
|
Actions
Actions 相当于组件中的 methods
。 它们可以使用 defineStore()
中的 actions
属性定义,并且它们非常适合定义业务逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
| export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
|
访问其他 store 操作
要使用另一个 store ,您可以直接在操作内部使用它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
|
模块化
我个人感觉pinia的模块化就是一个模块创建一个文件夹~~然后再引入那个文件夹就行了。
在router.js中使用
必须写在router.beforeEach里面。
1
2
3
4
5
6
7
8
| router.beforeEach((to) => {
const store = loginStore();
if (to.path !== "/login") {
if (!store.token) {
return "/login";
}
}
});
|
持久化,pinia-plugin-persist
插件 pinia-plugin-persistedstate`插件
1
2
| npm i pinia-plugin-persist --save
[or yarn add pinia-plugin-persist --save]
|
在index.ts
中引入持久化插件
(默认存储在localStorage)
1
2
3
4
5
6
7
8
| import { createPinia } from 'pinia'
// import piniaPluginpersist from 'pinia-plugin-persist'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(piniaPluginPersistedstate)
export default store
|
若要修改默认配置(这里修改保存在sessionStorage下)
1
2
3
4
5
6
7
8
9
| import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
const store = createPinia()
store.use(createPersistedState({
storage: sessionStorage
}))
export default store
|
使用,在store中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| export const loginStore = defineStore("main", {
//持久化
persist: {
enabled: true,
// 自定义持久化参数
strategies: [
{
// 自定义key
key: "token",
// 自定义存储方式,默认sessionStorage
storage: localStorage,
// 指定要持久化的数据,默认所有 state 都会进行缓存,可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
paths: ["token"],
},
{
key: "menulist",
storage: localStorage,
paths: ["menulist"],
},
],
},
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| export const loginStore = defineStore("main", {
//持久化
persist: {
storage: sessionStorage
},
// or
// persist: true
// or
// persist: [
// {
// storage: sessionStorage
// }
// ]
);
|
对保存的数据进行加密,SecureLS默认保存在localStorage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import { createPinia } from 'pinia'
import { type StorageLike, createPersistedState } from 'pinia-plugin-persistedstate'
import SecureLS from 'secure-ls';
const ls = new SecureLS({encodingType: 'rc4', isCompression: false, encryptionSecret: 's3cr3tPa$$w0rd@123' })
const customStorage: StorageLike = {
setItem(key: string, value: string){
ls.set(key, value)
},
getItem(key: string): string | null {
return ls.get(key)
}
}
const store = createPinia()
store.use(createPersistedState({
storage: customStorage
}))
export default store
|
修改为保存在sessionStorage
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
| import { createPinia } from 'pinia'
import { type StorageLike, createPersistedState } from 'pinia-plugin-persistedstate'
import SecureLS from 'secure-ls';
class CustomSecureLs extends SecureLS {
ls: Storage;
constructor(config?: { isCompression?: boolean, encodingType?: string, encryptionSecret?: string , encryptionNamespace?: string }){
super(config)
this.ls = sessionStorage
}
}
const ls = new CustomSecureLs({encodingType: 'rc4', isCompression: false, encryptionSecret: 's3cr3tPa$$w0rd@123' })
const customStorage: StorageLike = {
setItem(key: string, value: string){
ls.set(key, value)
},
getItem(key: string): string | null {
return ls.get(key)
}
}
const store = createPinia()
store.use(createPersistedState({
storage: customStorage
}))
export default store
|
6.element-plus
element-ui的vue3版本,首先安装它:
1
2
| npm install element-plus --save
[yarn add element-plus --save]
|
在main.ts中作出如下配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
| // main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus' // 引入element-plus
import 'element-plus/dist/index.css' // 引入element-plus的样式
const app = createApp(App)
app.use(router)
app.use(store)
app.use(ElementPlus) // use element-plus
app.mount('#app')
|
然后就可以使用element-plus的组件了,比较多,使用的时候直接参照官方文档就行。
7.封装axios
安装axios:
1
2
| npm i axios
[or yarn add axios]
|
在src文件夹下新建utils
文件夹,然后在其下创建request.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
| import axios from 'axios'
import { ElMessage } from 'element-plus'
const instance = axios.create({
baseURL: '',
timeout: 5000
})
instance.interceptors.request.use(
config => {
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
instance.interceptors.response.use(
response => {
const res = response.data
if (res.status !== 200) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res.data
}
},
error => {
console.log('err' + error) // for debug
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default instance
|
src下新建api文件夹,创建一个user.ts文件,并创建一个登录的请求:
1
2
3
4
5
6
7
8
9
| import request from '../utils/request'
export function login(data: any) {
return request({
url: '/user/login',
method: 'post',
data
})
}
|
然后在HelloWorld组件中onMounted中调用login接口:
1
2
3
4
5
6
| import { onMounted } from 'vue'
onMounted(() => {
login({ account: 'admin', password: '123456' }).then(res => {
console.log(res)
})
})
|
当然,现在还调不通,所以我们先配置下mock。
8.mockjs
安装mockjs:
1
2
| npm i mockjs -D
[yarn add mockjs -D]
|
在根目录新建mock文件夹,并新建index.ts文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // index.ts
import Mock from 'mockjs'
// 设置拦截ajax请求的相应时间
Mock.setup({
timeout: '200-600'
})
Mock.mock('/user/login', 'post', (params: any) => {
return {
data: { token: '123' },
status: 200,
message: 'success'
}
})
export default {}
|
简单设置一个login接口,让我们能够通过axios调通,然后在main.ts中引入mock:
启动项目:

有返回值,搞定
9.css预处理
vite是支持sass(scss)的,但是还是需要我们先安装一下,不然会报错
1
2
| npm install sass --save-dev
[or yarn add sass --save-dev]
|