diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py index 396296f..6dec1b1 100644 --- a/backend/dvadmin/system/models.py +++ b/backend/dvadmin/system/models.py @@ -14,7 +14,7 @@ STATUS_CHOICES = ( class Users(AbstractUser, CoreModel): - username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name='用户账号', help_text="用户账号") + username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name="用户账号", help_text="用户账号") email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱") mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话") avatar = models.CharField(max_length=255, verbose_name="头像", null=True, blank=True, help_text="头像") @@ -24,27 +24,36 @@ class Users(AbstractUser, CoreModel): (1, "男"), (2, "女"), ) - gender = models.IntegerField(choices=GENDER_CHOICES, default=0, verbose_name="性别", null=True, blank=True, - help_text="性别") + gender = models.IntegerField( + choices=GENDER_CHOICES, default=0, verbose_name="性别", null=True, blank=True, help_text="性别" + ) USER_TYPE = ( (0, "后台用户"), (1, "前台用户"), ) - user_type = models.IntegerField(choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, - help_text="用户类型") - post = models.ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False, help_text="关联岗位") - role = models.ManyToManyField(to='Role', verbose_name='关联角色', db_constraint=False, help_text="关联角色") - dept = models.ForeignKey(to='Dept', verbose_name='所属部门', on_delete=models.PROTECT, db_constraint=False, null=True, - blank=True, help_text="关联部门") + user_type = models.IntegerField( + choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, help_text="用户类型" + ) + post = models.ManyToManyField(to="Post", verbose_name="关联岗位", db_constraint=False, help_text="关联岗位") + role = models.ManyToManyField(to="Role", verbose_name="关联角色", db_constraint=False, help_text="关联角色") + dept = models.ForeignKey( + to="Dept", + verbose_name="所属部门", + on_delete=models.PROTECT, + db_constraint=False, + null=True, + blank=True, + help_text="关联部门", + ) def set_password(self, raw_password): - super().set_password(hashlib.md5(raw_password.encode(encoding='UTF-8')).hexdigest()) + super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest()) class Meta: db_table = table_prefix + "system_users" - verbose_name = '用户表' + verbose_name = "用户表" verbose_name_plural = verbose_name - ordering = ('-create_datetime',) + ordering = ("-create_datetime",) class Post(CoreModel): @@ -59,9 +68,9 @@ class Post(CoreModel): class Meta: db_table = table_prefix + "system_post" - verbose_name = '岗位表' + verbose_name = "岗位表" verbose_name_plural = verbose_name - ordering = ('sort',) + ordering = ("sort",) class Role(CoreModel): @@ -79,16 +88,17 @@ class Role(CoreModel): ) data_range = models.IntegerField(default=0, choices=DATASCOPE_CHOICES, verbose_name="数据权限范围", help_text="数据权限范围") remark = models.TextField(verbose_name="备注", help_text="备注", null=True, blank=True) - dept = models.ManyToManyField(to='Dept', verbose_name='数据权限-关联部门', db_constraint=False, help_text="数据权限-关联部门") - menu = models.ManyToManyField(to='Menu', verbose_name='关联菜单', db_constraint=False, help_text="关联菜单") - permission = models.ManyToManyField(to='MenuButton', verbose_name='关联菜单的接口按钮', db_constraint=False, - help_text="关联菜单的接口按钮") + dept = models.ManyToManyField(to="Dept", verbose_name="数据权限-关联部门", db_constraint=False, help_text="数据权限-关联部门") + menu = models.ManyToManyField(to="Menu", verbose_name="关联菜单", db_constraint=False, help_text="关联菜单") + permission = models.ManyToManyField( + to="MenuButton", verbose_name="关联菜单的接口按钮", db_constraint=False, help_text="关联菜单的接口按钮" + ) class Meta: - db_table = table_prefix + 'system_role' - verbose_name = '角色表' + db_table = table_prefix + "system_role" + verbose_name = "角色表" verbose_name_plural = verbose_name - ordering = ('sort',) + ordering = ("sort",) class Dept(CoreModel): @@ -97,21 +107,35 @@ class Dept(CoreModel): owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人") phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话") email = models.EmailField(max_length=32, verbose_name="邮箱", null=True, blank=True, help_text="邮箱") - status = models.BooleanField(default=True, verbose_name="部门状态", null=True, blank=True, - help_text="部门状态") - parent = models.ForeignKey(to='Dept', on_delete=models.CASCADE, default=None, verbose_name="上级部门", - db_constraint=False, null=True, blank=True, help_text="上级部门") + status = models.BooleanField(default=True, verbose_name="部门状态", null=True, blank=True, help_text="部门状态") + parent = models.ForeignKey( + to="Dept", + on_delete=models.CASCADE, + default=None, + verbose_name="上级部门", + db_constraint=False, + null=True, + blank=True, + help_text="上级部门", + ) class Meta: db_table = table_prefix + "system_dept" - verbose_name = '部门表' + verbose_name = "部门表" verbose_name_plural = verbose_name - ordering = ('sort',) + ordering = ("sort",) class Menu(CoreModel): - parent = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name="上级菜单", null=True, blank=True, - db_constraint=False, help_text="上级菜单") + parent = models.ForeignKey( + to="Menu", + on_delete=models.CASCADE, + verbose_name="上级菜单", + null=True, + blank=True, + db_constraint=False, + help_text="上级菜单", + ) icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标") name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称") sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序") @@ -130,14 +154,20 @@ class Menu(CoreModel): class Meta: db_table = table_prefix + "system_menu" - verbose_name = '菜单表' + verbose_name = "菜单表" verbose_name_plural = verbose_name - ordering = ('sort',) + ordering = ("sort",) class MenuButton(CoreModel): - menu = models.ForeignKey(to="Menu", db_constraint=False, related_name="menuPermission", on_delete=models.CASCADE, - verbose_name="关联菜单", help_text='关联菜单') + menu = models.ForeignKey( + to="Menu", + db_constraint=False, + related_name="menuPermission", + on_delete=models.CASCADE, + verbose_name="关联菜单", + help_text="关联菜单", + ) name = models.CharField(max_length=64, verbose_name="名称", help_text="名称") value = models.CharField(max_length=64, verbose_name="权限值", help_text="权限值") api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址") @@ -151,26 +181,34 @@ class MenuButton(CoreModel): class Meta: db_table = table_prefix + "system_menu_button" - verbose_name = '菜单权限表' + verbose_name = "菜单权限表" verbose_name_plural = verbose_name - ordering = ('-name',) + ordering = ("-name",) class Dictionary(CoreModel): TYPE_LIST = ( - (0, 'text'), - (1, 'number'), - (2, 'date'), - (3, 'datetime'), - (4, 'time'), - (5, 'files'), - (6, 'boolean'), - (7, 'images'), + (0, "text"), + (1, "number"), + (2, "date"), + (3, "datetime"), + (4, "time"), + (5, "files"), + (6, "boolean"), + (7, "images"), ) label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称") value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号", help_text="字典编号/实际值") - parent = models.ForeignKey(to='self', related_name='sublist', db_constraint=False, on_delete=models.PROTECT, - blank=True, null=True, verbose_name="父级", help_text="父级") + parent = models.ForeignKey( + to="self", + related_name="sublist", + db_constraint=False, + on_delete=models.PROTECT, + blank=True, + null=True, + verbose_name="父级", + help_text="父级", + ) type = models.IntegerField(choices=TYPE_LIST, default=0, verbose_name="数据值类型", help_text="数据值类型") color = models.CharField(max_length=20, blank=True, null=True, verbose_name="颜色", help_text="颜色") is_value = models.BooleanField(default=False, verbose_name="是否为value值", help_text="是否为value值,用来做具体值存放") @@ -179,15 +217,15 @@ class Dictionary(CoreModel): remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注") class Meta: - db_table = table_prefix + 'system_dictionary' + db_table = table_prefix + "system_dictionary" verbose_name = "字典表" verbose_name_plural = verbose_name - ordering = ('sort',) + ordering = ("sort",) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): super().save(force_insert, force_update, using, update_fields) - dispatch.refresh_dictionary() # 有更新则刷新字典配置 - + dispatch.refresh_dictionary() # 有更新则刷新字典配置 + def delete(self, using=None, keep_parents=False): res = super().delete(using, keep_parents) dispatch.refresh_dictionary() @@ -208,16 +246,16 @@ class OperationLog(CoreModel): status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态") class Meta: - db_table = table_prefix + 'system_operation_log' - verbose_name = '操作日志' + db_table = table_prefix + "system_operation_log" + verbose_name = "操作日志" verbose_name_plural = verbose_name - ordering = ('-create_datetime',) + ordering = ("-create_datetime",) def media_file_name(instance, filename): h = instance.md5sum basename, ext = os.path.splitext(filename) - return os.path.join('files', h[0:1], h[1:2], h + ext.lower()) + return os.path.join("files", h[0:1], h[1:2], h + ext.lower()) class FileList(CoreModel): @@ -234,10 +272,10 @@ class FileList(CoreModel): super(FileList, self).save(*args, **kwargs) class Meta: - db_table = table_prefix + 'system_file_list' - verbose_name = '文件管理' + db_table = table_prefix + "system_file_list" + verbose_name = "文件管理" verbose_name_plural = verbose_name - ordering = ('-create_datetime',) + ordering = ("-create_datetime",) class Area(CoreModel): @@ -247,14 +285,22 @@ class Area(CoreModel): pinyin = models.CharField(max_length=255, verbose_name="拼音", help_text="拼音") initials = models.CharField(max_length=20, verbose_name="首字母", help_text="首字母") enable = models.BooleanField(default=True, verbose_name="是否启用", help_text="是否启用") - pcode = models.ForeignKey(to='self', verbose_name='父地区编码', to_field="code", on_delete=models.CASCADE, - db_constraint=False, null=True, blank=True, help_text="父地区编码") + pcode = models.ForeignKey( + to="self", + verbose_name="父地区编码", + to_field="code", + on_delete=models.CASCADE, + db_constraint=False, + null=True, + blank=True, + help_text="父地区编码", + ) class Meta: db_table = table_prefix + "system_area" - verbose_name = '地区表' + verbose_name = "地区表" verbose_name_plural = verbose_name - ordering = ('code',) + ordering = ("code",) def __str__(self): return f"{self.name}" @@ -273,14 +319,21 @@ class ApiWhiteList(CoreModel): class Meta: db_table = table_prefix + "api_white_list" - verbose_name = '接口白名单' + verbose_name = "接口白名单" verbose_name_plural = verbose_name - ordering = ('-create_datetime',) + ordering = ("-create_datetime",) class SystemConfig(CoreModel): - parent = models.ForeignKey(to='self', verbose_name='父级', on_delete=models.CASCADE, - db_constraint=False, null=True, blank=True, help_text="父级") + parent = models.ForeignKey( + to="self", + verbose_name="父级", + on_delete=models.CASCADE, + db_constraint=False, + null=True, + blank=True, + help_text="父级", + ) title = models.CharField(max_length=50, verbose_name="标题", help_text="标题") key = models.CharField(max_length=20, verbose_name="键", help_text="键", db_index=True) value = models.JSONField(max_length=100, verbose_name="值", help_text="值", null=True, blank=True) @@ -288,35 +341,35 @@ class SystemConfig(CoreModel): status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态") data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True) FORM_ITEM_TYPE_LIST = ( - (0, 'text'), - (1, 'datetime'), - (2, 'date'), - (3, 'textarea'), - (4, 'select'), - (5, 'checkbox'), - (6, 'radio'), - (7, 'img'), - (8, 'file'), - (9, 'switch'), - (10, 'number'), - (11, 'array'), - (12, 'imgs'), - (13, 'foreignkey'), - (14, 'manytomany'), - (15, 'time'), - + (0, "text"), + (1, "datetime"), + (2, "date"), + (3, "textarea"), + (4, "select"), + (5, "checkbox"), + (6, "radio"), + (7, "img"), + (8, "file"), + (9, "switch"), + (10, "number"), + (11, "array"), + (12, "imgs"), + (13, "foreignkey"), + (14, "manytomany"), + (15, "time"), + ) + form_item_type = models.IntegerField( + choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, blank=True ) - form_item_type = models.IntegerField(choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, - blank=True) rule = models.JSONField(null=True, blank=True, verbose_name="校验规则", help_text="校验规则") placeholder = models.CharField(max_length=50, null=True, blank=True, verbose_name="提示信息", help_text="提示信息") setting = models.JSONField(null=True, blank=True, verbose_name="配置", help_text="配置") class Meta: db_table = table_prefix + "system_config" - verbose_name = '系统配置表' + verbose_name = "系统配置表" verbose_name_plural = verbose_name - ordering = ('sort',) + ordering = ("sort",) unique_together = (("key", "parent_id"),) def __str__(self): @@ -324,8 +377,8 @@ class SystemConfig(CoreModel): def save(self, force_insert=False, force_update=False, using=None, update_fields=None): super().save(force_insert, force_update, using, update_fields) - dispatch.refresh_system_config() # 有更新则刷新系统配置 - + dispatch.refresh_system_config() # 有更新则刷新系统配置 + def delete(self, using=None, keep_parents=False): res = super().delete(using, keep_parents) dispatch.refresh_system_config() @@ -333,9 +386,7 @@ class SystemConfig(CoreModel): class LoginLog(CoreModel): - LOGIN_TYPE_CHOICES = ( - (1, '普通登录'), - ) + LOGIN_TYPE_CHOICES = ((1, "普通登录"),) username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名") ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip") agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息") @@ -355,7 +406,7 @@ class LoginLog(CoreModel): login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型", help_text="登录类型") class Meta: - db_table = table_prefix + 'system_login_log' - verbose_name = '登录日志' + db_table = table_prefix + "system_login_log" + verbose_name = "登录日志" verbose_name_plural = verbose_name - ordering = ('-create_datetime',) + ordering = ("-create_datetime",) diff --git a/backend/dvadmin/utils/import_export.py b/backend/dvadmin/utils/import_export.py index 2f3db9c..5a01968 100644 --- a/backend/dvadmin/utils/import_export.py +++ b/backend/dvadmin/utils/import_export.py @@ -21,15 +21,17 @@ def import_to_data(file_url, field_data): # 创建一个空列表,存储Excel的数据 tables = [] for i, row in enumerate(range(table.max_row)): - if i == 0: continue + if i == 0: + continue array = {} for index, ele in enumerate(field_data.keys()): cell_value = table.cell(row=row + 1, column=index + 2).value + print(cell_value) # 由于excel导入数字类型后,会出现数字加 .0 的,进行处理 - if type(cell_value) is float and str(cell_value).split('.')[1] == '0': - cell_value = int(str(cell_value).split('.')[0]) + if type(cell_value) is float and str(cell_value).split(".")[1] == "0": + cell_value = int(str(cell_value).split(".")[0]) if type(cell_value) is str: - cell_value = cell_value.strip(' \t\n\r') + cell_value = cell_value.strip(" \t\n\r") array[ele] = cell_value tables.append(array) return tables diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index ee8cfdb..5a5c9f2 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -17,6 +17,7 @@ class ImportSerializerMixin: """ 自定义导出模板、导入功能 """ + # 导入字段 import_field_dict = {} # 导入序列化器 @@ -31,37 +32,42 @@ class ImportSerializerMixin: :param kwargs: :return: """ - assert self.import_field_dict, ( - "'%s' 请配置对应的导出模板字段。" - % self.__class__.__name__ - ) + assert self.import_field_dict, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__ # 导出模板 - if request.method == 'GET': + if request.method == "GET": # 示例数据 queryset = self.filter_queryset(self.get_queryset()) # 导出excel 表 - response = HttpResponse(content_type='application/msexcel') - response['Access-Control-Expose-Headers'] = f'Content-Disposition' - response['Content-Disposition'] = f'attachment;filename={quote(str(f"导入{get_verbose_name(queryset)}模板.xlsx"))}' + response = HttpResponse(content_type="application/msexcel") + response["Access-Control-Expose-Headers"] = f"Content-Disposition" + response[ + "Content-Disposition" + ] = f'attachment;filename={quote(str(f"导入{get_verbose_name(queryset)}模板.xlsx"))}' wb = Workbook() ws = wb.active row = get_column_letter(len(self.import_field_dict) + 1) column = 10 - ws.append(['序号', *self.import_field_dict.values()]) + ws.append(["序号", *self.import_field_dict.values()]) tab = Table(displayName="Table1", ref=f"A1:{row}{column}") # 名称管理器 - style = TableStyleInfo(name='TableStyleLight11', showFirstColumn=True, - showLastColumn=True, showRowStripes=True, showColumnStripes=True) + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) tab.tableStyleInfo = style ws.add_table(tab) wb.save(response) return response - updateSupport = request.data.get('updateSupport') + updateSupport = request.data.get("updateSupport") # 从excel中组织对应的数据结构,然后使用序列化器保存 - data = import_to_data(request.data.get('url'), self.import_field_dict) + data = import_to_data(request.data.get("url"), self.import_field_dict) queryset = self.filter_queryset(self.get_queryset()) - unique_list = [ele.attname for ele in queryset.model._meta.get_fields() if - hasattr(ele, 'unique') and ele.unique == True] + unique_list = [ + ele.attname for ele in queryset.model._meta.get_fields() if hasattr(ele, "unique") and ele.unique == True + ] for ele in data: # 获取 unique 字段 filter_dic = {i: ele.get(i) for i in list(set(self.import_field_dict.keys()) & set(unique_list))} @@ -80,6 +86,7 @@ class ExportSerializerMixin: """ 自定义导出功能 """ + # 导出字段 export_field_label = [] # 导出序列化器 @@ -93,27 +100,29 @@ class ExportSerializerMixin: :param kwargs: :return: """ - assert self.export_field_label, ( - "'%s' 请配置对应的导出模板字段。" - % self.__class__.__name__ - ) + assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__ queryset = self.filter_queryset(self.get_queryset()) data = self.export_serializer_class(queryset, many=True).data # 导出excel 表 - response = HttpResponse(content_type='application/msexcel') - response['Access-Control-Expose-Headers'] = f'Content-Disposition' - response['Content-Disposition'] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' + response = HttpResponse(content_type="application/msexcel") + response["Access-Control-Expose-Headers"] = f"Content-Disposition" + response["Content-Disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' wb = Workbook() ws = wb.active row = get_column_letter(len(self.export_field_label) + 1) column = 1 - ws.append(['序号', *self.export_field_label]) + ws.append(["序号", *self.export_field_label]) for index, results in enumerate(data): ws.append([index + 1, *list(results.values())]) column += 1 tab = Table(displayName="Table2", ref=f"A1:{row}{column}") # 名称管理器 - style = TableStyleInfo(name='TableStyleLight11', showFirstColumn=True, - showLastColumn=True, showRowStripes=True, showColumnStripes=True) + style = TableStyleInfo( + name="TableStyleLight11", + showFirstColumn=True, + showLastColumn=True, + showRowStripes=True, + showColumnStripes=True, + ) tab.tableStyleInfo = style ws.add_table(tab) wb.save(response) diff --git a/web/src/views/system/user/api.js b/web/src/views/system/user/api.js index d8f7d18..5743af2 100644 --- a/web/src/views/system/user/api.js +++ b/web/src/views/system/user/api.js @@ -1,13 +1,4 @@ -/* - * @创建文件时间: 2021-06-01 22:41:21 - * @Auther: 猿小天 - * @最后修改人: 猿小天 - * @最后修改时间: 2021-06-06 10:14:14 - * 联系Qq:1638245306 - * @文件介绍: 用户接口 - */ -import { request } from '@/api/service' - +import { request, downloadFile } from '@/api/service' export const urlPrefix = '/api/system/user/' export function GetList (query) { @@ -55,3 +46,15 @@ export function ResetPwd (obj) { data: obj }) } + +/** + * 导出 + * @param params + */ +export function exportData (params) { + return downloadFile({ + url: urlPrefix + 'export/', + params: params, + method: 'post' + }) +} diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue index 087e2bf..6be1535 100644 --- a/web/src/views/system/user/index.vue +++ b/web/src/views/system/user/index.vue @@ -21,6 +21,18 @@ > 新增 + 导出 + + 导入 +