Vue3.2+Vite+TS+pinia+router4搭建

1,931 阅读5分钟

Yang第一次不使用脚手架手动搭建项目

  • ✅Vue3.2x+Vite2.x+Typescript+Axios+Element Plus+Pinia+Vue-router4.x+Less+Vueuse
  • ✅自动导入插件
  • ✅简易版用ts封装axios,未使用类,感觉和js差别不大
    完整配置在最后

项目搭建:

创建vite项目

npm init vite@latest

更改vue2/vue3快捷模板

vscode:文件-->文件-->首选项-->用户片段

vue-router4

vue-router从3.x迁移到4.x

src/router/index.ts 
----------------------------------------------------------------------------
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
    {
        path: '/',
        name: 'login',
        component: () => import('../components/login.vue') //一定要有.vue后缀
    },
    {
        path: '/main',
        name: 'main',
        component: () => import("../components/main.vue") //一定要有.vue后缀
    },
]

const router = createRouter({
    history: createWebHistory(),
    routes,
})

export default router
--------------------------------------------------------------------------------


main.ts
--------------------------------------------------------------------------------
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index';//绝对不要加.ts后缀
createApp(App).use(router).mount("#app");
--------------------------------------------------------------------------------


组件中调用api
---------------------------------------------------------------------------------
import router from '../router';
router.push('/main')

pinia

新一代状态管理工具

src/store/index.ts
---------------------------------------------------------------------------------
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
    state: () => {
        return {
            count: 0,
        }
    },
    getters: {
        
    },
    actions: {
        
    }
})
---------------------------------------------------------------------------------

      
 main.ts
---------------------------------------------------------------------------------
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index';
import { createPinia } from 'pinia'

createApp(App).use(router).use(createPinia()).mount("#app");
---------------------------------------------------------------------------------

  
 组件中使用
---------------------------------------------------------------------------------
import {useStore} from './store/index';

let store =useStore()
store.count++

Axios

以登录注册请求封装为例

network/network.ts
---------------------------------------------------------------------------------
import axios from "axios";
export const request = axios.create({
  baseURL: "https://more.atcumt.com/",
  timeout: 5000,
});
---------------------------------------------------------------------------------


 network/login/login.ts
---------------------------------------------------------------------------------
import { ElMessage } from "element-plus";
import { request } from "../network";
interface getBody {
  studentId: string;
}
interface postBody {
  studentId: string;
  password: string;
}
export function getrequest(param: string) {
  return request.get(param);
}
export function postrequest(param: string, body: postBody) {
  return request.post(param, body);
}
request.interceptors.request.use((config) => {
  if (window.localStorage.getItem("token") != "null") {
    config!.headers!.Authorization = `Bearer ${window.localStorage.getItem(
      "token"
    )}`;
  }
  console.log(config!.headers!.Authorization);

  return config;
});
request.interceptors.response.use(
  (res) => {
    if (res.status == 200) {
      console.log(res);
      if (res.data.code == 200) {
        ElMessage.success({
          message: res.data.message,
          center: true,
        });
        return res;
      } else if (res.data.code == 1003) {
        ElMessage.error({
          message: res.data.message,
          center: true,
        });
        return;
      } else if (res.data.code == 1002) {
        ElMessage({
          message: res.data.message,
          center: true,
        });
        return;
      } else if (res.data.code == 1001) {
        console.log("1001");

        ElMessage({
          message: res.data.message,
          center: true,
        });
        return;
      }
    } else {
      console.log("error");
      ElMessage.error({
        message: res.data.message,
        center: true,
      });
    }
  },
  (err: any) => {
    ElMessage.error({
      message: "请检查网络设置",
      center: true,
    });
    return;
  }
);
---------------------------------------------------------------------------------

全局路由守卫
---------------------------------------------------------------------------------
 router.beforeEach((to,from,next) => {
    if (to.name !== 'login')
    {
        console.log('qwq');
        
        if (window.localStorage.getItem('userId')!=null)
        {
        store.getUserInfo(window.localStorage.getItem("userId") as string);
            }
        next()
        }
    else if (to.name !== 'login' && window.localStorage.getItem('token') == 'null')
    {
        console.log(window.localStorage.getItem("token"));
        
        console.log('to login');
        
        next({name:'login'})
    }
    else {
        next()
    }
})


路由守卫+pinia+axios存储用户信息 //localStorage存储get参数,pinia存信息
---------------------------------------------------------------------------------
import { defineStore } from "pinia";
import { getrequest } from "../network/login/login";
interface userInfo {
  studentId?: string;
  nickname?: string;
  avatar?: string;
  sex?: string;
  description?: string;
  follower?: number;
  fan?: number;
  isFollow?: boolean | undefined;
}

