init
This commit is contained in:
commit
5592d9905b
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
mock/login.js
Normal file
15
mock/login.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: "/api/login",
|
||||||
|
method: "post",
|
||||||
|
timeout: 1000,
|
||||||
|
response: {
|
||||||
|
code: 200,
|
||||||
|
message: "登录成功",
|
||||||
|
data: {
|
||||||
|
token: "@word(50, 100)", // @word()是mockjs的语法
|
||||||
|
refresh_token: "@word(50, 100)", // refresh_token是用来重新生成token的
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
25
mock/test.js
Normal file
25
mock/test.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: "/api/get",
|
||||||
|
method: "get",
|
||||||
|
response: ({ query }) => {
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
name: "hello world",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/api/post",
|
||||||
|
method: "post",
|
||||||
|
timeout: 2000,
|
||||||
|
response: {
|
||||||
|
code: 0,
|
||||||
|
data: {
|
||||||
|
name: "hello world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
1308
package-lock.json
generated
Normal file
1308
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "ec-admin-vue3",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"mock": "vite --mode mock",
|
||||||
|
"build": "vite build",
|
||||||
|
"serve": "vite preview"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"vue": "^3.0.5",
|
||||||
|
"vue-router": "^4.0.5",
|
||||||
|
"vuex": "^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^1.1.5",
|
||||||
|
"@vue/compiler-sfc": "^3.0.5",
|
||||||
|
"autoprefixer": "^10.2.5",
|
||||||
|
"element-plus": "^1.0.2-beta.35",
|
||||||
|
"less": "^4.1.1",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"vite": "^2.1.0",
|
||||||
|
"vite-plugin-mock": "^2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
// 兼容浏览器,添加前缀
|
||||||
|
'autoprefixer': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
13
src/App.vue
Normal file
13
src/App.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
<style lang="less">
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
10
src/api/login.js
Normal file
10
src/api/login.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 登录接口
|
||||||
|
export const Login = data => {
|
||||||
|
return request({
|
||||||
|
url: "/api/login",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
30
src/components/HelloWorld.vue
Normal file
30
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://vitejs.dev/guide/features.html" target="_blank">Vite Documentation</a> |
|
||||||
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button @click="state.count++">count is: {{ state.count }}</button>
|
||||||
|
<p>
|
||||||
|
Edit
|
||||||
|
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, reactive } from 'vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
msg: String
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({ count: 0 })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
11
src/main.js
Normal file
11
src/main.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
// 引入路由
|
||||||
|
import router from './router'
|
||||||
|
// 引入store
|
||||||
|
import store from './store'
|
||||||
|
// 引入element-plus
|
||||||
|
import ElementPlus from "element-plus";
|
||||||
|
import "element-plus/lib/theme-chalk/index.css";
|
||||||
|
|
||||||
|
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
|
||||||
33
src/router/index.js
Normal file
33
src/router/index.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// index.js
|
||||||
|
import { createRouter, createWebHistory } from "vue-router"
|
||||||
|
import home from './modules/home'
|
||||||
|
import login from './modules/login'
|
||||||
|
|
||||||
|
import { TOKEN } from '@/store/modules/user'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/home'
|
||||||
|
},
|
||||||
|
...home,
|
||||||
|
...login
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// vue-router4的路由守卫不再是通过next放行,而是通过return返回false或者一个路由地址
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
if (!window.localStorage[TOKEN] && to.name !== 'login') {
|
||||||
|
return {
|
||||||
|
name: 'login',
|
||||||
|
query: {
|
||||||
|
redirect: to.path // redirect是指登录之后可以跳回到redirect指定的页面
|
||||||
|
},
|
||||||
|
replace: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router;
|
||||||
10
src/router/modules/home.js
Normal file
10
src/router/modules/home.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// home.js
|
||||||
|
const Home = () => import("@/views/home/index.vue");
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
name: "home",
|
||||||
|
component: Home,
|
||||||
|
}
|
||||||
|
]
|
||||||
10
src/router/modules/login.js
Normal file
10
src/router/modules/login.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// login.js
|
||||||
|
const Login = () => import("@/views/login/index.vue");
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "login",
|
||||||
|
component: Login,
|
||||||
|
}
|
||||||
|
]
|
||||||
9
src/store/index.js
Normal file
9
src/store/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
//index.js
|
||||||
|
import { createStore } from "vuex";
|
||||||
|
import user from "./modules/user";
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
modules: {
|
||||||
|
user,
|
||||||
|
},
|
||||||
|
});
|
||||||
22
src/store/modules/user.js
Normal file
22
src/store/modules/user.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { getItem, setItem, removeItem } from "@/utils/storage"; //getItem和setItem是封装的操作localStorage的方法
|
||||||
|
export const TOKEN = "TOKEN";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
authorization: getItem(TOKEN),
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setToken (state, data) {
|
||||||
|
state.authorization = data;
|
||||||
|
// 保存到localStorage
|
||||||
|
setItem(TOKEN, data);
|
||||||
|
},
|
||||||
|
clearToken (state) {
|
||||||
|
state.authorization = '';
|
||||||
|
// 保存到localStorage
|
||||||
|
removeItem(TOKEN);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
};
|
||||||
80
src/utils/request.js
Normal file
80
src/utils/request.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import store from "@/store";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: "/",
|
||||||
|
timeout: 10000,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拦截请求
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const authorization = store.state.user;
|
||||||
|
if (authorization) {
|
||||||
|
config.headers.Authorization = `Bearer ${authorization.token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// console.log(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 拦截响应
|
||||||
|
service.interceptors.response.use(
|
||||||
|
// 响应成功进入第1个函数,该函数的参数是响应对象
|
||||||
|
(response) => {
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
// 响应失败进入第2个函数,该函数的参数是错误对象
|
||||||
|
async (error) => {
|
||||||
|
// 如果响应码是 401 ,则请求获取新的 token
|
||||||
|
// 响应拦截器中的 error 就是那个响应的错误对象
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
// 校验是否有 refresh_token
|
||||||
|
const { authorization } = store.state;
|
||||||
|
if (!authorization || !authorization.refresh_token) {
|
||||||
|
router.push("/login");
|
||||||
|
|
||||||
|
// 代码不要往后执行了
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果有refresh_token,则请求获取新的 token
|
||||||
|
try {
|
||||||
|
const res = await axios({
|
||||||
|
method: "PUT",
|
||||||
|
url: "/api/authorizations",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${authorization.refresh_token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 如果获取成功,则把新的 token 更新到容器中
|
||||||
|
// console.log('刷新 token 成功', res)
|
||||||
|
store.commit("setToken", {
|
||||||
|
token: res.data.data.token, // 最新获取的可用 token
|
||||||
|
refresh_token: authorization.refresh_token, // 还是原来的 refresh_token
|
||||||
|
});
|
||||||
|
// 把之前失败的用户请求继续发出去
|
||||||
|
// config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
|
||||||
|
// return 把 request 的请求结果继续返回给发请求的具体位置
|
||||||
|
return service(error.config);
|
||||||
|
} catch (err) {
|
||||||
|
// 如果获取失败,直接跳转 登录页
|
||||||
|
// console.log('请求刷线 token 失败', err)
|
||||||
|
router.push("/login");
|
||||||
|
// 清除token
|
||||||
|
store.commit("clearToken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.error(error.response.message);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default service;
|
||||||
20
src/utils/storage.js
Normal file
20
src/utils/storage.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const getItem = name => {
|
||||||
|
const data = window.localStorage.getItem(name);
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setItem = (name, value) => {
|
||||||
|
if (typeof value === "object") {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.localStorage.setItem(name, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeItem = name => {
|
||||||
|
window.localStorage.removeItem(name);
|
||||||
|
};
|
||||||
3
src/views/home/index.vue
Normal file
3
src/views/home/index.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
home
|
||||||
|
</template>
|
||||||
152
src/views/login/index.vue
Normal file
152
src/views/login/index.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<el-form
|
||||||
|
class="form"
|
||||||
|
:model="model"
|
||||||
|
:rules="rules"
|
||||||
|
ref="loginForm"
|
||||||
|
>
|
||||||
|
<h1 class="title">欢迎登录ERP系统</h1>
|
||||||
|
<el-form-item prop="userName">
|
||||||
|
<el-input
|
||||||
|
class="text"
|
||||||
|
v-model="model.userName"
|
||||||
|
prefix-icon="el-icon-user-solid"
|
||||||
|
clearable
|
||||||
|
placeholder="用户名"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
class="text"
|
||||||
|
v-model="model.password"
|
||||||
|
prefix-icon="el-icon-lock"
|
||||||
|
show-password
|
||||||
|
clearable
|
||||||
|
@input.enter="submit"
|
||||||
|
placeholder="密码"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
class="btn"
|
||||||
|
@click="submit"
|
||||||
|
>{{btnText}}</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
reactive,
|
||||||
|
toRefs,
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
} from "vue";
|
||||||
|
import { Login } from "@/api/login";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { useRouter, useRoute } from "vue-router";
|
||||||
|
export default defineComponent({
|
||||||
|
name: "login",
|
||||||
|
setup() {
|
||||||
|
const { ctx } = getCurrentInstance(); // 可以把ctx当成vue2中的this
|
||||||
|
const store = useStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const state = reactive({
|
||||||
|
model: {
|
||||||
|
userName: "admin",
|
||||||
|
password: "123456",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
userName: [
|
||||||
|
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
|
{
|
||||||
|
min: 6,
|
||||||
|
max: 12,
|
||||||
|
message: "长度在 6 到 12 个字符",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
btnText: computed(() => (state.loading ? "登录中..." : "登录")),
|
||||||
|
loginForm: ref(null),
|
||||||
|
submit: () => {
|
||||||
|
if (state.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.loginForm.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
state.loading = true;
|
||||||
|
const { code, data, message } = await Login(state.model);
|
||||||
|
state.loading = false;
|
||||||
|
if (+code === 200) {
|
||||||
|
ctx.$message.success({
|
||||||
|
message: "登录成功",
|
||||||
|
duration: 500,
|
||||||
|
onClose: () => {
|
||||||
|
const targetPath = route.query.redirect;
|
||||||
|
router.push(!!targetPath ? targetPath : "/");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
store.commit("user/setToken", data);
|
||||||
|
} else {
|
||||||
|
ctx.$message.error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.login {
|
||||||
|
transition: transform 1s;
|
||||||
|
transform: scale(1);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #2d3a4b;
|
||||||
|
.form {
|
||||||
|
width: 520px;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 160px auto 0;
|
||||||
|
.title {
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 0 0 24px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
font-size: 16px;
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
25
vite.config.js
Normal file
25
vite.config.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from "path";
|
||||||
|
import { viteMockServe } from "vite-plugin-mock";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default env => {
|
||||||
|
// console.log(111, env);
|
||||||
|
|
||||||
|
return defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteMockServe({
|
||||||
|
mockPath: "mock", // 指定mock目录中的文件全部是mock接口
|
||||||
|
localEnabled: env.mode === "mock", // 指定在mock模式下才启动mock服务(可以在package.json的启动命令中指定mode为mock)
|
||||||
|
supportTs: false, // mockPath目录中的文件是否支持ts文件,现在我们不使用ts,所以设为false
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
791
项目开发手册.md
Normal file
791
项目开发手册.md
Normal file
@ -0,0 +1,791 @@
|
|||||||
|
# Vue3项目开发手册
|
||||||
|
|
||||||
|
## 初始化项目
|
||||||
|
|
||||||
|
### 技术选型
|
||||||
|
|
||||||
|
Vue3 + Element-plus + Vite
|
||||||
|
|
||||||
|
- Vue3
|
||||||
|
|
||||||
|
>- 支持 Vue2 的大多数特性
|
||||||
|
>- 支持 Typescript
|
||||||
|
>- 使用 Proxy 代替 defineProperty 实现数据响应式
|
||||||
|
>- 重写虚拟 DOM 的实现和 Tree-Shaking
|
||||||
|
>- [Vue3中文官网](https://vue3js.cn/docs/zh/guide/introduction.html)
|
||||||
|
|
||||||
|
- Element-plus
|
||||||
|
|
||||||
|
> - 支持Vue3的element-ui组件库
|
||||||
|
>
|
||||||
|
> > element-ui只支持到vue2,element-plus可以支持vue3
|
||||||
|
>
|
||||||
|
> - [Element-plus中文官网](https://element-plus.gitee.io/#/zh-CN)
|
||||||
|
|
||||||
|
- Vite
|
||||||
|
|
||||||
|
>- 是一种新型前端构建工具,能够显著提升前端开发体验。
|
||||||
|
>
|
||||||
|
>- [Vite中文官网](https://www.pipipi.net/vite/)
|
||||||
|
|
||||||
|
### 创建项目
|
||||||
|
|
||||||
|
> 我们将使用vite和vue3手动进行前端项目架构。为方便大家从vue2过渡到vue3,本项目我们先不使用ts。
|
||||||
|
>
|
||||||
|
> ts项目我们后面再讲,并且我们将会使用更加集成化的框架,就不需要手动搭建项目了。
|
||||||
|
|
||||||
|
使用vite的命令创建项目:
|
||||||
|
|
||||||
|
> 需要 Node.js 版本 >= 12.0.0
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# npm 6.x
|
||||||
|
npm init @vitejs/app erp-vue3 --template vue
|
||||||
|
|
||||||
|
# npm 7+, 需要额外的双横线:
|
||||||
|
npm init @vitejs/app erp-vue3 -- --template vue
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn create @vitejs/app erp-vue3 --template vue
|
||||||
|
```
|
||||||
|
|
||||||
|
- npm init @vitejs/app 是固定的写法
|
||||||
|
|
||||||
|
- erp-vue3 是项目名称
|
||||||
|
|
||||||
|
- --template vue 是指定模板预设。指定vue就是使用vue3并且不使用ts。
|
||||||
|
|
||||||
|
> 支持的模板预设如下,有兴趣可以挨个试一下
|
||||||
|
>
|
||||||
|
> - `vanilla`
|
||||||
|
> - `vue`
|
||||||
|
> - `vue-ts`
|
||||||
|
> - `react`
|
||||||
|
> - `react-ts`
|
||||||
|
> - `preact`
|
||||||
|
> - `preact-ts`
|
||||||
|
> - `lit-element`
|
||||||
|
> - `lit-element-ts`
|
||||||
|
|
||||||
|
创建完后,会提示我们进入项目目录,安装依赖,运行项目:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd erp-vue3
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
最后我们来看下项目结构
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
├─node_modules -----------依赖包
|
||||||
|
├─public -----------------静态文件
|
||||||
|
├─index.html--------------主页面
|
||||||
|
├─src---------------------核心文件目录(我们开发的代码都在src目录)
|
||||||
|
| ├─assets---------------静态资源(图片,字体,css,js等)
|
||||||
|
| ├─components-----------公共组件
|
||||||
|
| ├─App.vue--------------根组件
|
||||||
|
| ├─main.js--------------入口文件
|
||||||
|
├─package.json------------项目基本信息和依赖包信息
|
||||||
|
├─vite.config.js----------配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完善项目架构
|
||||||
|
|
||||||
|
> 目前项目中还没有路由、状态管理、UI组件库、ajax库
|
||||||
|
|
||||||
|
### 路由
|
||||||
|
|
||||||
|
- 在src目录中创建views目录,用来存放页面
|
||||||
|
|
||||||
|
> 我们使用模块化的方式来管理页面,比如用户管理、权限管理等模块,每个模块下可能有多个页面,所以每个模块都要创建一个目录
|
||||||
|
|
||||||
|
- 在views目录中创建home目录(代表home模块),home目录中创建index.vue,index.vue代表home模块的主页面
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
home
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 同样的方式创建一个login模块
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
login
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 安装路由vue-router
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 安装最新版的vue-router
|
||||||
|
npm install vue-router@next
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在src目录中创建router目录,用来存放路由配置文件
|
||||||
|
|
||||||
|
> 路由配置就是配置views模块中的页面通过什么地址来访问,路由我们也使用模块化管理,views中有多少模块就创建多少路由配置文件,此外还需要一个index.js来统一分配路由模块
|
||||||
|
|
||||||
|
- router目录中创建modules目录, modules目录中创建home.js和login.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// home.js
|
||||||
|
const Home = () => import("../../views/home/index.vue");
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
name: "home",
|
||||||
|
component: Home,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// login.js
|
||||||
|
const Login = () => import("../../views/login/index.vue");
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "login",
|
||||||
|
component: Login,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- router目录中创建index.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// index.js
|
||||||
|
import { createRouter, createWebHistory } from "vue-router"
|
||||||
|
import home from './modules/home'
|
||||||
|
import login from './modules/login'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/home'
|
||||||
|
},
|
||||||
|
...home,
|
||||||
|
...login
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 挂载路由
|
||||||
|
|
||||||
|
> 在main.js中挂载路由
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
// 引入路由
|
||||||
|
import router from './router'
|
||||||
|
// 使用use注册路由
|
||||||
|
createApp(App).use(router).mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
|
- 配置alias路径别名
|
||||||
|
|
||||||
|
> 我们可以看到上面的引入路径比较深,这个时候可以配置一个src目录的别名,需要在vite.config.js中配置
|
||||||
|
|
||||||
|
```js
|
||||||
|
// vite.config.js
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
然后我们就可以修改路由组件的引用路径了,在router/modules/home.js中
|
||||||
|
|
||||||
|
```js
|
||||||
|
// home.js
|
||||||
|
// const Home = () => import("../../views/home/index.vue");
|
||||||
|
const Home = () => import("@/views/home/index.vue");
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: "/home",
|
||||||
|
name: "home",
|
||||||
|
component: Home,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
|
||||||
|
- 安装vuex状态管理插件
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 安装最新版的vuex
|
||||||
|
npm install vuex@next
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在src目录中创建store目录,用来存放状态模块
|
||||||
|
|
||||||
|
> 状态管理也使用模块化的方式
|
||||||
|
|
||||||
|
- 在store目录中创建modules目录,modules中创建一个模块,比如叫user.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// user.js
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在store目录中创建index.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
//index.js
|
||||||
|
import { createStore } from "vuex";
|
||||||
|
import user from "./modules/user";
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
modules: {
|
||||||
|
user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 挂载store
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
// 引入路由
|
||||||
|
import router from './router'
|
||||||
|
// 引入store
|
||||||
|
import store from './store'
|
||||||
|
// 使用use注册路由和store
|
||||||
|
createApp(App).use(router).use(store).mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 组件库 Element Plus
|
||||||
|
|
||||||
|
- 安装element-plus,并且在main.js中引入
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install element-plus
|
||||||
|
```
|
||||||
|
|
||||||
|
引入element-plus
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
// 引入路由
|
||||||
|
import router from './router'
|
||||||
|
// 引入store
|
||||||
|
import store from './store'
|
||||||
|
// 引入element-plus
|
||||||
|
import ElementPlus from "element-plus";
|
||||||
|
import "element-plus/lib/theme-chalk/index.css";
|
||||||
|
|
||||||
|
// 使用use注册路由和store
|
||||||
|
createApp(App).use(router).use(store).use(ElementPlus).mount('#app')
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- vue3中如何全局调用element-plus中的提示插件
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {getCurrentInstance} from 'vue';
|
||||||
|
|
||||||
|
setup () {
|
||||||
|
const { ctx } = getCurrentInstance(); // 可以把ctx当成vue2中的this
|
||||||
|
ctx.$message.success("yes")
|
||||||
|
ctx.$loading()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 接口管理
|
||||||
|
|
||||||
|
- 安装axios
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install axios
|
||||||
|
```
|
||||||
|
|
||||||
|
- 接口管理
|
||||||
|
|
||||||
|
> 项目中我们调用接口的时候,有可能有很多页面都会调用同一个接口,所以我们把所有的接口统一进行模块化管理
|
||||||
|
|
||||||
|
- 在src目录中创建api目录,用来存放所有的接口
|
||||||
|
|
||||||
|
- 在api目录中创建一个模块login.js,该文件包括跟登录注册相关的所有接口
|
||||||
|
|
||||||
|
```js
|
||||||
|
// login.js
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// 登录接口
|
||||||
|
export const Login = data => {
|
||||||
|
return axios.request({
|
||||||
|
url: "/api/login",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其它接口...
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在页面中调用登录接口
|
||||||
|
|
||||||
|
> 在views/login/index.vue中:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
login
|
||||||
|
<el-button type="primary" @click="login">登录</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, onMounted } from "vue";
|
||||||
|
import { Login } from "@/api/login";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "Login",
|
||||||
|
setup() {
|
||||||
|
const login = async () => {
|
||||||
|
const res = await Login({
|
||||||
|
userName: 'admin',
|
||||||
|
password: '123456'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### mock管理
|
||||||
|
|
||||||
|
> 上面调用登录接口,其实是404,因为现在还没有后端接口
|
||||||
|
>
|
||||||
|
> 当后端接口没有开发完成的时候,前端就需要根据接口文档mock数据进行开发
|
||||||
|
>
|
||||||
|
> vite中可以使用vite-plugin-mock插件创建mock服务
|
||||||
|
|
||||||
|
- 安装vite-plugin-mock
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install -D mockjs vite-plugin-mock
|
||||||
|
```
|
||||||
|
|
||||||
|
- 配置mock,需要在vite.config.js中配置
|
||||||
|
|
||||||
|
```js
|
||||||
|
// vite.config.js
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from "path";
|
||||||
|
import { viteMockServe } from "vite-plugin-mock";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default env => {
|
||||||
|
// console.log(env);
|
||||||
|
|
||||||
|
return defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteMockServe({
|
||||||
|
mockPath: "mock", // 指定mock目录中的文件全部是mock接口
|
||||||
|
localEnabled: env.mode === "mock", // 指定在mock模式下才启动mock服务(可以在package.json的启动命令中指定mode为mock)
|
||||||
|
supportTs: false, // mockPath目录中的文件是否支持ts文件,现在我们不使用ts,所以设为false
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- package.json中配置mode为mock的启动命令
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ...
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"mock": "vite --mode mock",
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
重新启动项目
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run mock
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在根目录中创建mock目录,用来存放mock接口
|
||||||
|
|
||||||
|
在mock目录中创建一个test.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// test.js
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: "/api/get",
|
||||||
|
method: "get",
|
||||||
|
response: {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
name: "hello world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
这个时候我们访问`http://localhost:3000/api/get`,可以看到返回的mock数据了
|
||||||
|
|
||||||
|
|
||||||
|
#### 封装axios
|
||||||
|
|
||||||
|
> 当我们登录成功之后,后端其实会返回一个token(token就是用来做登录认证的),后续所有请求都需要带上这个token,后端校验token是否有效
|
||||||
|
>
|
||||||
|
> 这个时候,前端要做的就是把token保存在本地存储中,然后所有的请求都要增加一个自定义请求头,用来传递token
|
||||||
|
>
|
||||||
|
> 为了更友好的对请求进行控制(比如添加全局请求头、统一处理错误),我们对axios进行封装,拦截请求和响应
|
||||||
|
|
||||||
|
- 在登录的mock接口中返回token
|
||||||
|
|
||||||
|
```js
|
||||||
|
// mock/login.js
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: "/api/login",
|
||||||
|
method: "post",
|
||||||
|
timeout: 1000,
|
||||||
|
response: {
|
||||||
|
code: 200,
|
||||||
|
message: "登录成功",
|
||||||
|
data: {
|
||||||
|
token: "@word(50, 100)", // @word()是mockjs的语法
|
||||||
|
refresh_token: "@word(50, 100)", // refresh_token是用来重新生成token的
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- 登录之后,把token保存在localStorage和vuex中
|
||||||
|
|
||||||
|
> 保存在vuex中为了更好的进行响应式控制
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 登录
|
||||||
|
const login = async () => {
|
||||||
|
const { code, data, message } = await Login({
|
||||||
|
userName: 'admin',
|
||||||
|
password: '123456'
|
||||||
|
})
|
||||||
|
if (+code === 200) {
|
||||||
|
store.commit("user/setToken", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
store/modules/user.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { getItem, setItem, removeItem } from "@/utils/storage"; //getItem和setItem是封装的操作localStorage的方法
|
||||||
|
export const TOKEN = "TOKEN";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
authorization: getItem(TOKEN),
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setToken(state, data) {
|
||||||
|
state.authorization = data;
|
||||||
|
// 保存到localStorage
|
||||||
|
setItem(TOKEN, data);
|
||||||
|
},
|
||||||
|
clearToken (state) {
|
||||||
|
state.authorization = '';
|
||||||
|
// 保存到localStorage
|
||||||
|
removeItem(TOKEN);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
在src中创建utils目录,在utils中创建storage.js
|
||||||
|
|
||||||
|
> utils目录主要是用来存放一些常用工具函数
|
||||||
|
|
||||||
|
```js
|
||||||
|
// storage.js
|
||||||
|
export const getItem = name => {
|
||||||
|
const data = window.localStorage.getItem(name);
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (err) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setItem = (name, value) => {
|
||||||
|
if (typeof value === "object") {
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.localStorage.setItem(name, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeItem = name => {
|
||||||
|
window.localStorage.removeItem(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在utils目录中创建request.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// request.js
|
||||||
|
import axios from "axios";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import store from "@/store";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: "/",
|
||||||
|
timeout: 10000,
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拦截请求
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const authorization = store.state.user;
|
||||||
|
if (authorization) {
|
||||||
|
config.headers.Authorization = `Bearer ${authorization.token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// console.log(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 拦截响应
|
||||||
|
service.interceptors.response.use(
|
||||||
|
// 响应成功进入第1个函数,该函数的参数是响应对象
|
||||||
|
(response) => {
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
// 响应失败进入第2个函数,该函数的参数是错误对象
|
||||||
|
async (error) => {
|
||||||
|
// 如果响应码是 401 ,则请求获取新的 token
|
||||||
|
// 响应拦截器中的 error 就是那个响应的错误对象
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
// 校验是否有 refresh_token
|
||||||
|
const { authorization } = store.state;
|
||||||
|
if (!authorization || !authorization.refresh_token) {
|
||||||
|
router.push("/login");
|
||||||
|
|
||||||
|
// 代码不要往后执行了
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果有refresh_token,则请求获取新的 token
|
||||||
|
try {
|
||||||
|
const res = await axios({
|
||||||
|
method: "PUT",
|
||||||
|
url: "/api/authorizations",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${authorization.refresh_token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 如果获取成功,则把新的 token 更新到容器中
|
||||||
|
// console.log('刷新 token 成功', res)
|
||||||
|
store.commit("setToken", {
|
||||||
|
token: res.data.data.token, // 最新获取的可用 token
|
||||||
|
refresh_token: authorization.refresh_token, // 还是原来的 refresh_token
|
||||||
|
});
|
||||||
|
// 把之前失败的用户请求继续发出去
|
||||||
|
// config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
|
||||||
|
// return 把 request 的请求结果继续返回给发请求的具体位置
|
||||||
|
return service(error.config);
|
||||||
|
} catch (err) {
|
||||||
|
// 如果获取失败,直接跳转 登录页
|
||||||
|
// console.log('请求刷线 token 失败', err)
|
||||||
|
router.push("/login");
|
||||||
|
// 清除token
|
||||||
|
store.commit("clearToken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.error(error.response.message);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default service;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
这时我们再回到api/login.js中,只需要引入上面的request.js就行了
|
||||||
|
|
||||||
|
```js
|
||||||
|
// login.js
|
||||||
|
// import axios from 'axios'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 登录接口
|
||||||
|
export const Login = data => {
|
||||||
|
return request({
|
||||||
|
url: "/api/login",
|
||||||
|
method: "post",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 权限控制
|
||||||
|
|
||||||
|
> 如果没有登录或者token失效的时候,页面需要重定向到登录页
|
||||||
|
|
||||||
|
- 全局路由守卫
|
||||||
|
|
||||||
|
> 编辑router/index.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 引入TOKEN变量
|
||||||
|
import { TOKEN } from '@/store/modules/user'
|
||||||
|
|
||||||
|
// 全局路由守卫,注意vue-router4的路由守卫不需要next跳转,而是通过return返回false或者一个路由地址
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
if (!window.localStorage[TOKEN] && to.name !== 'login') {
|
||||||
|
return {
|
||||||
|
name: 'login',
|
||||||
|
query: {
|
||||||
|
redirect: to.path // redirect是指登录之后可以跳回到redirect指定的页面
|
||||||
|
},
|
||||||
|
replace: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### css
|
||||||
|
|
||||||
|
- css预处理器
|
||||||
|
|
||||||
|
本项目我们使用less,先安装less
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install -D less
|
||||||
|
```
|
||||||
|
|
||||||
|
使用less
|
||||||
|
|
||||||
|
```html
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
- autoprefixer自动处理css3浏览器前缀
|
||||||
|
|
||||||
|
安装autoprefixer
|
||||||
|
|
||||||
|
> 不需要安装postcss,vite内部支持
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install autoprefixer -D
|
||||||
|
```
|
||||||
|
|
||||||
|
根目录创建postcss.config.js
|
||||||
|
|
||||||
|
```js
|
||||||
|
// postcss.config.js
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
// 兼容浏览器,添加前缀
|
||||||
|
'autoprefixer': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
package.json中配置浏览器版本
|
||||||
|
|
||||||
|
```js
|
||||||
|
//package.json
|
||||||
|
{
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
最终的项目结构如下
|
||||||
|
|
||||||
|
> 没有后缀的,代表是一个文件夹
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
├─node_modules -----------依赖包
|
||||||
|
├─mock -------------------mock接口
|
||||||
|
├─public -----------------静态文件
|
||||||
|
├─index.html--------------主页面
|
||||||
|
├─src---------------------核心文件目录(我们开发的代码都在src目录)
|
||||||
|
| ├─api------------------接口模块
|
||||||
|
| ├─assets---------------静态资源(图片,字体,css,js等)
|
||||||
|
| ├─components-----------公共组件
|
||||||
|
| ├─router---------------路由配置
|
||||||
|
| | ├─index.js----------路由主文件
|
||||||
|
| | ├─modules-----------路由模块
|
||||||
|
| ├─store----------------状态管理
|
||||||
|
| | ├─index.js----------状态主文件
|
||||||
|
| | ├─modules-----------状态模块
|
||||||
|
| ├─utils----------------工具函数
|
||||||
|
| | ├─request.js--------axios封装函数
|
||||||
|
| ├─views----------------页面
|
||||||
|
| ├─App.vue--------------根组件
|
||||||
|
| ├─main.js--------------入口文件
|
||||||
|
├─package.json------------项目基本信息和依赖包信息
|
||||||
|
├─postcss.config.js-------postcss配置文件
|
||||||
|
├─vite.config.js----------打包配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 登录
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user