309 lines
11 KiB
Python
309 lines
11 KiB
Python
from django.contrib.auth.models import AbstractUser
|
||
from django.db import models
|
||
|
||
from backend import settings
|
||
from utils.models import CoreModel, CommonStatus
|
||
from utils.utils import validate_mobile
|
||
|
||
|
||
# 菜单类型枚举
|
||
class MenuType(models.TextChoices):
|
||
CATALOG = 'catalog', '目录'
|
||
MENU = 'menu', '菜单'
|
||
BUTTON = 'button', '按钮'
|
||
EMBEDDED = 'embedded', '内嵌页面'
|
||
LINK = 'link', '外部链接'
|
||
|
||
# 菜单元数据模型(单独存储元数据,避免 JSONField)
|
||
class MenuMeta(CoreModel):
|
||
title = models.CharField(max_length=200, db_comment='标题')
|
||
icon = models.CharField(max_length=100, blank=True, db_comment='图标')
|
||
sort = models.IntegerField(default=0, db_comment='排序')
|
||
affix_tab = models.BooleanField(default=False, db_comment='固定标签页')
|
||
badge = models.CharField(max_length=50, blank=True, db_comment='徽章文本')
|
||
badge_type = models.CharField(max_length=20, blank=True, db_comment='徽章类型')
|
||
badge_variants = models.CharField(max_length=20, blank=True, db_comment='徽章样式')
|
||
iframe_src = models.URLField(blank=True, db_comment='内嵌页面URL')
|
||
link = models.URLField(blank=True, db_comment='外部链接')
|
||
hide_in_menu = models.BooleanField(default=False, db_comment='隐藏菜单')
|
||
hide_children_in_menu = models.BooleanField(default=False, db_comment='隐藏子菜单')
|
||
|
||
def __str__(self):
|
||
return self.title
|
||
|
||
class Meta:
|
||
db_table = 'system_menu_meta'
|
||
verbose_name = '菜单元数据'
|
||
verbose_name_plural = '菜单元数据'
|
||
|
||
|
||
|
||
class Dept(CoreModel):
|
||
pid = models.ForeignKey(
|
||
"self",
|
||
on_delete=models.CASCADE,
|
||
null=True,
|
||
blank=True,
|
||
related_name="children",
|
||
verbose_name="父部门 ID"
|
||
)
|
||
name = models.CharField(max_length=100, verbose_name="部门名称")
|
||
status = models.SmallIntegerField(
|
||
choices=CommonStatus.choices,
|
||
default=CommonStatus.DISABLED,
|
||
verbose_name="部门状态"
|
||
)
|
||
create_time = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
# 若数据中时间需自动解析,可在保存时处理:
|
||
# default=timezone.now # 或通过数据导入时赋值
|
||
)
|
||
sort = models.IntegerField(
|
||
default=0,
|
||
verbose_name="显示排序",
|
||
help_text="数值越小越靠前"
|
||
)
|
||
leader = models.CharField(
|
||
null=True,
|
||
blank=True,
|
||
max_length=20,
|
||
verbose_name="负责人"
|
||
)
|
||
phone = models.CharField(
|
||
max_length=20,
|
||
blank=True,
|
||
null=True,
|
||
verbose_name="联系电话"
|
||
)
|
||
email = models.EmailField(
|
||
blank=True,
|
||
null=True,
|
||
verbose_name="邮箱"
|
||
)
|
||
remark = models.TextField(blank=True, verbose_name="备注")
|
||
|
||
class Meta:
|
||
verbose_name = "部门管理"
|
||
verbose_name_plural = verbose_name
|
||
ordering = ["-create_time"] # 按创建时间倒序排列
|
||
|
||
# 主菜单模型
|
||
class Menu(CoreModel):
|
||
pid = models.ForeignKey(
|
||
'self',
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='children',
|
||
verbose_name='父菜单'
|
||
)
|
||
name = models.CharField(max_length=100, verbose_name='菜单名称')
|
||
status = models.IntegerField(choices=CommonStatus.choices, default=CommonStatus.ENABLED, verbose_name='状态')
|
||
type = models.CharField(choices=MenuType.choices, max_length=20, verbose_name='菜单类型')
|
||
sort = models.IntegerField(
|
||
default=0,
|
||
verbose_name="显示排序",
|
||
help_text="数值越小越靠前"
|
||
)
|
||
path = models.CharField(max_length=200, blank=True, verbose_name='路由路径')
|
||
component = models.CharField(max_length=200, blank=True, verbose_name='组件路径')
|
||
auth_code = models.CharField(max_length=100, blank=True, verbose_name='权限编码')
|
||
meta = models.OneToOneField(MenuMeta, on_delete=models.CASCADE, verbose_name='元数据')
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
class Meta:
|
||
verbose_name = '菜单'
|
||
verbose_name_plural = '菜单管理'
|
||
ordering = ['meta__sort', 'id']
|
||
|
||
class Role(CoreModel):
|
||
name = models.CharField(
|
||
max_length=100,
|
||
verbose_name='角色名称'
|
||
)
|
||
code = models.CharField(max_length=100, blank=True, verbose_name='角色标识')
|
||
status = models.IntegerField(
|
||
choices=CommonStatus.choices,
|
||
default=CommonStatus.ENABLED,
|
||
verbose_name='角色状态'
|
||
)
|
||
sort = models.IntegerField(
|
||
default=0,
|
||
verbose_name="显示排序",
|
||
help_text="数值越小越靠前"
|
||
)
|
||
remark = models.TextField(
|
||
blank=True,
|
||
verbose_name='备注'
|
||
)
|
||
# 与菜单权限的多对多关联(假设菜单模型为 Menu,权限字段为 auth_code)
|
||
permissions = models.ManyToManyField(
|
||
'Menu', # 引用之前设计的 Menu 模型
|
||
through='RolePermission',
|
||
verbose_name='关联权限'
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = '角色管理'
|
||
verbose_name_plural = verbose_name
|
||
ordering = ["-create_time"] # 按创建时间倒序排列
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
# 中间表:角色与权限的关联(可扩展字段如权限生效时间)
|
||
class RolePermission(CoreModel):
|
||
role = models.ForeignKey(
|
||
Role,
|
||
on_delete=models.CASCADE,
|
||
verbose_name='角色'
|
||
)
|
||
menu = models.ForeignKey(
|
||
'Menu',
|
||
on_delete=models.CASCADE,
|
||
verbose_name='菜单/权限'
|
||
)
|
||
# 可选:记录权限关联时间
|
||
create_time = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name='权限关联时间'
|
||
)
|
||
|
||
class Meta:
|
||
db_table = 'system_role_permission'
|
||
verbose_name = '角色权限关联'
|
||
verbose_name_plural = verbose_name
|
||
|
||
class DictType(CoreModel):
|
||
"""字典类型表"""
|
||
name = models.CharField(max_length=100, default='', verbose_name='字典名称')
|
||
value = models.CharField(max_length=100, default='', verbose_name='字典类型', db_index=True)
|
||
status = models.IntegerField(
|
||
choices=CommonStatus.choices,
|
||
default=CommonStatus.ENABLED,
|
||
verbose_name='状态'
|
||
)
|
||
deleted_time = models.DateTimeField(null=True, blank=True, verbose_name='删除时间')
|
||
|
||
class Meta:
|
||
verbose_name = '字典类型'
|
||
verbose_name_plural = '字典类型'
|
||
db_table = 'system_dict_type'
|
||
ordering = ['-id']
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
|
||
class DictData(CoreModel):
|
||
"""字典数据表"""
|
||
sort = models.IntegerField(default=0, verbose_name='字典排序')
|
||
label = models.CharField(max_length=100, default='', verbose_name='字典标签')
|
||
value = models.CharField(max_length=100, default='', verbose_name='字典键值')
|
||
dict_type = models.ForeignKey(
|
||
DictType,
|
||
on_delete=models.CASCADE,
|
||
related_name='dict_data',
|
||
verbose_name='字典类型'
|
||
)
|
||
status = models.IntegerField(
|
||
choices=CommonStatus.choices,
|
||
default=CommonStatus.ENABLED,
|
||
verbose_name='状态'
|
||
)
|
||
color_type = models.CharField(max_length=100, blank=True, default='', verbose_name='颜色类型')
|
||
css_class = models.CharField(max_length=100, blank=True, default='', verbose_name='css 样式')
|
||
|
||
class Meta:
|
||
verbose_name = '字典数据'
|
||
verbose_name_plural = '字典数据'
|
||
db_table = 'system_dict_data'
|
||
ordering = ['sort', 'id']
|
||
|
||
def __str__(self):
|
||
return self.label
|
||
|
||
|
||
class Post(CoreModel):
|
||
code = models.CharField(max_length=64, db_comment='岗位编码')
|
||
name = models.CharField(max_length=50, db_comment='岗位名称')
|
||
sort = models.IntegerField(default=0, db_comment='显示顺序')
|
||
status = models.IntegerField(
|
||
choices=CommonStatus.choices,
|
||
default=CommonStatus.ENABLED,
|
||
verbose_name='状态'
|
||
)
|
||
|
||
class Meta:
|
||
db_table = 'system_post'
|
||
verbose_name = '岗位信息表'
|
||
verbose_name_plural = verbose_name
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
|
||
class User(AbstractUser, CoreModel):
|
||
mobile = models.CharField(max_length=11, null=True, validators=[validate_mobile], db_comment="手机号")
|
||
nickname = models.CharField(max_length=50, blank=True, null=True, db_comment="昵称")
|
||
gender = models.SmallIntegerField(blank=True, null=True, default=0, db_comment='性别')
|
||
language = models.CharField('语言', max_length=20, blank=True, null=True, db_comment="语言")
|
||
city = models.CharField('城市', max_length=20, blank=True, null=True, db_comment="城市")
|
||
province = models.CharField('省份', max_length=50, blank=True, null=True, db_comment="省份")
|
||
country = models.CharField('国家', max_length=50, blank=True, null=True, db_comment="国家")
|
||
avatar_url = models.URLField('头像', blank=True, null=True, db_comment="头像")
|
||
|
||
dept = models.ManyToManyField(
|
||
'Dept', blank=True, verbose_name='部门', db_constraint=False,
|
||
related_name='users'
|
||
)
|
||
role = models.ManyToManyField(
|
||
'Role', blank=True, verbose_name='角色', db_constraint=False,
|
||
related_name='users'
|
||
)
|
||
post = models.ManyToManyField(
|
||
'Post', blank=True, verbose_name='岗位', db_constraint=False,
|
||
related_name='users'
|
||
)
|
||
status = models.IntegerField(
|
||
choices=CommonStatus.choices,
|
||
default=CommonStatus.ENABLED,
|
||
verbose_name='状态'
|
||
)
|
||
login_ip = models.GenericIPAddressField(blank=True, null=True, db_comment="最后登录IP")
|
||
|
||
class Meta:
|
||
verbose_name = '用户数据'
|
||
verbose_name_plural = verbose_name
|
||
db_table = 'system_users'
|
||
|
||
@property
|
||
def get_role_name(self):
|
||
return [role.name for role in self.role.all()]
|
||
|
||
|
||
class LoginLog(CoreModel):
|
||
"""
|
||
系统访问记录
|
||
"""
|
||
class LoginResult(models.IntegerChoices):
|
||
FAILED = 0, '失败'
|
||
SUCCESS = 1, '成功'
|
||
username = models.CharField(max_length=50, default='', db_comment='用户账号')
|
||
result = models.IntegerField(choices=LoginResult.choices, default=LoginResult.SUCCESS, db_comment='登录结果')
|
||
user_ip = models.CharField(max_length=50, db_comment='用户 IP')
|
||
user_agent = models.CharField(max_length=512, db_comment='浏览器 UA')
|
||
location = models.CharField(max_length=200, db_comment='<IP> 地理位置', blank=True, default='')
|
||
|
||
class Meta:
|
||
db_table = 'system_login_log'
|
||
verbose_name = '系统访问记录'
|
||
verbose_name_plural = verbose_name
|
||
ordering = ['-id']
|
||
|
||
def __str__(self):
|
||
return f"{self.username} - {self.user_ip}"
|