let userInfo: userInfo = {
  studentId: "",
  nickname: "",
  avatar: "",
  sex: "",
  description: "",
  follower: 0,
  fan: 0,
  isFollow: undefined,
};
export const useStore = defineStore("main", {
  state: () => {
    return {
      userInfo,
    };
  },
  getters: {},
  actions: {
    getUserInfo(studentId: string) {
      getrequest(`user/info/${studentId}`).then((res) => {
        this.userInfo.studentId = res.data.data.userInfo.studentId;
        this.userInfo.nickname = res.data.data.userInfo.nickname;
        this.userInfo.avatar = res.data.data.userInfo.avatar;
        this.userInfo.sex = res.data.data.userInfo.sex;
        this.userInfo.description = res.data.data.userInfo.description;
        this.userInfo.fan = res.data.data.userInfo.fan;
      });
    },
  },
});

Vueuse

实用Hooks库

npm i @vueuse/core

无需配置,开箱即用

完整配置

Vue3.2x+Vite2.x+Typescript+Axios+Element Plus+Pinia+Vue-router4.x+Vueuse项目完整配置

创建项目
npm init vite@latest
----------------------------------------
npm i
npm install element-plus --save 
npm install -D unplugin-vue-components unplugin-auto-import
npm install vue-router@4
npm install pinia 
npm install axios
npm install less less-loader --save-dev //less
npm i @vueuse/core

main.ts
----------------------------------------------------------------------
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index';
import { createPinia } from 'pinia'

createApp(App).use(router).use(createPinia()).mount("#app");
----------------------------------------------------------------------

router/index.ts
----------------------------------------------------------------------
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
    {
        path: '/',
        name: 'login',
        component: () => import('../components/login.vue'), 
    },
    {
        path: '/main',
        name: 'main',
        component: () => import("../components/main.vue"),
    },
]

const router = createRouter({
    history: createWebHistory(),
    routes,
})

export default router
----------------------------------------------------------------------


store/index.ts
----------------------------------------------------------------------
路由守卫+pinia+axios存储用户信息 //localStorage存储get参数,pinia存信息
---------------------------------------------------------------------------------
import { defineStore } from "pinia";
import { getrequest } from "../network/login/login";
interface userInfo {
  studentId?: string;
  nickname?: string;
  avatar?: string;
  sex?: string;
  description?: string;
  follower?: number;
  fan?: number;
  isFollow?: boolean | undefined;
}

let userInfo: userInfo = {
  studentId: "",
  nickname: "",
  avatar: "",
  sex: "",
  description: "",
  follower: 0,
  fan: 0,
  isFollow: undefined,
};
export const useStore = defineStore("main", {
  state: () => {
    return {
      userInfo,
    };
  },
  getters: {},
  actions: {
    getUserInfo(studentId: string) {
      getrequest(`user/info/${studentId}`).then((res) => {
        this.userInfo.studentId = res.data.data.userInfo.studentId;
        this.userInfo.nickname = res.data.data.userInfo.nickname;
        this.userInfo.avatar = res.data.data.userInfo.avatar;
        this.userInfo.sex = res.data.data.userInfo.sex;
        this.userInfo.description = res.data.data.userInfo.description;
        this.userInfo.fan = res.data.data.userInfo.fan;
      });
    },
  },
});
----------------------------------------------------------------------

network/network.ts
---------------------------------------------------------------------------------
import axios from "axios";
export const request = axios.create({
  baseURL: "https://more.atcumt.com/",
  timeout: 5000,
});
---------------------------------------------------------------------------------


 network/login/login.ts
---------------------------------------------------------------------------------
import { ElMessage } from "element-plus";
import { request } from "../network";
interface getBody {
  studentId: string;
}
interface postBody {
  studentId: string;
  password: string;
}
export function getrequest(param: string) {
  return request.get(param);
}
export function postrequest(param: string, body: postBody) {
  return request.post(param, body);
}
request.interceptors.request.use((config) => {
  if (window.localStorage.getItem("token") != "null") {
    config!.headers!.Authorization = `Bearer ${window.localStorage.getItem(
      "token"
    )}`;
  }
  console.log(config!.headers!.Authorization);

  return config;
});
request.interceptors.response.use(
  (res) => {
    if (res.status == 200) {
      console.log(res);
      if (res.data.code == 200) {
        ElMessage.success({
          message: res.data.message,
          center: true,
        });
        return res;
      } else if (res.data.code == 1003) {
        ElMessage.error({
          message: res.data.message,
          center: true,
        });
        return;
      } else if (res.data.code == 1002) {
        ElMessage({
          message: res.data.message,
          center: true,
        });
        return;
      } else if (res.data.code == 1001) {
        console.log("1001");

        ElMessage({
          message: res.data.message,
          center: true,
        });
        return;
      }
    } else {
      console.log("error");
      ElMessage.error({
        message: res.data.message,
        center: true,
      });
    }
  },
  (err: any) => {
    ElMessage.error({
      message: "请检查网络设置",
      center: true,
    });
    return;
  }
);
---------------------------------------------------------------------------------

