mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
fix(monitor): 修复定时任务权限标识错误
refactor(base_crud): 优化数据权限过滤逻辑 fix(user): 修正用户详情部门查询错误 feat(menu): 添加菜单标题字段并优化路由更新 style(auth): 优化登录页面背景样式 fix(profile): 修复表单样式问题 perf(redis): 优化缓存清除逻辑 fix(role): 移除默认角色操作限制 chore: 清理无用导入和注释
This commit is contained in:
@@ -25,7 +25,7 @@ router = APIRouter(route_class=OperationLogRoute)
|
||||
@router.get("/detail/{id}", summary="获取定时任务详情", description="获取定时任务详情")
|
||||
async def get_obj_detail_controller(
|
||||
id: int = Path(..., description="定时任务ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:query"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await JobService.get_job_detail_service(id=id, auth=auth)
|
||||
logger.info(f"获取定时任务详情成功 {id}")
|
||||
@@ -35,7 +35,7 @@ async def get_obj_detail_controller(
|
||||
async def get_obj_list_controller(
|
||||
page: PaginationQueryParams = Depends(),
|
||||
search: JobQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:query"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict_list = await JobService.get_job_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||
@@ -45,7 +45,7 @@ async def get_obj_list_controller(
|
||||
@router.post("/create", summary="创建定时任务", description="创建定时任务")
|
||||
async def create_obj_controller(
|
||||
data: JobCreateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:create"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:create"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await JobService.create_job_service(auth=auth, data=data)
|
||||
logger.info(f"创建定时任务成功: {result_dict}")
|
||||
@@ -54,7 +54,7 @@ async def create_obj_controller(
|
||||
@router.put("/update", summary="修改定时任务", description="修改定时任务")
|
||||
async def update_obj_controller(
|
||||
data: JobUpdateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:update"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:update"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await JobService.update_job_service(auth=auth, data=data)
|
||||
logger.info(f"修改定时任务成功: {result_dict}")
|
||||
@@ -63,7 +63,7 @@ async def update_obj_controller(
|
||||
@router.delete("/delete", summary="删除定时任务", description="删除定时任务")
|
||||
async def delete_obj_controller(
|
||||
ids: list[int] = Body(..., description="ID列表"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:delete"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:delete"]))
|
||||
) -> JSONResponse:
|
||||
await JobService.delete_job_service(auth=auth, ids=ids)
|
||||
logger.info(f"删除定时任务成功: {id}")
|
||||
@@ -72,7 +72,7 @@ async def delete_obj_controller(
|
||||
@router.post('/export', summary="导出定时任务", description="导出定时任务")
|
||||
async def export_obj_list_controller(
|
||||
search: JobQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:export"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:export"]))
|
||||
) -> StreamingResponse:
|
||||
# 获取全量数据
|
||||
result_dict_list = await JobService.get_job_list_service(search=search, auth=auth)
|
||||
@@ -89,7 +89,7 @@ async def export_obj_list_controller(
|
||||
|
||||
@router.delete("/clear", summary="清空定时任务日志", description="清空定时任务日志")
|
||||
async def clear_obj_log_controller(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:delete"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:delete"]))
|
||||
) -> JSONResponse:
|
||||
await JobService.clear_job_service(auth=auth)
|
||||
logger.info(f"清空定时任务成功")
|
||||
@@ -99,13 +99,13 @@ async def clear_obj_log_controller(
|
||||
async def option_obj_controller(
|
||||
id: int = Body(..., description="定时任务ID"),
|
||||
option: int = Body(..., description="操作类型 1: 暂停 2: 恢复 3: 重启"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:job:update"]))
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:update"]))
|
||||
) -> JSONResponse:
|
||||
await JobService.option_job_service(auth=auth, id=id, option=option)
|
||||
logger.info(f"操作定时任务成功: {id}")
|
||||
return SuccessResponse(msg="操作定时任务成功")
|
||||
|
||||
@router.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(permissions=["system:job:query"]))])
|
||||
@router.get("/log", summary="获取定时任务日志", description="获取定时任务日志", dependencies=[Depends(AuthPermission(permissions=["monitor:job:query"]))])
|
||||
async def get_job_log_controller():
|
||||
data = [
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ class ConfigModel(ModelBase):
|
||||
creator = relationship(
|
||||
"UserModel",
|
||||
foreign_keys=creator_id,
|
||||
lazy="joined",
|
||||
lazy="selectin",
|
||||
post_update=True,
|
||||
uselist=False
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Boolean, Column, String, Integer, DateTime, ForeignKey, Text
|
||||
from sqlalchemy.orm import relationship
|
||||
from typing import Optional, List
|
||||
|
||||
from app.core.base_model import ModelBase
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Optional
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.core.base_schema import BaseSchema
|
||||
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
class PositionCreateSchema(BaseModel):
|
||||
"""岗位创建模型"""
|
||||
@@ -22,3 +22,11 @@ class PositionUpdateSchema(PositionCreateSchema):
|
||||
class PositionOutSchema(PositionCreateSchema, BaseSchema):
|
||||
"""岗位信息响应模型"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class PositionOptionsOut(PositionCreateSchema):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(description="主键ID")
|
||||
created_at: DateTimeStr = Field(description="创建时间")
|
||||
updated_at: DateTimeStr = Field(description="更新时间")
|
||||
@@ -7,7 +7,7 @@ from app.api.v1.schemas.system.dept_schema import DeptOutSchema
|
||||
from app.api.v1.schemas.system.menu_schema import MenuOutSchema
|
||||
from app.core.base_schema import BaseSchema
|
||||
from app.core.validator import role_permission_request_validator
|
||||
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
class RoleCreateSchema(BaseModel):
|
||||
"""角色创建模型"""
|
||||
@@ -43,3 +43,13 @@ class RoleOutSchema(RoleCreateSchema, BaseSchema):
|
||||
|
||||
menus: List[MenuOutSchema] = Field(default_factory=list, description='角色菜单列表')
|
||||
depts: List[DeptOutSchema] = Field(default_factory=list, description='角色部门列表')
|
||||
|
||||
|
||||
class RoleOptionsOut(RoleCreateSchema):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(description="主键ID")
|
||||
created_at: DateTimeStr = Field(description="创建时间")
|
||||
updated_at: DateTimeStr = Field(description="更新时间")
|
||||
menus: List[MenuOutSchema] = Field(default_factory=list, description='角色菜单列表')
|
||||
depts: List[DeptOutSchema] = Field(default_factory=list, description='角色部门列表')
|
||||
@@ -3,8 +3,8 @@
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel, ConfigDict, Field, EmailStr, field_validator
|
||||
|
||||
from app.api.v1.schemas.system.position_schema import PositionOutSchema
|
||||
from app.api.v1.schemas.system.role_schema import RoleOutSchema
|
||||
from app.api.v1.schemas.system.position_schema import PositionOptionsOut
|
||||
from app.api.v1.schemas.system.role_schema import RoleOptionsOut
|
||||
from app.api.v1.schemas.system.dept_schema import DeptOutSchema
|
||||
from app.core.validator import DateTimeStr, mobile_validator
|
||||
from app.core.base_schema import BaseSchema
|
||||
@@ -76,6 +76,7 @@ class UserCreateSchema(CurrentUserUpdateSchema):
|
||||
|
||||
class UserUpdateSchema(UserCreateSchema):
|
||||
"""更新"""
|
||||
model_config = ConfigDict(from_attributes=True, exclude={"password"})
|
||||
id: int = Field(..., description="主键ID")
|
||||
|
||||
|
||||
@@ -86,5 +87,5 @@ class UserOutSchema(UserCreateSchema, BaseSchema):
|
||||
last_login: Optional[DateTimeStr] = Field(default=None, description="最后登录时间")
|
||||
dept_name: Optional[str] = Field(default=None, description='部门名称')
|
||||
dept: Optional[DeptOutSchema] = Field(default=None, description='部门')
|
||||
roles: List[RoleOutSchema] = Field(default=[], description='角色')
|
||||
positions: List[PositionOutSchema] = Field(default=[], description='岗位')
|
||||
roles: Optional[List[RoleOptionsOut]] = Field(default=[], description='角色')
|
||||
positions: Optional[List[PositionOptionsOut]] = Field(default=[], description='岗位')
|
||||
|
||||
@@ -116,8 +116,11 @@ class CacheService:
|
||||
:param redis: Redis对象
|
||||
:return: 操作缓存响应信息
|
||||
"""
|
||||
cache_keys = await RedisCURD(redis).get_keys
|
||||
cache_keys = await RedisCURD(redis).get_keys()
|
||||
if cache_keys:
|
||||
await RedisCURD(redis).delete(*cache_keys)
|
||||
|
||||
return True
|
||||
|
||||
# 避免清除所有的缓存,而采用上面的方式,只清除本系统内指定的所有缓存
|
||||
# return await RedisCURD(redis).clear()
|
||||
|
||||
@@ -57,8 +57,8 @@ class OnlineService:
|
||||
async def clear_online_service(cls, redis: Redis) -> bool:
|
||||
"""强制下线在线用户"""
|
||||
# 删除 token
|
||||
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*")
|
||||
await RedisCURD(redis).delete(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:*")
|
||||
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*")
|
||||
await RedisCURD(redis).clear(f"{RedisInitKeyConfig.REFRESH_TOKEN.key}:*")
|
||||
|
||||
logger.info(f"清除所有在线用户会话成功")
|
||||
return True
|
||||
|
||||
@@ -176,7 +176,7 @@ class UserService:
|
||||
raise CustomException(msg="用户不存在")
|
||||
# 获取部门名称
|
||||
if user.dept_id:
|
||||
dept = await DeptCRUD(auth).get_by_id_crud(id=user.dept_id)
|
||||
dept = await DeptCRUD(auth).get_by_id_crud(id=auth.user.dept_id)
|
||||
user.dept_name = dept.name if dept else None
|
||||
user_dict = UserOutSchema.model_validate(user).model_dump()
|
||||
|
||||
@@ -189,7 +189,7 @@ class UserService:
|
||||
MenuOutSchema.model_validate(menu).model_dump()
|
||||
for role in auth.user.roles
|
||||
for menu in role.menus
|
||||
if menu.status and menu.type in [1, 2]
|
||||
if menu.status and menu.type in [1, 2, 4]
|
||||
]
|
||||
user_dict["menus"] = menus
|
||||
return user_dict
|
||||
@@ -259,7 +259,7 @@ class UserService:
|
||||
|
||||
# 更新密码
|
||||
new_password_hash = PwdUtil.set_password_hash(password=data.password)
|
||||
new_user = await UserCRUD(auth).change_password_crud(id=user.id, password_hash=new_password_hash)
|
||||
new_user = await UserCRUD(auth).change_password_crud(id=data.id, password_hash=new_password_hash)
|
||||
return UserOutSchema.model_validate(new_user).model_dump()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -192,7 +192,7 @@ class Settings(BaseSettings):
|
||||
UPLOAD_MACHINE: str = 'A' # 上传机器标识
|
||||
ALLOWED_EXTENSIONS: list[str] = [ # 允许的文件类型
|
||||
# 图片
|
||||
'.bmp', '.gif', '.jpg', '.jpeg', '.png', '.ico',
|
||||
'.bmp', '.gif', '.jpg', '.jpeg', '.png', '.ico', '.svg',
|
||||
# 文档
|
||||
'.csv', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.html', '.htm', '.txt', '.pdf',
|
||||
# 压缩包
|
||||
|
||||
@@ -55,8 +55,13 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
if hasattr(self.model, "creator"):
|
||||
sql = sql.options(selectinload(self.model.creator))
|
||||
|
||||
# sql = await self.__filter_permissions(sql)
|
||||
|
||||
result: Result = await self.db.execute(sql)
|
||||
obj = result.scalars().unique().first()
|
||||
# if not obj:
|
||||
# raise CustomException(msg="该信息不存在")
|
||||
|
||||
return obj
|
||||
except Exception as e:
|
||||
raise CustomException(msg=f"获取查询失败: {str(e)}")
|
||||
@@ -82,6 +87,9 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
.where(*conditions)
|
||||
.order_by(*self.__order_by(order))
|
||||
.distinct())
|
||||
# 预加载creator关系
|
||||
if hasattr(self.model, "creator"):
|
||||
sql = sql.options(selectinload(self.model.creator))
|
||||
sql = await self.__filter_permissions(sql)
|
||||
result: Result = await self.db.execute(sql)
|
||||
return result.scalars().unique().all()
|
||||
@@ -219,14 +227,18 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
|
||||
async def __filter_permissions(self, sql: Select[Any]) -> Select[Any]:
|
||||
"""过滤数据权限"""
|
||||
# 1. 如果模型没有creator字段,则不需要过滤
|
||||
# 如果不需要检查数据权限,则直接返回
|
||||
if not self.current_user or not self.auth.check_data_scope:
|
||||
return sql
|
||||
|
||||
# 1. 如果模型没有创建人creator字段,则不需要权限判断
|
||||
if not hasattr(self.model, "creator"):
|
||||
return sql
|
||||
|
||||
sql = sql.options(selectinload(self.model.creator))
|
||||
|
||||
# 2. 超级管理员可以查看所有数据
|
||||
if not self.current_user or self.current_user.is_superuser:
|
||||
if self.current_user.is_superuser:
|
||||
return sql
|
||||
|
||||
# 3. 如果用户没有部门或角色,则只能查看自己的数据
|
||||
@@ -243,23 +255,21 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
# 3: 本部门及以下数据权限
|
||||
# 4: 全部数据权限
|
||||
# 5: 自定义数据权限
|
||||
# 5. 处理各种数据权限范围
|
||||
|
||||
# 获取当前用户所绑定角色的数据权限范围
|
||||
for role in self.current_user.roles:
|
||||
# 如果有全部数据权限,直接返回所有数据
|
||||
if role.data_scope == 4:
|
||||
return sql
|
||||
for dept in role.depts:
|
||||
dept_ids.add(dept.id)
|
||||
|
||||
data_scopes.add(role.data_scope)
|
||||
# 如果是自定义权限,添加自定义部门
|
||||
if role.data_scope == 5:
|
||||
dept_ids.update({dept.id for dept in role.depts})
|
||||
|
||||
conditions = []
|
||||
if 4 in data_scopes:
|
||||
# 4、全部数据权限
|
||||
return sql
|
||||
|
||||
# 5. 处理各种数据权限范围
|
||||
if 1 in data_scopes:
|
||||
# 1、仅本人数据
|
||||
conditions.append(self.model.creator_id == self.current_user.id)
|
||||
return sql.where(self.model.creator_id == self.current_user.id)
|
||||
|
||||
if 2 in data_scopes:
|
||||
# 2、本部门数据
|
||||
@@ -270,16 +280,11 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
dept_objs = await CRUDBase(DeptModel, self.auth).list()
|
||||
id_map = get_child_id_map(dept_objs)
|
||||
dept_child_ids = get_child_recursion(id=self.current_user.dept_id, id_map=id_map)
|
||||
dept_ids.update(dept_child_ids)
|
||||
for child_id in dept_child_ids:
|
||||
dept_ids.add(child_id)
|
||||
|
||||
if dept_ids:
|
||||
conditions.append(self.model.creator.has(UserModel.dept_id.in_(list(dept_ids))))
|
||||
|
||||
# 6. 组合所有条件
|
||||
if conditions:
|
||||
return sql.where(and_(*conditions))
|
||||
|
||||
return sql.where(self.model.creator_id == self.current_user.id)
|
||||
# 5、自定义权限
|
||||
return sql.where(self.model.creator.has(UserModel.dept_id.in_(list(dept_ids))))
|
||||
|
||||
def __order_by(self, order_by: List[Dict[str, str]]) -> List[ColumnElement]:
|
||||
"""
|
||||
|
||||
@@ -103,8 +103,13 @@ export interface MenuForm {
|
||||
hidden?: boolean;
|
||||
always_show?: boolean;
|
||||
title?: string;
|
||||
params?: { key: string; value: string; }[];
|
||||
params?: KeyValue[];
|
||||
affix?: boolean;
|
||||
status?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface KeyValue {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export const UserAPI = {
|
||||
},
|
||||
|
||||
getUserDetail(query: number) {
|
||||
return request<ApiResponse>({
|
||||
return request<ApiResponse<UserInfo>>({
|
||||
url: `/system/user/detail/${query}`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -246,9 +246,9 @@ export interface UserForm {
|
||||
dept_id?: number;
|
||||
dept_name?: string;
|
||||
role_ids?: number[];
|
||||
roleNames?: string;
|
||||
roleNames?: string[];
|
||||
position_ids?: number[];
|
||||
positionNames?: string;
|
||||
positionNames?: string[];
|
||||
password?: string;
|
||||
gender?: number;
|
||||
email?: string;
|
||||
|
||||
@@ -26,7 +26,7 @@ export const useUserStore = defineStore("user", {
|
||||
// 获取用户信息
|
||||
async getUserInfo() {
|
||||
const response = await UserAPI.getCurrentUserInfo();
|
||||
const routers = response.data.data.menus;
|
||||
const routers: MenuTable[] = response.data.data.menus || [];
|
||||
delete response.data.data.menus;
|
||||
this.setRoute(routers);
|
||||
this.basicInfo = { ...this.basicInfo, ...response.data.data };
|
||||
@@ -37,7 +37,8 @@ export const useUserStore = defineStore("user", {
|
||||
this.basicInfo = info;
|
||||
},
|
||||
|
||||
setRoute(routers: any) {
|
||||
// 设置路由
|
||||
setRoute(routers: MenuTable[]) {
|
||||
this.routeList = routers;
|
||||
this.hasGetRoute = true;
|
||||
},
|
||||
|
||||
@@ -122,18 +122,18 @@
|
||||
<span>基本设置</span>
|
||||
</template>
|
||||
<div>
|
||||
<el-form ref="ruleFormRef" :model="infoFormState" :rules="rules" :inline="true" label-suffix=":">
|
||||
<el-form ref="ruleFormRef" :model="infoFormState" :rules="rules" :inline="true" label-width="80px" label-suffix=":">
|
||||
|
||||
<el-form-item label="姓名" name="name">
|
||||
<el-input v-model="infoFormState.name" placeholder="请输入姓名" prefix-icon="User" clearable />
|
||||
<el-input v-model="infoFormState.name" placeholder="请输入姓名" prefix-icon="User" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号" name="mobile">
|
||||
<el-input v-model="infoFormState.mobile" placeholder="请输入手机号码" prefix-icon="Phone" clearable />
|
||||
<el-input v-model="infoFormState.mobile" placeholder="请输入手机号码" prefix-icon="Phone" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" name="email">
|
||||
<el-input v-model="infoFormState.email" placeholder="请输入邮箱" prefix-icon="Message" clearable />
|
||||
<el-input v-model="infoFormState.email" placeholder="请输入邮箱" prefix-icon="Message" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="性别" name="gender">
|
||||
@@ -159,9 +159,9 @@
|
||||
<span>安全设置</span>
|
||||
</template>
|
||||
<div>
|
||||
<el-form ref="ruleFormRef" :model="passwordFormState" :rules="resetPasswordRules" label-suffix=":">
|
||||
<el-form ref="ruleFormRef" :model="passwordFormState" :rules="resetPasswordRules" label-width="80px" label-suffix=":">
|
||||
<el-form-item label="当前密码" name="old_password">
|
||||
<el-input v-model.trim="passwordFormState.old_password" :placeholder="t('login.password')" type="password" show-password clearable>
|
||||
<el-input v-model.trim="passwordFormState.old_password" :placeholder="t('login.password')" type="password" show-password clearable style="width: 240px;">
|
||||
<template #prefix>
|
||||
<Lock />
|
||||
</template>
|
||||
@@ -169,7 +169,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="新密码" name="new_password">
|
||||
<el-input v-model.trim="passwordFormState.new_password" type="password" :placeholder="t('login.newPassword')" show-password clearable>
|
||||
<el-input v-model.trim="passwordFormState.new_password" type="password" :placeholder="t('login.newPassword')" show-password clearable style="width: 240px;">
|
||||
<template #prefix>
|
||||
<Key />
|
||||
</template>
|
||||
@@ -177,7 +177,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认新密码" name="confirm_password">
|
||||
<el-input v-model.trim="passwordFormState.confirm_password" type="password" :placeholder="t('login.message.password.confirm')" show-password clearable>
|
||||
<el-input v-model.trim="passwordFormState.confirm_password" type="password" :placeholder="t('login.message.password.confirm')" show-password clearable style="width: 240px;">
|
||||
<template #prefix>
|
||||
<Check />
|
||||
</template>
|
||||
@@ -507,4 +507,13 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 修复表单输入框清除按钮导致的宽度变化问题 */
|
||||
.el-input {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -879,7 +879,6 @@ const handleClear = () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await JobAPI.clearJob();
|
||||
ElMessage.success("清空成功");
|
||||
handleResetQuery();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
||||
@@ -101,10 +101,10 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { LocationQuery, RouteLocationRaw, useRoute } from "vue-router";
|
||||
import { LocationQuery, RouteLocationRaw, useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { onActivated, onMounted, watch } from "vue";
|
||||
import AuthAPI, {type LoginFormData, type CaptchaInfo } from "@/api/system/auth";
|
||||
import router from "@/router";
|
||||
import { useAppStore, useUserStore } from "@/store";
|
||||
import CommonWrapper from "@/components/CommonWrapper/index.vue";
|
||||
|
||||
@@ -113,9 +113,27 @@ const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 组件挂载时获取验证码
|
||||
onMounted(() => getCaptcha());
|
||||
|
||||
// 组件激活时获取验证码(适用于KeepAlive缓存的情况)
|
||||
onActivated(() => {
|
||||
getCaptcha();
|
||||
// 重置登录表单
|
||||
loginForm.captcha = '';
|
||||
});
|
||||
|
||||
// 监听路由变化,确保每次进入登录页面都有最新验证码
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
getCaptcha();
|
||||
loginForm.captcha = '';
|
||||
}
|
||||
);
|
||||
|
||||
const loginFormRef = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
// 是否大写锁定
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-container" :style="{ 'background-image': configStore.configData?.sys_login_background?.config_value ? `url(${configStore.configData.sys_login_background.config_value})` : '/background.svg' }">
|
||||
<!-- 右侧切换主题、语言按钮 -->
|
||||
<div class="action-bar">
|
||||
<el-tooltip :content="t('login.themeToggle')" placement="bottom">
|
||||
@@ -93,17 +93,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
// 添加伪元素作为背景层
|
||||
.login-container::before {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background: url("/background.svg");
|
||||
.login-container {
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
|
||||
@@ -242,6 +242,10 @@
|
||||
<el-input v-model="formData.name" placeholder="请输入菜单名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="菜单标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入菜单标题" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="菜单类型" prop="type">
|
||||
<el-radio-group v-model="formData.type" @change="handleMenuTypeChange">
|
||||
<el-radio :value="MenuTypeEnum.CATALOG">目录</el-radio>
|
||||
@@ -255,7 +259,7 @@
|
||||
<el-input v-model="formData.route_path" placeholder="请输入外链完整路径" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="routeName">
|
||||
<el-form-item prop="routeName">
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
路由名称
|
||||
@@ -464,6 +468,7 @@ defineOptions({
|
||||
});
|
||||
|
||||
import { useAppStore } from "@/store/modules/app.store";
|
||||
import { useUserStore } from "@/store/modules/user.store";
|
||||
import { DeviceEnum } from "@/enums/settings/device.enum";
|
||||
|
||||
import MenuAPI, { MenuPageQuery, MenuForm, MenuTable } from "@/api/system/menu";
|
||||
@@ -471,6 +476,7 @@ import { MenuTypeEnum } from "@/enums/system/menu.enum";
|
||||
import { formatTree, listToTree } from "@/utils/common";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -514,7 +520,7 @@ const formData = reactive<MenuForm>({
|
||||
hidden: false,
|
||||
always_show: false,
|
||||
title: '',
|
||||
params: [] as { key: string; value: string }[],
|
||||
params: undefined,
|
||||
affix: false,
|
||||
status: true,
|
||||
description: undefined,
|
||||
@@ -716,6 +722,7 @@ async function handleSubmit() {
|
||||
if (id) {
|
||||
try {
|
||||
await MenuAPI.updateMenu(formData)
|
||||
await userStore.getUserInfo();
|
||||
dialogVisible.visible = false;
|
||||
resetForm();
|
||||
handleResetQuery();
|
||||
@@ -727,6 +734,7 @@ async function handleSubmit() {
|
||||
} else {
|
||||
try {
|
||||
await MenuAPI.createMenu(formData)
|
||||
await userStore.getUserInfo();
|
||||
dialogVisible.visible = false;
|
||||
resetForm();
|
||||
handleResetQuery();
|
||||
@@ -750,6 +758,7 @@ async function handleDelete(ids: number[]) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await MenuAPI.deleteMenu(ids);
|
||||
await userStore.getUserInfo();
|
||||
handleResetQuery();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
||||
@@ -146,8 +146,7 @@
|
||||
size="small"
|
||||
link
|
||||
icon="document"
|
||||
@click="scope.row.id === 1 ? ElMessage.warning('系统默认角色,不可操作') : handleOpenDialog('detail', scope.row.id)"
|
||||
:disabled="scope.row.id === 1"
|
||||
@click="handleOpenDialog('detail', scope.row.id)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
|
||||
@@ -257,7 +257,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号码" prop="mobile">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" />
|
||||
</el-form-item>
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-form-item label="密码" prop="password" v-if="dialogVisible.type === 'create'">
|
||||
<el-input v-model="formData.password" placeholder="请输入密码" type="password" show-password clearable />
|
||||
</el-form-item>
|
||||
|
||||
@@ -548,6 +548,8 @@ async function handleCloseDialog() {
|
||||
// 打开弹窗
|
||||
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
|
||||
dialogVisible.type = type;
|
||||
// 动态设置密码验证规则
|
||||
rules.password[0].required = type === 'create';
|
||||
if (id) {
|
||||
const response = await UserAPI.getUserDetail(id);
|
||||
if (type === 'detail') {
|
||||
@@ -556,6 +558,9 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
|
||||
} else if (type === 'update') {
|
||||
dialogVisible.title = "修改用户";
|
||||
Object.assign(formData, response.data.data);
|
||||
// 确保角色和岗位ID正确设置
|
||||
formData.role_ids = (response.data.data.roles || []).map(item => item.id as number);
|
||||
formData.position_ids = (response.data.data.positions || []).map(item => item.id as number);
|
||||
}
|
||||
} else {
|
||||
dialogVisible.title = "新增用户";
|
||||
@@ -596,8 +601,11 @@ async function handleSubmit() {
|
||||
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
|
||||
const id = formData.id;
|
||||
if (id) {
|
||||
try {
|
||||
await UserAPI.updateUser({ id, ...formData })
|
||||
try {
|
||||
// 编辑用户时,不传递密码字段
|
||||
const updateData = { id, ...formData };
|
||||
delete updateData.password;
|
||||
await UserAPI.updateUser(updateData)
|
||||
dialogVisible.visible = false;
|
||||
resetForm();
|
||||
handleCloseDialog();
|
||||
|
||||
@@ -47,7 +47,7 @@ export default defineConfig(({ mode }: ConfigEnv) => {
|
||||
target: env.VITE_API_BASE_URL, // 代理目标地址:https://后端地址
|
||||
secure: false, // 请求是否https
|
||||
changeOrigin: true, // 是否跨域
|
||||
// rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
|
||||
rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user