Vue+vite+ts+axios+element plus
一、介绍
- vue
- vite
- TypeScript
- Scss
- Element Plus
- Route
- axios
- Vuex(使用pinia代替了)
- json-viewer
二、步骤
2.1、创建项目
-
使用Vite快速构建项目
-
Vite 是一种新型前端构建工具,能够显著提升前端开发体验。
-
vite的缺省端口是3000,如果需要修改这个端口,可以编辑文件package.json,在scritps中找到启动命令,这里是dev,在vite后面增加--port 5419。这样,启动端口就修改为5419.
# 项目名称不建议用大写,容易报错 $ npm init vite@latest vue.admin √ Select a framework: » Vue √ Select a variant: » TypeScript Scaffolding project in D:\vsdemo\vs2022\HisApi\vue.admin... Done. Now run: cd vue.admin npm install npm run dev $ cd vue.admin $ npm install added 44 packages, and audited 45 packages in 19s 4 packages are looking for funding run `npm fund` for details found 0 vulnerabilities $ npm run dev > vue.admin@0.0.0 dev > vite VITE v3.1.8 ready in 440 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose
-
-
目录介绍
- node_modules:模块包
- public:公共资源
- src:项目目录
- assets:静态资源
- components:组件
- App.vue:根组件
- main.ts:根函数入口、全局配置生效的地方
- style.css:
- vite-env.d.ts
- package.json:项目配置文件、项目的标题、版本、模块版本等信息
- tsconfig.json:ts配置文件
- vite.config.ts:vite的配置文件
2.2、pnpm
-
节省磁盘空间并提升安装速度
-
安装
$ npm install -g pnpm $ pnpm -v 7.13.6 ## 启动 $ pnpm dev > vue.admin@0.0.0 dev D:\vsdemo\vs2022\HisApi\vue.admin > vite VITE v3.1.8 ready in 413 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose
2.3、scss
$ pnpm install sass
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 5, reused 5, downloaded 0, added 0
Progress: resolved 75, reused 48, downloaded 1, added 0
Progress: resolved 85, reused 48, downloaded 12, added 0
Packages: +17 -1
+++++++++++++++++-
dependencies:
+ sass 1.55.0
2.4、Element Plus
$ pnpm install element-plus
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 6, reused 6, downloaded 0, added 0
Progress: resolved 89, reused 63, downloaded 1, added 0
Progress: resolved 103, reused 64, downloaded 9, added 0
Packages: +21
+++++++++++++++++++++
Progress: resolved 107, reused 64, downloaded 18, added 17
Progress: resolved 107, reused 64, downloaded 20, added 20
Progress: resolved 107, reused 64, downloaded 21, added 20
.../node_modules/vue-demi postinstall$ node ./scripts/postinstall.js
.../node_modules/vue-demi postinstall: Done
Progress: resolved 107, reused 64, downloaded 21, added 21, done
dependencies:
+ element-plus 2.2.19
Done in 8.5s
-
自动导入(推荐)
$ pnpm install -D unplugin-vue-components unplugin-auto-import Progress: resolved 0, reused 1, downloaded 0, added 0 Progress: resolved 8, reused 7, downloaded 0, added 0 Progress: resolved 94, reused 72, downloaded 3, added 0 Progress: resolved 135, reused 85, downloaded 24, added 0 Packages: +34 ++++++++++++++++++++++++++++++++++ devDependencies: + unplugin-auto-import 0.11.2 + unplugin-vue-components 0.22.8 Done in 4.3s Progress: resolved 141, reused 85, downloaded 34, added 34, done
-
修改 vite.config.ts
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue(), // ... AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }),] })
2.5、router
-
安装
$ pnpm install vue-router@next Progress: resolved 0, reused 1, downloaded 0, added 0 Progress: resolved 9, reused 9, downloaded 0, added 0 Progress: resolved 107, reused 105, downloaded 1, added 0 Packages: +2 ++ dependencies: + vue-router 4.0.13 (4.1.5 is available) Done in 3.3s Progress: resolved 143, reused 119, downloaded 2, added 2, done
-
src目录创建views文件夹,新建文件Login.vue
<template>
<div>Login</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
</style>
-
src目录下新建文件夹router,文件夹新建 路由文件index.ts 写入页面和路由映射关系
import { createRouter, createWebHistory } from "vue-router"; import Login from "../views/Login.vue"; const router = createRouter({ history: createWebHistory(), routes: [{ path: "/login", component: Login }], }); export default router;
-
在main.ts中引入路由
import { createApp } from "vue"; import "./style.css"; import App from "./App.vue"; import router from "./router"; createApp(App).use(router).mount("#app");
-
在App.vue里面加上路由标签
<template>
<router-view></router-view>
</template>
2.6、Vuex(可选)我这里选择使用的是pinia
-
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
-
$ pnpm install vuex@next Progress: resolved 0, reused 1, downloaded 0, added 0 Progress: resolved 11, reused 10, downloaded 0, added 0 Packages: +1 + Progress: resolved 144, reused 121, downloaded 1, added 1, done dependencies: + vuex 4.0.2 (4.1.0 is available)
-
src目录下新建文件夹store,新建文件index.ts
import { createStore } from "vuex"; const store = createStore({ // 状态变量 state() { return { Token: "", NikeName: "", }; }, // 方法 mutations: { SettingNikeName(state: any, NickName) { state.NickName = NickName; }, SettingToken(state: any, Token) { state.Token = Token; }, }, }); export default store;
-
main.ts
import store from "./store/index"; createApp(App).use(store).use(router).mount("#app");
2.6、pinia
- 安装
$ pnpm i pinia@next
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 10, reused 10, downloaded 0, added 0
Progress: resolved 108, reused 107, downloaded 0, added 0
Packages: +1
+
dependencies:
+ pinia 2.0.0-rc.10 (2.0.23 is available)
- 设置为全局对象,在main.js中引用
import { createPinia } from "pinia";
// 创建pinia实例
const pinia = createPinia();
createApp(App).use(router).use(pinia).mount("#app");
- 定义
// store/index.ts
// store/index.ts
import { defineStore } from "pinia";
// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const GlobalStore = defineStore({
// id:必须,在所有store中唯一
id: "myGlobaState",
// 返回的对象函数
state: () => ({
count: 10,
title: "医院信息系统",
}),
getters: {},
actions: {
setCount(n: number) {
this.count = n;
},
},
});
- 使用
<template>
<div>Login:{{ count }}</div>
<el-button type="primary" @click="clickAdd">{{ count }}++</el-button>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { GlobalStore } from "../store";
let store = GlobalStore();
//如果直接取state的值必须使用computed才能实现数据的响应式 如果直接取 store.state.a 则不会监听到数据的变化,或者使用getter,就可以不使用computed (这边和vuex是一样的)
let count = computed(() => store.count);
const clickAdd = () => {
store.setCount(store.count + 1);
};
</script>
<style lang="scss" scoped>
</style>
- 对比:
- 两个代码的对比我们可以看出使用 pinia 更加的简洁,轻便。
- pinia 取消了原有的 mutations,合并成了 actions,且我们在取值的时候可以直接点到那个值,而不需要在.state,方法也是如此。
2.7、echarts
-
安装
$ pnpm install echarts Progress: resolved 0, reused 1, downloaded 0, added 0 Progress: resolved 11, reused 11, downloaded 0, added 0 Progress: resolved 117, reused 114, downloaded 0, added 0 Packages: +3 +++ Progress: resolved 147, reused 122, downloaded 2, added 1 Progress: resolved 147, reused 122, downloaded 2, added 2 dependencies: + echarts 5.4.0 Done in 5.5s Progress: resolved 147, reused 122, downloaded 3, added 3, done
2.8、axios
-
安装
$ pnpm install axios Progress: resolved 0, reused 1, downloaded 0, added 0 Progress: resolved 22, reused 21, downloaded 0, added 0 Progress: resolved 151, reused 125, downloaded 3, added 0 Progress: resolved 153, reused 125, downloaded 6, added 0 Packages: +9 +++++++++ Progress: resolved 156, reused 125, downloaded 9, added 9, done dependencies: + axios 1.1.3 Done in 4.6s
-
src目录下新建http文件夹,新建index.ts文件
import axios from 'axios' //需要拦截器的地方使用instance对象, 有自定义返回逻辑的地方沿用axios,在组件内部处理返回结果即可 import instance from './filter' const http = "/api"; //获取token,,因为instance开启了withCredentials 所以这里要用axios,因为instance有连接器功效,必须携带token export const getToken = (name: string, password: string) => { return axios.get(http + '/Login/GetToken?name=' + name + '&password=' + password); }; //菜单模块 //获取列表 export const getMenuDataNew = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Menu/GetMenus", parms) } //添加 export const addMenu = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Menu/Add", parms) } //修改 export const editMenu = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Menu/Edit", parms) } //删除 export const delMenu = async (id: number) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(http + "/Menu/Del?id=" + id) } //BatchDel export const batchDelMenu = async (ids: string) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(http + "/Menu/BatchDel?ids=" + ids) } //分配菜单 export const settingMenu = async (rid: string,mids: string) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(`${http}/Menu/SettingMenu?rid=${rid}&mids=${mids}`) } //角色模块 //获取列表 export const getRoleData = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Role/GetRoles", parms) } //添加 export const addRole = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Role/Add", parms) } //修改 export const editRole = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Role/Edit", parms) } //删除 export const delRole = async (id: number) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(http + "/Role/Del?id=" + id) } //BatchDel export const batchDelRole = async (ids: string) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(http + "/Role/BatchDel?ids=" + ids) } //用户模块 //获取列表 export const getUserData = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Users/GetUsers", parms) } //添加 export const addUsers = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Users/Add", parms) } //修改 export const editUsers = async (parms: {}) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.post(http + "/Users/Edit", parms) } //删除 export const delUsers = async (id: number) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(http + "/Users/Del?id=" + id) } //BatchDel export const batchDelUsers = async (ids: string) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(http + "/Users/BatchDel?ids=" + ids) } //分配 export const settingRole = async (pid: string,rids: string) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(`${http}/Users/SettingRole?pid=${pid}&rids=${rids}`) } //根据角色获取菜单 export const getUserMenus = async () => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(`${http}/Menu/GetUserMenus`) } //个人中心修改用户昵称和密码 export const editNickNameOrPassword = async (nickName: string,password: string) => { instance.defaults.headers.common['Authorization'] = "Bearer " + localStorage["token"]; return instance.get(`${http}/Users/EditNickNameOrPassword?nickName=${nickName}&password=${password}`) }
-
在需要使用的组件里导入http中的方法即可
import { getToken } from '../../http/index' //请求后端数据,获取token,并将token放入localStorage const token = await getToken(form.userName, form.passWord) as any as string const user: UserInfo = JSON.parse(new Tool().FormatToken(token)) localStorage["token"] = token localStorage["nickname"] = user.NickName store.commit("SettingNickName",user.NickName) store.commit("SettingToken",token) router.push({ path: '/desktop' });
-
拦截器:
- 对api的返回结果解析,返回统一的格式。
- 对于错误信息,在拦截器中弹窗提示,业务层页面只需关注页面,无需过度关注交互
//导入axios import axios from 'axios' import { ElMessage } from 'element-plus' //创建一个axios实例 const instance = axios.create({ headers: { 'content-type': 'application/json', }, // true:在跨域请求时,会携带用户凭证 // false(默认):在跨域请求时,不会携带用户凭证;返回的 response 里也会忽略 cookie // withCredentials: true, timeout: 5000 //5秒 }) //http 拦截器 instance.interceptors.response.use( response=>{ //拦截请求,统一相应 if (response.data.isSuccess) { return response.data.result } else { ElMessage.error(response.data.msg) return response.data.result } }, //error也可以处理 error=>{ if (error.response) { switch (error.response.status) { case 401: ElMessage.warning("资源没有访问权限!") break case 404: ElMessage.warning("接口不存在,请检查接口地址是否正确!") break case 500: ElMessage.warning("内部服务器错误,请联系系统管理员!") break default: return Promise.reject(error.response.data) // 返回接口返回的错误信息 } } else { ElMessage.error("遇到跨域错误,请设置代理或者修改后端允许跨域访问!") } } ) export default instance
2.9、icons-vue
-
安装
$ pnpm install @element-plus/icons-vue
-
使用
<script lang="ts" setup> import { CoffeeCup, Apple, Drizzling, Headset } from '@element-plus/icons-vue' // import { defineProps } from 'vue'; // 新版不需要引用 import { CardModel } from '../class/CardModel' defineProps({ info: CardModel }) </script> <div class="left"> <coffee-cup v-if="info?.Icon == 'CoffeeCup'" /> <apple v-if="info?.Icon == 'Apple'" /> <drizzling v-if="info?.Icon == 'Drizzling'" /> <headset v-if="info?.Icon == 'Headset'" /> </div>
2.10、前端解决跨域问题
-
和后端解决是两种方案,而不是必须前后端都解决跨域问题;
-
代码 vite.config.ts
// vite.config.ts ,和plugins平级添加 server:{ port:3000, open:true, proxy:{ '/api':{ target:'http://localhost:5294/api', changeOrigin:true, rewrite:(path) => path.replace(/^\/api/,'') } }, }
2.11、ts的class
-
创建类
export class CardModel { Icon: string = ''; Title: string = ''; Count: number = 0; }
-
使用
import { CardModel } from '../class/CardModel' defineProps({ info: CardModel })
2.12 json-viewer
- 安装
# 需要依赖clipboard,先安装clipboard
$ pnpm install clipboard
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 40, reused 39, downloaded 0, added 0
Packages: +5
+++++
Progress: resolved 161, reused 134, downloaded 4, added 0
dependencies:
+ clipboard 2.0.11
Done in 2.8s
Progress: resolved 161, reused 134, downloaded 5, added 5, done
Administrator@wanghx MINGW64 /d/vsdemo/vs2022/HisApi/vue-vite-ts-admin
# 再安装vue3-json-viewer
$ pnpm i vue3-json-viewer
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 30, reused 29, downloaded 0, added 0
Packages: +1
+
Progress: resolved 162, reused 139, downloaded 1, added 1, done
dependencies:
+ vue3-json-viewer 2.2.2
- main.ts
import { createApp } from "vue";
import "./style.css";
import "vue3-json-viewer/dist/index.css";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import JsonViewer from "vue3-json-viewer";
// 创建pinia实例
const pinia = createPinia();
createApp(App).use(router).use(JsonViewer).use(pinia).mount("#app");
}
- src\declaration.d.ts 防止main.ts报错
declare module "vue3-json-viewer" {
const vis: any;
export default vis;
}
<template>
<div>
<json-viewer :value="jsonData" copyable boxed sort :theme="theme" />
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
let theme = ref("dark"); // light,dark
const form = {
jsonData:
'[{"name":"黑子","sex":"男","Age":25,"abc":null,"hobby":["篮球","跑步","看电影","王者荣耀"],"normal":true},{"name":"张三","sex":"男","Age":25,"hobby":["上天","入地"],"normal":false}]',
};
const jsonData = JSON.parse(form.jsonData);
</script>
世界上没有什么事情是跑步解决不了的,如果有,那就再跑一会!