全局路由守卫
---------------------------------------------------------------------------------
 router.beforeEach((to,from,next) => {
    if (to.name !== 'login')
    {
        console.log('qwq');
        
        if (window.localStorage.getItem('userId')!=null)
        {
        store.getUserInfo(window.localStorage.getItem("userId") as string);
            }
        next()
        }
    else if (to.name !== 'login' && window.localStorage.getItem('token') == 'null')
    {
        console.log(window.localStorage.getItem("token"));
        
        console.log('to login');
        
        next({name:'login'})
    }
    else {
        next()
    }
})


vite.config.js //自动导入插件配置
----------------------------------------------------------------------
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'

export default defineConfig({
  plugins: [vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),]
})
----------------------------------------------------------------------

踩坑

Element Plus的css无故丢失?

正确操作1/25 找到css的CDN文件扒下来

cdn.jsdelivr.net/npm/element…

//index.html
<link rel="stylesheet" href="./src/assets/css/index.css" />

::v-deep报错

改写法弃用,转化为 :deep( )

动态img :src不加载

vite中不要使用绝对路径!打包有问题

//webpack
require('url')
//vite
new URL('url', import.meta.url).href

请求用户信息请求头格式

Bearer空格token

请求用户信息存储

local存get请求的参数,pinia存用户信息

Element Plus的icon只能手动按需引入

TS中遍历对象给变量赋值报错

解决方法:在tsconfig.json中添加

"suppressImplicitAnyIndexErrors":true,

echarts图标部署到服务器二次渲染失效

解决方法每次挂载前都先移除再添加

document.querySelector('.zhexian')?.removeAttribute('_echarts_instance_')
document.querySelector(".huan")?.removeAttribute("_echarts_instance_");
 var myChart = echarts.init(
      document.querySelector(".zhexian") as HTMLElement
 )
 ......

el-icon配合Font awesome字体图标动态使用 血泪教训!!

Font awesome

很多办法如动态组件component :is='' 都渲染不出来

el-menu配置经验

//基本架构
<el-menu>
  <template v-for>
    <template v-if>  //一级栏判断:sub-menu类型
  		<el-sub-menu>
      	<el-icon><i class=''>字体图标<el-icon>
       	<template #title>{{}} </template>
		<tempalte> //submenu类型下的二级菜单...
      <el-menu-item>
       icon
				<span>{{}}</span>
       </el-menu-item>
		</template>
     	 </el-sub-menu>
     </tempalte>

	   <template v-else> //一级栏判断: menu-item类型
     	 <el-menu-item>
       icon
				<span>{{}}</span>
//用带#title的插槽会出现故障动画,用span会丢失缩进去后的黑框提示
       </el-menu-item>
     </tempalte>
  </el-menu>

弹性盒子下的 justfy-content:right 在微信和360浏览器失效

改用 justify-content: flex-end 兼容性更强

面包屑配置

    <el-breadcrumb separator="" v-if="router.currentRoute.value.name == '首页'">
      <el-breadcrumb-item
        ><a
          :href="router.currentRoute.value.matched[1].path"
          style="font-size: 27px"
          >首页</a
        ></el-breadcrumb-item
      >
      <el-breadcrumb-item></el-breadcrumb-item>
    </el-breadcrumb>

    <el-breadcrumb
      separator="/"
      v-if="router.currentRoute.value.name != '首页'"
    >
      <el-breadcrumb-item
        :to="{ path: router.currentRoute.value.matched[0].path }"
        >首页</el-breadcrumb-item
      >
      <el-breadcrumb-item
        ><a :href="router.currentRoute.value.matched[1].path">{{
          router.currentRoute.value.name
        }}</a></el-breadcrumb-item
      >
    </el-breadcrumb>

reactive更新数组页面未及时渲染

重要用 ref 或者push方法

路由跳转左侧menu不跳转

设置 : default-active =" router . currentRoute . value . path " 属性

跨域:被拒绝的patch请求

简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求称为"预检"请求(preflight)

patch请求属于非简单请求

预检请求

预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。

Access-Control-Allow-Methods:必选
它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
以前失败的原因:后端未加allow methods