fix(monitor): 修复定时任务权限标识错误

refactor(base_crud): 优化数据权限过滤逻辑

fix(user): 修正用户详情部门查询错误

feat(menu): 添加菜单标题字段并优化路由更新

style(auth): 优化登录页面背景样式

fix(profile): 修复表单样式问题

perf(redis): 优化缓存清除逻辑

fix(role): 移除默认角色操作限制

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