From 0dcf8ae7941e6364ed2242ca46db7ee722c887a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E5=AD=90-=E6=9D=8E?= <1537080775@qq.com> Date: Thu, 4 Jul 2024 05:51:38 +0000 Subject: [PATCH 01/12] =?UTF-8?q?update=20backend/dvadmin/utils/models.py.?= =?UTF-8?q?=20=E8=B7=A8models=E5=BC=95=E7=94=A8=E6=A8=A1=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E6=97=B6=E5=80=99=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 木子-李 <1537080775@qq.com> --- backend/dvadmin/utils/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/dvadmin/utils/models.py b/backend/dvadmin/utils/models.py index fb0c2ba..7073d0f 100644 --- a/backend/dvadmin/utils/models.py +++ b/backend/dvadmin/utils/models.py @@ -117,9 +117,13 @@ def get_all_models_objects(model_name=None): def get_model_from_app(app_name): """获取模型里的字段""" model_module = import_module(app_name + '.models') + exclude_models = getattr(model_module, 'exclude_models', []) filter_model = [ - getattr(model_module, item) for item in dir(model_module) - if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase) + value for key, value in model_module.__dict__.items() + if key != 'CoreModel' + and isinstance(value, type) + and issubclass(value, models.Model) + and key not in exclude_models ] model_list = [] for model in filter_model: From 71ca7370e214fb56738b3c45f0819bd13d6baa9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E5=AD=90-=E6=9D=8E?= <1537080775@qq.com> Date: Thu, 4 Jul 2024 14:56:42 +0000 Subject: [PATCH 02/12] update web/src/utils/columnPermission.ts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 木子-李 <1537080775@qq.com> --- web/src/utils/columnPermission.ts | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/web/src/utils/columnPermission.ts b/web/src/utils/columnPermission.ts index 4a9d9f1..aff1441 100644 --- a/web/src/utils/columnPermission.ts +++ b/web/src/utils/columnPermission.ts @@ -24,31 +24,17 @@ export const handleColumnPermission = async (func: Function, crudOptions: any,ex const columns = crudOptions.columns; const excludeColumns = ['_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn) for (let col in columns) { - if (excludeColumns.includes(col)) { - continue - }else{ - if (columns[col].column) { - columns[col].column.show = false - } else { - columns[col]['column'] = { - show: false - } - } - columns[col].addForm = { - show: false - } - columns[col].editForm = { - show: false - } - } - for (let item of res.data) { if (excludeColumns.includes(item.field_name)) { continue } else if(item.field_name === col) { columns[col].column.show = item['is_query'] // 如果列表不可见,则禁止在列设置中选择 - if(!item['is_query'])columns[col].column.columnSetDisabled = true + // 只有列表不可见,才修改列配置,这样才不影响默认的配置 + if(!item['is_query']){ + columns[col].column.show = false + columns[col].column.columnSetDisabled = true + } columns[col].addForm = { show: item['is_create'] } From 630ec1e774ca3b530abbce69480641d0ff1ed2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B0=8F=E6=B6=9B?= <1537080775@qq.com> Date: Fri, 5 Jul 2024 10:24:07 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat(20240705=5FtableSelector):=20?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E9=80=89=E6=8B=A9=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加树形结构懒加载 --- web/src/components/tableSelector/index.vue | 296 +++++++++++---------- 1 file changed, 152 insertions(+), 144 deletions(-) diff --git a/web/src/components/tableSelector/index.vue b/web/src/components/tableSelector/index.vue index 7ad3cad..ab6ca7d 100644 --- a/web/src/components/tableSelector/index.vue +++ b/web/src/components/tableSelector/index.vue @@ -1,203 +1,211 @@ From 6d587fc1e2761790c515553650f55eb5b29cfe3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B0=8F=E6=B6=9B?= <1537080775@qq.com> Date: Fri, 5 Jul 2024 17:24:36 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat(20240705=5Farea):=20=E5=9C=B0?= =?UTF-8?q?=E5=8C=BA=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化地区管理:增删改查 - 优化tableSelect组件:增加树形结构和懒加载 --- backend/dvadmin/system/views/area.py | 50 ++- web/src/components/tableSelector/index.vue | 4 +- web/src/utils/columnPermission.ts | 1 - web/src/views/system/areas/crud.tsx | 436 ++++++++++----------- 4 files changed, 237 insertions(+), 254 deletions(-) diff --git a/backend/dvadmin/system/views/area.py b/backend/dvadmin/system/views/area.py index 3a72728..7a19eb0 100644 --- a/backend/dvadmin/system/views/area.py +++ b/backend/dvadmin/system/views/area.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pypinyin from django.db.models import Q from rest_framework import serializers @@ -15,6 +16,11 @@ class AreaSerializer(CustomModelSerializer): """ pcode_count = serializers.SerializerMethodField(read_only=True) hasChild = serializers.SerializerMethodField() + pcode_info = serializers.SerializerMethodField() + + def get_pcode_info(self, instance): + pcode = Area.objects.filter(code=instance.pcode_id).values("name", "code") + return pcode def get_pcode_count(self, instance: Area): return Area.objects.filter(pcode=instance).count() @@ -36,6 +42,18 @@ class AreaCreateUpdateSerializer(CustomModelSerializer): 地区管理 创建/更新时的列化器 """ + def to_internal_value(self, data): + pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(data["name"], style=pypinyin.NORMAL)]) + data["level"] = 1 + data["pinyin"] = pinyin + data["initials"] = pinyin[0].upper() if pinyin else "#" + pcode = data["pcode"] if 'pcode' in data else None + if pcode: + pcode = Area.objects.get(pk=pcode) + data["pcode"] = pcode.code + data["level"] = pcode.level + 1 + return super().to_internal_value(data) + class Meta: model = Area fields = '__all__' @@ -52,20 +70,28 @@ class AreaViewSet(CustomModelViewSet, FieldPermissionMixin): """ queryset = Area.objects.all() serializer_class = AreaSerializer + create_serializer_class = AreaCreateUpdateSerializer + update_serializer_class = AreaCreateUpdateSerializer extra_filter_class = [] - def get_queryset(self): + def list(self, request, *args, **kwargs): self.request.query_params._mutable = True params = self.request.query_params - pcode = params.get('pcode', None) - page = params.get('page', None) - limit = params.get('limit', None) - if page: - del params['page'] - if limit: - del params['limit'] - if params and pcode: - queryset = self.queryset.filter(enable=True, pcode=pcode) - else: + known_params = {'page', 'limit', 'pcode'} + # 使用集合操作检查是否有未知参数 + other_params_exist = any(param not in known_params for param in params) + if other_params_exist: queryset = self.queryset.filter(enable=True) - return queryset + else: + pcode = params.get('pcode', None) + params['limit'] = 999 + if params and pcode: + queryset = self.queryset.filter(enable=True, pcode=pcode) + else: + queryset = self.queryset.filter(enable=True, level=1) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True, request=request) + return self.get_paginated_response(serializer.data) + serializer = self.get_serializer(queryset, many=True, request=request) + return SuccessResponse(data=serializer.data, msg="获取成功") diff --git a/web/src/components/tableSelector/index.vue b/web/src/components/tableSelector/index.vue index ab6ca7d..8e8c91a 100644 --- a/web/src/components/tableSelector/index.vue +++ b/web/src/components/tableSelector/index.vue @@ -2,7 +2,7 @@ { const handleCurrentChange = (val: any) => { const { tableConfig } = props; if (!tableConfig.isMultiple && val) { - data.value = val[tableConfig.label]; + data.value = [val[tableConfig.label]]; emit('update:modelValue', val[tableConfig.value]); } }; diff --git a/web/src/utils/columnPermission.ts b/web/src/utils/columnPermission.ts index aff1441..7ef06de 100644 --- a/web/src/utils/columnPermission.ts +++ b/web/src/utils/columnPermission.ts @@ -28,7 +28,6 @@ export const handleColumnPermission = async (func: Function, crudOptions: any,ex if (excludeColumns.includes(item.field_name)) { continue } else if(item.field_name === col) { - columns[col].column.show = item['is_query'] // 如果列表不可见,则禁止在列设置中选择 // 只有列表不可见,才修改列配置,这样才不影响默认的配置 if(!item['is_query']){ diff --git a/web/src/views/system/areas/crud.tsx b/web/src/views/system/areas/crud.tsx index 01d1860..1a1e47b 100644 --- a/web/src/views/system/areas/crud.tsx +++ b/web/src/views/system/areas/crud.tsx @@ -1,244 +1,202 @@ import * as api from './api'; -import { - dict, - UserPageQuery, - AddReq, - DelReq, - EditReq, - compute, - CreateCrudOptionsProps, - CreateCrudOptionsRet -} from '@fast-crud/fast-crud'; -import {dictionary} from '/@/utils/dictionary'; -import {successMessage} from '/@/utils/message'; -import {auth} from "/@/utils/authFunction"; +import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'; +import { dictionary } from '/@/utils/dictionary'; +import { successMessage } from '/@/utils/message'; +import { auth } from '/@/utils/authFunction'; +import tableSelector from '/@/components/tableSelector/index.vue'; +import { shallowRef } from 'vue'; -export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps): CreateCrudOptionsRet { - const pageRequest = async (query: UserPageQuery) => { - return await api.GetList(query); - }; - const editRequest = async ({form, row}: EditReq) => { - form.id = row.id; - return await api.UpdateObj(form); - }; - const delRequest = async ({row}: DelReq) => { - return await api.DelObj(row.id); - }; - const addRequest = async ({form}: AddReq) => { - return await api.AddObj(form); - }; +export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const pageRequest = async (query: UserPageQuery) => { + return await api.GetList(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + form.id = row.id; + return await api.UpdateObj(form); + }; + const delRequest = async ({ row }: DelReq) => { + return await api.DelObj(row.id); + }; + const addRequest = async ({ form }: AddReq) => { + return await api.AddObj(form); + }; - /** - * 懒加载 - * @param row - * @returns {Promise} - */ - const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => { - pageRequest({pcode: tree.code}).then((res: APIResponseData) => { - resolve(res.data); - }); - }; + /** + * 懒加载 + * @param row + * @returns {Promise} + */ + const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => { + pageRequest({ pcode: tree.code }).then((res: APIResponseData) => { + resolve(res.data); + }); + }; - return { - crudOptions: { - request: { - pageRequest, - addRequest, - editRequest, - delRequest, - }, - actionbar: { - buttons: { - add: { - show: auth('area:Create'), - } - } - }, - rowHandle: { - //固定右侧 - fixed: 'right', - width: 200, - buttons: { - view: { - show: false, - }, - edit: { - iconRight: 'Edit', - type: 'text', - show: auth('area:Update') - }, - remove: { - iconRight: 'Delete', - type: 'text', - show: auth('area:Delete') - }, - }, - }, - pagination: { - show: false, - }, - table: { - rowKey: 'id', - lazy: true, - load: loadContentMethod, - treeProps: {children: 'children', hasChildren: 'hasChild'}, - }, - columns: { - _index: { - title: '序号', - form: {show: false}, - column: { - type: 'index', - align: 'center', - width: '70px', - columnSetDisabled: true, //禁止在列设置中选择 - }, - }, - // pcode: { - // title: '父级地区', - // show: false, - // search: { - // show: true, - // }, - // type: 'dict-tree', - // form: { - // component: { - // showAllLevels: false, // 仅显示最后一级 - // props: { - // elProps: { - // clearable: true, - // showAllLevels: false, // 仅显示最后一级 - // props: { - // checkStrictly: true, // 可以不需要选到最后一级 - // emitPath: false, - // clearable: true, - // }, - // }, - // }, - // }, - // }, - // }, - name: { - title: '名称', - search: { - show: true, - }, - treeNode: true, - type: 'input', - column: { - minWidth: 120, - }, - form: { - rules: [ - // 表单校验规则 - {required: true, message: '名称必填项'}, - ], - component: { - placeholder: '请输入名称', - }, - }, - }, - code: { - title: '地区编码', - search: { - show: true, - }, - type: 'input', - column: { - minWidth: 90, - }, - form: { - rules: [ - // 表单校验规则 - {required: true, message: '地区编码必填项'}, - ], - component: { - placeholder: '请输入地区编码', - }, - }, - }, - pinyin: { - title: '拼音', - search: { - disabled: true, - }, - type: 'input', - column: { - minWidth: 120, - }, - form: { - rules: [ - // 表单校验规则 - {required: true, message: '拼音必填项'}, - ], - component: { - placeholder: '请输入拼音', - }, - }, - }, - level: { - title: '地区层级', - search: { - disabled: true, - }, - type: 'input', - column: { - minWidth: 100, - }, - form: { - disabled: false, - rules: [ - // 表单校验规则 - {required: true, message: '拼音必填项'}, - ], - component: { - placeholder: '请输入拼音', - }, - }, - }, - initials: { - title: '首字母', - column: { - minWidth: 100, - }, - form: { - rules: [ - // 表单校验规则 - {required: true, message: '首字母必填项'}, - ], - - component: { - placeholder: '请输入首字母', - }, - }, - }, - enable: { - title: '是否启用', - search: { - show: true, - }, - type: 'dict-radio', - column: { - minWidth: 90, - component: { - name: 'fs-dict-switch', - activeText: '', - inactiveText: '', - style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6', - onChange: compute((context) => { - return () => { - api.UpdateObj(context.row).then((res: APIResponseData) => { - successMessage(res.msg as string); - }); - }; - }), - }, - }, - dict: dict({ - data: dictionary('button_status_bool'), - }), - }, - }, - }, - }; + return { + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + actionbar: { + buttons: { + add: { + show: auth('area:Create'), + }, + }, + }, + rowHandle: { + //固定右侧 + fixed: 'right', + width: 200, + buttons: { + view: { + show: false, + }, + edit: { + iconRight: 'Edit', + type: 'text', + show: auth('area:Update'), + }, + remove: { + iconRight: 'Delete', + type: 'text', + show: auth('area:Delete'), + }, + }, + }, + pagination: { + show: false, + }, + table: { + rowKey: 'id', + lazy: true, + load: loadContentMethod, + treeProps: { children: 'children', hasChildren: 'hasChild' }, + }, + columns: { + _index: { + title: '序号', + form: { show: false }, + column: { + type: 'index', + align: 'center', + width: '70px', + columnSetDisabled: true, //禁止在列设置中选择 + }, + }, + name: { + title: '名称', + search: { + show: true, + }, + treeNode: true, + type: 'input', + column: { + minWidth: 120, + }, + form: { + rules: [ + // 表单校验规则 + { required: true, message: '名称必填项' }, + ], + component: { + placeholder: '请输入名称', + }, + }, + }, + pcode: { + title: '父级地区', + search: { + disabled: true, + }, + width: 130, + type: 'table-selector', + form: { + component: { + name: shallowRef(tableSelector), + vModel: 'modelValue', + displayLabel: compute(({ row }) => { + if (row) { + return row.pcode_info; + } + return null; + }), + tableConfig: { + url: '/api/system/area/', + label: 'name', + value: 'id', + isTree: true, + isMultiple: false, + lazy: true, + load: loadContentMethod, + treeProps: { children: 'children', hasChildren: 'hasChild' }, + columns: [ + { + prop: 'name', + label: '地区', + width: 150, + }, + { + prop: 'code', + label: '地区编码', + }, + ], + }, + }, + }, + column: { + show: false, + }, + }, + code: { + title: '地区编码', + search: { + show: true, + }, + type: 'input', + column: { + minWidth: 90, + }, + form: { + rules: [ + // 表单校验规则 + { required: true, message: '地区编码必填项' }, + ], + component: { + placeholder: '请输入地区编码', + }, + }, + }, + enable: { + title: '是否启用', + search: { + show: true, + }, + type: 'dict-radio', + column: { + minWidth: 90, + component: { + name: 'fs-dict-switch', + activeText: '', + inactiveText: '', + style: '--el-switch-on-color: var(--el-color-primary); --el-switch-off-color: #dcdfe6', + onChange: compute((context) => { + return () => { + api.UpdateObj(context.row).then((res: APIResponseData) => { + successMessage(res.msg as string); + }); + }; + }), + }, + }, + dict: dict({ + data: dictionary('button_status_bool'), + }), + }, + }, + }, + }; }; From 6f8bae8d5ced721319250178351a0986f377f89a Mon Sep 17 00:00:00 2001 From: lxy <46486798@qq.com> Date: Mon, 8 Jul 2024 09:33:51 +0000 Subject: [PATCH 05/12] =?UTF-8?q?update=20backend/dvadmin/utils/middleware?= =?UTF-8?q?.py.=20=E5=BD=93=E5=90=8C=E4=B8=80=E6=97=B6=E5=88=BB=E8=BF=9B?= =?UTF-8?q?=E6=9D=A5=E5=A4=9A=E4=B8=AA=E8=AF=B7=E6=B1=82=E4=B8=94=E9=83=BD?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=AE=8C=E6=88=90=E5=93=8D=E5=BA=94=E6=97=B6?= =?UTF-8?q?=EF=BC=8Coperation=5Flog=5Fid=E4=BC=9A=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E6=9C=80=E5=90=8E=E4=B8=80=E4=B8=AA=E8=BF=9B=E6=9D=A5=E7=9A=84?= =?UTF-8?q?ID=EF=BC=8C=E5=AF=BC=E8=87=B4=E4=B9=8B=E5=89=8D=E6=8C=89?= =?UTF-8?q?=E8=BF=9B=E6=9D=A5=E7=9A=84=E8=AF=B7=E6=B1=82=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=88=B0=E5=90=8C=E4=B8=80=E4=B8=AAid=E4=B8=8A=EF=BC=8C?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lxy <46486798@qq.com> --- backend/dvadmin/utils/middleware.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/dvadmin/utils/middleware.py b/backend/dvadmin/utils/middleware.py index f4717a4..6029c5a 100644 --- a/backend/dvadmin/utils/middleware.py +++ b/backend/dvadmin/utils/middleware.py @@ -32,6 +32,14 @@ class ApiLoggingMiddleware(MiddlewareMixin): request.request_path = get_request_path(request) def __handle_response(self, request, response): + + # 判断有无log_id属性,使用All记录时,会出现此情况 + if request.request_data.get('log_id', None) is None: + return + + # 移除log_id,不记录此ID + log_id = request.request_data.pop('log_id') + # request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性 body = getattr(request, 'request_data', {}) # 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数) @@ -60,7 +68,7 @@ class ApiLoggingMiddleware(MiddlewareMixin): 'status': True if response.data.get('code') in [2000, ] else False, 'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')}, } - operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=self.operation_log_id) + operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=log_id) if not operation_log.request_modular and settings.API_MODEL_MAP.get(request.request_path, None): operation_log.request_modular = settings.API_MODEL_MAP[request.request_path] operation_log.save() @@ -71,7 +79,8 @@ class ApiLoggingMiddleware(MiddlewareMixin): if self.methods == 'ALL' or request.method in self.methods: log = OperationLog(request_modular=get_verbose_name(view_func.cls.queryset)) log.save() - self.operation_log_id = log.id + # self.operation_log_id = log.id + request.request_data['log_id'] = log.id return From 3dcef90bbe9ea0377898b3e461909c37fb4c2d68 Mon Sep 17 00:00:00 2001 From: lxy <46486798@qq.com> Date: Tue, 9 Jul 2024 03:56:45 +0000 Subject: [PATCH 06/12] =?UTF-8?q?update=20backend/dvadmin/utils/field=5Fpe?= =?UTF-8?q?rmission.py.=20=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=B8=AA=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E6=8B=A5=E6=9C=89=E5=A4=9A=E4=B8=AA=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=9D=83=E9=99=90=EF=BC=8C=E5=AF=BC=E8=87=B4=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=9F=90=E4=BA=9B=E6=A8=A1=E5=9D=97=E6=B2=A1=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=A5=BD=E5=AF=BC=E8=87=B4=E7=9A=84=E6=97=A0=E6=9D=83=E9=99=90?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=88=E5=9C=B0=E5=8C=BA=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=80=81=E7=99=BB=E5=BD=95=E6=97=A5=E5=BF=97=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=9D=83=E9=99=90=EF=BC=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=A4=9A=E4=B8=AA=E7=9B=B8=E5=90=8C=E7=9A=84?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D=E6=9D=83=E9=99=90=EF=BC=8C=E7=9B=B8?= =?UTF-8?q?=E5=90=8C=E5=AD=97=E6=AE=B5=E5=90=8D=E7=9A=84=E6=9D=83=E9=99=90?= =?UTF-8?q?True=E5=80=BC=E4=BF=9D=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lxy <46486798@qq.com> --- backend/dvadmin/utils/field_permission.py | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/dvadmin/utils/field_permission.py b/backend/dvadmin/utils/field_permission.py index 20b4cb9..7c259f6 100644 --- a/backend/dvadmin/utils/field_permission.py +++ b/backend/dvadmin/utils/field_permission.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- + +from itertools import groupby + from django.db.models import F from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated @@ -35,4 +38,34 @@ class FieldPermissionMixin: data= FieldPermission.objects.filter( field__model=model['model'],role__in=roles ).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name')) + + """ + 合并权限 + + 这段代码首先根据 field_name 对列表进行排序, + 然后使用 groupby 按 field_name 进行分组。 + 对于每个组,它创建一个新的字典 merged, + 并遍历组中的每个字典,将布尔值字段使用逻辑或(or)操作符进行合并(如果 merged 中还没有该字段,则默认为 False), + 其他字段(如 field_name)则直接取组的关键字(即 key) + """ + + # 使用field_name对列表进行分组, # groupby 需要先对列表进行排序,因为它只能对连续相同的元素进行分组。 + grouped = groupby(sorted(list(data), key=lambda x: x['field_name']), key=lambda x: x['field_name']) + + data = [] + + # 遍历分组,合并权限 + for key, group in grouped: + + # 初始化一个空字典来存储合并后的结果 + merged = {} + for item in group: + # 合并权限, True值优先 + merged['is_create'] = merged.get('is_create', False) or item['is_create'] + merged['is_query'] = merged.get('is_query', False) or item['is_query'] + merged['is_update'] = merged.get('is_update', False) or item['is_update'] + merged['field_name'] = key + + data.append(merged) + return DetailResponse(data=data) \ No newline at end of file From 1942f1af4ef0ae666ef7293996acc2f591108a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E9=AB=98?= <794071084@qq.com> Date: Wed, 10 Jul 2024 02:22:37 +0000 Subject: [PATCH 07/12] =?UTF-8?q?=E6=9B=B4=E6=94=B9npm=E9=95=9C=E5=83=8F?= =?UTF-8?q?=E6=BA=90=E4=B8=BAnpmmirror.com=20=E5=B0=86npm=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E5=91=BD=E4=BB=A4=E4=B8=AD=E7=9A=84registry=E4=BB=8E?= =?UTF-8?q?=E6=B7=98=E5=AE=9Dnpm=E6=BA=90=E6=94=B9=E4=B8=BAnpmmirror.com?= =?UTF-8?q?=EF=BC=8C=E5=8E=9F=E6=9C=89npm=E6=BA=90=E5=B7=B2=E7=BB=8F?= =?UTF-8?q?=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 老高 <794071084@qq.com> --- README.zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.zh.md b/README.zh.md index dea0106..a95a364 100644 --- a/README.zh.md +++ b/README.zh.md @@ -114,7 +114,7 @@ cd web # 安装依赖 npm install yarn -yarn install --registry=https://registry.npm.taobao.org +yarn install --registry=https://registry.npmmirror.com # 启动服务 yarn build From 9a8506448f76d1087f3a31c2704c30bf3850bfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=BD=E5=A5=87=E5=AE=9D=E5=AE=9D?= <11259906+haoqibb@user.noreply.gitee.com> Date: Thu, 25 Jul 2024 01:56:55 +0000 Subject: [PATCH 08/12] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=9C=9F=E9=97=B4=E6=9D=A1=E4=BB=B6=E8=BF=87=E6=BB=A4=EF=BC=8C?= =?UTF-8?q?=E5=8C=85=E5=90=AB=E6=88=AA=E6=AD=A2=E6=97=A5=E6=9C=9F=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E6=95=B0=E6=8D=AE=20=E5=9B=A0=E4=B8=BA=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=97=A5=E6=9C=9F=E6=98=AF=E4=B8=80=E4=B8=AAdatetime?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=EF=BC=8C=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8lte=E4=B8=8D=E4=BC=9A=E5=8C=85=E5=90=AB?= =?UTF-8?q?=E6=88=AA=E6=AD=A2=E6=97=A5=E7=9A=84=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 好奇宝宝 <11259906+haoqibb@user.noreply.gitee.com> --- backend/dvadmin/utils/filters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/dvadmin/utils/filters.py b/backend/dvadmin/utils/filters.py index 05c0dfb..d09a8a1 100644 --- a/backend/dvadmin/utils/filters.py +++ b/backend/dvadmin/utils/filters.py @@ -37,11 +37,11 @@ class CoreModelFilterBankend(BaseFilterBackend): if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]): create_filter = Q() if create_datetime_after and create_datetime_before: - create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=create_datetime_before) + create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=f'{create_datetime_before} 23:59:59') elif create_datetime_after: create_filter &= Q(create_datetime__gte=create_datetime_after) elif create_datetime_before: - create_filter &= Q(create_datetime__lte=create_datetime_before) + create_filter &= Q(create_datetime__lte=f'{create_datetime_before} 23:59:59') # 更新时间范围过滤条件 update_filter = Q() From 3979628281b89e0b710a88eeaa51fbb57acebf4a Mon Sep 17 00:00:00 2001 From: lxy <46486798@qq.com> Date: Mon, 29 Jul 2024 03:19:29 +0000 Subject: [PATCH 09/12] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=AC=A1=E6=95=B0=E6=B2=A1=E6=9C=89=E4=BF=9D=E5=AD=98=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20update=20backend/dvadmin/system/views/login.py.=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95=E6=AC=A1=E6=95=B0=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lxy <46486798@qq.com> --- backend/dvadmin/system/views/login.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/dvadmin/system/views/login.py b/backend/dvadmin/system/views/login.py index 4023a82..1996906 100644 --- a/backend/dvadmin/system/views/login.py +++ b/backend/dvadmin/system/views/login.py @@ -124,6 +124,7 @@ class LoginSerializer(TokenObtainPairSerializer): user.is_active = False user.save() raise CustomValidationError("账号已被锁定,联系管理员解锁") + user.save() count = 5 - user.login_error_count raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~") From 1981567c59bfdc01243730bf4b8d5a297966d291 Mon Sep 17 00:00:00 2001 From: lxy <10179281+lxy0722@user.noreply.gitee.com> Date: Tue, 30 Jul 2024 17:12:54 +0800 Subject: [PATCH 10/12] =?UTF-8?q?fixed=2092a17fa=20from=20https://gitee.co?= =?UTF-8?q?m/lxy0722/django-vue3-admin/pulls/1=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=9C=A8=E6=B7=B1=E8=89=B2=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=9A=84?= =?UTF-8?q?=E8=83=8C=E6=99=AF=E8=89=B2=E6=98=BE=E7=A4=BA=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/views/system/dept/components/DeptUserCom/index.vue | 3 ++- web/src/views/system/dept/index.vue | 2 +- web/src/views/system/menu/index.vue | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/views/system/dept/components/DeptUserCom/index.vue b/web/src/views/system/dept/components/DeptUserCom/index.vue index fb50ff9..f52db55 100644 --- a/web/src/views/system/dept/components/DeptUserCom/index.vue +++ b/web/src/views/system/dept/components/DeptUserCom/index.vue @@ -277,7 +277,8 @@ const { resetCrudOptions } = useCrud({ padding: 0 10px; border-radius: 8px 0 0 8px; box-sizing: border-box; - background-color: #fff; + color: var(--next-bg-topBarColor); + background-color: var(--el-fill-color-blank);; } .dept-user-com-table { height: calc(100% - 200px); diff --git a/web/src/views/system/dept/index.vue b/web/src/views/system/dept/index.vue index 60b803b..52df92b 100644 --- a/web/src/views/system/dept/index.vue +++ b/web/src/views/system/dept/index.vue @@ -133,7 +133,7 @@ onMounted(() => { } .dept-left { - background-color: #fff; + background-color: var(--el-fill-color-blank);; border-radius: 0 8px 8px 0; padding: 10px; } diff --git a/web/src/views/system/menu/index.vue b/web/src/views/system/menu/index.vue index 0637a3f..f7d05bd 100644 --- a/web/src/views/system/menu/index.vue +++ b/web/src/views/system/menu/index.vue @@ -138,7 +138,7 @@ onMounted(() => { .menu-box { height: 100%; padding: 10px; - background-color: #fff; + background-color: var(--el-fill-color-blank);; box-sizing: border-box; } From 5cf2eef7adfa5ec0e178005a6be1f5bec5565508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E5=AD=90-=E6=9D=8E?= <1537080775@qq.com> Date: Thu, 1 Aug 2024 14:05:49 +0000 Subject: [PATCH 11/12] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dcelery=E6=97=B6?= =?UTF-8?q?=E5=8C=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 木子-李 <1537080775@qq.com> --- backend/application/__init__.py | 5 +++++ backend/application/celery.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/application/__init__.py b/backend/application/__init__.py index e69de29..b7ddb6f 100644 --- a/backend/application/__init__.py +++ b/backend/application/__init__.py @@ -0,0 +1,5 @@ +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/backend/application/celery.py b/backend/application/celery.py index d57b92a..10bce56 100644 --- a/backend/application/celery.py +++ b/backend/application/celery.py @@ -15,7 +15,7 @@ else: from celery import Celery app = Celery(f"application") -app.config_from_object('django.conf:settings') +app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) platforms.C_FORCE_ROOT = True From b6e05c997d03889966257d0aeaa7924f5cccfffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B0=8F=E6=B6=9B?= <1537080775@qq.com> Date: Tue, 27 Aug 2024 17:31:29 +0800 Subject: [PATCH 12/12] =?UTF-8?q?feat(20240827=5FBatchDelete):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 增加表格多选 2. 运行跨页多选 3. 显示多选数据 4. 移除多选数据 5. 增加批量删除 --- web/src/utils/columnPermission.ts | 2 +- .../menu/components/MenuButtonCom/api.ts | 7 + .../menu/components/MenuButtonCom/crud.tsx | 52 ++++ .../menu/components/MenuButtonCom/index.vue | 61 ++++- .../menu/components/MenuFieldCom/api.ts | 7 + .../menu/components/MenuFieldCom/crud.tsx | 51 +++- .../menu/components/MenuFieldCom/index.vue | 232 ++++++++++-------- web/src/views/system/menu/index.vue | 4 +- 8 files changed, 312 insertions(+), 104 deletions(-) diff --git a/web/src/utils/columnPermission.ts b/web/src/utils/columnPermission.ts index 7ef06de..adff4e1 100644 --- a/web/src/utils/columnPermission.ts +++ b/web/src/utils/columnPermission.ts @@ -22,7 +22,7 @@ export const handleColumnPermission = async (func: Function, crudOptions: any,ex } } const columns = crudOptions.columns; - const excludeColumns = ['_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn) + const excludeColumns = ['checked','_index','id', 'create_datetime', 'update_datetime'].concat(excludeColumn) for (let col in columns) { for (let item of res.data) { if (excludeColumns.includes(item.field_name)) { diff --git a/web/src/views/system/menu/components/MenuButtonCom/api.ts b/web/src/views/system/menu/components/MenuButtonCom/api.ts index d91f920..612c49d 100644 --- a/web/src/views/system/menu/components/MenuButtonCom/api.ts +++ b/web/src/views/system/menu/components/MenuButtonCom/api.ts @@ -48,3 +48,10 @@ export function BatchAdd(obj: AddReq) { }); } +export function BatchDelete(keys: any) { + return request({ + url: apiPrefix + 'multiple_delete/', + method: 'delete', + data: { keys }, + }); +} diff --git a/web/src/views/system/menu/components/MenuButtonCom/crud.tsx b/web/src/views/system/menu/components/MenuButtonCom/crud.tsx index 908cf2a..79675fb 100644 --- a/web/src/views/system/menu/components/MenuButtonCom/crud.tsx +++ b/web/src/views/system/menu/components/MenuButtonCom/crud.tsx @@ -4,6 +4,8 @@ import {auth} from '/@/utils/authFunction' import {request} from '/@/utils/service'; import { successNotification } from '/@/utils/message'; import { ElMessage } from 'element-plus'; +import { nextTick, ref } from 'vue'; +import XEUtils from 'xe-utils'; //此处为crudOptions配置 export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet { const pageRequest = async () => { @@ -22,7 +24,42 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti const addRequest = async ({form}: AddReq) => { return await api.AddObj({...form, ...{menu: context!.selectOptions.value.id}}); }; + // 记录选中的行 + const selectedRows = ref([]); + + const onSelectionChange = (changed: any) => { + const tableData = crudExpose.getTableData(); + const unChanged = tableData.filter((row: any) => !changed.includes(row)); + // 添加已选择的行 + XEUtils.arrayEach(changed, (item: any) => { + const ids = XEUtils.pluck(selectedRows.value, 'id'); + if (!ids.includes(item.id)) { + selectedRows.value = XEUtils.union(selectedRows.value, [item]); + } + }); + // 剔除未选择的行 + XEUtils.arrayEach(unChanged, (unItem: any) => { + selectedRows.value = XEUtils.remove(selectedRows.value, (item: any) => item.id !== unItem.id); + }); + }; + const toggleRowSelection = () => { + // 多选后,回显默认勾选 + const tableRef = crudExpose.getBaseTableRef(); + const tableData = crudExpose.getTableData(); + const selected = XEUtils.filter(tableData, (item: any) => { + const ids = XEUtils.pluck(selectedRows.value, 'id'); + return ids.includes(item.id); + }); + + nextTick(() => { + XEUtils.arrayEach(selected, (item) => { + tableRef.toggleRowSelection(item, true); + }); + }); + }; + return { + selectedRows, crudOptions: { pagination:{ show:false @@ -84,6 +121,11 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti editRequest, delRequest, }, + table: { + rowKey: 'id', //设置你的主键id, 默认rowKey=id + onSelectionChange, + onRefreshed: () => toggleRowSelection(), + }, form: { col: {span: 24}, labelWidth: '100px', @@ -93,6 +135,16 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti }, }, columns: { + $checked: { + title: '选择', + form: { show: false }, + column: { + type: 'selection', + align: 'center', + width: '70px', + columnSetDisabled: true, //禁止在列设置中选择 + }, + }, _index: { title: '序号', form: {show: false}, diff --git a/web/src/views/system/menu/components/MenuButtonCom/index.vue b/web/src/views/system/menu/components/MenuButtonCom/index.vue index 9dddaf9..ec55e35 100644 --- a/web/src/views/system/menu/components/MenuButtonCom/index.vue +++ b/web/src/views/system/menu/components/MenuButtonCom/index.vue @@ -1,19 +1,72 @@ diff --git a/web/src/views/system/menu/index.vue b/web/src/views/system/menu/index.vue index 0637a3f..ca544a1 100644 --- a/web/src/views/system/menu/index.vue +++ b/web/src/views/system/menu/index.vue @@ -16,12 +16,12 @@ -
+
-
+