From b678588d13b4e3bae53f723aad30f71b2e310bab Mon Sep 17 00:00:00 2001
From: zhangtao <9480807882@qq.com>
Date: Tue, 11 Nov 2025 23:34:30 +0800
Subject: [PATCH 1/2] =?UTF-8?q?refactor(schema):=20=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?=E9=87=8D=E5=A4=8D=E7=9A=84=E6=A8=A1=E5=9E=8B=E9=AA=8C=E8=AF=81?=
=?UTF-8?q?=E9=80=BB=E8=BE=91=E5=B9=B6=E7=AE=80=E5=8C=96=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E9=AA=8C=E8=AF=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
统一移除各模块schema中重复的model_validator前置处理逻辑,改用更简洁的字段验证方式
优化字段验证逻辑,移除冗余的字符串处理和类型转换代码
---
.../api/v1/module_application/ai/schema.py | 2 +-
.../api/v1/module_application/job/schema.py | 26 +-------
.../api/v1/module_application/myapp/schema.py | 27 +-------
.../app/api/v1/module_common/file/schema.py | 13 ----
.../api/v1/module_generator/demo/schema.py | 66 +++++--------------
.../app/api/v1/module_system/dept/schema.py | 30 +--------
.../app/api/v1/module_system/dict/schema.py | 25 +++----
.../app/api/v1/module_system/log/schema.py | 19 ------
.../app/api/v1/module_system/notice/schema.py | 21 ------
.../app/api/v1/module_system/params/schema.py | 29 +-------
.../api/v1/module_system/position/schema.py | 29 +-------
.../app/api/v1/module_system/role/schema.py | 44 -------------
.../app/api/v1/module_system/tenant/schema.py | 53 +--------------
.../app/api/v1/module_system/user/schema.py | 49 +-------------
backend/requirements.txt | 2 +-
15 files changed, 34 insertions(+), 401 deletions(-)
diff --git a/backend/app/api/v1/module_application/ai/schema.py b/backend/app/api/v1/module_application/ai/schema.py
index 4de29572..28e8909f 100644
--- a/backend/app/api/v1/module_application/ai/schema.py
+++ b/backend/app/api/v1/module_application/ai/schema.py
@@ -4,7 +4,7 @@
from pydantic import ConfigDict, Field, HttpUrl, BaseModel
from app.core.base_schema import BaseSchema
-from app.common.enums import McpLLMProvider, McpType
+from app.common.enums import McpType
class ChatQuerySchema(BaseModel):
diff --git a/backend/app/api/v1/module_application/job/schema.py b/backend/app/api/v1/module_application/job/schema.py
index fb7aeb23..f416159f 100644
--- a/backend/app/api/v1/module_application/job/schema.py
+++ b/backend/app/api/v1/module_application/job/schema.py
@@ -2,7 +2,7 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from typing import Optional
-import re
+
from app.core.base_schema import BaseSchema
from app.core.validator import DateTimeStr, datetime_validator
@@ -26,30 +26,6 @@ class JobCreateSchema(BaseModel):
description: Optional[str] = Field(default=None, max_length=255, description='描述')
status: Optional[bool] = Field(default=False, description='任务状态:启动,停止')
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- """前置归一化:字符串去空格、布尔/数字兼容转换。"""
- if isinstance(data, dict):
- for key in ('name', 'func', 'trigger', 'args', 'kwargs', 'jobstore', 'executor', 'trigger_args', 'start_date', 'end_date', 'description'):
- val = data.get(key)
- if isinstance(val, str):
- data[key] = val.strip()
- for bkey in ('coalesce', 'status'):
- val = data.get(bkey)
- if isinstance(val, str):
- lowered = val.strip().lower()
- if lowered in {'true', '1', 'y', 'yes'}:
- data[bkey] = True
- elif lowered in {'false', '0', 'n', 'no'}:
- data[bkey] = False
- elif isinstance(val, int):
- data[bkey] = bool(val)
- val = data.get('max_instances')
- if isinstance(val, str) and val.strip().isdigit():
- data['max_instances'] = int(val.strip())
- return data
-
@field_validator('trigger')
@classmethod
def _validate_trigger(cls, v: str) -> str:
diff --git a/backend/app/api/v1/module_application/myapp/schema.py b/backend/app/api/v1/module_application/myapp/schema.py
index 8edd9cc0..817d11e2 100644
--- a/backend/app/api/v1/module_application/myapp/schema.py
+++ b/backend/app/api/v1/module_application/myapp/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from typing import Optional
-from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
from urllib.parse import urlparse
@@ -15,31 +15,6 @@ class ApplicationCreateSchema(BaseModel):
status: bool = Field(True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
- @model_validator(mode="before")
- @classmethod
- def _normalize(cls, data):
- """模型级前置处理:去除首尾空格,空字符串转为 None(可选字段),并规范布尔。"""
- if isinstance(data, dict):
- for key in ("name", "access_url", "icon_url", "description"):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- # 将可选字段的空字符串转换为 None
- if key in ("icon_url", "description") and val == "":
- val = None
- data[key] = val
- # 规范布尔字符串/数字为布尔值
- status_val = data.get("status")
- if isinstance(status_val, str):
- lowered = status_val.strip().lower()
- if lowered in {"true", "1", "y", "yes"}:
- data["status"] = True
- elif lowered in {"false", "0", "n", "no"}:
- data["status"] = False
- elif isinstance(status_val, int):
- data["status"] = bool(status_val)
- return data
-
@field_validator('name')
@classmethod
def _validate_name_length(cls, v: str) -> str:
diff --git a/backend/app/api/v1/module_common/file/schema.py b/backend/app/api/v1/module_common/file/schema.py
index 29b352a9..5d1add78 100644
--- a/backend/app/api/v1/module_common/file/schema.py
+++ b/backend/app/api/v1/module_common/file/schema.py
@@ -48,19 +48,6 @@ class ImportModel(BaseModel):
filed_info: Optional[list[ImportFieldModel]] = Field(description='字段关联表')
file_name: Optional[str] = Field(description='文件名')
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- if isinstance(data, dict):
- for key in ('table_name', 'sheet_name', 'file_name'):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- if val == '':
- val = None
- data[key] = val
- return data
-
@model_validator(mode='after')
def _validate(self):
# excel_column 不重复(忽略 None)
diff --git a/backend/app/api/v1/module_generator/demo/schema.py b/backend/app/api/v1/module_generator/demo/schema.py
index d8f15aa9..2c0ae60d 100644
--- a/backend/app/api/v1/module_generator/demo/schema.py
+++ b/backend/app/api/v1/module_generator/demo/schema.py
@@ -8,68 +8,34 @@
class DemoCreateSchema(BaseModel):
"""新增模型"""
- name: str = Field(..., max_length=50, description='名称')
+ name: str = Field(..., min_length=2, max_length=50, description='名称')
status: bool = Field(True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
@field_validator('name')
@classmethod
- def _validate_name(cls, v: str) -> str:
+ def validate_name(cls, v: str) -> str:
+ """验证名称字段的格式和内容"""
+ # 去除首尾空格
v = v.strip()
if not v:
raise ValueError('名称不能为空')
return v
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- if isinstance(data, dict):
- for key in ('name', 'description'):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- if key == 'description' and val == '':
- val = None
- data[key] = val
- # status兼容
- val = data.get('status')
- if isinstance(val, str):
- lowered = val.strip().lower()
- if lowered in {'true', '1', 'y', 'yes'}:
- data['status'] = True
- elif lowered in {'false', '0', 'n', 'no'}:
- data['status'] = False
- elif isinstance(val, int):
- data['status'] = bool(val)
- return data
-
- @model_validator(mode='wrap')
- @classmethod
- def _wrap(cls, data, handler):
- # 进一步处理:压缩名称/描述中的多余空白,并支持更多 status 同义词
- if isinstance(data, dict):
- name = data.get('name')
- if isinstance(name, str):
- data['name'] = ' '.join(name.split())
- status_val = data.get('status')
- if isinstance(status_val, str):
- lowered = status_val.strip().lower()
- if lowered in {'enabled', 'enable', 'on'}:
- data['status'] = True
- elif lowered in {'disabled', 'disable', 'off'}:
- data['status'] = False
- desc = data.get('description')
- if isinstance(desc, str):
- data['description'] = ' '.join(desc.split())
- result = handler(data)
- return result
-
@model_validator(mode='after')
- def _check_disabled_requires_description(self):
- # 业务示例:禁用时必须填写描述
- if self.status is False and (self.description is None or (isinstance(self.description, str) and self.description.strip() == '')):
- raise ValueError('禁用时必须填写描述')
+ def _after_validation(self):
+ """
+ 核心业务规则校验
+ """
+ # 长度校验:名称最小长度
+ if len(self.name) < 2 or len(self.name) > 50:
+ raise ValueError('名称长度必须在2-50个字符之间')
+ # 格式校验:名称只能包含字母、数字、下划线和中划线
+ if not self.name.isalnum() and not all(c in '-_' for c in self.name):
+ raise ValueError('名称只能包含字母、数字、下划线和中划线')
+
return self
+
class DemoUpdateSchema(DemoCreateSchema):
diff --git a/backend/app/api/v1/module_system/dept/schema.py b/backend/app/api/v1/module_system/dept/schema.py
index 913ee4ab..84fc3731 100644
--- a/backend/app/api/v1/module_system/dept/schema.py
+++ b/backend/app/api/v1/module_system/dept/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-from typing import Optional, List
-from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
+from typing import Optional
+from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
@@ -36,32 +36,6 @@ def validate_code(cls, value: Optional[str]):
raise ValueError("部门编码必须以字母开头,且仅包含字母/数字/下划线")
return v
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- if isinstance(data, dict):
- for key in ('code', 'description'):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- if key == 'description' and val == '':
- val = None
- data[key] = val
- pid = data.get('parent_id')
- if isinstance(pid, str) and pid.strip().isdigit():
- data['parent_id'] = int(pid.strip())
- # status兼容
- status_val = data.get('status')
- if isinstance(status_val, str):
- lowered = status_val.strip().lower()
- if lowered in {'true', '1', 'y', 'yes'}:
- data['status'] = True
- elif lowered in {'false', '0', 'n', 'no'}:
- data['status'] = False
- elif isinstance(status_val, int):
- data['status'] = bool(status_val)
- return data
-
class DeptUpdateSchema(DeptCreateSchema):
"""部门更新模型"""
diff --git a/backend/app/api/v1/module_system/dict/schema.py b/backend/app/api/v1/module_system/dict/schema.py
index 274c8454..35bb9b1f 100644
--- a/backend/app/api/v1/module_system/dict/schema.py
+++ b/backend/app/api/v1/module_system/dict/schema.py
@@ -1,5 +1,5 @@
import re
-from pydantic import BaseModel, ConfigDict, Field, field_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from typing import Optional
from app.core.base_schema import BaseSchema
@@ -54,25 +54,16 @@ class DictDataCreateSchema(BaseModel):
is_default: Optional[bool] = Field(default=None, description='是否默认(Y是 N否)')
status: Optional[bool] = Field(default=None, description='状态(1正常 0停用)')
description: Optional[str] = Field(default=None, max_length=255, description="描述")
-
- @field_validator('dict_label')
- @classmethod
- def validate_dict_label(cls, value: str):
- if not value or value.strip() == '':
+
+ @model_validator(mode='after')
+ def validate_after(self):
+ if self.dict_label is None or self.dict_label.strip() == '':
raise ValueError('字典标签不能为空')
- return value
-
- @field_validator('dict_value')
- def validate_dict_value(cls, value: str):
- if not value or value.strip() == '':
+ if self.dict_value is None or self.dict_value.strip() == '':
raise ValueError('字典键值不能为空')
- return value
-
- @field_validator('dict_type')
- def validate_dict_type(cls, value: str):
- if not value or value.strip() == '':
+ if self.dict_type is None or self.dict_type.strip() == '':
raise ValueError('字典类型不能为空')
- return value
+ return self
class DictDataUpdateSchema(DictDataCreateSchema):
diff --git a/backend/app/api/v1/module_system/log/schema.py b/backend/app/api/v1/module_system/log/schema.py
index a404b85d..8c2bba2d 100644
--- a/backend/app/api/v1/module_system/log/schema.py
+++ b/backend/app/api/v1/module_system/log/schema.py
@@ -23,25 +23,6 @@ class OperationLogCreateSchema(BaseModel):
description: Optional[str] = Field(default=None, max_length=255, description="描述")
creator_id: Optional[int] = Field(default=None, description="创建人ID")
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, values):
- if isinstance(values, dict):
- # 字符串去空格
- for k in ["request_path", "request_method", "request_payload", "request_ip", "login_location", "request_os", "request_browser", "response_json", "process_time", "description"]:
- if k in values and isinstance(values[k], str):
- values[k] = values[k].strip() or None if values[k].strip() == "" and k in {"request_payload", "response_json", "description"} else values[k].strip()
- # 方法大写
- if "request_method" in values and isinstance(values["request_method"], str):
- values["request_method"] = values["request_method"].strip().upper()
- # 响应码转整数
- if "response_code" in values and isinstance(values["response_code"], str):
- try:
- values["response_code"] = int(values["response_code"].strip())
- except Exception:
- pass
- return values
-
@field_validator("type")
@classmethod
def _validate_type(cls, value: Optional[int]):
diff --git a/backend/app/api/v1/module_system/notice/schema.py b/backend/app/api/v1/module_system/notice/schema.py
index 80fdd12b..908924ac 100644
--- a/backend/app/api/v1/module_system/notice/schema.py
+++ b/backend/app/api/v1/module_system/notice/schema.py
@@ -14,25 +14,6 @@ class NoticeCreateSchema(BaseModel):
status: bool = Field(default=True, description="是否启用(True:启用 False:禁用)")
description: Optional[str] = Field(default=None, max_length=255, description="描述")
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, values):
- if isinstance(values, dict):
- # 字符串去空格
- for k in ["notice_title", "notice_type", "notice_content", "description"]:
- if k in values and isinstance(values[k], str):
- values[k] = values[k].strip() or None if values[k].strip() == "" and k == "description" else values[k].strip()
- # 布尔兼容
- if "status" in values and isinstance(values["status"], str):
- values["status"] = values["status"].strip().lower() in {"true", "1", "yes", "y"}
- # 类型映射
- mapping = {"1": "1", "2": "2", "通知": "1", "公告": "2", "notice": "1", "announcement": "2"}
- if "notice_type" in values and isinstance(values["notice_type"], str):
- v = values["notice_type"].strip().lower()
- if v in mapping:
- values["notice_type"] = mapping[v]
- return values
-
@field_validator("notice_type")
@classmethod
def _validate_notice_type(cls, value: str):
@@ -46,8 +27,6 @@ def _validate_after(self):
raise ValueError("公告标题不能为空")
if not self.notice_content.strip():
raise ValueError("公告内容不能为空")
- if self.status is False and (not self.description or not str(self.description).strip()):
- raise ValueError("禁用状态下必须填写描述")
return self
diff --git a/backend/app/api/v1/module_system/params/schema.py b/backend/app/api/v1/module_system/params/schema.py
index 43d59832..50750940 100644
--- a/backend/app/api/v1/module_system/params/schema.py
+++ b/backend/app/api/v1/module_system/params/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from typing import Optional
-from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
@@ -15,33 +15,6 @@ class ParamsCreateSchema(BaseModel):
status: bool = Field(default=True, description="状态(True:正常 False:停用)")
description: Optional[str] = Field(default=None, max_length=500, description="描述")
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- """前置归一化:字符串去空格、空串转 None、布尔兼容转换,并规范键为小写。"""
- if isinstance(data, dict):
- for key in ('config_name', 'config_key', 'config_value', 'description'):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- if key in ('config_value', 'description') and val == '':
- val = None
- data[key] = val
- # 规范键为小写
- if isinstance(data.get('config_key'), str):
- data['config_key'] = data['config_key'].lower()
- # 规范布尔
- for bkey in ('config_type', 'status'):
- val = data.get(bkey)
- if isinstance(val, str):
- lowered = val.strip().lower()
- if lowered in {'true', '1', 'y', 'yes'}:
- data[bkey] = True
- elif lowered in {'false', '0', 'n', 'no'}:
- data[bkey] = False
- elif isinstance(val, int):
- data[bkey] = bool(val)
- return data
@field_validator('config_key')
@classmethod
diff --git a/backend/app/api/v1/module_system/position/schema.py b/backend/app/api/v1/module_system/position/schema.py
index f58d3c3f..479dcbd7 100644
--- a/backend/app/api/v1/module_system/position/schema.py
+++ b/backend/app/api/v1/module_system/position/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from typing import Optional
-from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
from app.core.validator import DateTimeStr
@@ -21,33 +21,6 @@ def _validate_name(cls, v: str) -> str:
raise ValueError('岗位名称不能为空')
return v
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- if isinstance(data, dict):
- for key in ('name', 'description'):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- if key == 'description' and val == '':
- val = None
- data[key] = val
- # order字符串转为整数
- order_val = data.get('order')
- if isinstance(order_val, str) and order_val.strip().isdigit():
- data['order'] = int(order_val.strip())
- # status兼容
- status_val = data.get('status')
- if isinstance(status_val, str):
- lowered = status_val.strip().lower()
- if lowered in {'true', '1', 'y', 'yes'}:
- data['status'] = True
- elif lowered in {'false', '0', 'n', 'no'}:
- data['status'] = False
- elif isinstance(status_val, int):
- data['status'] = bool(status_val)
- return data
-
class PositionUpdateSchema(PositionCreateSchema):
"""岗位更新模型"""
diff --git a/backend/app/api/v1/module_system/role/schema.py b/backend/app/api/v1/module_system/role/schema.py
index ba7b7d3b..e0a137c0 100644
--- a/backend/app/api/v1/module_system/role/schema.py
+++ b/backend/app/api/v1/module_system/role/schema.py
@@ -30,38 +30,6 @@ def validate_code(cls, value: Optional[str]):
raise ValueError("角色编码需字母开头,允许字母/数字/下划线,长度2-40")
return v
- @field_validator("name")
- @classmethod
- def validate_name(cls, value: str):
- v = value.strip()
- if not v:
- raise ValueError("角色名称不能为空")
- return v
-
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, values):
- if isinstance(values, dict):
- for k in ["name", "code", "description"]:
- if k in values and isinstance(values[k], str):
- values[k] = values[k].strip() or None if values[k].strip() == "" else values[k].strip()
- # bool 兼容
- if "status" in values and isinstance(values["status"], str):
- values["status"] = values["status"].strip().lower() in {"true", "1", "yes", "y"}
- # 数字兼容
- if "order" in values and isinstance(values["order"], str):
- try:
- values["order"] = int(values["order"].strip())
- except Exception:
- pass
- return values
-
- @model_validator(mode='after')
- def _validate_after(self):
- if self.status is False and (not self.description or not str(self.description).strip()):
- raise ValueError("禁用状态下必须填写描述")
- return self
-
class RolePermissionSettingSchema(BaseModel):
"""角色权限配置模型"""
@@ -70,18 +38,6 @@ class RolePermissionSettingSchema(BaseModel):
menu_ids: List[int] = Field(default_factory=list, description='菜单ID列表')
dept_ids: List[int] = Field(default_factory=list, description='部门ID列表')
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, values):
- if isinstance(values, dict):
- for k in ["role_ids", "menu_ids", "dept_ids"]:
- if k in values and values[k] is not None:
- try:
- values[k] = list({int(x) for x in values[k]})
- except Exception:
- pass
- return values
-
@model_validator(mode='after')
def validate_fields(self):
"""验证权限配置字段"""
diff --git a/backend/app/api/v1/module_system/tenant/schema.py b/backend/app/api/v1/module_system/tenant/schema.py
index 17a9dea1..9a683536 100644
--- a/backend/app/api/v1/module_system/tenant/schema.py
+++ b/backend/app/api/v1/module_system/tenant/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from typing import Optional
-from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
@@ -20,57 +20,6 @@ def _validate_name(cls, v: str) -> str:
raise ValueError('名称不能为空')
return v
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, data):
- if isinstance(data, dict):
- for key in ('name', 'description'):
- val = data.get(key)
- if isinstance(val, str):
- val = val.strip()
- if key == 'description' and val == '':
- val = None
- data[key] = val
- # status兼容
- val = data.get('status')
- if isinstance(val, str):
- lowered = val.strip().lower()
- if lowered in {'true', '1', 'y', 'yes'}:
- data['status'] = True
- elif lowered in {'false', '0', 'n', 'no'}:
- data['status'] = False
- elif isinstance(val, int):
- data['status'] = bool(val)
- return data
-
- @model_validator(mode='wrap')
- @classmethod
- def _wrap(cls, data, handler):
- # 进一步处理:压缩名称/描述中的多余空白,并支持更多 status 同义词
- if isinstance(data, dict):
- name = data.get('name')
- if isinstance(name, str):
- data['name'] = ' '.join(name.split())
- status_val = data.get('status')
- if isinstance(status_val, str):
- lowered = status_val.strip().lower()
- if lowered in {'enabled', 'enable', 'on'}:
- data['status'] = True
- elif lowered in {'disabled', 'disable', 'off'}:
- data['status'] = False
- desc = data.get('description')
- if isinstance(desc, str):
- data['description'] = ' '.join(desc.split())
- result = handler(data)
- return result
-
- @model_validator(mode='after')
- def _check_disabled_requires_description(self):
- # 业务示例:禁用时必须填写描述
- if self.status is False and (self.description is None or (isinstance(self.description, str) and self.description.strip() == '')):
- raise ValueError('禁用时必须填写描述')
- return self
-
class TenantUpdateSchema(TenantCreateSchema):
"""更新模型"""
diff --git a/backend/app/api/v1/module_system/user/schema.py b/backend/app/api/v1/module_system/user/schema.py
index aee98e5f..9084f5cb 100644
--- a/backend/app/api/v1/module_system/user/schema.py
+++ b/backend/app/api/v1/module_system/user/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from typing import Optional, List
-from pydantic import BaseModel, ConfigDict, Field, EmailStr, field_validator, model_validator
+from pydantic import BaseModel, ConfigDict, Field, EmailStr, field_validator
from app.core.validator import DateTimeStr, mobile_validator
from app.core.base_schema import BaseSchema, CommonSchema
@@ -59,24 +59,6 @@ def validate_username(cls, value: str):
raise ValueError("账号需字母开头,3-32位,仅含字母/数字/_ . -")
return v
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, values):
- if isinstance(values, dict):
- for k in ["name", "username", "password", "description"]:
- if k in values and isinstance(values[k], str):
- values[k] = values[k].strip() or values[k]
- # role_ids 去重并转为 int
- if "role_ids" in values and values["role_ids"] is not None:
- try:
- values["role_ids"] = list[int]({int(x) for x in values["role_ids"]})
- except Exception:
- pass
- # mobile 空串转 None
- if "mobile" in values and isinstance(values["mobile"], str) and values["mobile"].strip() == "":
- values["mobile"] = None
- return values
-
class UserForgetPasswordSchema(BaseModel):
"""忘记密码"""
@@ -116,35 +98,6 @@ class UserCreateSchema(CurrentUserUpdateSchema):
role_ids: Optional[List[int]] = Field(default=[], description='角色ID')
position_ids: Optional[List[int]] = Field(default=[], description='岗位ID')
- @model_validator(mode='before')
- @classmethod
- def _normalize(cls, values):
- if isinstance(values, dict):
- # 字符串去空格和空串转 None
- for k in ["username", "password", "description", "name"]:
- if k in values and isinstance(values[k], str):
- values[k] = values[k].strip() or None if values[k].strip() == "" else values[k].strip()
- # bool 兼容
- for k in ["status", "is_superuser"]:
- if k in values:
- v = values[k]
- if isinstance(v, str):
- values[k] = v.strip().lower() in {"true", "1", "yes", "y"}
- # 列表转 int 去重
- for k in ["role_ids", "position_ids"]:
- if k in values and values[k] is not None:
- try:
- values[k] = list({int(x) for x in values[k]})
- except Exception:
- pass
- return values
-
- @model_validator(mode='after')
- def _validate_after(self):
- if self.status is False and (not self.description or not str(self.description).strip()):
- raise ValueError("禁用状态下必须填写备注描述")
- return self
-
class UserUpdateSchema(UserCreateSchema):
"""更新"""
diff --git a/backend/requirements.txt b/backend/requirements.txt
index ec2e9da9..a1a09e70 100755
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -33,4 +33,4 @@ openai==1.55.2 # ai 大模型
rich==13.9.4 # 终端打印美化
sqlglot[rs]==27.8.0 # sql 解析
pydantic_validation_decorator==0.1.4 # 模型验证
-loguru
\ No newline at end of file
+loguru==0.7.3
\ No newline at end of file
From 8b16da0062e9811feaa521b77d92d19479658a94 Mon Sep 17 00:00:00 2001
From: zhangtao <9480807882@qq.com>
Date: Thu, 13 Nov 2025 00:38:06 +0800
Subject: [PATCH 2/2] =?UTF-8?q?refactor(frontend):=20=E7=BB=9F=E4=B8=80?=
=?UTF-8?q?=E8=A1=A8=E6=A0=BC=E9=AB=98=E5=BA=A6=E5=B1=9E=E6=80=A7=E7=BB=91?=
=?UTF-8?q?=E5=AE=9A=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
fix(backend): 修复租户管理菜单图标错误
style(backend): 优化启动banner显示格式
refactor(backend): 重构日志系统使用loguru替代原生logging
feat(backend): 增加租户名称校验规则
refactor(backend): 优化应用启动流程和日志初始化
docs(frontend): 更新租户管理页面标题和描述
---
.../app/api/v1/module_system/tenant/schema.py | 15 +-
backend/app/config/setting.py | 42 +-
backend/app/core/logger.py | 190 ++++---
backend/app/plugin/init_app.py | 12 +-
backend/app/scripts/data/system_menu.json | 2 +-
backend/app/utils/common_util.py | 4 +-
backend/banner.txt | 3 +-
backend/main.py | 20 +-
backend/templates/vue/index.vue.j2 | 2 +-
.../views/module_application/job/index.vue | 2 +-
.../module_application/workflow/index.vue | 2 +-
.../views/module_generator/backcode/index.vue | 2 +-
.../src/views/module_generator/demo/index.vue | 2 +-
.../src/views/module_monitor/cache/index.vue | 2 +-
.../src/views/module_monitor/online/index.vue | 2 +-
.../views/module_monitor/resource/index.vue | 2 +-
.../src/views/module_system/dept/index.vue | 2 +-
.../src/views/module_system/dict/index.vue | 2 +-
.../src/views/module_system/log/index.vue | 2 +-
.../src/views/module_system/menu/index.vue | 2 +-
.../src/views/module_system/notice/index.vue | 2 +-
.../src/views/module_system/param/index.vue | 2 +-
.../views/module_system/position/index.vue | 2 +-
.../src/views/module_system/role/index.vue | 2 +-
.../src/views/module_system/tenant/index.vue | 504 +++++++++---------
.../src/views/module_system/user/index.vue | 2 +-
26 files changed, 415 insertions(+), 411 deletions(-)
diff --git a/backend/app/api/v1/module_system/tenant/schema.py b/backend/app/api/v1/module_system/tenant/schema.py
index 9a683536..d2f5bb47 100644
--- a/backend/app/api/v1/module_system/tenant/schema.py
+++ b/backend/app/api/v1/module_system/tenant/schema.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from typing import Optional
-from pydantic import BaseModel, ConfigDict, Field, field_validator
+from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.core.base_schema import BaseSchema
@@ -20,6 +20,19 @@ def _validate_name(cls, v: str) -> str:
raise ValueError('名称不能为空')
return v
+ @model_validator(mode='after')
+ def _after_validation(self):
+ """
+ 核心业务规则校验
+ """
+ # 长度校验:名称最小长度
+ if len(self.name) < 2 or len(self.name) > 50:
+ raise ValueError('名称长度必须在2-50个字符之间')
+ # 格式校验:名称只能包含字母、数字、下划线和中划线
+ if not self.name.isalnum() and not all(c in '-_' for c in self.name):
+ raise ValueError('名称只能包含字母、数字、下划线和中划线')
+
+ return self
class TenantUpdateSchema(TenantCreateSchema):
"""更新模型"""
diff --git a/backend/app/config/setting.py b/backend/app/config/setting.py
index 0bc0e145..a83dd129 100755
--- a/backend/app/config/setting.py
+++ b/backend/app/config/setting.py
@@ -272,47 +272,7 @@ def UVICORN_CONFIG(self) -> Dict[str, Any]:
"host": self.SERVER_HOST,
"port": self.SERVER_PORT,
"reload": self.RELOAD,
- "log_config": {
- "version": 1,
- "disable_existing_loggers": False,
- "formatters": {
- "default": {
- "()": "uvicorn.logging.DefaultFormatter",
- "fmt": self.LOGGER_FORMAT,
- "use_colors": None,
- },
- "access": {
- "()": "uvicorn.logging.AccessFormatter",
- "fmt": self.LOGGER_FORMAT,
- },
- },
- "handlers": {
- "console": {
- "formatter": "default",
- "class": "logging.StreamHandler",
- "stream": "ext://sys.stderr",
- },
- "access_console": {
- "formatter": "access",
- "class": "logging.StreamHandler",
- "stream": "ext://sys.stdout",
- },
- "file": {
- "formatter": "default",
- "class": "logging.handlers.TimedRotatingFileHandler",
- "filename": str(self.LOGGER_DIR.joinpath("info.log")),
- "when": self.WHEN,
- "interval": self.INTERVAL,
- "backupCount": self.BACKUPCOUNT,
- "encoding": self.ENCODING,
- },
- },
- "loggers": {
- "uvicorn": {"handlers": ["file", "console"], "level": self.LOGGER_LEVEL, "propagate": False},
- "uvicorn.error": {"handlers": ["file", "console"], "level": self.LOGGER_LEVEL, "propagate": False},
- "uvicorn.access": {"handlers": ["file", "access_console"], "level": self.LOGGER_LEVEL, "propagate": False},
- },
- },
+ "log_config": None,
"workers": self.WORKERS,
"limit_concurrency": self.LIMIT_CONCURRENCY,
"backlog": self.BACKLOG,
diff --git a/backend/app/core/logger.py b/backend/app/core/logger.py
index 527ced91..d04312fb 100644
--- a/backend/app/core/logger.py
+++ b/backend/app/core/logger.py
@@ -1,90 +1,116 @@
# -*- coding: utf-8 -*-
import logging
-from logging.handlers import TimedRotatingFileHandler
+import sys
from pathlib import Path
-import re
+from loguru import logger
from app.config.setting import settings
-
-
-class AppLogger:
- """应用级日志管理器:一次性配置 + 获取。"""
-
- def __init__(self) -> None:
- self._logger = logging.getLogger(__name__)
- self._configured = False
-
- def _create_file_handler(self, stem: str, level: int, log_dir: Path, formatter: logging.Formatter) -> TimedRotatingFileHandler:
- file_path = log_dir / f"{stem}.log"
- handler = TimedRotatingFileHandler(
- filename=str(file_path),
- when=settings.WHEN,
- interval=settings.INTERVAL,
- backupCount=settings.BACKUPCOUNT,
- encoding=settings.ENCODING,
+from app.utils.common_util import worship
+
+class InterceptHandler(logging.Handler):
+ """
+ 日志拦截处理器:将所有 Python 标准日志重定向到 Loguru
+
+ 工作原理:
+ 1. 继承自 logging.Handler
+ 2. 重写 emit 方法处理日志记录
+ 3. 将标准库日志转换为 Loguru 格式
+ """
+ def emit(self, record: logging.LogRecord) -> None:
+ # 尝试获取日志级别名称
+ try:
+ level = logger.level(record.levelname).name
+ except ValueError:
+ level = record.levelno
+
+ # 获取调用帧信息,增加None检查
+ frame, depth = logging.currentframe(), 2
+ if frame is not None:
+ while frame and frame.f_code.co_filename == logging.__file__:
+ frame = frame.f_back
+ depth += 1
+
+ # 使用 Loguru 记录日志
+ logger.opt(depth=depth, exception=record.exc_info).log(
+ level,
+ record.getMessage()
)
- handler.setLevel(level)
- handler.setFormatter(formatter)
- handler.suffix = "_%Y-%m-%d.log" # 设置正确的后缀格式
-
- def namer(default_name: str) -> str:
- # 统一处理轮转后的文件名,确保格式为stem_YYYY-MM-DD.log
- file_name = Path(default_name).name
- # 提取日期部分
- base_name = file_name.split('.')[0] # 获取基本名称(不包含扩展名)
- date_match = re.search(r'(\d{4}-\d{2}-\d{2})', base_name)
- if date_match:
- date_part = date_match.group(1)
- return f"{stem}_{date_part}.log"
-
- # 如果没有找到日期部分,返回原始名称
- return default_name
-
- def rotator(source: str, dest: str) -> None:
- # 确保目录存在
- Path(dest).parent.mkdir(parents=True, exist_ok=True)
- # 重命名文件
- Path(source).rename(dest)
-
- handler.namer = namer
- handler.rotator = rotator
- return handler
-
- def configure(self) -> logging.Logger:
- if self._configured:
- return self._logger
-
- # 基础设置
- self._logger.setLevel(settings.LOGGER_LEVEL)
- self._logger.handlers.clear()
- self._logger.propagate = False
-
- # 目录与格式
- log_dir = Path(settings.LOGGER_DIR)
- log_dir.mkdir(parents=True, exist_ok=True)
- formatter = logging.Formatter(settings.LOGGER_FORMAT)
-
- # 文件处理器
- self._logger.addHandler(self._create_file_handler("info", logging.INFO, log_dir, formatter))
- self._logger.addHandler(self._create_file_handler("error", logging.ERROR, log_dir, formatter))
-
- # 控制台处理器
- console = logging.StreamHandler()
- console.setLevel(settings.LOGGER_LEVEL)
- console.setFormatter(formatter)
- self._logger.addHandler(console)
-
- self._configured = True
- return self._logger
-
- def get_logger(self) -> logging.Logger:
- return self.configure()
-
-def get_logger() -> logging.Logger:
- AL = AppLogger()
- return AL.get_logger()
-
-# 模块级兼容实例
-logger = get_logger()
+def setup_logging():
+ """
+ 配置日志系统
+
+ 功能:
+ 1. 控制台彩色输出
+ 2. 文件日志轮转
+ 3. 错误日志单独存储
+ 4. 异步日志记录
+ """
+ # 添加上下文信息
+ logger.configure(extra={"app_name": "FastapiAdmin"})
+ # 步骤1:移除默认处理器
+ logger.remove()
+
+ # 步骤2:定义日志格式
+ log_format = (
+ # 时间信息
+ "{time:YYYY-MM-DD HH:mm:ss.SSS} | "
+ # 日志级别,居中对齐
+ "{level: <8} | "
+ # 文件、函数和行号
+ "{name}:{function}:{line} - "
+ # 日志消息
+ "{message}"
+ )
+
+ # 步骤3:配置控制台输出
+ logger.add(
+ sys.stdout,
+ format=log_format,
+ level="DEBUG" if settings.DEBUG else "INFO",
+ enqueue=True, # 启用异步写入
+ backtrace=True, # 显示完整的异常回溯
+ diagnose=True, # 显示变量值等诊断信息
+ colorize=True # 启用彩色输出
+ )
+
+ # 步骤4:创建日志目录
+ log_dir = Path(settings.LOGGER_DIR)
+ # 确保日志目录存在,如果不存在则创建
+ log_dir.mkdir(parents=True, exist_ok=True)
+
+ # 步骤5:配置常规日志文件
+ logger.add(
+ str(log_dir / "info.log"),
+ format=log_format,
+ level="INFO",
+ rotation="00:00", # 每天午夜轮转
+ retention=settings.LOG_RETENTION_DAYS,
+ compression="gz",
+ encoding=settings.ENCODING,
+ enqueue=True
+ )
+
+ # 步骤6:配置错误日志文件
+ logger.add(
+ str(log_dir / "error.log"),
+ format=log_format,
+ level="ERROR",
+ rotation="00:00", # 每天午夜轮转
+ retention=settings.LOG_RETENTION_DAYS,
+ compression="gz",
+ encoding=settings.ENCODING,
+ enqueue=True,
+ backtrace=True,
+ diagnose=True
+ )
+
+ # 步骤7:配置标准库日志
+ logging.basicConfig(handlers=[InterceptHandler()], level=settings.LOGGER_LEVEL, force=True)
+ logger_name_list = [name for name in logging.root.manager.loggerDict]
+ # 步骤8:配置第三方库日志
+ for logger_name in logger_name_list:
+ _logger = logging.getLogger(logger_name)
+ _logger.handlers = [InterceptHandler()]
+ _logger.propagate = False
diff --git a/backend/app/plugin/init_app.py b/backend/app/plugin/init_app.py
index f4ade57c..570a5149 100644
--- a/backend/app/plugin/init_app.py
+++ b/backend/app/plugin/init_app.py
@@ -34,8 +34,6 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
返回:
- AsyncGenerator[Any, Any]: 生命周期上下文生成器。
"""
- logger.info(worship())
-
await InitializeData().init_db()
logger.info(f"✅️ 初始化 {settings.DATABASE_TYPE} 数据库初始化完成...")
await import_modules_async(modules=settings.EVENT_LIST, desc="全局事件", app=app, status=True)
@@ -46,19 +44,15 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, Any]:
logger.info('✅️ 初始化Redis数据字典完成...')
await SchedulerUtil.init_system_scheduler()
logger.info('✅️ 初始化定时任务完成...')
- scheduler_status = SchedulerUtil.get_job_status()
- scheduler_jobs = len(SchedulerUtil.get_all_jobs())
-
- logger.info(f'✅️ {settings.TITLE} 服务成功启动...')
- # 控制台输出优化:展示服务信息与文档地址
+
console_run(
host=settings.SERVER_HOST,
port=settings.SERVER_PORT,
reload=settings.RELOAD,
workers=settings.WORKERS,
redis_ready=True,
- scheduler_jobs=scheduler_jobs,
- scheduler_status=scheduler_status,
+ scheduler_jobs=len(SchedulerUtil.get_all_jobs()),
+ scheduler_status=SchedulerUtil.get_job_status(),
)
yield
diff --git a/backend/app/scripts/data/system_menu.json b/backend/app/scripts/data/system_menu.json
index 1f555ce7..da331e2d 100644
--- a/backend/app/scripts/data/system_menu.json
+++ b/backend/app/scripts/data/system_menu.json
@@ -158,7 +158,7 @@
{
"name": "租户管理",
"type": 2,
- "icon": "el-icon-DataLine",
+ "icon": "el-icon-Avatar",
"order": 3,
"permission": "module_system:tenant:query",
"route_name": "Tenant",
diff --git a/backend/app/utils/common_util.py b/backend/app/utils/common_util.py
index c819c658..a1929e47 100644
--- a/backend/app/utils/common_util.py
+++ b/backend/app/utils/common_util.py
@@ -15,7 +15,7 @@
from app.core.exceptions import CustomException
-def worship() -> str | None:
+def worship() -> None:
"""
获取项目启动Banner(优先读取 banner.txt)
"""
@@ -24,7 +24,7 @@ def worship() -> str | None:
raw = banner_file.read_text(encoding='utf-8')
# 支持文件内使用 {TITLE} / {VERSION} 等占位符
banner = raw.format(TITLE=settings.TITLE, VERSION=settings.VERSION)
- return banner
+ logger.info(banner)
def import_module(module: str, desc: str) -> Any:
"""
diff --git a/backend/banner.txt b/backend/banner.txt
index 8b88f962..def025c7 100644
--- a/backend/banner.txt
+++ b/backend/banner.txt
@@ -1,3 +1,4 @@
+✅️ {TITLE} (v{VERSION}) 服务开始启动...
___ _ _ __ _
.' ..] / |_ (_) | ] (_)
@@ -6,5 +7,3 @@
| | // | |, `'.'. | |,// | |,| \__/ | | | // | |,| \__/ | | | | | | | | | | | | |
[___] \'-;__/[\__) )\__/\'-;__/| ;.__/ [___]\'-;__/ '.__.;__][___||__||__][___][___||__]
[__|
-
- :: {TITLE} :: ({VERSION}) 服务开始启动...
diff --git a/backend/main.py b/backend/main.py
index aabc86b5..44a3b6ea 100755
--- a/backend/main.py
+++ b/backend/main.py
@@ -9,6 +9,12 @@
from app.common.enums import EnvironmentEnum
from app.config.setting import settings
+from app.core.logger import setup_logging
+from app.utils.common_util import worship
+
+
+# 全局标志,用于跟踪日志是否已初始化
+LOGGING_INITIALIZED = False
shell_app = typer.Typer()
@@ -17,6 +23,7 @@
alembic_cfg = Config("alembic.ini")
def create_app() -> FastAPI:
+ global LOGGING_INITIALIZED
from app.plugin.init_app import (
register_middlewares,
register_exceptions,
@@ -25,6 +32,10 @@ def create_app() -> FastAPI:
reset_api_docs,
lifespan
)
+ # 初始化日志系统(确保每个工作进程都会初始化日志,但避免重复初始化)
+ if not LOGGING_INITIALIZED:
+ setup_logging()
+ LOGGING_INITIALIZED = True
# 创建FastAPI应用
app = FastAPI(**settings.FASTAPI_CONFIG, lifespan=lifespan)
@@ -46,11 +57,12 @@ def run(env: EnvironmentEnum = typer.Option(EnvironmentEnum.DEV, "--env", help="
typer.echo("项目启动中..")
# 设置环境变量
os.environ["ENVIRONMENT"] = env.value
+ # 初始化日志系统(确保uvicorn主进程的日志也被正确配置)
+ setup_logging()
+ worship()
+
# 启动uvicorn服务
- uvicorn.run(
- app='main:create_app',
- **settings.UVICORN_CONFIG
- )
+ uvicorn.run(app=f'main:create_app', **settings.UVICORN_CONFIG)
@shell_app.command()
def revision(env: EnvironmentEnum = typer.Option(EnvironmentEnum.DEV, "--env", help="运行环境 (dev, prod)")) -> None:
diff --git a/backend/templates/vue/index.vue.j2 b/backend/templates/vue/index.vue.j2
index ce467f38..24ca3937 100644
--- a/backend/templates/vue/index.vue.j2
+++ b/backend/templates/vue/index.vue.j2
@@ -141,7 +141,7 @@
:data="pageTableData"
highlight-current-row
class="data-table__content"
- height="450"
+ :height="450"
border
stripe
@selection-change="handleSelectionChange"
diff --git a/frontend/src/views/module_application/job/index.vue b/frontend/src/views/module_application/job/index.vue
index b0229bb2..17750f9d 100644
--- a/frontend/src/views/module_application/job/index.vue
+++ b/frontend/src/views/module_application/job/index.vue
@@ -145,7 +145,7 @@
:data="pageTableData"
class="data-table__content"
highlight-current-row
- height="450"
+ :height="450"
border
stripe
@selection-change="handleSelectionChange"
diff --git a/frontend/src/views/module_application/workflow/index.vue b/frontend/src/views/module_application/workflow/index.vue
index 5be7719b..baf490a2 100644
--- a/frontend/src/views/module_application/workflow/index.vue
+++ b/frontend/src/views/module_application/workflow/index.vue
@@ -92,7 +92,7 @@
-
+
diff --git a/frontend/src/views/module_generator/backcode/index.vue b/frontend/src/views/module_generator/backcode/index.vue
index ba8b6492..b6e21b19 100644
--- a/frontend/src/views/module_generator/backcode/index.vue
+++ b/frontend/src/views/module_generator/backcode/index.vue
@@ -76,7 +76,7 @@
:data="tableList"
highlight-current-row
class="data-table__content"
- height="450"
+ :height="450"
border
stripe
@selection-change="handleTableSelectionChange"
diff --git a/frontend/src/views/module_generator/demo/index.vue b/frontend/src/views/module_generator/demo/index.vue
index 3cb0d05a..7520b638 100644
--- a/frontend/src/views/module_generator/demo/index.vue
+++ b/frontend/src/views/module_generator/demo/index.vue
@@ -143,7 +143,7 @@
-
+
diff --git a/frontend/src/views/module_monitor/cache/index.vue b/frontend/src/views/module_monitor/cache/index.vue
index a742f297..bd6df96d 100644
--- a/frontend/src/views/module_monitor/cache/index.vue
+++ b/frontend/src/views/module_monitor/cache/index.vue
@@ -452,6 +452,6 @@ onUnmounted(() => {
diff --git a/frontend/src/views/module_monitor/online/index.vue b/frontend/src/views/module_monitor/online/index.vue
index b7bf846d..502dc1d2 100644
--- a/frontend/src/views/module_monitor/online/index.vue
+++ b/frontend/src/views/module_monitor/online/index.vue
@@ -67,7 +67,7 @@
-
+
diff --git a/frontend/src/views/module_monitor/resource/index.vue b/frontend/src/views/module_monitor/resource/index.vue
index 4f430239..1f83699f 100644
--- a/frontend/src/views/module_monitor/resource/index.vue
+++ b/frontend/src/views/module_monitor/resource/index.vue
@@ -104,7 +104,7 @@
:data="fileList"
row-key="file_url"
class="data-table__content"
- height="450"
+ :height="450"
border
stripe
@selection-change="handleSelectionChange"
diff --git a/frontend/src/views/module_system/dept/index.vue b/frontend/src/views/module_system/dept/index.vue
index 6a2679cf..0b6d4cff 100644
--- a/frontend/src/views/module_system/dept/index.vue
+++ b/frontend/src/views/module_system/dept/index.vue
@@ -102,7 +102,7 @@
-
+
diff --git a/frontend/src/views/module_system/dict/index.vue b/frontend/src/views/module_system/dict/index.vue
index 45253010..8f056820 100644
--- a/frontend/src/views/module_system/dict/index.vue
+++ b/frontend/src/views/module_system/dict/index.vue
@@ -119,7 +119,7 @@
-
+
diff --git a/frontend/src/views/module_system/log/index.vue b/frontend/src/views/module_system/log/index.vue
index 50805bf0..e2a8431d 100644
--- a/frontend/src/views/module_system/log/index.vue
+++ b/frontend/src/views/module_system/log/index.vue
@@ -88,7 +88,7 @@
-
+
diff --git a/frontend/src/views/module_system/menu/index.vue b/frontend/src/views/module_system/menu/index.vue
index 09abbeab..55842243 100644
--- a/frontend/src/views/module_system/menu/index.vue
+++ b/frontend/src/views/module_system/menu/index.vue
@@ -90,7 +90,7 @@
-
+
diff --git a/frontend/src/views/module_system/notice/index.vue b/frontend/src/views/module_system/notice/index.vue
index f75e084f..43cd2145 100644
--- a/frontend/src/views/module_system/notice/index.vue
+++ b/frontend/src/views/module_system/notice/index.vue
@@ -128,7 +128,7 @@
-
+
diff --git a/frontend/src/views/module_system/param/index.vue b/frontend/src/views/module_system/param/index.vue
index a61e81ed..49e1d5e3 100644
--- a/frontend/src/views/module_system/param/index.vue
+++ b/frontend/src/views/module_system/param/index.vue
@@ -105,7 +105,7 @@
-
+
diff --git a/frontend/src/views/module_system/position/index.vue b/frontend/src/views/module_system/position/index.vue
index b9fdbe0e..f3d827b7 100644
--- a/frontend/src/views/module_system/position/index.vue
+++ b/frontend/src/views/module_system/position/index.vue
@@ -114,7 +114,7 @@
-
+
diff --git a/frontend/src/views/module_system/role/index.vue b/frontend/src/views/module_system/role/index.vue
index b10d02b1..55cb8d0e 100644
--- a/frontend/src/views/module_system/role/index.vue
+++ b/frontend/src/views/module_system/role/index.vue
@@ -115,7 +115,7 @@
-
+
diff --git a/frontend/src/views/module_system/tenant/index.vue b/frontend/src/views/module_system/tenant/index.vue
index cc6fe518..c35aa2a6 100644
--- a/frontend/src/views/module_system/tenant/index.vue
+++ b/frontend/src/views/module_system/tenant/index.vue
@@ -1,260 +1,260 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 查询
-
-
- 重置
-
-
-
-
- {{ isExpand ? "收起" : "展开" }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-