feat: 切换角色
This commit is contained in:
parent
9766950106
commit
d79b39b7eb
@ -28,4 +28,3 @@ async def menu_put(pk: int, data: MenuIn) -> Response:
|
|||||||
if await put_menu(pk, data) == 0:
|
if await put_menu(pk, data) == 0:
|
||||||
return Response(code=400, msg="菜单不存在")
|
return Response(code=400, msg="菜单不存在")
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
@ -4,10 +4,15 @@ from fastapi import Query
|
|||||||
|
|
||||||
from core.utils import list_to_tree
|
from core.utils import list_to_tree
|
||||||
from dbhelper.relation import role_assigned_menu
|
from dbhelper.relation import role_assigned_menu
|
||||||
from dbhelper.role import (del_role, get_role, get_role_menus, get_roles,
|
from dbhelper.role import (
|
||||||
new_role, put_role)
|
del_role,
|
||||||
from schemas import (ListAll, Response, RoleIn, RoleInfo, RoleMenuIn,
|
get_role,
|
||||||
RoleQuery, RoleRead)
|
get_role_menus,
|
||||||
|
get_roles,
|
||||||
|
new_role,
|
||||||
|
put_role,
|
||||||
|
)
|
||||||
|
from schemas import ListAll, Response, RoleIn, RoleInfo, RoleMenuIn, RoleQuery, RoleRead
|
||||||
|
|
||||||
|
|
||||||
async def role_add(data: RoleIn) -> Response[RoleInfo]:
|
async def role_add(data: RoleIn) -> Response[RoleInfo]:
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
from fastapi import Query
|
from fastapi import Depends, Query
|
||||||
|
from starlette.requests import Request
|
||||||
|
|
||||||
from core.security import get_password_hash
|
from core.security import check_token, get_password_hash
|
||||||
from dbhelper.user import (del_user, get_user, get_user_info, get_users,
|
from dbhelper.user import (
|
||||||
insert_user, put_user)
|
del_user,
|
||||||
|
get_user,
|
||||||
|
get_user_info,
|
||||||
|
get_users,
|
||||||
|
insert_user,
|
||||||
|
put_user,
|
||||||
|
select_role,
|
||||||
|
)
|
||||||
from schemas import Response, UserAdd, UserInfo, UserPut, UserQuery, UserRead
|
from schemas import Response, UserAdd, UserInfo, UserPut, UserQuery, UserRead
|
||||||
from schemas.common import ListAll
|
from schemas.common import ListAll
|
||||||
|
|
||||||
@ -63,3 +71,11 @@ async def user_put(pk: int, data: UserPut) -> Response:
|
|||||||
if isinstance(result, int):
|
if isinstance(result, int):
|
||||||
return Response(code=400, msg=f"角色不存在{result}")
|
return Response(code=400, msg=f"角色不存在{result}")
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
|
async def user_select_role(rid: int, user=Depends(check_token)):
|
||||||
|
"""用户切换角色"""
|
||||||
|
res = await select_role(user.id, rid)
|
||||||
|
if res == 0:
|
||||||
|
return Response(code=400, msg=f"角色不存在{res}")
|
||||||
|
return Response()
|
||||||
|
@ -74,7 +74,7 @@ async def check_permissions(request: Request, user: UserModel = Depends(check_to
|
|||||||
whitelist = [f"/user/{user.id}", f"/role/{active_rid}/menu"]
|
whitelist = [f"/user/{user.id}", f"/role/{active_rid}/menu"]
|
||||||
flag = request.url.path in whitelist and request.method == "GET"
|
flag = request.url.path in whitelist and request.method == "GET"
|
||||||
if flag:
|
if flag:
|
||||||
return
|
return user
|
||||||
|
|
||||||
api = request.url.path
|
api = request.url.path
|
||||||
for k, v in request.path_params.items():
|
for k, v in request.path_params.items():
|
||||||
|
@ -104,3 +104,9 @@ async def put_user(uid: int, data: UserPut):
|
|||||||
await UserRoleModel.bulk_create(
|
await UserRoleModel.bulk_create(
|
||||||
[UserRoleModel(uid=uid, **role.dict()) for role in roles]
|
[UserRoleModel(uid=uid, **role.dict()) for role in roles]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def select_role(uid: int, rid: int):
|
||||||
|
"""用户切换角色"""
|
||||||
|
await UserRoleModel.filter(uid=uid, rid__not=rid).update(status=1)
|
||||||
|
return await UserRoleModel.filter(uid=uid, rid=rid).update(status=5)
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -4,10 +4,24 @@ 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, menu_put
|
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 (
|
||||||
role_has_menu, role_put, role_query)
|
assigned_menu,
|
||||||
from controller.user import (user_add, user_arr, user_del, user_info,
|
role_add,
|
||||||
user_list, user_put)
|
role_arr,
|
||||||
|
role_del,
|
||||||
|
role_has_menu,
|
||||||
|
role_put,
|
||||||
|
role_query,
|
||||||
|
)
|
||||||
|
from controller.user import (
|
||||||
|
user_add,
|
||||||
|
user_arr,
|
||||||
|
user_del,
|
||||||
|
user_info,
|
||||||
|
user_list,
|
||||||
|
user_put,
|
||||||
|
user_select_role,
|
||||||
|
)
|
||||||
from core.security import check_permissions
|
from core.security import check_permissions
|
||||||
|
|
||||||
|
|
||||||
@ -107,9 +121,7 @@ class Route(routing.APIRoute):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
has_perm = {
|
has_perm = {"dependencies": [Depends(check_permissions)]}
|
||||||
# "dependencies": [Depends(check_permissions)]
|
|
||||||
}
|
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
Route.post("/login", endpoint=login, tags=["公共"], summary="登录"),
|
Route.post("/login", endpoint=login, tags=["公共"], summary="登录"),
|
||||||
@ -129,6 +141,9 @@ routes = [
|
|||||||
Route.post(
|
Route.post(
|
||||||
"/user/query", endpoint=user_list, tags=["用户管理"], summary="用户列表查询", **has_perm
|
"/user/query", endpoint=user_list, tags=["用户管理"], summary="用户列表查询", **has_perm
|
||||||
),
|
),
|
||||||
|
Route.put(
|
||||||
|
"/user/role/{rid}", endpoint=user_select_role, tags=["用户管理"], summary="用户切换角色"
|
||||||
|
),
|
||||||
# 角色管理,
|
# 角色管理,
|
||||||
Route.get("/role", endpoint=role_arr, tags=["角色管理"], summary="角色列表", **has_perm),
|
Route.get("/role", endpoint=role_arr, tags=["角色管理"], summary="角色列表", **has_perm),
|
||||||
Route.post("/role", endpoint=role_add, tags=["角色管理"], summary="角色新增", **has_perm),
|
Route.post("/role", endpoint=role_add, tags=["角色管理"], summary="角色新增", **has_perm),
|
||||||
|
@ -309,20 +309,6 @@ 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",
|
||||||
|
42
frontend/src/components/layout/layout-header.vue
Normal file
42
frontend/src/components/layout/layout-header.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import UserInfo from "@/components/layout/layout-info/layout-info.vue";
|
||||||
|
import { MenuUnfoldOutlined, MenuFoldOutlined } from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
// 记录图标状态
|
||||||
|
const collapsed = ref(false);
|
||||||
|
|
||||||
|
const emits = defineEmits(["changeFold"]);
|
||||||
|
|
||||||
|
// 修改图标状态同时传递参数给父组件让其变更菜单收缩
|
||||||
|
const clickMenuFold = () => {
|
||||||
|
collapsed.value = !collapsed.value;
|
||||||
|
// 父组件需要绑定这个事件
|
||||||
|
emits("changeFold", collapsed.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<menu-unfold-outlined
|
||||||
|
v-if="collapsed"
|
||||||
|
class="trigger"
|
||||||
|
@click="clickMenuFold"
|
||||||
|
/>
|
||||||
|
<menu-fold-outlined v-else class="trigger" @click="clickMenuFold" />
|
||||||
|
<UserInfo />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.trigger {
|
||||||
|
margin-left: 16px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
margin-right: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
41
frontend/src/components/layout/layout-info/layout-info.vue
Normal file
41
frontend/src/components/layout/layout-info/layout-info.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { userStore } from "@/stores/user";
|
||||||
|
|
||||||
|
import SelectRole from "./select-role.vue";
|
||||||
|
|
||||||
|
const store = userStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const roleChangeRef = ref();
|
||||||
|
|
||||||
|
const onClick = ({ key }) => {
|
||||||
|
if (key === "1") {
|
||||||
|
// 点击切换角色
|
||||||
|
roleChangeRef.value?.showModal();
|
||||||
|
} else {
|
||||||
|
store.$reset();
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="right">
|
||||||
|
<a-dropdown>
|
||||||
|
<a class="ant-dropdown-link" @click.prevent>
|
||||||
|
{{ store.userInfo.nickname }} - {{ store.userInfo.roles[0].name }}
|
||||||
|
</a>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="onClick">
|
||||||
|
<a-menu-item key="1">切换角色</a-menu-item>
|
||||||
|
<a-menu-item key="2">退出登录</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
<SelectRole ref="roleChangeRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
69
frontend/src/components/layout/layout-info/select-role.vue
Normal file
69
frontend/src/components/layout/layout-info/select-role.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { userStore } from "@/stores/user";
|
||||||
|
|
||||||
|
const store = userStore();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const visible = ref(false);
|
||||||
|
|
||||||
|
const currentRoleId = ref(store.userInfo.roles[0].id);
|
||||||
|
|
||||||
|
// 角色列表选项
|
||||||
|
const options = computed(() => {
|
||||||
|
return store.userInfo.roles.map((role) => ({
|
||||||
|
label: role.name,
|
||||||
|
value: role.id,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
loading.value = true;
|
||||||
|
store.userSelectRole(currentRoleId.value);
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false;
|
||||||
|
visible.value = false;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
showModal,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="select-role">
|
||||||
|
<a-modal v-model:visible="visible" title="切换角色" @ok="handleOk">
|
||||||
|
<template #footer>
|
||||||
|
<a-button key="back" @click="handleCancel">取消</a-button>
|
||||||
|
<a-button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="handleOk"
|
||||||
|
>确定</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<span>选择角色:</span>
|
||||||
|
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-select
|
||||||
|
v-model:value="currentRoleId"
|
||||||
|
size="default"
|
||||||
|
style="width: 400px"
|
||||||
|
:options="options"
|
||||||
|
></a-select>
|
||||||
|
</a-space>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -1,7 +1,6 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import { userStore } from "@/stores/user";
|
import { userStore } from "@/stores/user";
|
||||||
import { loadRouter } from "@/utils/loadCpn";
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
@ -21,3 +21,11 @@ export function getMenus(rid) {
|
|||||||
url: `/role/${rid}/menu`,
|
url: `/role/${rid}/menu`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改用户信息
|
||||||
|
export function selectRole(rid) {
|
||||||
|
return request({
|
||||||
|
url: `/user/role/${rid}`,
|
||||||
|
method: "put",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { message } from "ant-design-vue";
|
|||||||
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { loadRouter, loadDefaultMenu } from "@/utils/loadCpn";
|
import { loadRouter, loadDefaultMenu } from "@/utils/loadCpn";
|
||||||
import { getMenus, getUserInfo, login } from "@/service/user";
|
import { getMenus, getUserInfo, login, selectRole } from "@/service/user";
|
||||||
|
|
||||||
export const userStore = defineStore(
|
export const userStore = defineStore(
|
||||||
"user",
|
"user",
|
||||||
@ -28,14 +28,13 @@ export const userStore = defineStore(
|
|||||||
userMenus.value = [];
|
userMenus.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 非setup语法时的actions
|
/**
|
||||||
const loginAction = async (data) => {
|
* 获取用户信息 & 菜单路由
|
||||||
// 1. 登录
|
* @param {*} uid 用户id
|
||||||
const res = await login(data);
|
*/
|
||||||
token.value = res.data.token;
|
const getUserData = async (uid) => {
|
||||||
|
|
||||||
// 2. 获取用户信息
|
// 2. 获取用户信息
|
||||||
const info = await getUserInfo(res.data.id);
|
const info = await getUserInfo(uid);
|
||||||
userInfo.value = info.data;
|
userInfo.value = info.data;
|
||||||
|
|
||||||
// 3. 获取权限信息
|
// 3. 获取权限信息
|
||||||
@ -55,7 +54,13 @@ export const userStore = defineStore(
|
|||||||
} else {
|
} else {
|
||||||
router.push("/main");
|
router.push("/main");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginAction = async (data) => {
|
||||||
|
// 1. 登录
|
||||||
|
const res = await login(data);
|
||||||
|
token.value = res.data.token;
|
||||||
|
await getUserData(res.data.id);
|
||||||
// 弹框提示登录成功
|
// 弹框提示登录成功
|
||||||
message.success("登录成功.");
|
message.success("登录成功.");
|
||||||
};
|
};
|
||||||
@ -65,6 +70,13 @@ export const userStore = defineStore(
|
|||||||
loadRouter(userMenus.value);
|
loadRouter(userMenus.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 切换角色
|
||||||
|
const userSelectRole = async (rid) => {
|
||||||
|
await selectRole(rid);
|
||||||
|
// 重新拿用户信息
|
||||||
|
await getUserData(userInfo.value.id);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
accessToken,
|
accessToken,
|
||||||
@ -75,6 +87,7 @@ export const userStore = defineStore(
|
|||||||
$reset,
|
$reset,
|
||||||
loginAction,
|
loginAction,
|
||||||
loadRoleRouter,
|
loadRoleRouter,
|
||||||
|
userSelectRole,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,15 @@ export default (config) => {
|
|||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
userStore().isLoading = !userStore().isLoading;
|
userStore().isLoading = !userStore().isLoading;
|
||||||
message.error(err);
|
if (err.response.data?.msg) {
|
||||||
|
message.error(err.response.data.msg);
|
||||||
|
} else if (err.response.data?.detail) {
|
||||||
|
// 请求参数缺失
|
||||||
|
message.error(err.response.data?.detail[0].msg);
|
||||||
|
} else {
|
||||||
|
message.error(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import router from "@/router";
|
|
||||||
|
|
||||||
import { userStore } from "@/stores/user";
|
|
||||||
|
|
||||||
import SiderMenu from "@/components/layout/sider-menu.vue";
|
import SiderMenu from "@/components/layout/sider-menu.vue";
|
||||||
const store = userStore();
|
import LayoutHeader from "@/components/layout/layout-header.vue";
|
||||||
|
|
||||||
|
// a-ayout-sider 折叠状态响应式数据
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
|
|
||||||
const logout = () => {
|
// header组件 折叠按钮事件 触发a-layout-sider折叠
|
||||||
store.$reset();
|
const changeSiderFold = (subValue) => {
|
||||||
router.push("/login");
|
collapsed.value = subValue;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -25,7 +23,7 @@ const logout = () => {
|
|||||||
<a-layout>
|
<a-layout>
|
||||||
<a-layout-header style="background: #fff; padding: 0">
|
<a-layout-header style="background: #fff; padding: 0">
|
||||||
<!-- 页头 -->
|
<!-- 页头 -->
|
||||||
<a-button @click="logout">退出</a-button>
|
<LayoutHeader @changeFold="changeSiderFold" />
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
<!-- 面包屑 -->
|
<!-- 面包屑 -->
|
||||||
<a-layout-content
|
<a-layout-content
|
||||||
|
Loading…
Reference in New Issue
Block a user