From 5ef2045eebbe8b4a3917f012f701cf815dadeb2d Mon Sep 17 00:00:00 2001 From: Angelo Date: Tue, 7 Jun 2022 12:05:27 +0800 Subject: [PATCH 01/27] fix(backend): removed extra print log --- backend/application/dispatch.py | 42 +++++++++++++++++--------- backend/dvadmin/utils/import_export.py | 1 - 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/backend/application/dispatch.py b/backend/application/dispatch.py index ae77479..a56b022 100644 --- a/backend/application/dispatch.py +++ b/backend/application/dispatch.py @@ -9,7 +9,7 @@ def is_tenants_mode(): 判断是否为租户模式 :return: """ - return hasattr(connection, 'tenant') and connection.tenant.schema_name + return hasattr(connection, "tenant") and connection.tenant.schema_name # ================================================= # @@ -17,27 +17,37 @@ def is_tenants_mode(): # ================================================= # def _get_all_dictionary(): from dvadmin.system.models import Dictionary + queryset = Dictionary.objects.filter(status=True, is_value=False) data = [] for instance in queryset: - data.append({ - "id": instance.id, - "value": instance.value, - "children": list(Dictionary.objects.filter(parent=instance.id).filter(status=1). - values('label', 'value', 'type', 'color')) - }) + data.append( + { + "id": instance.id, + "value": instance.value, + "children": list( + Dictionary.objects.filter(parent=instance.id) + .filter(status=1) + .values("label", "value", "type", "color") + ), + } + ) return {ele.get("value"): ele for ele in data} def _get_all_system_config(): data = {} from dvadmin.system.models import SystemConfig - system_config_obj = SystemConfig.objects.filter(status=True, parent_id__isnull=False).values( - 'parent__key', 'key', 'value', 'form_item_type').order_by('sort') + + system_config_obj = ( + SystemConfig.objects.filter(status=True, parent_id__isnull=False) + .values("parent__key", "key", "value", "form_item_type") + .order_by("sort") + ) for system_config in system_config_obj: - value = system_config.get('value', '') - if value and system_config.get('form_item_type') == 7: - value = value[0].get('url') + value = system_config.get("value", "") + if value and system_config.get("form_item_type") == 7: + value = value[0].get("url") data[f"{system_config.get('parent__key')}.{system_config.get('key')}"] = value return data @@ -50,12 +60,12 @@ def init_dictionary(): try: if is_tenants_mode(): from django_tenants.utils import tenant_context, get_tenant_model + for tenant in get_tenant_model().objects.filter(): with tenant_context(tenant): settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary() else: settings.DICTIONARY_CONFIG = _get_all_dictionary() - print("初始化字典配置完成") except Exception as e: print("请先进行数据库迁移!") return @@ -71,12 +81,14 @@ def init_system_config(): if is_tenants_mode(): from django_tenants.utils import tenant_context, get_tenant_model + for tenant in get_tenant_model().objects.filter(): with tenant_context(tenant): settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config() else: settings.SYSTEM_CONFIG = _get_all_system_config() - print("初始化系统配置完成") + if settings.SYSTEM_CONFIG: + print("初始化系统配置完成") except Exception as e: print("请先进行数据库迁移!") return @@ -89,6 +101,7 @@ def refresh_dictionary(): """ if is_tenants_mode(): from django_tenants.utils import tenant_context, get_tenant_model + for tenant in get_tenant_model().objects.filter(): with tenant_context(tenant): settings.DICTIONARY_CONFIG[connection.tenant.schema_name] = _get_all_dictionary() @@ -103,6 +116,7 @@ def refresh_system_config(): """ if is_tenants_mode(): from django_tenants.utils import tenant_context, get_tenant_model + for tenant in get_tenant_model().objects.filter(): with tenant_context(tenant): settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config() diff --git a/backend/dvadmin/utils/import_export.py b/backend/dvadmin/utils/import_export.py index 5a01968..7b9a68b 100644 --- a/backend/dvadmin/utils/import_export.py +++ b/backend/dvadmin/utils/import_export.py @@ -26,7 +26,6 @@ def import_to_data(file_url, field_data): 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]) From e422f0a9f11adf5e05fc19c531e32b31d885c2ea Mon Sep 17 00:00:00 2001 From: Angelo Date: Tue, 7 Jun 2022 12:07:47 +0800 Subject: [PATCH 02/27] fix(backend): removed extra print log --- backend/application/dispatch.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/application/dispatch.py b/backend/application/dispatch.py index a56b022..3f19225 100644 --- a/backend/application/dispatch.py +++ b/backend/application/dispatch.py @@ -87,8 +87,6 @@ def init_system_config(): settings.SYSTEM_CONFIG[connection.tenant.schema_name] = _get_all_system_config() else: settings.SYSTEM_CONFIG = _get_all_system_config() - if settings.SYSTEM_CONFIG: - print("初始化系统配置完成") except Exception as e: print("请先进行数据库迁移!") return From a7de6911f85bf2c5543b8f1f179c1ac3ce09c639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Thu, 9 Jun 2022 17:37:39 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.请求返回结果的参数过滤器; 2.一对多组件; 3.多对多组件; --- backend/dvadmin/system/views/user.py | 17 ++++++++++ web/src/components/foreign-key/README.md | 28 ++++++++++++++++ web/src/components/foreign-key/index.js | 15 +++++++++ web/src/components/foreign-key/index.vue | 31 ++++++++++++++++++ web/src/components/index.js | 2 ++ web/src/components/many-to-many/README.md | 34 +++++++++++++++++++ web/src/components/many-to-many/index.js | 15 +++++++++ web/src/components/many-to-many/index.vue | 40 +++++++++++++++++++++++ web/src/libs/util.js | 4 ++- web/src/libs/util.params.js | 15 +++++++++ web/src/views/system/user/crud.js | 10 ++++++ web/src/views/system/user/index.vue | 6 ++-- 12 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 web/src/components/foreign-key/README.md create mode 100644 web/src/components/foreign-key/index.js create mode 100644 web/src/components/foreign-key/index.vue create mode 100644 web/src/components/many-to-many/README.md create mode 100644 web/src/components/many-to-many/index.js create mode 100644 web/src/components/many-to-many/index.vue create mode 100644 web/src/libs/util.params.js diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index ef9f40b..4ef20fd 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -1,12 +1,14 @@ import hashlib from django.contrib.auth.hashers import make_password +from django_restql.fields import DynamicSerializerMethodField from rest_framework import serializers from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from application import dispatch from dvadmin.system.models import Users +from dvadmin.system.views.role import RoleSerializer from dvadmin.utils.json_response import ErrorResponse, DetailResponse from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.validator import CustomUniqueValidator @@ -17,6 +19,8 @@ class UserSerializer(CustomModelSerializer): """ 用户管理-序列化器 """ + dept_name = serializers.CharField(source='dept.name',read_only=True) + role_info = DynamicSerializerMethodField() class Meta: model = Users @@ -26,6 +30,19 @@ class UserSerializer(CustomModelSerializer): "post": {"required": False}, } + def get_role_info(self, instance,parsed_query): + roles = instance.role.all() + + # You can do what ever you want in here + + # `parsed_query` param is passed to BookSerializer to allow further querying + serializer = RoleSerializer( + roles, + many=True, + parsed_query=parsed_query + ) + return serializer.data + class UsersInitSerializer(CustomModelSerializer): """ diff --git a/web/src/components/foreign-key/README.md b/web/src/components/foreign-key/README.md new file mode 100644 index 0000000..4f7c5c1 --- /dev/null +++ b/web/src/components/foreign-key/README.md @@ -0,0 +1,28 @@ +# 一对多表格显示配置说明 +本组件用于多对多返回数据使用,例如:角色信息 +```angular2html +dept_name = "dvadmin团队" + +#crud的配置 +component: { +name: 'foreignKey', +valueBinding: 'dept_name' +} +``` +## crud.js +``` + { + component: { + name: 'foreignKey', + valueBinding: 'dept_name', + } + } +``` + +## 配置说明 + + +| Name | Description | Type | Required | Default | +| ---------- | ---------------- | ------- | -------- | -------------- | +| name | 字段所使用的组件 | String | true | foreignKey | +| valueBinding | row中的key | String | true | - | diff --git a/web/src/components/foreign-key/index.js b/web/src/components/foreign-key/index.js new file mode 100644 index 0000000..6190ec2 --- /dev/null +++ b/web/src/components/foreign-key/index.js @@ -0,0 +1,15 @@ +import { d2CrudPlus } from 'd2-crud-plus' +import group from './group' + +function install (Vue, options) { + Vue.component('foreign-key', () => import('./index')) + if (d2CrudPlus != null) { + // 注册字段类型`demo-extend` + d2CrudPlus.util.columnResolve.addTypes(group) + } +} + +// 导出install, 通过`vue.use(D2pDemoExtend)`安装后 ,`demo-extend` 就可以在`crud.js`中使用了 +export default { + install +} diff --git a/web/src/components/foreign-key/index.vue b/web/src/components/foreign-key/index.vue new file mode 100644 index 0000000..720eaa5 --- /dev/null +++ b/web/src/components/foreign-key/index.vue @@ -0,0 +1,31 @@ + + diff --git a/web/src/components/index.js b/web/src/components/index.js index 73ffe1f..943e6c8 100644 --- a/web/src/components/index.js +++ b/web/src/components/index.js @@ -7,3 +7,5 @@ Vue.component('d2-container', d2Container) Vue.component('d2-icon', () => import('./d2-icon')) Vue.component('d2-icon-svg', () => import('./d2-icon-svg/index.vue')) Vue.component('importExcel', () => import('./importExcel/index.vue')) +Vue.component('foreignKey', () => import('./foreign-key/index.vue')) +Vue.component('manyToMany', () => import('./many-to-many/index.vue')) diff --git a/web/src/components/many-to-many/README.md b/web/src/components/many-to-many/README.md new file mode 100644 index 0000000..516302e --- /dev/null +++ b/web/src/components/many-to-many/README.md @@ -0,0 +1,34 @@ +# 多对多表格显示配置说明 +本组件用于多对多返回数据使用,例如:角色信息 +```angular2html +role_info = [ + {"id":1,"name":"普通用户"}, + {"id":2,"name":"管理员"} +] + +#crud的配置 +component: { +name: 'manyToMany', +valueBinding: 'role_info', +children: 'name' +} +``` +## crud.js +``` + { + component: { + name: 'manyToMany', + valueBinding: 'role_name', + children: 'name' + } + } +``` + +## 配置说明 + + +| Name | Description | Type | Required | Default | +| ---------- | ---------------- | ------- | -------- | -------------- | +| name | 字段所使用的组件 | String | true | manyToMany | +| valueBinding | row中的key | String | true | - | +| children | 数组中的key | String | true | name | diff --git a/web/src/components/many-to-many/index.js b/web/src/components/many-to-many/index.js new file mode 100644 index 0000000..e8ba588 --- /dev/null +++ b/web/src/components/many-to-many/index.js @@ -0,0 +1,15 @@ +import { d2CrudPlus } from 'd2-crud-plus' +import group from './group' + +function install (Vue, options) { + Vue.component('many-to-many', () => import('./index')) + if (d2CrudPlus != null) { + // 注册字段类型`demo-extend` + d2CrudPlus.util.columnResolve.addTypes(group) + } +} + +// 导出install, 通过`vue.use(D2pDemoExtend)`安装后 ,`demo-extend` 就可以在`crud.js`中使用了 +export default { + install +} diff --git a/web/src/components/many-to-many/index.vue b/web/src/components/many-to-many/index.vue new file mode 100644 index 0000000..c6bd225 --- /dev/null +++ b/web/src/components/many-to-many/index.vue @@ -0,0 +1,40 @@ + + diff --git a/web/src/libs/util.js b/web/src/libs/util.js index 0b00d8b..9d0abd1 100644 --- a/web/src/libs/util.js +++ b/web/src/libs/util.js @@ -2,11 +2,13 @@ import cookies from './util.cookies' import db from './util.db' import log from './util.log' import dayjs from 'dayjs' +import filterParams from './util.params' const util = { cookies, db, - log + log, + filterParams } /** diff --git a/web/src/libs/util.params.js b/web/src/libs/util.params.js new file mode 100644 index 0000000..72632a6 --- /dev/null +++ b/web/src/libs/util.params.js @@ -0,0 +1,15 @@ +/** + * 对请求参数进行过滤 + * that=>this + * array:其他字段数组 + */ +const filterParams = function (that, array) { + const arr = that.crud.columns + const columnKeys = arr.map(item => { + return item.key + }) + const newArray = [...columnKeys, array, 'id'] + return '{' + newArray.toString() + '}' +} + +export default filterParams diff --git a/web/src/views/system/user/crud.js b/web/src/views/system/user/crud.js index 9838106..19c7167 100644 --- a/web/src/views/system/user/crud.js +++ b/web/src/views/system/user/crud.js @@ -128,6 +128,7 @@ export const crudOptions = (vm) => { placeholder: '请输入密码' }, value: vm.systemConfig('base.default_password'), + editDisabled: true, itemProps: { class: { yxtInput: true } } @@ -193,6 +194,10 @@ export const crudOptions = (vm) => { pagination: true, props: { multiple: false } } + }, + component: { + name: 'foreignKey', + valueBinding: 'dept_name' } }, { @@ -368,6 +373,11 @@ export const crudOptions = (vm) => { ] } } + }, + component: { + name: 'manyToMany', + valueBinding: 'role_info', + children: 'name' } } ].concat(vm.commonEndColumns({ diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue index 6be1535..ce9e5b7 100644 --- a/web/src/views/system/user/index.vue +++ b/web/src/views/system/user/index.vue @@ -81,7 +81,7 @@ import * as api from './api' import { crudOptions } from './crud' import { d2CrudPlus } from 'd2-crud-plus' - +import util from '@/libs/util' export default { name: 'user', mixins: [d2CrudPlus.crud], @@ -133,7 +133,9 @@ export default { return crudOptions(this) }, pageRequest (query) { - return api.GetList(query) + const columnKeys = util.filterParams(this,['dept_name','role_info{name}']) + const params = { query: columnKeys,...query } + return api.GetList(params) }, addRequest (row) { return api.AddObj(row) From 2316d93843b62c1adccfcfa6e052c32eea6df663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Sat, 11 Jun 2022 20:48:36 +0800 Subject: [PATCH 04/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96table-seletor=E9=80=89=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/application/settings.py | 1 + .../table-selector/table-selector.vue | 54 ++++++------------- 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/backend/application/settings.py b/backend/application/settings.py index c8ce5e9..d88d9af 100644 --- a/backend/application/settings.py +++ b/backend/application/settings.py @@ -378,4 +378,5 @@ PLUGINS_URL_PATTERNS = [] # from dvadmin_upgrade_center.settings import * # 升级中心 # from dvadmin_celery.settings import * # celery 异步任务 # ... +from dvadmin_drag_h5.settings import * # ********** 一键导入插件配置结束 ********** diff --git a/web/src/components/table-selector/table-selector.vue b/web/src/components/table-selector/table-selector.vue index 0aeda52..45833ad 100644 --- a/web/src/components/table-selector/table-selector.vue +++ b/web/src/components/table-selector/table-selector.vue @@ -201,6 +201,7 @@ export default { // this.dict = d2CrudPlus.util.dict.mergeDefault(this.dict, true) // } // this.initData() + console.log(this) }, computed: { _elProps () { @@ -327,32 +328,23 @@ export default { this.$emit('current-change', event) }, openDialog () { - if (this.disabled) { + const that = this + if (that.disabled) { return } - this.dialogVisible = true - setTimeout(() => { - if (this.selected != null) { - const ids = this.selected.map( - (item) => item[this._elProps.props.value] - ) - ids.forEach((id) => { - const current = this.$refs.elTree.store.nodesMap[id] - if (current != null) { - this.doExpandParent(current) - } - }) - this.$nextTick(() => { - if (this.multiple) { - // this.$refs.elTree.setCheckedKeys(ids, this.leafOnly); - this.$refs.elTree.setCheckboxRow(ids) - } else if (ids.length > 0) { - // this.$refs.elTree.setCurrentKey(ids[0]); - this.$refs.elTree.setRadioRow(ids[0]) - } - }) - } - }, 1) + that.dialogVisible = true + if (that.value != null) { + that.$nextTick(() => { + const refs = Object.assign({}, that.$refs) + const { elTree } = refs + console.log(elTree) + if (that.multiple) { + elTree.setCheckboxRow(that.selected, true) + } else { + elTree.setRadioRow(that.selected[0]) + } + }) + } }, doExpandParent (node) { if (node.parent != null) { @@ -412,20 +404,6 @@ export default { // 获取选中的行数据 refreshSelected () { let nodes = null - // if (this.multiple) { - // nodes = this.$refs.elTree.getCheckedNodes( - // this.leafOnly, - // this.includeHalfChecked - // ); - // } else { - // const node = this.$refs.elTree.getCurrentNode(); - // if (node == null) { - // nodes = []; - // } else { - // nodes = [node]; - // } - // } - if (this.multiple) { nodes = this.$refs.elTree.getCheckboxRecords() } else { From c016d4a903567799107cab83e41ad9640d9fcdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Mon, 13 Jun 2022 13:25:40 +0800 Subject: [PATCH 05/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E9=B2=81=E7=8F=ADh5=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E6=B3=A8=E5=86=8Csettings.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/application/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/application/settings.py b/backend/application/settings.py index d88d9af..c8ce5e9 100644 --- a/backend/application/settings.py +++ b/backend/application/settings.py @@ -378,5 +378,4 @@ PLUGINS_URL_PATTERNS = [] # from dvadmin_upgrade_center.settings import * # 升级中心 # from dvadmin_celery.settings import * # celery 异步任务 # ... -from dvadmin_drag_h5.settings import * # ********** 一键导入插件配置结束 ********** From c01634b0ed848d2d5ee7f195770b46f4169a7353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Mon, 13 Jun 2022 22:56:14 +0800 Subject: [PATCH 06/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=B7=BB=E5=8A=A0=E7=94=9F=E4=BA=A7=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E7=9A=84=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/.env.preview | 3 +-- web/.env.production | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 web/.env.production diff --git a/web/.env.preview b/web/.env.preview index 7bbf2aa..1855d20 100644 --- a/web/.env.preview +++ b/web/.env.preview @@ -1,5 +1,4 @@ -# 构建预览页面 - +# 预览环境 # 指定构建模式 NODE_ENV=production diff --git a/web/.env.production b/web/.env.production new file mode 100644 index 0000000..8b3fa56 --- /dev/null +++ b/web/.env.production @@ -0,0 +1,15 @@ +# 生产环境 +# 指定构建模式 +NODE_ENV=production + +# 标记当前构建方式 +VUE_APP_BUILD_MODE=PREVIEW +# 页面 title 前缀 +VUE_APP_TITLE=企业级后台管理系统 +# 显示源码按钮 +VUE_APP_SCOURCE_LINK=FALSE + +# 部署路径 +VUE_APP_PUBLIC_PATH=/ +# 启用权限管理 +VUE_APP_PM_ENABLED = true From 8817edd603f841786d88712f15d54971833b7b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Tue, 14 Jun 2022 00:10:10 +0800 Subject: [PATCH 07/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=BC=E5=87=BA=E6=95=B0=E6=8D=AE=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E8=AE=BE=E7=BD=AE=E5=88=97=E5=AE=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/utils/import_export_mixin.py | 39 ++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index 5a5c9f2..98f221b 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -91,6 +91,21 @@ class ExportSerializerMixin: export_field_label = [] # 导出序列化器 export_serializer_class = None + # 表格表头最大宽度,默认50个字符 + export_column_width = 50 + + def get_string_len(self, string): + """ + 获取字符串最大长度 + :param string: + :return: + """ + length = 4 + if string is None: + return length + for char in string: + length += 2.1 if ord(char) > 256 else 1 + return round(length, 1) if length <= self.export_column_width else self.export_column_width def export_data(self, request: Request, *args, **kwargs): """ @@ -109,13 +124,31 @@ class ExportSerializerMixin: response["Content-Disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' wb = Workbook() ws = wb.active + header_data = ["序号", *self.export_field_label] + df_len_max = [self.get_string_len(ele) for ele in header_data] row = get_column_letter(len(self.export_field_label) + 1) column = 1 - ws.append(["序号", *self.export_field_label]) + ws.append(header_data) for index, results in enumerate(data): - ws.append([index + 1, *list(results.values())]) + results_list = [] + for inx, result in enumerate(results.values()): + # 布尔值进行更新 + if result is True: + result = "是" + elif result is False: + result = "否" + # 计算最大列宽度 + result_column_width = self.get_string_len(result) + if result_column_width > df_len_max[inx + 1]: + df_len_max[inx + 1] = result_column_width + + results_list.append(result) + ws.append([index + 1, *results_list]) column += 1 - tab = Table(displayName="Table2", ref=f"A1:{row}{column}") # 名称管理器 + #  更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width + tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 style = TableStyleInfo( name="TableStyleLight11", showFirstColumn=True, From f768cc6deafde5dd0efd407f704f2e6efc44f9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Tue, 14 Jun 2022 22:59:23 +0800 Subject: [PATCH 08/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=A8=A1=E6=9D=BF=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/user.py | 36 +++++++++--- backend/dvadmin/utils/import_export_mixin.py | 58 +++++++++++++++++++- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index 4ef20fd..771270d 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -7,7 +7,7 @@ from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from application import dispatch -from dvadmin.system.models import Users +from dvadmin.system.models import Users, Role, Dept from dvadmin.system.views.role import RoleSerializer from dvadmin.utils.json_response import ErrorResponse, DetailResponse from dvadmin.utils.serializers import CustomModelSerializer @@ -19,7 +19,7 @@ class UserSerializer(CustomModelSerializer): """ 用户管理-序列化器 """ - dept_name = serializers.CharField(source='dept.name',read_only=True) + dept_name = serializers.CharField(source='dept.name', read_only=True) role_info = DynamicSerializerMethodField() class Meta: @@ -30,7 +30,7 @@ class UserSerializer(CustomModelSerializer): "post": {"required": False}, } - def get_role_info(self, instance,parsed_query): + def get_role_info(self, instance, parsed_query): roles = instance.role.all() # You can do what ever you want in here @@ -239,11 +239,33 @@ class UserViewSet(CustomModelViewSet): "name": "用户名称", "email": "用户邮箱", "mobile": "手机号码", - "gender": "用户性别(男/女/未知)", - "is_active": "帐号状态(启用/禁用)", + "gender": { + "title": "用户性别", + "choices": { + "data": [{"未知": 2}, {"男": 1}, {"女": 0}], + } + }, + "is_active": { + "title": "帐号状态", + "choices": { + "data": [{"启用": True}, {"禁用": False}], + } + }, "password": "登录密码", - "dept": "部门ID", - "role": "角色ID", + "dept": { + "title": "部门", + "choices": { + "queryset": Dept.objects.filter(status=True), + "values_list": "name" + } + }, + "role": { + "title": "角色", + "choices": { + "queryset": Role.objects.filter(status=True), + "values_list": "name" + } + }, } @action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated]) diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index 98f221b..1b60318 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -4,7 +4,8 @@ from urllib.parse import quote from django.db import transaction from django.http import HttpResponse from openpyxl import Workbook -from openpyxl.utils import get_column_letter +from openpyxl.worksheet.datavalidation import DataValidation +from openpyxl.utils import get_column_letter, quote_sheetname from openpyxl.worksheet.table import Table, TableStyleInfo from rest_framework.request import Request @@ -15,13 +16,28 @@ from dvadmin.utils.request_util import get_verbose_name class ImportSerializerMixin: """ - 自定义导出模板、导入功能 + 自定义导入模板、导入功能 """ # 导入字段 import_field_dict = {} # 导入序列化器 import_serializer_class = None + # 表格表头最大宽度,默认50个字符 + export_column_width = 50 + + def get_string_len(self, string): + """ + 获取字符串最大长度 + :param string: + :return: + """ + length = 4 + if string is None: + return length + for char in string: + length += 2.1 if ord(char) > 256 else 1 + return round(length, 1) if length <= self.export_column_width else self.export_column_width @transaction.atomic # Django 事务,防止出错 def import_data(self, request: Request, *args, **kwargs): @@ -44,10 +60,46 @@ class ImportSerializerMixin: "Content-Disposition" ] = f'attachment;filename={quote(str(f"导入{get_verbose_name(queryset)}模板.xlsx"))}' wb = Workbook() + ws1 = wb.create_sheet("data", 1) + ws1.sheet_state = 'hidden' ws = wb.active row = get_column_letter(len(self.import_field_dict) + 1) column = 10 - ws.append(["序号", *self.import_field_dict.values()]) + header_data = ["序号", ] + validation_data_dict = {} + for index, ele in enumerate(self.import_field_dict.values()): + if isinstance(ele, dict): + header_data.append(ele.get('title')) + choices = ele.get('choices', {}) + if choices.get('data'): + data_list = [] + for data in choices.get('data'): + data_list.extend(data.keys()) + validation_data_dict[ele.get('title')] = data_list + elif choices.get('queryset') and choices.get('values_list'): + data_list = choices.get('queryset').values_list(choices.get('values_list'), flat=True) + validation_data_dict[ele.get('title')] = list(data_list) + else: + continue + column_letter = get_column_letter(len(validation_data_dict)) + dv = DataValidation(type="list", + formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[ele.get('title')]) + 1}", + allow_blank=True) + ws.add_data_validation(dv) + dv.add(f"{get_column_letter(index + 2)}2:{get_column_letter(index + 2)}1048576") + else: + header_data.append(ele) + # 添加数据列 + ws1.append(list(validation_data_dict.keys())) + for index, validation_data in enumerate(validation_data_dict.values()): + for inx, ele in enumerate(validation_data): + ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele + # 插入导出模板正式数据 + df_len_max = [self.get_string_len(ele) for ele in header_data] + ws.append(header_data) + #  更新列宽 + for index, width in enumerate(df_len_max): + ws.column_dimensions[get_column_letter(index + 1)].width = width tab = Table(displayName="Table1", ref=f"A1:{row}{column}") # 名称管理器 style = TableStyleInfo( name="TableStyleLight11", From 5a23609dddf2ad855cf7d2f9c4bb99fe0d403445 Mon Sep 17 00:00:00 2001 From: Angelo Date: Tue, 14 Jun 2022 23:58:59 +0800 Subject: [PATCH 09/27] fix(user): fixed user export data form --- backend/dvadmin/system/views/user.py | 88 +++++++++++++--------------- web/src/views/system/user/index.vue | 7 ++- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index 771270d..d5e5c87 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -19,7 +19,8 @@ class UserSerializer(CustomModelSerializer): """ 用户管理-序列化器 """ - dept_name = serializers.CharField(source='dept.name', read_only=True) + + dept_name = serializers.CharField(source="dept.name", read_only=True) role_info = DynamicSerializerMethodField() class Meta: @@ -36,11 +37,7 @@ class UserSerializer(CustomModelSerializer): # You can do what ever you want in here # `parsed_query` param is passed to BookSerializer to allow further querying - serializer = RoleSerializer( - roles, - many=True, - parsed_query=parsed_query - ) + serializer = RoleSerializer(roles, many=True, parsed_query=parsed_query) return serializer.data @@ -51,14 +48,29 @@ class UsersInitSerializer(CustomModelSerializer): class Meta: model = Users - fields = ["username", "email", 'mobile', 'avatar', "name", 'gender', 'user_type', "dept", 'user_type', - 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'creator', 'dept_belong_id', - 'password', 'last_login', 'is_superuser'] - read_only_fields = ['id'] - extra_kwargs = { - 'creator': {'write_only': True}, - 'dept_belong_id': {'write_only': True} - } + fields = [ + "username", + "email", + "mobile", + "avatar", + "name", + "gender", + "user_type", + "dept", + "user_type", + "first_name", + "last_name", + "email", + "is_staff", + "is_active", + "creator", + "dept_belong_id", + "password", + "last_login", + "is_superuser", + ] + read_only_fields = ["id"] + extra_kwargs = {"creator": {"write_only": True}, "dept_belong_id": {"write_only": True}} class UserCreateSerializer(CustomModelSerializer): @@ -68,9 +80,7 @@ class UserCreateSerializer(CustomModelSerializer): username = serializers.CharField( max_length=50, - validators=[ - CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一") - ], + validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")], ) password = serializers.CharField( required=False, @@ -108,17 +118,13 @@ class UserUpdateSerializer(CustomModelSerializer): username = serializers.CharField( max_length=50, - validators=[ - CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一") - ], + validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")], ) # password = serializers.CharField(required=False, allow_blank=True) mobile = serializers.CharField( max_length=50, - validators=[ - CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一") - ], - allow_blank=True + validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")], + allow_blank=True, ) def save(self, **kwargs): @@ -142,13 +148,15 @@ class ExportUserProfileSerializer(CustomModelSerializer): 用户导出 序列化器 """ - last_login = serializers.DateTimeField( - format="%Y-%m-%d %H:%M:%S", required=False, read_only=True - ) - dept__deptName = serializers.CharField(source="dept.deptName", default="") + last_login = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True) + is_active = serializers.SerializerMethodField(read_only=True) + dept__deptName = serializers.CharField(source="dept.name", default="") dept__owner = serializers.CharField(source="dept.owner", default="") gender = serializers.CharField(source="get_gender_display", read_only=True) + def get_is_active(self, instance): + return "启用" if instance.is_active else "停用" + class Meta: model = Users fields = ( @@ -167,9 +175,7 @@ class ExportUserProfileSerializer(CustomModelSerializer): class UserProfileImportSerializer(CustomModelSerializer): def save(self, **kwargs): data = super().save(**kwargs) - password = hashlib.new( - "md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8") - ).hexdigest() + password = hashlib.new("md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")).hexdigest() data.set_password(password) data.save() return data @@ -243,29 +249,17 @@ class UserViewSet(CustomModelViewSet): "title": "用户性别", "choices": { "data": [{"未知": 2}, {"男": 1}, {"女": 0}], - } + }, }, "is_active": { "title": "帐号状态", "choices": { "data": [{"启用": True}, {"禁用": False}], - } + }, }, "password": "登录密码", - "dept": { - "title": "部门", - "choices": { - "queryset": Dept.objects.filter(status=True), - "values_list": "name" - } - }, - "role": { - "title": "角色", - "choices": { - "queryset": Role.objects.filter(status=True), - "values_list": "name" - } - }, + "dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_list": "name"}}, + "role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_list": "name"}}, } @action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated]) diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue index ce9e5b7..a7447c5 100644 --- a/web/src/views/system/user/index.vue +++ b/web/src/views/system/user/index.vue @@ -133,8 +133,11 @@ export default { return crudOptions(this) }, pageRequest (query) { - const columnKeys = util.filterParams(this,['dept_name','role_info{name}']) - const params = { query: columnKeys,...query } + const columnKeys = util.filterParams(this, [ + 'dept_name', + 'role_info{name}' + ]) + const params = { query: columnKeys, ...query } return api.GetList(params) }, addRequest (row) { From c7103f89705bcec6c98a3a5e890933ed13cc4ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Wed, 15 Jun 2022 00:45:27 +0800 Subject: [PATCH 10/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/user.py | 89 ++++++++------------ backend/dvadmin/utils/import_export.py | 32 +++++-- backend/dvadmin/utils/import_export_mixin.py | 14 +-- 3 files changed, 72 insertions(+), 63 deletions(-) diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index d5e5c87..a3d8a38 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -19,8 +19,7 @@ class UserSerializer(CustomModelSerializer): """ 用户管理-序列化器 """ - - dept_name = serializers.CharField(source="dept.name", read_only=True) + dept_name = serializers.CharField(source='dept.name', read_only=True) role_info = DynamicSerializerMethodField() class Meta: @@ -37,7 +36,11 @@ class UserSerializer(CustomModelSerializer): # You can do what ever you want in here # `parsed_query` param is passed to BookSerializer to allow further querying - serializer = RoleSerializer(roles, many=True, parsed_query=parsed_query) + serializer = RoleSerializer( + roles, + many=True, + parsed_query=parsed_query + ) return serializer.data @@ -48,29 +51,14 @@ class UsersInitSerializer(CustomModelSerializer): class Meta: model = Users - fields = [ - "username", - "email", - "mobile", - "avatar", - "name", - "gender", - "user_type", - "dept", - "user_type", - "first_name", - "last_name", - "email", - "is_staff", - "is_active", - "creator", - "dept_belong_id", - "password", - "last_login", - "is_superuser", - ] - read_only_fields = ["id"] - extra_kwargs = {"creator": {"write_only": True}, "dept_belong_id": {"write_only": True}} + fields = ["username", "email", 'mobile', 'avatar', "name", 'gender', 'user_type', "dept", 'user_type', + 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'creator', 'dept_belong_id', + 'password', 'last_login', 'is_superuser'] + read_only_fields = ['id'] + extra_kwargs = { + 'creator': {'write_only': True}, + 'dept_belong_id': {'write_only': True} + } class UserCreateSerializer(CustomModelSerializer): @@ -80,7 +68,9 @@ class UserCreateSerializer(CustomModelSerializer): username = serializers.CharField( max_length=50, - validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")], + validators=[ + CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一") + ], ) password = serializers.CharField( required=False, @@ -118,13 +108,17 @@ class UserUpdateSerializer(CustomModelSerializer): username = serializers.CharField( max_length=50, - validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")], + validators=[ + CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一") + ], ) # password = serializers.CharField(required=False, allow_blank=True) mobile = serializers.CharField( max_length=50, - validators=[CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")], - allow_blank=True, + validators=[ + CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一") + ], + allow_blank=True ) def save(self, **kwargs): @@ -148,15 +142,13 @@ class ExportUserProfileSerializer(CustomModelSerializer): 用户导出 序列化器 """ - last_login = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True) - is_active = serializers.SerializerMethodField(read_only=True) - dept__deptName = serializers.CharField(source="dept.name", default="") + last_login = serializers.DateTimeField( + format="%Y-%m-%d %H:%M:%S", required=False, read_only=True + ) + dept__deptName = serializers.CharField(source="dept.deptName", default="") dept__owner = serializers.CharField(source="dept.owner", default="") gender = serializers.CharField(source="get_gender_display", read_only=True) - def get_is_active(self, instance): - return "启用" if instance.is_active else "停用" - class Meta: model = Users fields = ( @@ -175,20 +167,13 @@ class ExportUserProfileSerializer(CustomModelSerializer): class UserProfileImportSerializer(CustomModelSerializer): def save(self, **kwargs): data = super().save(**kwargs) - password = hashlib.new("md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")).hexdigest() + password = hashlib.new( + "md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8") + ).hexdigest() data.set_password(password) data.save() return data - def run_validation(self, data={}): - # 把excel 数据进行格式转换 - if type(data) is dict: - data["role"] = str(data["role"]).split(",") - data["dept_id"] = str(data["dept"]).split(",") - data["gender"] = {"男": "1", "女": "0", "未知": "2"}.get(data["gender"]) - data["is_active"] = {"启用": True, "禁用": False}.get(data["is_active"]) - return super().run_validation(data) - class Meta: model = Users exclude = ( @@ -248,18 +233,18 @@ class UserViewSet(CustomModelViewSet): "gender": { "title": "用户性别", "choices": { - "data": [{"未知": 2}, {"男": 1}, {"女": 0}], - }, + "data": {"未知": 2, "男": 1, "女": 0}, + } }, "is_active": { "title": "帐号状态", "choices": { - "data": [{"启用": True}, {"禁用": False}], - }, + "data": {"启用": True, "禁用": False}, + } }, "password": "登录密码", - "dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_list": "name"}}, - "role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_list": "name"}}, + "dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}}, + "role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}}, } @action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated]) diff --git a/backend/dvadmin/utils/import_export.py b/backend/dvadmin/utils/import_export.py index 7b9a68b..e71a4b2 100644 --- a/backend/dvadmin/utils/import_export.py +++ b/backend/dvadmin/utils/import_export.py @@ -5,32 +5,52 @@ import openpyxl from django.conf import settings -def import_to_data(file_url, field_data): +def import_to_data(file_url, field_data, m2m_fields=None): """ 读取导入的excel文件 - :param request: + :param file_url: :param field_data: 首行数据源 - :param data: 数据源 - :param FilName: 文件名 + :param m2m_fields: 多对多字段 :return: """ # 读取excel 文件 file_path_dir = os.path.join(settings.BASE_DIR, file_url) workbook = openpyxl.load_workbook(file_path_dir) table = workbook[workbook.sheetnames[0]] + # 获取参数映射 + validation_data_dict = {} + for key, value in field_data.items(): + if isinstance(value, dict): + choices = value.get('choices', {}) + data_dict = {} + if choices.get('data'): + for k, v in choices.get('data').items(): + data_dict[k] = v + elif choices.get('queryset') and choices.get('values_name'): + data_list = choices.get('queryset').values(choices.get('values_name'), 'id') + for ele in data_list: + data_dict[ele.get(choices.get('values_name'))] = ele.get('id') + else: + continue + validation_data_dict[key] = data_dict # 创建一个空列表,存储Excel的数据 tables = [] for i, row in enumerate(range(table.max_row)): if i == 0: continue array = {} - for index, ele in enumerate(field_data.keys()): + for index, key in enumerate(field_data.keys()): cell_value = table.cell(row=row + 1, column=index + 2).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 str: cell_value = cell_value.strip(" \t\n\r") - array[ele] = cell_value + if key in validation_data_dict: + array[key] = validation_data_dict.get(key, {}).get(cell_value, None) + if key in m2m_fields: + array[key] = [array[key]] + else: + array[key] = 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 1b60318..5a6d49d 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -73,11 +73,10 @@ class ImportSerializerMixin: choices = ele.get('choices', {}) if choices.get('data'): data_list = [] - for data in choices.get('data'): - data_list.extend(data.keys()) + data_list.extend(choices.get('data').keys()) validation_data_dict[ele.get('title')] = data_list - elif choices.get('queryset') and choices.get('values_list'): - data_list = choices.get('queryset').values_list(choices.get('values_list'), flat=True) + elif choices.get('queryset') and choices.get('values_name'): + data_list = choices.get('queryset').values_list(choices.get('values_name'), flat=True) validation_data_dict[ele.get('title')] = list(data_list) else: continue @@ -115,8 +114,13 @@ class ImportSerializerMixin: updateSupport = request.data.get("updateSupport") # 从excel中组织对应的数据结构,然后使用序列化器保存 - data = import_to_data(request.data.get("url"), self.import_field_dict) queryset = self.filter_queryset(self.get_queryset()) + # 获取多对多字段 + m2m_fields = [ + ele.attname for ele in queryset.model._meta.get_fields() if + hasattr(ele, "many_to_many") and ele.many_to_many == True + ] + data = import_to_data(request.data.get("url"), self.import_field_dict, m2m_fields) unique_list = [ ele.attname for ele in queryset.model._meta.get_fields() if hasattr(ele, "unique") and ele.unique == True ] From f19948cfe172252bfbbd3023e7f122008185a04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Wed, 15 Jun 2022 00:55:20 +0800 Subject: [PATCH 11/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E5=AF=BC=E5=87=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/system/views/user.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/dvadmin/system/views/user.py b/backend/dvadmin/system/views/user.py index a3d8a38..0830984 100644 --- a/backend/dvadmin/system/views/user.py +++ b/backend/dvadmin/system/views/user.py @@ -145,10 +145,14 @@ class ExportUserProfileSerializer(CustomModelSerializer): last_login = serializers.DateTimeField( format="%Y-%m-%d %H:%M:%S", required=False, read_only=True ) - dept__deptName = serializers.CharField(source="dept.deptName", default="") - dept__owner = serializers.CharField(source="dept.owner", default="") + is_active = serializers.SerializerMethodField(read_only=True) + dept_name = serializers.CharField(source="dept.name", default="") + dept_owner = serializers.CharField(source="dept.owner", default="") gender = serializers.CharField(source="get_gender_display", read_only=True) + def get_is_active(self, instance): + return "启用" if instance.is_active else "停用" + class Meta: model = Users fields = ( @@ -159,8 +163,8 @@ class ExportUserProfileSerializer(CustomModelSerializer): "gender", "is_active", "last_login", - "dept__deptName", - "dept__owner", + "dept_name", + "dept_owner", ) From ee43a2773c1af006544aa5f832328235cba61050 Mon Sep 17 00:00:00 2001 From: Angelo Date: Wed, 15 Jun 2022 08:29:21 +0800 Subject: [PATCH 12/27] feat(import): added support to manytomany field --- backend/dvadmin/utils/import_export.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/backend/dvadmin/utils/import_export.py b/backend/dvadmin/utils/import_export.py index e71a4b2..84fd5ed 100644 --- a/backend/dvadmin/utils/import_export.py +++ b/backend/dvadmin/utils/import_export.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import os +import re import openpyxl from django.conf import settings @@ -21,15 +22,15 @@ def import_to_data(file_url, field_data, m2m_fields=None): validation_data_dict = {} for key, value in field_data.items(): if isinstance(value, dict): - choices = value.get('choices', {}) + choices = value.get("choices", {}) data_dict = {} - if choices.get('data'): - for k, v in choices.get('data').items(): + if choices.get("data"): + for k, v in choices.get("data").items(): data_dict[k] = v - elif choices.get('queryset') and choices.get('values_name'): - data_list = choices.get('queryset').values(choices.get('values_name'), 'id') + elif choices.get("queryset") and choices.get("values_name"): + data_list = choices.get("queryset").values(choices.get("values_name"), "id") for ele in data_list: - data_dict[ele.get(choices.get('values_name'))] = ele.get('id') + data_dict[ele.get(choices.get("values_name"))] = ele.get("id") else: continue validation_data_dict[key] = data_dict @@ -49,7 +50,15 @@ def import_to_data(file_url, field_data, m2m_fields=None): if key in validation_data_dict: array[key] = validation_data_dict.get(key, {}).get(cell_value, None) if key in m2m_fields: - array[key] = [array[key]] + array[key] = list( + filter( + lambda x: x, + [ + validation_data_dict.get(key, {}).get(value, None) + for value in re.split(r"[,;:|.,;:\s]\s*", cell_value) + ], + ) + ) else: array[key] = cell_value tables.append(array) From afa5864717eea8c9d159b6fd417b7c8bdfab2e44 Mon Sep 17 00:00:00 2001 From: Angelo Date: Wed, 15 Jun 2022 09:21:30 +0800 Subject: [PATCH 13/27] feat(user): added batch delete button for user management --- web/src/views/system/user/api.js | 8 ++++++++ web/src/views/system/user/crud.js | 7 ++++++- web/src/views/system/user/index.vue | 18 +++++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/web/src/views/system/user/api.js b/web/src/views/system/user/api.js index 5743af2..3b67ea9 100644 --- a/web/src/views/system/user/api.js +++ b/web/src/views/system/user/api.js @@ -33,6 +33,14 @@ export function DelObj (id) { }) } +export function BatchDel (keys) { + return request({ + url: urlPrefix + 'multiple_delete/', + method: 'delete', + data: { keys } + }) +} + /** * 重置密码 * @param obj diff --git a/web/src/views/system/user/crud.js b/web/src/views/system/user/crud.js index 19c7167..f61aab6 100644 --- a/web/src/views/system/user/crud.js +++ b/web/src/views/system/user/crud.js @@ -9,7 +9,12 @@ export const crudOptions = (vm) => { options: { height: '100%', tableType: 'vxe-table', - rowKey: true // 必须设置,true or false + rowKey: true, + rowId: 'id' + }, + selectionRow: { + align: 'center', + width: 46 }, rowHandle: { width: 240, diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue index a7447c5..af78319 100644 --- a/web/src/views/system/user/index.vue +++ b/web/src/views/system/user/index.vue @@ -21,9 +21,12 @@ > 新增 + + 批量删除 + 导出 @@ -42,6 +45,16 @@ @columns-filter-changed="handleColumnsFilterChanged" /> + + + Date: Wed, 15 Jun 2022 21:57:52 +0800 Subject: [PATCH 14/27] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dvadmin/utils/import_export.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/dvadmin/utils/import_export.py b/backend/dvadmin/utils/import_export.py index 84fd5ed..882ff4c 100644 --- a/backend/dvadmin/utils/import_export.py +++ b/backend/dvadmin/utils/import_export.py @@ -47,6 +47,8 @@ def import_to_data(file_url, field_data, m2m_fields=None): cell_value = int(str(cell_value).split(".")[0]) if type(cell_value) is str: cell_value = cell_value.strip(" \t\n\r") + if cell_value is None: + continue if key in validation_data_dict: array[key] = validation_data_dict.get(key, {}).get(cell_value, None) if key in m2m_fields: From 7e91f07b497398b81bc2469aeacfa77bfdae105b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Thu, 16 Jun 2022 09:02:31 +0800 Subject: [PATCH 15/27] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=EF=BC=8C=E5=8F=AF=E6=A0=B9=E6=8D=AE=E5=89=8D?= =?UTF-8?q?=E7=AB=AFSearch=E8=BF=9B=E8=A1=8C=E8=BF=87=E6=BB=A4=E5=AF=BC?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/api/service.js | 5 +++-- web/src/views/system/user/index.vue | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src/api/service.js b/web/src/api/service.js index 4262a4c..7470667 100644 --- a/web/src/api/service.js +++ b/web/src/api/service.js @@ -213,13 +213,14 @@ const refreshTken = function () { * 下载文件 * @param url * @param params + * @param method * @param filename */ -export const downloadFile = function ({ url, data, method, filename }) { +export const downloadFile = function ({ url, params, method, filename }) { request({ url: url, method: method, - data: data, + params: params, responseType: 'blob' // headers: {Accept: 'application/vnd.openxmlformats-officedocument'} }).then(res => { diff --git a/web/src/views/system/user/index.vue b/web/src/views/system/user/index.vue index af78319..f897164 100644 --- a/web/src/views/system/user/index.vue +++ b/web/src/views/system/user/index.vue @@ -166,12 +166,14 @@ export default { return api.BatchDel(ids) }, onExport () { + const that = this this.$confirm('是否确认导出所有数据项?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(function () { - return api.exportData() + const query = that.getSearch().getForm() + return api.exportData({ ...query }) }) }, // 重置密码弹框 From 78e95f764a82fb461a3f92d39b99d2f99a3fe529 Mon Sep 17 00:00:00 2001 From: Angelo Date: Fri, 17 Jun 2022 09:24:28 +0800 Subject: [PATCH 16/27] fix(export): convert int type to str type when export data --- backend/dvadmin/utils/import_export_mixin.py | 37 ++++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index 5a6d49d..1a35e34 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -61,29 +61,33 @@ class ImportSerializerMixin: ] = f'attachment;filename={quote(str(f"导入{get_verbose_name(queryset)}模板.xlsx"))}' wb = Workbook() ws1 = wb.create_sheet("data", 1) - ws1.sheet_state = 'hidden' + ws1.sheet_state = "hidden" ws = wb.active row = get_column_letter(len(self.import_field_dict) + 1) column = 10 - header_data = ["序号", ] + header_data = [ + "序号", + ] validation_data_dict = {} for index, ele in enumerate(self.import_field_dict.values()): if isinstance(ele, dict): - header_data.append(ele.get('title')) - choices = ele.get('choices', {}) - if choices.get('data'): + header_data.append(ele.get("title")) + choices = ele.get("choices", {}) + if choices.get("data"): data_list = [] - data_list.extend(choices.get('data').keys()) - validation_data_dict[ele.get('title')] = data_list - elif choices.get('queryset') and choices.get('values_name'): - data_list = choices.get('queryset').values_list(choices.get('values_name'), flat=True) - validation_data_dict[ele.get('title')] = list(data_list) + data_list.extend(choices.get("data").keys()) + validation_data_dict[ele.get("title")] = data_list + elif choices.get("queryset") and choices.get("values_name"): + data_list = choices.get("queryset").values_list(choices.get("values_name"), flat=True) + validation_data_dict[ele.get("title")] = list(data_list) else: continue column_letter = get_column_letter(len(validation_data_dict)) - dv = DataValidation(type="list", - formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[ele.get('title')]) + 1}", - allow_blank=True) + dv = DataValidation( + type="list", + formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[ele.get('title')]) + 1}", + allow_blank=True, + ) ws.add_data_validation(dv) dv.add(f"{get_column_letter(index + 2)}2:{get_column_letter(index + 2)}1048576") else: @@ -117,8 +121,9 @@ class ImportSerializerMixin: queryset = self.filter_queryset(self.get_queryset()) # 获取多对多字段 m2m_fields = [ - ele.attname for ele in queryset.model._meta.get_fields() if - hasattr(ele, "many_to_many") and ele.many_to_many == True + ele.attname + for ele in queryset.model._meta.get_fields() + if hasattr(ele, "many_to_many") and ele.many_to_many == True ] data = import_to_data(request.data.get("url"), self.import_field_dict, m2m_fields) unique_list = [ @@ -193,6 +198,8 @@ class ExportSerializerMixin: result = "是" elif result is False: result = "否" + if isinstance(result, int): + result = str(result) # 计算最大列宽度 result_column_width = self.get_string_len(result) if result_column_width > df_len_max[inx + 1]: From 92ca593a3b255b9d26f7e0a038908fd14adeaa4e Mon Sep 17 00:00:00 2001 From: Angelo Date: Fri, 17 Jun 2022 09:52:25 +0800 Subject: [PATCH 17/27] fix(menuButtom): form.name should use dict.label not dict.value --- web/src/views/system/menuButton/crud.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/src/views/system/menuButton/crud.js b/web/src/views/system/menuButton/crud.js index e102e0b..4bff9d4 100644 --- a/web/src/views/system/menuButton/crud.js +++ b/web/src/views/system/menuButton/crud.js @@ -104,12 +104,13 @@ export const crudOptions = (vm) => { }, valueChange (key, value, form, { getColumn, mode, component, immediate, getComponent }) { if (value != null) { - console.log('component.dictOptions', component.dictOptions) + // console.log('component.dictOptions', component.dictOptions) const obj = component.dictOptions.find(item => { - console.log(item.label, value) + // console.log(item.label, value) return item.value === value }) if (obj && obj.value) { + form.name = obj.label form.value = obj.value } } From dc939118c910366bb92be17a0dcfa4855bae301b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Tue, 21 Jun 2022 08:53:23 +0800 Subject: [PATCH 18/27] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=AF=B9?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=8F=82=E6=95=B0=E8=BF=9B=E8=A1=8C=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/libs/util.params.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/libs/util.params.js b/web/src/libs/util.params.js index 72632a6..14d176b 100644 --- a/web/src/libs/util.params.js +++ b/web/src/libs/util.params.js @@ -8,7 +8,8 @@ const filterParams = function (that, array) { const columnKeys = arr.map(item => { return item.key }) - const newArray = [...columnKeys, array, 'id'] + let newArray = [...columnKeys, array, 'id'] + newArray = [...new Set(newArray)] return '{' + newArray.toString() + '}' } From 890e81cd06c1bbd4a99d8cdf8e8ab4a63eabe969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 24 Jun 2022 17:03:44 +0800 Subject: [PATCH 19/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8DBUG:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复一对多,多对多组件的bug --- web/src/components/many-to-many/index.vue | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/web/src/components/many-to-many/index.vue b/web/src/components/many-to-many/index.vue index c6bd225..3157a00 100644 --- a/web/src/components/many-to-many/index.vue +++ b/web/src/components/many-to-many/index.vue @@ -11,6 +11,10 @@ export default { props: { color: { require: false + }, + value: { + type: String, + required: false } }, data () { @@ -19,6 +23,19 @@ export default { key: 'name' } }, + watch:{ + value(nv,ov){ + const { row } = this.$parent.scope + const { children } = this.$parent + if (children) { + const valueBinding = this.$parent.valueBinding + this.setValue(row[valueBinding]) + this.key = children + } else { + this.setValue([]) + } + } + }, created () { const { row } = this.$parent.scope const { children } = this.$parent From 25b32ffd12c45a02e740d5532e00ba691984efc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8C=BF=E5=B0=8F=E5=A4=A9?= <1638245306@qq.com> Date: Fri, 24 Jun 2022 17:07:19 +0800 Subject: [PATCH 20/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8DBUG:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复一对多,多对多组件的bug --- backend/dvadmin/system/models.py | 19 +++++++++++++++++++ web/src/components/foreign-key/index.vue | 11 +++++++++++ web/src/components/many-to-many/index.vue | 9 ++++++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py index 6dec1b1..6b5750f 100644 --- a/backend/dvadmin/system/models.py +++ b/backend/dvadmin/system/models.py @@ -410,3 +410,22 @@ class LoginLog(CoreModel): verbose_name = "登录日志" verbose_name_plural = verbose_name ordering = ("-create_datetime",) + + +class MessageCenter(CoreModel): + title = models.CharField(max_length=100,verbose_name="标题",help_text="标题") + content = models.TextField(verbose_name="内容",help_text="内容") + target_type=models.IntegerField(default=0,verbose_name="目标类型",help_text="目标类型") + target_user = models.ForeignKey(to=Users,related_name="target_user",null=True,blank=True,db_constraint=False,on_delete=models.CASCADE,verbose_name="目标用户",help_text="目标用户") + target_dept = models.ForeignKey(to=Dept, null=True, blank=True, db_constraint=False, on_delete=models.CASCADE, + verbose_name="目标部门", help_text="目标部门") + target_role = models.ForeignKey(to=Role, null=True, blank=True, db_constraint=False, on_delete=models.CASCADE, + verbose_name="目标角色", help_text="目标角色") + is_read=models.BooleanField(default=False,blank=True,verbose_name="是否已读",help_text="是否已读") + + class Meta: + db_table = table_prefix + "message_center" + verbose_name = "消息中心" + verbose_name_plural = verbose_name + ordering = ("-create_datetime",) + diff --git a/web/src/components/foreign-key/index.vue b/web/src/components/foreign-key/index.vue index 720eaa5..78465ab 100644 --- a/web/src/components/foreign-key/index.vue +++ b/web/src/components/foreign-key/index.vue @@ -9,6 +9,10 @@ export default { props: { color: { require: false + }, + value: { + type: String, + required: false } }, data () { @@ -16,6 +20,13 @@ export default { currentValue: '' } }, + watch:{ + value(nv,ov){ + const { row } = this.$parent.scope + const valueBinding = this.$parent.valueBinding + this.setValue(row[valueBinding]) + } + }, created () { const { row } = this.$parent.scope const valueBinding = this.$parent.valueBinding diff --git a/web/src/components/many-to-many/index.vue b/web/src/components/many-to-many/index.vue index 3157a00..c1add8c 100644 --- a/web/src/components/many-to-many/index.vue +++ b/web/src/components/many-to-many/index.vue @@ -1,6 +1,9 @@