1# 方式一:推荐(可选安装router、pinia等)
2npm create vue@latest
3# 初始化配置
4✔ Project name: … vue3-vite-app
5✔ Add TypeScript? … Yes
6✔ Add JSX Support? … No
7✔ Add Vue Router for Single Page Application development? … Yes
8✔ Add Pinia for state management? … Yes
9✔ Add Vitest for Unit Testing? … No
10✔ Add an End-to-End Testing Solution? › No
11✔ Add ESLint for code quality? … Yes
12✔ Add Prettier for code formatting? … Yes
13
14
15# 方式二:vite初始化项目(简洁版)
16npm create vite@latest
17# 初始化配置
18✔ Project name: … vue3-vite-app
19✔ Select a framework: › Vue
20✔ Select a variant: › TypeScript
1npm i vue-router@next -S
2npm i axios
3npm i less
4npm i pinia
5npm i @types/node -D
6# element-plus
7npm install element-plus --save
8# 按需引入
9npm install -D unplugin-vue-components unplugin-auto-import
10
11# cookie
12npm i js-cookie # js版
13npm i @types/js-cookie # ts版
/src/router/index.ts
1import { createRouter, createWebHashHistory } from "vue-router";
2
3const routes = [
4 {
5 path: "/",
6 name: "home",
7 component :() => import("../views/home/home.vue")
8 },
9 {
10 path: "/login",
11 name: "login",
12 component: () => import('../views/login/login.vue')
13
14 }
15]
16
17const router = createRouter({
18 history: createWebHashHistory(),
19 routes,
20})
21
22export default router
/src/main.ts
1import { createApp } from 'vue'
2import App from './App.vue'
3import router from './router'
4
5createApp(App).use(router).mount('#app')
找不到模块“…/views/xxxx.vue”或其相应的类型声明。
env.d.ts
1declare module "*.vue" {
2 import type { DefineComponent } from "vue";
3 // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
4 const component: DefineComponent<{}, {}, any>;
5 export default component;
6}
1// api/index.ts
2import axios from 'axios'
3const instance = axios.create({
4 baseURL: '',
5 timeout: 1000,
6 headers: { 'X-Custom-Header': '' }
7});
8
9instance.interceptors.request.use(config => {
10 // 判断token
11 return config
12}, err => {
13 return Promise.reject(err)
14})
15instance.interceptors.response.use(res => {
16 return res.data
17}, err => {
18 return Promise.reject(err)
19})
20
21export default instance
1// vite.config.ts
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4import * as path from 'path'
5const resolve = (dir: string) => path.join(__dirname, dir)
6
7export default defineConfig({
8 plugins: [ vue() ],
9 resolve: {
10 alias: {
11 '@': resolve('src'),
12 comps: resolve('src/components'),
13 views: resolve('src/views'),
14 }
15 }
16})
1// tsconfig.json
2{
3 "compilerOptions": {
4 "target": "ESNext",
5 "useDefineForClassFields": true,
6 "module": "ESNext",
7 "moduleResolution": "Node",
8 "strict": true,
9 "jsx": "preserve",
10 "resolveJsonModule": true,
11 "isolatedModules": true,
12 "esModuleInterop": true,
13 "lib": ["ESNext", "DOM"],
14 "skipLibCheck": true,
15 "noEmit": true,
16 "baseUrl": "",
17 "paths": {
18 "@/*":["src/*"]
19 }
20
21 },
22 "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
23 "references": [{ "path": "./tsconfig.node.json" }]
24}
1// vite.config.ts
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4
5export default defineConfig({
6 plugins: [ vue() ],
7 server:{
8 // Vue3在这里配置跨域
9 proxy: {
10 "/api": {
11 target: "",
12 changeOrigin: true,
13 rewrite: (path) => path.replace(/^\/api/, "")
14 }
15 }
16 }
17})
1// vite.config.ts
2import { defineConfig } from 'vite'
3import vue from '@vitejs/plugin-vue'
4
5// element-plus 按需自动导入
6import AutoImport from 'unplugin-auto-import/vite'
7import Components from 'unplugin-vue-components/vite'
8import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
9
10
11
12// https://vitejs.dev/config/
13export default defineConfig({
14 plugins: [
15 vue(),
16 AutoImport({
17 resolvers: [ElementPlusResolver()],
18 }),
19 Components({
20 resolvers: [ElementPlusResolver()],
21 }),
22 ]
23})
localStorage中只能存字符串,不能存对象,否则拿不到值
因此,在存入时要用window.localStorage.setItem('key', JSON.stringify(object))将对象转为字符串,在获取时要用JSON.parse(localStorage.getItem('key'))转换再拿到值
1// 设置
2localStorage.setItem('token', token);
3
4// 读取
5localStorage.getItem('token');
6
7// 移除指定
8localStorage.removeItem('token');
9
10// 移除所有
11localStorage.clear();
1// 设置
2sessionStorage.setItem('token', token);
3
4// 读取
5sessionStorage.getItem('token');
6
7// 移除指定
8sessionStorage.removeItem('token');
9
10// 移除所有
11sessionStorage.clear();
1import Cookie from 'js-cookie'
2
3// 设置
4Cookie.set("token", token, { expires: 7 })
5
6// 读取
7Cookie.get('token')
8
9// 删除
10Cookie.remove('token')
1// 路由拦截:页面刷新100%会执行的位置
2router.beforeEach(async (to, from, next) => {
3 const token = Cookie.get("token");
4 const store = useStore()
5 if (token && store.menus.length === 0) {
6 const getAdminInfo = await getAdminInfoApi()
7 store.setMenu(getAdminInfo.data.menus);
8 }
9 next()
10});
权限管理的核心就是addRoute
1import { createRouter, createWebHashHistory } from "vue-router";
2import Cookie from "js-cookie";
3
4import useStore from "@/store";
5import { getAdminInfoApi } from "@/api/api";
6
7const routes = [
8 {
9 path: "/login",
10 name: "login",
11 component: () => import(/* webpackChunkName: login" */ "../views/login/login.vue"),
12 }
13];
14
15const router = createRouter({
16 history: createWebHashHistory(), // createWebHashHistory() hash模式 createWebHistory() history
17 routes,
18});
19
20// 动态加载路由
21const getRouter = () => {
22 let children = new Array();
23 // 拿到用户权限列表
24 const store = useStore();
25 let menus = store.getMenu;
26 menus.map((item) => {
27 item.children!.map((v) => {
28 let itemRouter = {
29 path: `/${item.name}/${v.name}`,
30 name: v.name,
31 component: () => import(`../views/${item.name}/${v.name}.vue`),
32 }
33 children.push(itemRouter);
34 });
35 });
36
37 let myRoute = {
38 path: '/home',
39 name: 'home',
40 component: () => import(/* webpackChunkName: "home" */ "../views/home/home.vue"),
41 children:children
42 }
43 // 动态添加路由
44 router.addRoute(myRoute);
45};
46
47// 路由拦截 页面刷新100%会执行的位置
48router.beforeEach(async (to, from, next) => {
49 const store = useStore();
50 const token = Cookie.get("token");
51 // 用户登录了,但是页面刷新导致pinia数据丢失了
52 if (token && store.menus.length === 0) {
53 // 重新发请求
54 const getAdminInfo = await getAdminInfoApi();
55 store.setMenu(getAdminInfo.data.menus);
56 // 重新生成动态路由
57 getRouter();
58 next(to); // 这里的next()不能为空
59 } else if (
60 token &&
61 store.menus.length !== 0 &&
62 from.path == "/login" &&
63 to.path == "/home"
64 ) {
65 // 第一次刚刚成功登录的时候,只需要生成一下路由即可
66 getRouter();
67 next("/ums/admin");
68 } else if (!token && to.path !== "/login") {
69 // 未登录,就想访问其他页
70 next("/login");
71 } else if (token && to.path == "/login") {
72 // 已登录,还想访问登录页
73 next(from);
74 } else {
75 next();
76 }
77});
78export default router;
1<!-- element ui 第一种 -->
2<el-upload
3 class="avatar-uploader"
4 action="http://kumanxuan1.f3322.net:8360/admin/upload/goodNewPic"
5 :show-file-list="false"
6 :on-success="onSuccess"
7 :before-upload="onBefore"
8 name="good_pic"
9>
10 <img v-if="imageUrl" :src="imageUrl" class="avatar" />
11 <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
12</el-upload>
1<!-- element ui 第二种 常用-->
2<el-upload
3 class="avatar-uploader"
4 action="http://kumanxuan1.f3322.net:8360/admin/upload/goodNewPic"
5 :show-file-list="false"
6 :http-request="twoUpload"
7>
8 <img v-if="imageUrl2" :src="imageUrl2" class="avatar" />
9 <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
10</el-upload>
1<!-- 第三种 终极通用版 -->
2<input type="file" ref="file" />
3<el-button @click="lastUpload">上传</el-button>