feat: 动态菜单
This commit is contained in:
parent
9ce271d691
commit
0417ceb6d4
@ -1,6 +1,6 @@
|
|||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
|
|
||||||
from dbhelper.menu import del_menu, get_menus, insert_menu
|
from dbhelper.menu import del_menu, get_menus, insert_menu, put_menu
|
||||||
from schemas import ListAll, MenuIn, MenuRead, Response
|
from schemas import ListAll, MenuIn, MenuRead, Response
|
||||||
|
|
||||||
|
|
||||||
@ -21,3 +21,11 @@ async def menu_del(pk: int) -> Response:
|
|||||||
if await del_menu(pk) == 0:
|
if await del_menu(pk) == 0:
|
||||||
return Response(code=400, msg="菜单不存在")
|
return Response(code=400, msg="菜单不存在")
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
|
async def menu_put(pk: int, data: MenuIn) -> Response:
|
||||||
|
"""更新菜单"""
|
||||||
|
if await put_menu(pk, data) == 0:
|
||||||
|
return Response(code=400, msg="菜单不存在")
|
||||||
|
return Response()
|
||||||
|
|
||||||
|
@ -67,3 +67,8 @@ async def get_apis(pk: int):
|
|||||||
AND srm.rid = (?) and m.status != 9""",
|
AND srm.rid = (?) and m.status != 9""",
|
||||||
[pk],
|
[pk],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def put_menu(pk: int, data):
|
||||||
|
"""更新菜单"""
|
||||||
|
return await MenuModel.filter(id=pk).update(**data.dict())
|
BIN
backend/mini.db
BIN
backend/mini.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -3,7 +3,7 @@ from typing import Any, Callable, get_type_hints
|
|||||||
from fastapi import Depends, routing
|
from fastapi import Depends, routing
|
||||||
|
|
||||||
from controller.common import about, login
|
from controller.common import about, login
|
||||||
from controller.menu import menu_add, menu_arr, menu_del
|
from controller.menu import menu_add, menu_arr, menu_del, menu_put
|
||||||
from controller.role import (assigned_menu, role_add, role_arr, role_del,
|
from controller.role import (assigned_menu, role_add, role_arr, role_del,
|
||||||
role_has_menu, role_put, role_query)
|
role_has_menu, role_put, role_query)
|
||||||
from controller.user import (user_add, user_arr, user_del, user_info,
|
from controller.user import (user_add, user_arr, user_del, user_info,
|
||||||
@ -107,7 +107,9 @@ class Route(routing.APIRoute):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
has_perm = {"dependencies": [Depends(check_permissions)]}
|
has_perm = {
|
||||||
|
# "dependencies": [Depends(check_permissions)]
|
||||||
|
}
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
Route.post("/login", endpoint=login, tags=["公共"], summary="登录"),
|
Route.post("/login", endpoint=login, tags=["公共"], summary="登录"),
|
||||||
@ -157,6 +159,9 @@ routes = [
|
|||||||
Route.delete(
|
Route.delete(
|
||||||
"/menu/{pk}", endpoint=menu_del, tags=["菜单管理"], summary="菜单删除", **has_perm
|
"/menu/{pk}", endpoint=menu_del, tags=["菜单管理"], summary="菜单删除", **has_perm
|
||||||
),
|
),
|
||||||
|
Route.put(
|
||||||
|
"/menu/{pk}", endpoint=menu_put, tags=["菜单管理"], summary="菜单更新", **has_perm
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = [routes]
|
__all__ = [routes]
|
||||||
|
@ -43,7 +43,7 @@ params = [
|
|||||||
"/menu",
|
"/menu",
|
||||||
MenuIn( # id 1
|
MenuIn( # id 1
|
||||||
name="系统管理",
|
name="系统管理",
|
||||||
meta={"icon": "Group"},
|
meta={"icon": "AppstoreOutlined"},
|
||||||
path="/system",
|
path="/system",
|
||||||
type=0,
|
type=0,
|
||||||
component=None,
|
component=None,
|
||||||
@ -57,7 +57,7 @@ params = [
|
|||||||
"/menu",
|
"/menu",
|
||||||
MenuIn( # id 2
|
MenuIn( # id 2
|
||||||
name="系统设置",
|
name="系统设置",
|
||||||
meta={"icon": "setting"},
|
meta={"icon": "SettingOutlined"},
|
||||||
path="/system",
|
path="/system",
|
||||||
type=0,
|
type=0,
|
||||||
component=None,
|
component=None,
|
||||||
@ -72,7 +72,7 @@ params = [
|
|||||||
"/menu",
|
"/menu",
|
||||||
MenuIn( # id 3
|
MenuIn( # id 3
|
||||||
name="用户管理",
|
name="用户管理",
|
||||||
meta={"icon": "User"},
|
meta={"icon": "TeamOutlined"},
|
||||||
path="/system/user",
|
path="/system/user",
|
||||||
type=1,
|
type=1,
|
||||||
component="/system/user.vue",
|
component="/system/user.vue",
|
||||||
@ -86,7 +86,7 @@ params = [
|
|||||||
"/menu",
|
"/menu",
|
||||||
MenuIn( # id 4
|
MenuIn( # id 4
|
||||||
name="角色管理",
|
name="角色管理",
|
||||||
meta={"icon": "Role"},
|
meta={"icon": "UserOutlined"},
|
||||||
path="/system/role",
|
path="/system/role",
|
||||||
type=1,
|
type=1,
|
||||||
component="/system/role.vue",
|
component="/system/role.vue",
|
||||||
@ -100,7 +100,7 @@ params = [
|
|||||||
"/menu",
|
"/menu",
|
||||||
MenuIn( # id 5
|
MenuIn( # id 5
|
||||||
name="菜单管理",
|
name="菜单管理",
|
||||||
meta={"icon": "Menu"},
|
meta={"icon": "MenuOutlined"},
|
||||||
path="/system/menu",
|
path="/system/menu",
|
||||||
type=1,
|
type=1,
|
||||||
component="/system/menu.vue",
|
component="/system/menu.vue",
|
||||||
@ -114,7 +114,7 @@ params = [
|
|||||||
"/menu",
|
"/menu",
|
||||||
MenuIn( # id 6
|
MenuIn( # id 6
|
||||||
name="关于",
|
name="关于",
|
||||||
meta={"icon": "Menu"},
|
meta={"icon": "DashboardOutlined"},
|
||||||
path="/setting/about",
|
path="/setting/about",
|
||||||
type=1,
|
type=1,
|
||||||
component="/setting/about.vue",
|
component="/setting/about.vue",
|
||||||
@ -309,6 +309,20 @@ params = [
|
|||||||
method="DELETE",
|
method="DELETE",
|
||||||
).dict(),
|
).dict(),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"/menu",
|
||||||
|
MenuIn(
|
||||||
|
name="修改菜单",
|
||||||
|
meta={"icon": "Update"},
|
||||||
|
path=None,
|
||||||
|
type=2,
|
||||||
|
component=None,
|
||||||
|
pid=5,
|
||||||
|
identifier="menu:update",
|
||||||
|
api="/menu/{pk}",
|
||||||
|
method="PUT",
|
||||||
|
).dict(),
|
||||||
|
),
|
||||||
# 分配权限
|
# 分配权限
|
||||||
(
|
(
|
||||||
"/role/assigned/menu",
|
"/role/assigned/menu",
|
||||||
|
896
frontend/package-lock.json
generated
896
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,8 +7,8 @@
|
|||||||
"preview": "vite preview --port 4173"
|
"preview": "vite preview --port 4173"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ant-design-vue": "^3.2.12",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"element-plus": "^2.2.16",
|
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"pinia": "^2.0.21",
|
"pinia": "^2.0.21",
|
||||||
"pinia-plugin-persistedstate": "^2.2.0",
|
"pinia-plugin-persistedstate": "^2.2.0",
|
||||||
@ -17,8 +17,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^3.0.3",
|
"@vitejs/plugin-vue": "^3.0.3",
|
||||||
"unplugin-auto-import": "^0.11.2",
|
"unplugin-vue-components": "^0.22.7",
|
||||||
"unplugin-vue-components": "^0.22.4",
|
|
||||||
"vite": "^3.0.9"
|
"vite": "^3.0.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from "vue-router";
|
||||||
|
import { Spin } from "ant-design-vue";
|
||||||
|
import { userStore } from "./stores/user";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RouterView />
|
<Spin :spinning="userStore().isLoading" tip="数据请求中" size="large">
|
||||||
|
<RouterView />
|
||||||
|
</Spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
|
.ant-spin-nested-loading,
|
||||||
|
.ant-spin-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
body,html, #app{
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
4
frontend/src/assets/css/base.css
Normal file
4
frontend/src/assets/css/base.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
html,#app{
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
69
frontend/src/assets/img/background.svg
Normal file
69
frontend/src/assets/img/background.svg
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Group 21</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
|
||||||
|
<g id="Group-21" transform="translate(77.000000, 73.000000)">
|
||||||
|
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
|
||||||
|
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
|
||||||
|
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
|
||||||
|
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
|
||||||
|
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
|
||||||
|
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
|
||||||
|
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
|
||||||
|
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
|
||||||
|
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
|
||||||
|
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
|
||||||
|
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
|
||||||
|
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
|
||||||
|
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
|
||||||
|
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
|
||||||
|
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
|
||||||
|
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
|
||||||
|
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
|
||||||
|
</g>
|
||||||
|
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
|
||||||
|
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
|
||||||
|
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
|
||||||
|
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
|
||||||
|
</g>
|
||||||
|
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
|
||||||
|
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
|
||||||
|
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
|
||||||
|
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
|
||||||
|
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
|
||||||
|
</g>
|
||||||
|
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
|
||||||
|
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
|
||||||
|
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
|
||||||
|
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
|
||||||
|
</g>
|
||||||
|
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
|
||||||
|
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
|
||||||
|
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
|
||||||
|
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
|
||||||
|
</g>
|
||||||
|
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
|
||||||
|
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||||
|
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||||
|
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
|
||||||
|
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
|
||||||
|
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
|
||||||
|
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
|
||||||
|
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
|
||||||
|
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
|
||||||
|
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
|
||||||
|
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
|
||||||
|
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
BIN
frontend/src/assets/img/logo.png
Normal file
BIN
frontend/src/assets/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
|
Before Width: | Height: | Size: 308 B |
44
frontend/src/components/layout/sider-menu.vue
Normal file
44
frontend/src/components/layout/sider-menu.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { userStore } from "@/stores/user";
|
||||||
|
import { loadIconCpn } from "@/utils/loadCpn";
|
||||||
|
|
||||||
|
const store = userStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="sider-menu">
|
||||||
|
<div class="logo"></div>
|
||||||
|
<a-menu theme="dark" mode="inline">
|
||||||
|
<template v-for="menu in store.userMenus" :key="menu.id">
|
||||||
|
<!-- 0 目录 顶层菜单 -->
|
||||||
|
<template v-if="menu.type === 0">
|
||||||
|
<a-sub-menu :key="`${menu.id}`">
|
||||||
|
<template #icon>
|
||||||
|
<component :is="loadIconCpn(menu.meta.icon)"></component>
|
||||||
|
</template>
|
||||||
|
<template #title>{{ menu.name }}</template>
|
||||||
|
<!-- 1 组件 子菜单项 -->
|
||||||
|
<template v-for="sub in menu.children" :key="sub.id">
|
||||||
|
<a-menu-item>
|
||||||
|
<template #icon>
|
||||||
|
<component :is="loadIconCpn(sub.meta.icon)"></component>
|
||||||
|
</template>
|
||||||
|
<span>{{ sub.name }}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
</a-sub-menu>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.logo {
|
||||||
|
height: 32px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
margin: 16px;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,17 +1,17 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
import router from './router'
|
import router from "./router";
|
||||||
import store from './stores'
|
import store from "./stores";
|
||||||
|
|
||||||
import 'normalize.css'
|
import "normalize.css";
|
||||||
import '@/assets/base.css'
|
import "@/assets/css/base.css";
|
||||||
import 'element-plus/theme-chalk/el-message.css'
|
|
||||||
import 'element-plus/theme-chalk/el-loading.css'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
import "ant-design-vue/dist/antd.css";
|
||||||
|
|
||||||
app.use(store)
|
const app = createApp(App);
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.use(store);
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
|
@ -1,44 +1,43 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import { userStore } from '@/stores/user'
|
import { message } from "ant-design-vue";
|
||||||
import { ElMessage } from 'element-plus'
|
import { userStore } from "@/stores/user";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: "/",
|
||||||
redirect: '/main'
|
redirect: "/main",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: "/login",
|
||||||
meta: {title: '登录页'},
|
meta: { title: "登录页" },
|
||||||
component: () => import('@/views/login.vue')
|
component: () => import("@/views/login.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/main',
|
path: "/main",
|
||||||
meta: {title: '主页'},
|
meta: { title: "主页" },
|
||||||
component: () => import('@/views/main.vue')
|
component: () => import("@/views/main.vue"),
|
||||||
}
|
},
|
||||||
|
];
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: routes
|
routes: routes,
|
||||||
})
|
});
|
||||||
|
|
||||||
// 导航守卫
|
// 导航守卫
|
||||||
router.beforeEach((to) => {
|
router.beforeEach((to) => {
|
||||||
// 修改页面标题
|
// 修改页面标题
|
||||||
if(to.meta.title) {
|
if (to.meta.title) {
|
||||||
document.title = to.meta.title
|
document.title = to.meta.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.path !== "/login") {
|
if (to.path !== "/login") {
|
||||||
if (userStore().token){
|
if (userStore().token) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
ElMessage.warning("请登录")
|
message.warning("请登录");
|
||||||
return '/login'
|
return "/login";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
export default router
|
export default router;
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import { ElMessage, ElLoading } from 'element-plus'
|
|
||||||
import {userStore} from '@/stores/user'
|
|
||||||
|
|
||||||
let loading
|
|
||||||
|
|
||||||
export default (config) => {
|
|
||||||
|
|
||||||
const instance = axios.create({
|
|
||||||
baseURL: import.meta.env.VITE_BASE_URL,
|
|
||||||
timeout: 10000,
|
|
||||||
})
|
|
||||||
|
|
||||||
instance.interceptors.request.use(config => {
|
|
||||||
loading = ElLoading.service({
|
|
||||||
lock: true,
|
|
||||||
text: '请求中...',
|
|
||||||
background: 'rabg(0,0,0,0.7)'
|
|
||||||
})
|
|
||||||
config.headers.Authorization = userStore().accessToken
|
|
||||||
return config
|
|
||||||
})
|
|
||||||
|
|
||||||
instance.interceptors.response.use(res => {
|
|
||||||
if (res.data.code !== 200 ){
|
|
||||||
ElMessage.error(res.data.msg)
|
|
||||||
}
|
|
||||||
loading.close()
|
|
||||||
return res.data
|
|
||||||
}, err => {
|
|
||||||
ElMessage.error(err)
|
|
||||||
loading.close()
|
|
||||||
return Promise.reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
return instance(config)
|
|
||||||
}
|
|
@ -1,23 +1,23 @@
|
|||||||
import request from "./request";
|
import request from "@/utils/request";
|
||||||
|
|
||||||
export function login(data) {
|
export function login(data) {
|
||||||
return request({
|
return request({
|
||||||
url: "/login",
|
url: "/login",
|
||||||
method: 'post',
|
method: "post",
|
||||||
data
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
export function getUserInfo(uid){
|
export function getUserInfo(uid) {
|
||||||
return request({
|
return request({
|
||||||
url: `/user/${uid}`
|
url: `/user/${uid}`,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取权限信息
|
// 获取权限信息
|
||||||
export function getMenus(rid){
|
export function getMenus(rid) {
|
||||||
return request({
|
return request({
|
||||||
url: `/role/${rid}/menu`
|
url: `/role/${rid}/menu`,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,64 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from "vue";
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from "pinia";
|
||||||
import { ElMessage } from 'element-plus'
|
import { message } from "ant-design-vue";
|
||||||
import {getMenus, getUserInfo, login} from '@/service/user'
|
import { getMenus, getUserInfo, login } from "@/service/user";
|
||||||
import router from '@/router'
|
import router from "@/router";
|
||||||
|
|
||||||
export const userStore = defineStore('user', () => {
|
export const userStore = defineStore(
|
||||||
const token = ref("")
|
"user",
|
||||||
const userInfo = ref({})
|
() => {
|
||||||
const userMenus = ref([])
|
const token = ref("");
|
||||||
|
const userInfo = ref({});
|
||||||
|
const userMenus = ref([]);
|
||||||
|
|
||||||
// getter
|
const isLoading = ref(false);
|
||||||
const accessToken = computed(() => 'Bearer ' + token.value)
|
|
||||||
|
|
||||||
// setup store 不提供$reset 需要自己重置
|
// getter
|
||||||
// https://github.com/vuejs/pinia/issues/1056
|
const accessToken = computed(() => "Bearer " + token.value);
|
||||||
const $reset = () => {
|
|
||||||
token.value = ""
|
|
||||||
userInfo.value = {}
|
|
||||||
userMenus.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// 非setup语法时的actions
|
// setup store 不提供$reset 需要自己重置
|
||||||
const loginAction = async (data) => {
|
// https://github.com/vuejs/pinia/issues/1056
|
||||||
|
const $reset = () => {
|
||||||
|
token.value = "";
|
||||||
|
userInfo.value = {};
|
||||||
|
userMenus.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
// 1. 登录
|
// 非setup语法时的actions
|
||||||
const res = await login(data)
|
const loginAction = async (data) => {
|
||||||
token.value = res.data.token
|
// 1. 登录
|
||||||
|
const res = await login(data);
|
||||||
|
token.value = res.data.token;
|
||||||
|
|
||||||
// 2. 获取用户信息
|
// 2. 获取用户信息
|
||||||
const info = await getUserInfo(res.data.id)
|
const info = await getUserInfo(res.data.id);
|
||||||
userInfo.value = info.data
|
userInfo.value = info.data;
|
||||||
|
|
||||||
// 3. 获取权限信息
|
// 3. 获取权限信息
|
||||||
const menus = await getMenus(info.data.roles[0].id)
|
const menus = await getMenus(info.data.roles[0].id);
|
||||||
userMenus.value = menus.data
|
userMenus.value = menus.data;
|
||||||
|
|
||||||
// 4. 跳转
|
// 4. 跳转
|
||||||
router.push("/main")
|
router.push("/main");
|
||||||
|
|
||||||
// 弹框提示登录成功
|
// 弹框提示登录成功
|
||||||
ElMessage.success("登录成功.")
|
message.success("登录成功.");
|
||||||
}
|
};
|
||||||
|
|
||||||
return { token, accessToken, userInfo, userMenus,
|
return {
|
||||||
$reset, loginAction }
|
token,
|
||||||
}, {
|
accessToken,
|
||||||
|
userInfo,
|
||||||
|
userMenus,
|
||||||
|
isLoading,
|
||||||
|
$reset,
|
||||||
|
loginAction,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
persist: true, // 解决pinia刷新时数据丢失问题
|
persist: true, // 解决pinia刷新时数据丢失问题
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// export const userStore = defineStore('user',{
|
// export const userStore = defineStore('user',{
|
||||||
// state: () => ({
|
// state: () => ({
|
||||||
@ -66,4 +78,4 @@ export const userStore = defineStore('user', () => {
|
|||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// persist: true
|
// persist: true
|
||||||
// })
|
// })
|
||||||
|
16
frontend/src/utils/loadCpn.js
Normal file
16
frontend/src/utils/loadCpn.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 动态加载组件
|
||||||
|
import { h } from "vue";
|
||||||
|
import * as icons from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态加载antd icon
|
||||||
|
* @param {*} iconName
|
||||||
|
* @returns 组件对象
|
||||||
|
* jsx:使用 h(loadIconCpn('UserField'))
|
||||||
|
* template: 使用 <component :is="loadIconCpn("UserField")">
|
||||||
|
*/
|
||||||
|
function loadIconCpn(iconName) {
|
||||||
|
return icons[iconName];
|
||||||
|
}
|
||||||
|
|
||||||
|
export { loadIconCpn };
|
34
frontend/src/utils/request.js
Normal file
34
frontend/src/utils/request.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import { userStore } from "@/stores/user";
|
||||||
|
|
||||||
|
export default (config) => {
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_BASE_URL,
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.interceptors.request.use((config) => {
|
||||||
|
userStore().isLoading = !userStore().isLoading;
|
||||||
|
config.headers.Authorization = userStore().accessToken;
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
(res) => {
|
||||||
|
userStore().isLoading = !userStore().isLoading;
|
||||||
|
if (res.data.code !== 200) {
|
||||||
|
message.error(res.data.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
userStore().isLoading = !userStore().isLoading;
|
||||||
|
message.error(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return instance(config);
|
||||||
|
};
|
@ -1,59 +1,82 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { User, Lock } from '@element-plus/icons-vue'
|
import { UserOutlined, LockOutlined } from "@ant-design/icons-vue";
|
||||||
import {ref,reactive} from 'vue'
|
import { ref, reactive, computed } from "vue";
|
||||||
|
|
||||||
import { userStore } from '@/stores/user';
|
import { userStore } from "@/stores/user";
|
||||||
|
|
||||||
const store = userStore()
|
const store = userStore();
|
||||||
// 表单配置
|
// 表单配置
|
||||||
const rules = {
|
const rules = {
|
||||||
username: [
|
username: [
|
||||||
{required: true, message: '请输入用户名', trigger: 'blur'},
|
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||||
{min:5, max:20, message: '5~20', trigger: 'blur'}
|
{ min: 5, max: 20, message: "5~20", trigger: "blur" },
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{required: true, message: '请输入密码', trigger: 'blur'},
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
{min:6, max:12, message: '6~12', trigger: 'blur'}
|
{ min: 6, max: 12, message: "6~12", trigger: "blur" },
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const formRef = ref()
|
const formRef = ref();
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
username: 'admin',
|
username: "admin",
|
||||||
password: '123456'
|
password: "123456",
|
||||||
})
|
});
|
||||||
|
// 计算属性 登录按钮是否可以点击
|
||||||
|
const disabled = computed(() => {
|
||||||
|
return !(formData.username && formData.password);
|
||||||
|
});
|
||||||
|
|
||||||
// 事件
|
// 事件
|
||||||
const submitForm = (formEl) => {
|
const submitForm = (formEl) => {
|
||||||
if (!formEl) return
|
if (!formEl) return;
|
||||||
formEl.validate( valid => {
|
formEl.validate().then(
|
||||||
if (valid) {
|
(res) => {
|
||||||
// 验证通过,执行登录逻辑
|
store.loginAction(formData);
|
||||||
store.loginAction(formData)
|
},
|
||||||
}
|
(err) => err
|
||||||
})
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<div class="continer">
|
<div class="continer">
|
||||||
<h1>Mini RBAC</h1>
|
<h1>Mini RBAC</h1>
|
||||||
<el-form ref="formRef" :model="formData" :rules="rules" status-icon>
|
|
||||||
<el-form-item prop="username">
|
<a-form ref="formRef" :model="formData" :rules="rules">
|
||||||
<el-input placeholder="用户名" clearable :prefix-icon="User"
|
<a-form-item has-feedback name="username">
|
||||||
v-model.trim="formData.username"/>
|
<a-input
|
||||||
</el-form-item>
|
v-model:value.trim="formData.username"
|
||||||
<el-form-item prop="password">
|
placeholder="Username"
|
||||||
<el-input placeholder="密码" show-password :prefix-icon="Lock"
|
>
|
||||||
v-model.trim="formData.password"/>
|
<template #prefix>
|
||||||
</el-form-item>
|
<UserOutlined style="color: rgba(0, 0, 0, 0.25)" />
|
||||||
<el-form-item>
|
</template>
|
||||||
<el-button type="primary" @click="submitForm(formRef)" >登录</el-button>
|
</a-input>
|
||||||
</el-form-item>
|
</a-form-item>
|
||||||
</el-form>
|
<a-form-item has-feedback name="password">
|
||||||
|
<a-input-password
|
||||||
|
v-model:value.trim="formData.password"
|
||||||
|
placeholder="Password"
|
||||||
|
autocomplete="on"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<LockOutlined style="color: rgba(0, 0, 0, 0.25)" />
|
||||||
|
</template>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
html-type="submit"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="submitForm(formRef)"
|
||||||
|
>登录</a-button
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -73,10 +96,10 @@
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
.continer h1{
|
.continer h1 {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.continer .el-button {
|
.continer .ant-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,53 +1,72 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import router from '@/router';
|
import { ref } from "vue";
|
||||||
import { userStore } from '@/stores/user';
|
import router from "@/router";
|
||||||
const store = userStore()
|
|
||||||
|
|
||||||
const logout = () => {
|
import { userStore } from "@/stores/user";
|
||||||
store.$reset()
|
|
||||||
router.push('/login')
|
import SiderMenu from "@/components/layout/sider-menu.vue";
|
||||||
}
|
const store = userStore();
|
||||||
|
|
||||||
|
const collapsed = ref(false);
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
store.$reset();
|
||||||
|
router.push("/login");
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<el-container>
|
<a-layout>
|
||||||
<el-aside width="200px">Aside</el-aside>
|
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
|
||||||
<el-container>
|
<!-- 动态菜单 -->
|
||||||
<el-header>Header <el-button @click="logout">
|
<SiderMenu />
|
||||||
注销
|
</a-layout-sider>
|
||||||
</el-button></el-header>
|
<a-layout>
|
||||||
<el-main>Main</el-main>
|
<a-layout-header style="background: #fff; padding: 0">
|
||||||
<el-footer>Footer</el-footer>
|
<!-- 页头 -->
|
||||||
</el-container>
|
<a-button @click="logout"></a-button>
|
||||||
</el-container>
|
</a-layout-header>
|
||||||
</div>
|
<!-- 面包屑 -->
|
||||||
|
<a-layout-content
|
||||||
|
:style="{
|
||||||
|
margin: '24px 16px',
|
||||||
|
background: '#F0F2F5',
|
||||||
|
minHeight: '280px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<router-view />
|
||||||
|
</a-layout-content>
|
||||||
|
</a-layout>
|
||||||
|
</a-layout>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.main {
|
.main,
|
||||||
height: 100%;
|
.ant-layout {
|
||||||
}
|
|
||||||
.layout-container-demo .el-header {
|
|
||||||
position: relative;
|
|
||||||
background-color: var(--el-color-primary-light-7);
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
.layout-container-demo .el-aside {
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
background: var(--el-color-primary-light-8);
|
|
||||||
}
|
|
||||||
.layout-container-demo .el-menu {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
.layout-container-demo .el-main {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.layout-container-demo .toolbar {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
right: 20px;
|
}
|
||||||
|
|
||||||
|
#components-layout-demo-custom-trigger .trigger {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 64px;
|
||||||
|
padding: 0 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-layout-demo-custom-trigger .trigger:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-layout-demo-custom-trigger .logo {
|
||||||
|
height: 32px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-layout .site-layout-background {
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,34 +1,35 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from "@vitejs/plugin-vue";
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
import Components from "unplugin-vue-components/vite";
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
|
||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue(), AutoImport({
|
plugins: [
|
||||||
resolvers: [ElementPlusResolver()],
|
vue(),
|
||||||
}), Components({
|
Components({
|
||||||
resolvers: [ElementPlusResolver()],
|
resolvers: [AntDesignVueResolver()],
|
||||||
}),],
|
}),
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: { // 代理
|
proxy: {
|
||||||
'/api': {
|
// 代理
|
||||||
target: 'http://localhost:8000',
|
"/api": {
|
||||||
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||||
},
|
},
|
||||||
'/socket.io': {
|
"/socket.io": {
|
||||||
target: 'ws://localhost:5000',
|
target: "ws://localhost:5000",
|
||||||
ws: true
|
ws: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user