1. 创建 vite 项目
npm create vite@latest
ele
2. 安装sass/less ( 一般我使用sass )
cnpm add -D sass
cnpm add -D less
3. 自动导入 两个插件 使用之后,不用导入vue中hook reactive ref
cnpm install -D unplugin-vue-components unplugin-auto-import
在 vite.config.ts 中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 自动导入vue中hook reactive ref等
import AutoImport from "unplugin-auto-import/vite"
//自动导入ui-组件 比如说ant-design-vue element-plus等
import Components from 'unplugin-vue-components/vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
//安装两行后你会发现在组件中不用再导入ref,reactive等
imports: ['vue', 'vue-router'],
//存放的位置
dts: "src/auto-import.d.ts",
}),
Components({
// 引入组件的,包括自定义组件
// 存放的位置
dts: "src/components.d.ts",
}),
],
})
记得在 main.ts 中引入 框架样式
import 'element-plus/dist/index.css'
这一步做完最好重新运行项目
4. 安装 router
cnpm install vue-router@4
在src下创建一个 routes 文件夹,再创建一个 index.ts 文件
import { createRouter, createWebHistory } from "vue-router";
let routes= [
{
path: "/",
redirect: "/home"
},
{
path: '/home',
name: 'home',
//使用import可以路由懒加载,如果不使用,太多组件一起加载会造成白屏
component: () => import('../views/home.vue')
}
]
// 路由
const router = createRouter({
history: createWebHistory(),
routes
})
// 导出
export default router
然后再到 main.ts 文件引入
import { createApp } from 'vue'
import './style.css'
import router from "./routes/index";
import App from './App.vue'
createApp(App).use(router).mount('#app')
在 src 下新建 views 文件夹,整理页面( 清理多余的 components 文件,整理 app.vue 文件 )
app.vue 页面记得配置路由占位符
<template>
<div>
<router-view />
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
基本模板如下
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
5. 安装pinia 因为是vue3+ts,安装pinia更好点,vuex拥抱ts没有pinia好
cnpm install pinia
在 main.ts 引入
import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App).use(pinia)
在src下创建一个 store 文件夹,再创建一个 home.ts 文件
其它名也可以,因为pinia它有一个根文件,会把 defineStore 第一个参数当id值,相当于vuex中的 module 自动引入,也会在Vue.js devtools 插件中以第一个参数名展示(下面展示)
注意:defineStore第一个参数很重要,而且是唯一值。它的命名在devtools 插件能方便找到这个文件的数据,方便调试。
home.ts 文件
import { defineStore } from 'pinia'
// useMain 可以是 useUser、useCart 之类的名字
// defineStore('main',{..}) 在devtools 就使用 main 这个名
export const useMain = defineStore('main', {
// 相当于data
state: () => {
return {
// 所有这些属性都将自动推断其类型,如果推断失败可以试下 as xxx
counter: 0,
name: 'Eduardo',
}
},
// 相当于计算属性
getters: {
doubleCount: (state) => {
return state.counter * 2
},
},
// 相当于vuex的 mutation + action,可以同时写同步和异步的代码
actions: {
increment() {
this.counter++
},
randomizeCounter() {
setTimeout(() => {
this.counter = Math.round(100 * Math.random())
}, 0);
},
},
})
新建 testPinia.vue 文件 测试 pinia 是否生效。
<template>
<div>
<div>counter:{{ counter }}</div>
<div>doubleCount:{{ doubleCount }}</div>
<el-button @click="main.randomizeCounter()">counter(round)</el-button>
<el-button type="primary" @click="main.increment()">counter++</el-button>
<div>{{ name }}</div>
<el-button @click="amend()">修改</el-button>
</div>
</template>
<script setup lang='ts'>
//引入想要的pinia文件 {} 里面就是对应导出的名字
import { useMain } from '../store/home'
import { storeToRefs } from 'pinia';
const main = useMain()
// 解构main里面的state和getters的数据,
// 使用storeToRefs解构才有响应式,响应式可以直接修改数据,不过这我只用来渲染
let { counter, name, doubleCount } = storeToRefs(main)
//(常用方法三种)
//常用方法一: 使用数据
console.log(counter);
//使用方法(方法目前不能解构)
main.increment()
// 常用方法二:修改数据
counter.value = 9999
//常用方法三:
//进阶使用$patch,多个修改
const amend = () => {
main.$patch((state: any) => {
state.counter += 10;
state.name = '张三'
})
}
</script>
6. 安装和封装 axios
为什么要封装axios?
axios.get().then() 这样的书写,会有缺陷,在以下缺点
请求头能不能统一处理
解决: 创建一个 request/request.js 文件夹,
在里面可以使用axios.create创建实例对象
也可以在里面设置 请求 与 响应 拦截器
不便于接口的统一管理
解决:在 request 文件夹加多一个api文件来管理所有接口,
(会先导入rerequest.js的实例)
并使用函数,不然每次发请求时都会跑一次api文件
容易出现回调地狱
LogoutAPI () 最终的结果是返回proise对象
解决:acync + await
await 后面一般放promise对象
注意:但封装axios后还是可以用 .then()
开始安装
cnpm install axios
封装 request 先在 src 下创建一个 request 文件夹,并添加一个 getBaseUrl.ts 文件
const getBaseUrl = () => {
// 当项目要部署的时候把这里的 baseURL 更换就行了
let BASE_URL = "";
// 本地
// BASE_URL = "/api";
// 云
// BASE_URL = "";
// mock
BASE_URL = "/api";
return BASE_URL;
};
export default getBaseUrl;
添加一个 request.ts 文件
import axios from 'axios'
import getBaseUrl from './getBaseUrl'
// 创建axios实例
const request = axios.create({
baseURL: getBaseUrl(),// 所有的请求地址前缀部分(没有后端请求不用写)
timeout: 80000, // 请求超时时间(毫秒)
// 这一步千万注释掉 会影响后端的 cors 跨域
// withCredentials: true,// 异步请求携带cookie
// headers: {
// 设置后端需要的传参类型
// 'Content-Type': 'application/json',
// 'token': x-auth-token',//一开始就要token
// 'X-Requested-With': 'XMLHttpRequest',
// },
})
// request拦截器
request.interceptors.request.use(
config => {
// 如果你要去localStor获取token,(如果你有)
// let token = localStorage.getItem("x-auth-token");
// if (token) {
//添加请求头
//config.headers["Authorization"]="Bearer "+ token
// }
return config
},
error => {
// 对请求错误做些什么
Promise.reject(error)
}
)
// response 拦截器
request.interceptors.response.use(
response => {
// 对响应数据做点什么
return response.data
},
error => {
// 对响应错误做点什么
//响应错误
let message = "";
if (error.response && error.response.status) {
const status = error.response.status;
switch (status) {
case 400:
message = "请求错误";
break;
case 401:
message = "请求错误";
break;
case 404:
message = "请求地址出错";
break;
case 408:
message = "请求超时";
break;
case 500:
message = "服务器内部错误!";
if(error.response.data && error.response.data == '该账号已存在,请重新输入!'){
message = "该账号已存在,请重新输入!";
}
break;
case 501:
message = "服务未实现!";
break;
case 502:
message = "网关错误!";
break;
case 503:
message = "服务不可用!";
break;
case 504:
message = "网关超时!";
break;
case 505:
message = "HTTP版本不受支持";
break;
default:
message = "请求失败";
}
return Promise.reject(error);
}
return Promise.reject(error);
}
)
export default request
新建 api 文件夹,新建 mock.ts 文件夹 里面是基本的接口调用例子,和 mock 接口
import request from "../request";
export const getTestApi = (data: any) => {
return request.get("/getMenu", {
params: data, timeout: 1000,
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
};
export const postTestApi = (data: any) => {
return request.post("/getMenu", {
data: data, timeout: 1000,
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
};
export const deleteTestApi = (data: any) => {
return request.delete("/getMenu", {
data: data, timeout: 1000,
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
};
export const putTestApi = (data: any) => {
return request.put("/getMenu", {
data: data, timeout: 1000,
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
};
export const getMockFirstApi = () => {
return request.get("/first")
};
export const getMockMenuApi = (data: any) => {
return request.get("/menu", {
params: data,
})
};
在 vite.config.ts 文件 配置 proxy 代理
proxy 是 前端 开发阶段 使用的 跨域处理,部署的时候都是 后端使用 cors 或者 nginx 反向代理
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 自动导入vue中hook reactive ref等
import AutoImport from "unplugin-auto-import/vite"
//自动导入ui-组件 比如说ant-design-vue element-plus等
import Components from 'unplugin-vue-components/vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
// ...
],
server: {
host: '0.0.0.0', // 指定服务器应该监听哪个 IP 地址
open: false, //自动打开
port: 8888,
proxy: {
'/api': { // 匹配请求路径,
// 本地
// target: 'http://127.0.0.1:3000', // 代理的目标地址
// mock
target: 'http://127.0.0.1:3300', // 代理的目标地址
// 开发模式,默认的127.0.0.1,开启后代理服务会把origin修改为目标地址
changeOrigin: true,
// secure: true, // 是否https接口
// ws: true, // 是否代理websockets
// 路径重写,**** 如果你的后端有统一前缀(如:/api),就不开启;没有就开启
//简单来说,就是是否改路径 加某些东西
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
7. 配置@别名
cnpm install @types/node
安装这个包 让 nodeJS 支持 ts
让 vscode 认识@符号, 在 tsconfig.json 文件
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
让 vite 识别@符号,在 vite.config.ts 文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 配置@别名
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
// ↓解析配置
resolve: {
// ↓路径别名
alias: {
"@": resolve(__dirname, "./src")
}
}
})
将 request.ts 文件 中 进行测试 @ 功能
// import getBaseUrl from './getBaseUrl'
import getBaseUrl from '@/request/getBaseUrl'
9. 安装element-puls组件库
cnpm install element-plus --save
vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
重新运行 打开 testPinia 页面 看到
10. 进行 mock 数据
在 src 同级目录下 创建 mock 文件夹 开始 mock 假数据,在 mock 文件夹下
cnpm install --save express
创建 index.js , data 文件夹
index.js
const express = require("express")
const app = express()
const url = require("url")
app.get("/first", (req, res)=>{
res.send(' first 接口调用成功')
})
const menu = require("./data/menu.json")
app.get("/menu", (req, res)=>{
const user = url.parse(req.url, true).query.user
res.send(menu)
})
app.listen(3300, ()=>{
console.log("服务器运行在 3300")
})
在 data 文件夹 下,新建 menu.json
{
"code": 200,
"msg": "操作成功",
"data": {
"menu": [
{
"id": 666,
"title": "123123",
"path": "",
"component": null,
"children": [
{
"id": 888,
"title": "数字字典",
"path": "/mockPackage/test",
"component": "mockPackage/test",
"children": []
}
]
}
],
"authoritys": [
"ROLE_admin",
"ROLE_normal"
]
}
}
新建 testMockApi.vue 页面,进行 mock 接口 测试
<template>
<div>
testMockApi
<br>
<br>
<br>
第一个接口: {{ state.firstMsg }}
<br>
<br>
<br>
第二个接口:
{{ state.menuData.menu }}
<br>
<br>
<br>
{{ state.menuData.authoritys }}
</div>
</template>
<script setup lang="ts">
import { getMockFirstApi, getMockMenuApi } from "@/request/api/mock"
interface stateType {
firstMsg: any,
menuData: object
}
const state: stateType = reactive({
firstMsg: "",
menuData: {}
})
const getData = async () => {
state.firstMsg = await getMockFirstApi()
const { data } = await getMockMenuApi("")
state.menuData = data
}
getData()
</script>
<style scoped></style>
修改 style.css 样式
body {
margin: 0;
padding: 0;
}
#app {
height: 100vh;
width: 100vw;
}