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

  • https://echarts.apache.org/handbook/zh/get-started

  • 安装

    $ 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>
posted @ 2022-10-23 20:12  his365  阅读(120)  评论(0编辑  收藏  举报