Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -98,4 +98,5 @@ media/
|
|||||||
__pypackages__/
|
__pypackages__/
|
||||||
package-lock.json
|
package-lock.json
|
||||||
gunicorn.pid
|
gunicorn.pid
|
||||||
|
plugins/*
|
||||||
!plugins/__init__.py
|
!plugins/__init__.py
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
from django.core.cache import cache
|
||||||
|
from dvadmin.utils.validator import CustomValidationError
|
||||||
|
|
||||||
|
dispatch_db_type = getattr(settings, 'DISPATCH_DB_TYPE', 'memory') # redis
|
||||||
|
|
||||||
|
|
||||||
def is_tenants_mode():
|
def is_tenants_mode():
|
||||||
@@ -68,6 +72,9 @@ def init_dictionary():
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
cache.set(f"init_dictionary", _get_all_dictionary())
|
||||||
|
return
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
from django_tenants.utils import tenant_context, get_tenant_model
|
from django_tenants.utils import tenant_context, get_tenant_model
|
||||||
|
|
||||||
@@ -88,7 +95,9 @@ def init_system_config():
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
cache.set(f"init_system_config", _get_all_system_config())
|
||||||
|
return
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
from django_tenants.utils import tenant_context, get_tenant_model
|
from django_tenants.utils import tenant_context, get_tenant_model
|
||||||
|
|
||||||
@@ -107,6 +116,9 @@ def refresh_dictionary():
|
|||||||
刷新字典配置
|
刷新字典配置
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
cache.set(f"init_dictionary", _get_all_dictionary())
|
||||||
|
return
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
from django_tenants.utils import tenant_context, get_tenant_model
|
from django_tenants.utils import tenant_context, get_tenant_model
|
||||||
|
|
||||||
@@ -122,6 +134,9 @@ def refresh_system_config():
|
|||||||
刷新系统配置
|
刷新系统配置
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
cache.set(f"init_system_config", _get_all_system_config())
|
||||||
|
return
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
from django_tenants.utils import tenant_context, get_tenant_model
|
from django_tenants.utils import tenant_context, get_tenant_model
|
||||||
|
|
||||||
@@ -141,6 +156,11 @@ def get_dictionary_config(schema_name=None):
|
|||||||
:param schema_name: 对应字典配置的租户schema_name值
|
:param schema_name: 对应字典配置的租户schema_name值
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
init_dictionary_data = cache.get(f"init_dictionary")
|
||||||
|
if not init_dictionary_data:
|
||||||
|
refresh_dictionary()
|
||||||
|
return cache.get(f"init_dictionary") or {}
|
||||||
if not settings.DICTIONARY_CONFIG:
|
if not settings.DICTIONARY_CONFIG:
|
||||||
refresh_dictionary()
|
refresh_dictionary()
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
@@ -157,6 +177,12 @@ def get_dictionary_values(key, schema_name=None):
|
|||||||
:param schema_name: 对应字典配置的租户schema_name值
|
:param schema_name: 对应字典配置的租户schema_name值
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
dictionary_config = cache.get(f"init_dictionary")
|
||||||
|
if not dictionary_config:
|
||||||
|
refresh_dictionary()
|
||||||
|
dictionary_config = cache.get(f"init_dictionary")
|
||||||
|
return dictionary_config.get(key)
|
||||||
dictionary_config = get_dictionary_config(schema_name)
|
dictionary_config = get_dictionary_config(schema_name)
|
||||||
return dictionary_config.get(key)
|
return dictionary_config.get(key)
|
||||||
|
|
||||||
@@ -169,8 +195,8 @@ def get_dictionary_label(key, name, schema_name=None):
|
|||||||
:param schema_name: 对应字典配置的租户schema_name值
|
:param schema_name: 对应字典配置的租户schema_name值
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
children = get_dictionary_values(key, schema_name) or []
|
res = get_dictionary_values(key, schema_name) or []
|
||||||
for ele in children:
|
for ele in res.get('children'):
|
||||||
if ele.get("value") == str(name):
|
if ele.get("value") == str(name):
|
||||||
return ele.get("label")
|
return ele.get("label")
|
||||||
return ""
|
return ""
|
||||||
@@ -187,6 +213,11 @@ def get_system_config(schema_name=None):
|
|||||||
:param schema_name: 对应字典配置的租户schema_name值
|
:param schema_name: 对应字典配置的租户schema_name值
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
init_dictionary_data = cache.get(f"init_system_config")
|
||||||
|
if not init_dictionary_data:
|
||||||
|
refresh_system_config()
|
||||||
|
return cache.get(f"init_system_config") or {}
|
||||||
if not settings.SYSTEM_CONFIG:
|
if not settings.SYSTEM_CONFIG:
|
||||||
refresh_system_config()
|
refresh_system_config()
|
||||||
if is_tenants_mode():
|
if is_tenants_mode():
|
||||||
@@ -203,10 +234,32 @@ def get_system_config_values(key, schema_name=None):
|
|||||||
:param schema_name: 对应系统配置的租户schema_name值
|
:param schema_name: 对应系统配置的租户schema_name值
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
if dispatch_db_type == 'redis':
|
||||||
|
system_config = cache.get(f"init_system_config")
|
||||||
|
if not system_config:
|
||||||
|
refresh_system_config()
|
||||||
|
system_config = cache.get(f"init_system_config")
|
||||||
|
return system_config.get(key)
|
||||||
system_config = get_system_config(schema_name)
|
system_config = get_system_config(schema_name)
|
||||||
return system_config.get(key)
|
return system_config.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_config_values_to_dict(key, schema_name=None):
|
||||||
|
"""
|
||||||
|
获取系统配置数据并转换为字典 **仅限于数组类型系统配置
|
||||||
|
:param key: 对应系统配置的key值(字典编号)
|
||||||
|
:param schema_name: 对应系统配置的租户schema_name值
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
values_dict = {}
|
||||||
|
config_values = get_system_config_values(key, schema_name)
|
||||||
|
if not isinstance(config_values, list):
|
||||||
|
raise CustomValidationError("该方式仅限于数组类型系统配置")
|
||||||
|
for ele in get_system_config_values(key, schema_name):
|
||||||
|
values_dict[ele.get('key')] = ele.get('value')
|
||||||
|
return values_dict
|
||||||
|
|
||||||
|
|
||||||
def get_system_config_label(key, name, schema_name=None):
|
def get_system_config_label(key, name, schema_name=None):
|
||||||
"""
|
"""
|
||||||
获取获取系统配置label值
|
获取获取系统配置label值
|
||||||
|
|||||||
@@ -672,6 +672,53 @@
|
|||||||
"model": "ApiWhiteList"
|
"model": "ApiWhiteList"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "下载中心",
|
||||||
|
"icon": "ele-Download",
|
||||||
|
"sort": 9,
|
||||||
|
"is_link": false,
|
||||||
|
"is_catalog": false,
|
||||||
|
"web_path": "/downloadCenter",
|
||||||
|
"component": "system/downloadCenter/index",
|
||||||
|
"component_name": "downloadCenter",
|
||||||
|
"status": true,
|
||||||
|
"cache": false,
|
||||||
|
"visible": true,
|
||||||
|
"parent": 277,
|
||||||
|
"children": [],
|
||||||
|
"menu_button": [
|
||||||
|
{
|
||||||
|
"name": "查询",
|
||||||
|
"value": "Search",
|
||||||
|
"api": "/api/system/downloadCenter/",
|
||||||
|
"method": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "详情",
|
||||||
|
"value": "Retrieve",
|
||||||
|
"api": "/api/system/downloadCenter/{id}/",
|
||||||
|
"method": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "新增",
|
||||||
|
"value": "Create",
|
||||||
|
"api": "/api/system/downloadCenter/",
|
||||||
|
"method": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "编辑",
|
||||||
|
"value": "Update",
|
||||||
|
"api": "/api/system/downloadCenter/{id}/",
|
||||||
|
"method": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "删除",
|
||||||
|
"value": "Delete",
|
||||||
|
"api": "/api/system/downloadCenter/{id}/",
|
||||||
|
"method": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"menu_button": [],
|
"menu_button": [],
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
from time import time
|
||||||
|
from pathlib import PurePosixPath
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser, UserManager
|
from django.contrib.auth.models import AbstractUser, UserManager
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -596,3 +598,41 @@ class MessageCenterTargetUser(CoreModel):
|
|||||||
db_table = table_prefix + "message_center_target_user"
|
db_table = table_prefix + "message_center_target_user"
|
||||||
verbose_name = "消息中心目标用户表"
|
verbose_name = "消息中心目标用户表"
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
def media_file_name_downloadcenter(instance:'DownloadCenter', filename):
|
||||||
|
h = instance.md5sum
|
||||||
|
basename, ext = os.path.splitext(filename)
|
||||||
|
return PurePosixPath("files", "dlct", h[:1], h[1:2], basename + '-' + str(time()).replace('.', '') + ext.lower())
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCenter(CoreModel):
|
||||||
|
TASK_STATUS_CHOICES = [
|
||||||
|
(0, '任务已创建'),
|
||||||
|
(1, '任务进行中'),
|
||||||
|
(2, '任务完成'),
|
||||||
|
(3, '任务失败'),
|
||||||
|
]
|
||||||
|
task_name = models.CharField(max_length=255, verbose_name="任务名称", help_text="任务名称")
|
||||||
|
task_status = models.SmallIntegerField(default=0, choices=TASK_STATUS_CHOICES, verbose_name='是否可下载', help_text='是否可下载')
|
||||||
|
file_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="文件名", help_text="文件名")
|
||||||
|
url = models.FileField(upload_to=media_file_name_downloadcenter, null=True, blank=True)
|
||||||
|
size = models.BigIntegerField(default=0, verbose_name="文件大小", help_text="文件大小")
|
||||||
|
md5sum = models.CharField(max_length=36, null=True, blank=True, verbose_name="文件md5", help_text="文件md5")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.url:
|
||||||
|
if not self.md5sum: # file is new
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
for chunk in self.url.chunks():
|
||||||
|
md5.update(chunk)
|
||||||
|
self.md5sum = md5.hexdigest()
|
||||||
|
if not self.size:
|
||||||
|
self.size = self.url.size
|
||||||
|
super(DownloadCenter, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = table_prefix + "download_center"
|
||||||
|
verbose_name = "下载中心"
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
ordering = ("-create_datetime",)
|
||||||
|
|||||||
107
backend/dvadmin/system/tasks.py
Normal file
107
backend/dvadmin/system/tasks.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
from hashlib import md5
|
||||||
|
from io import BytesIO
|
||||||
|
from datetime import datetime
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.worksheet.table import Table, TableStyleInfo
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
|
from application.celery import app
|
||||||
|
from dvadmin.system.models import DownloadCenter
|
||||||
|
|
||||||
|
def is_number(num):
|
||||||
|
try:
|
||||||
|
float(num)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import unicodedata
|
||||||
|
unicodedata.numeric(num)
|
||||||
|
return True
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_string_len(string):
|
||||||
|
"""
|
||||||
|
获取字符串最大长度
|
||||||
|
:param string:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
length = 4
|
||||||
|
if string is None:
|
||||||
|
return length
|
||||||
|
if is_number(string):
|
||||||
|
return length
|
||||||
|
for char in string:
|
||||||
|
length += 2.1 if ord(char) > 256 else 1
|
||||||
|
return round(length, 1) if length <= 50 else 50
|
||||||
|
|
||||||
|
@app.task
|
||||||
|
def async_export_data(data: list, filename: str, dcid: int, export_field_label: dict):
|
||||||
|
instance = DownloadCenter.objects.get(pk=dcid)
|
||||||
|
instance.task_status = 1
|
||||||
|
instance.save()
|
||||||
|
sleep(2)
|
||||||
|
try:
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
header_data = ["序号", *export_field_label.values()]
|
||||||
|
hidden_header = ["#", *export_field_label.keys()]
|
||||||
|
df_len_max = [get_string_len(ele) for ele in header_data]
|
||||||
|
row = get_column_letter(len(export_field_label) + 1)
|
||||||
|
column = 1
|
||||||
|
ws.append(header_data)
|
||||||
|
for index, results in enumerate(data):
|
||||||
|
results_list = []
|
||||||
|
for h_index, h_item in enumerate(hidden_header):
|
||||||
|
for key, val in results.items():
|
||||||
|
if key == h_item:
|
||||||
|
if val is None or val == "":
|
||||||
|
results_list.append("")
|
||||||
|
elif isinstance(val, datetime):
|
||||||
|
val = val.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
results_list.append(val)
|
||||||
|
else:
|
||||||
|
results_list.append(val)
|
||||||
|
# 计算最大列宽度
|
||||||
|
result_column_width = get_string_len(val)
|
||||||
|
if h_index != 0 and result_column_width > df_len_max[h_index]:
|
||||||
|
df_len_max[h_index] = result_column_width
|
||||||
|
ws.append([index + 1, *results_list])
|
||||||
|
column += 1
|
||||||
|
# 更新列宽
|
||||||
|
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,
|
||||||
|
showLastColumn=True,
|
||||||
|
showRowStripes=True,
|
||||||
|
showColumnStripes=True,
|
||||||
|
)
|
||||||
|
tab.tableStyleInfo = style
|
||||||
|
ws.add_table(tab)
|
||||||
|
stream = BytesIO()
|
||||||
|
wb.save(stream)
|
||||||
|
stream.seek(0)
|
||||||
|
s = md5()
|
||||||
|
while True:
|
||||||
|
chunk = stream.read(1024)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
s.update(chunk)
|
||||||
|
stream.seek(0)
|
||||||
|
instance.md5sum = s.hexdigest()
|
||||||
|
instance.file_name = filename
|
||||||
|
instance.url.save(filename, ContentFile(stream.read()))
|
||||||
|
instance.task_status = 2
|
||||||
|
except Exception as e:
|
||||||
|
instance.task_status = 3
|
||||||
|
instance.description = str(e)[:250]
|
||||||
|
instance.save()
|
||||||
@@ -18,6 +18,7 @@ from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermi
|
|||||||
from dvadmin.system.views.system_config import SystemConfigViewSet
|
from dvadmin.system.views.system_config import SystemConfigViewSet
|
||||||
from dvadmin.system.views.user import UserViewSet
|
from dvadmin.system.views.user import UserViewSet
|
||||||
from dvadmin.system.views.menu_field import MenuFieldViewSet
|
from dvadmin.system.views.menu_field import MenuFieldViewSet
|
||||||
|
from dvadmin.system.views.download_center import DownloadCenterViewSet
|
||||||
|
|
||||||
system_url = routers.SimpleRouter()
|
system_url = routers.SimpleRouter()
|
||||||
system_url.register(r'menu', MenuViewSet)
|
system_url.register(r'menu', MenuViewSet)
|
||||||
@@ -36,6 +37,7 @@ system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionView
|
|||||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||||
system_url.register(r'column', MenuFieldViewSet)
|
system_url.register(r'column', MenuFieldViewSet)
|
||||||
system_url.register(r'login_log', LoginLogViewSet)
|
system_url.register(r'login_log', LoginLogViewSet)
|
||||||
|
system_url.register(r'download_center', DownloadCenterViewSet)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|||||||
46
backend/dvadmin/system/views/download_center.py
Normal file
46
backend/dvadmin/system/views/download_center.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from django.conf import settings
|
||||||
|
from django_filters.rest_framework import FilterSet, CharFilter
|
||||||
|
|
||||||
|
from dvadmin.utils.serializers import CustomModelSerializer
|
||||||
|
from dvadmin.utils.viewset import CustomModelViewSet
|
||||||
|
from dvadmin.system.models import DownloadCenter
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCenterSerializer(CustomModelSerializer):
|
||||||
|
url = serializers.SerializerMethodField(read_only=True)
|
||||||
|
|
||||||
|
def get_url(self, instance):
|
||||||
|
if self.request.query_params.get('prefix'):
|
||||||
|
if settings.ENVIRONMENT in ['local']:
|
||||||
|
prefix = 'http://127.0.0.1:8000'
|
||||||
|
elif settings.ENVIRONMENT in ['test']:
|
||||||
|
prefix = 'http://{host}/api'.format(host=self.request.get_host())
|
||||||
|
else:
|
||||||
|
prefix = 'https://{host}/api'.format(host=self.request.get_host())
|
||||||
|
return (f'{prefix}/media/{str(instance.url)}')
|
||||||
|
return f'media/{str(instance.url)}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DownloadCenter
|
||||||
|
fields = "__all__"
|
||||||
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCenterFilterSet(FilterSet):
|
||||||
|
task_name = CharFilter(field_name='task_name', lookup_expr='icontains')
|
||||||
|
file_name = CharFilter(field_name='file_name', lookup_expr='icontains')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DownloadCenter
|
||||||
|
fields = ['task_status', 'task_name', 'file_name']
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCenterViewSet(CustomModelViewSet):
|
||||||
|
queryset = DownloadCenter.objects.all()
|
||||||
|
serializer_class = DownloadCenterSerializer
|
||||||
|
filter_class = DownloadCenterFilterSet
|
||||||
|
permission_classes = []
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(creator=self.request.user)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@@ -11,8 +12,10 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from dvadmin.utils.import_export import import_to_data
|
from dvadmin.utils.import_export import import_to_data
|
||||||
from dvadmin.utils.json_response import DetailResponse
|
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
|
||||||
from dvadmin.utils.request_util import get_verbose_name
|
from dvadmin.utils.request_util import get_verbose_name
|
||||||
|
from dvadmin.system.tasks import async_export_data
|
||||||
|
from dvadmin.system.models import DownloadCenter
|
||||||
|
|
||||||
|
|
||||||
class ImportSerializerMixin:
|
class ImportSerializerMixin:
|
||||||
@@ -301,6 +304,17 @@ class ExportSerializerMixin:
|
|||||||
assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
|
assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
|
||||||
assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__
|
assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__
|
||||||
data = self.export_serializer_class(queryset, many=True, request=request).data
|
data = self.export_serializer_class(queryset, many=True, request=request).data
|
||||||
|
try:
|
||||||
|
from dvadmin3_celery import settings
|
||||||
|
async_export_data.delay(
|
||||||
|
data,
|
||||||
|
str(f"导出{get_verbose_name(queryset)}-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"),
|
||||||
|
DownloadCenter.objects.create(creator=request.user, task_name=f'{get_verbose_name(queryset)}数据导出任务').pk,
|
||||||
|
self.export_field_label
|
||||||
|
)
|
||||||
|
return SuccessResponse(msg="导入任务已创建,请前往‘下载中心’等待下载")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
# 导出excel 表
|
# 导出excel 表
|
||||||
response = HttpResponse(content_type="application/msexcel")
|
response = HttpResponse(content_type="application/msexcel")
|
||||||
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
||||||
|
|||||||
@@ -28,5 +28,4 @@ uvicorn==0.30.3
|
|||||||
gunicorn==22.0.0
|
gunicorn==22.0.0
|
||||||
gevent==24.2.1
|
gevent==24.2.1
|
||||||
Pillow==10.4.0
|
Pillow==10.4.0
|
||||||
dvadmin-celery==1.0.5
|
|
||||||
pyinstaller==6.9.0
|
pyinstaller==6.9.0
|
||||||
@@ -10,6 +10,7 @@ import { errorLog, errorCreate } from './tools.ts';
|
|||||||
import { Local, Session } from '/@/utils/storage';
|
import { Local, Session } from '/@/utils/storage';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { getBaseURL } from './baseUrl';
|
import { getBaseURL } from './baseUrl';
|
||||||
|
import { successMessage } from './message.js';
|
||||||
/**
|
/**
|
||||||
* @description 创建请求实例
|
* @description 创建请求实例
|
||||||
*/
|
*/
|
||||||
@@ -204,6 +205,8 @@ export const requestForMock = createRequestFunction(serviceForMock);
|
|||||||
* @param filename
|
* @param filename
|
||||||
*/
|
*/
|
||||||
export const downloadFile = function ({ url, params, method, filename = '文件导出' }: any) {
|
export const downloadFile = function ({ url, params, method, filename = '文件导出' }: any) {
|
||||||
|
// return request({ url: url, method: method, params: params })
|
||||||
|
// .then((res: any) => successMessage(res.msg));
|
||||||
request({
|
request({
|
||||||
url: url,
|
url: url,
|
||||||
method: method,
|
method: method,
|
||||||
@@ -211,6 +214,8 @@ export const downloadFile = function ({ url, params, method, filename = '文件
|
|||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
// headers: {Accept: 'application/vnd.openxmlformats-officedocument'}
|
// headers: {Accept: 'application/vnd.openxmlformats-officedocument'}
|
||||||
}).then((res: any) => {
|
}).then((res: any) => {
|
||||||
|
// console.log(res.headers['content-type']); // 根据content-type不同来判断是否异步下载
|
||||||
|
if (res.headers['content-type'] === 'application/json') return successMessage('导入任务已创建,请前往‘下载中心’等待下载');
|
||||||
const xlsxName = window.decodeURI(res.headers['content-disposition'].split('=')[1])
|
const xlsxName = window.decodeURI(res.headers['content-disposition'].split('=')[1])
|
||||||
const fileName = xlsxName || `${filename}.xlsx`
|
const fileName = xlsxName || `${filename}.xlsx`
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|||||||
49
web/src/views/system/downloadCenter/api.ts
Normal file
49
web/src/views/system/downloadCenter/api.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { request } from '/@/utils/service';
|
||||||
|
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
|
export const apiPrefix = '/api/system/download_center/';
|
||||||
|
|
||||||
|
export function GetPermission() {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + 'field_permission/',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetList(query: PageQuery) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix,
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function GetObj(id: InfoReq) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + id,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddObj(obj: AddReq) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix,
|
||||||
|
method: 'post',
|
||||||
|
data: obj,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdateObj(obj: EditReq) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + obj.id + '/',
|
||||||
|
method: 'put',
|
||||||
|
data: obj,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DelObj(id: DelReq) {
|
||||||
|
return request({
|
||||||
|
url: apiPrefix + id + '/',
|
||||||
|
method: 'delete',
|
||||||
|
data: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
160
web/src/views/system/downloadCenter/crud.tsx
Normal file
160
web/src/views/system/downloadCenter/crud.tsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { CrudOptions, AddReq, DelReq, EditReq, dict, CrudExpose, compute } from '@fast-crud/fast-crud';
|
||||||
|
import * as api from './api';
|
||||||
|
import { dictionary } from '/@/utils/dictionary';
|
||||||
|
import { successMessage } from '../../../utils/message';
|
||||||
|
import { auth } from '/@/utils/authFunction'
|
||||||
|
|
||||||
|
interface CreateCrudOptionsTypes {
|
||||||
|
output: any;
|
||||||
|
crudOptions: CrudOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
//此处为crudOptions配置
|
||||||
|
export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExpose; }): CreateCrudOptionsTypes {
|
||||||
|
const pageRequest = async (query: any) => {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
//权限判定
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// @ts-ignore
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: {
|
||||||
|
pageRequest,
|
||||||
|
addRequest,
|
||||||
|
editRequest,
|
||||||
|
delRequest,
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
actionbar: {
|
||||||
|
buttons: {
|
||||||
|
add: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
buttons: {
|
||||||
|
export: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowHandle: {
|
||||||
|
//固定右侧
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120,
|
||||||
|
buttons: {
|
||||||
|
view: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
show: compute(ctx => ctx.row.task_status === 2),
|
||||||
|
text: '下载文件',
|
||||||
|
type: 'warning',
|
||||||
|
click: (ctx) => window.open(ctx.row.url, '_blank')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
col: { span: 24 },
|
||||||
|
labelWidth: '100px',
|
||||||
|
wrapper: {
|
||||||
|
is: 'el-dialog',
|
||||||
|
width: '600px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
_index: {
|
||||||
|
title: '序号',
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
type: 'index',
|
||||||
|
align: 'center',
|
||||||
|
width: '70px',
|
||||||
|
columnSetDisabled: true, //禁止在列设置中选择
|
||||||
|
},
|
||||||
|
},
|
||||||
|
task_name: {
|
||||||
|
title: '任务名',
|
||||||
|
type: 'text',
|
||||||
|
column: {
|
||||||
|
minWidth: 160,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
file_name: {
|
||||||
|
title: '文件名',
|
||||||
|
type: 'text',
|
||||||
|
column: {
|
||||||
|
minWidth: 160,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
title: '文件大小(b)',
|
||||||
|
type: 'number',
|
||||||
|
column: {
|
||||||
|
width: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
task_status: {
|
||||||
|
title: '任务状态',
|
||||||
|
type: 'dict-select',
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: '任务已创建', value: 0 },
|
||||||
|
{ label: '任务进行中', value: 1 },
|
||||||
|
{ label: '任务完成', value: 2 },
|
||||||
|
{ label: '任务失败', value: 3 },
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
column: {
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
create_datetime: {
|
||||||
|
title: '创建时间',
|
||||||
|
column: {
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update_datetime: {
|
||||||
|
title: '创建时间',
|
||||||
|
column: {
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
42
web/src/views/system/downloadCenter/index.vue
Normal file
42
web/src/views/system/downloadCenter/index.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<fs-page>
|
||||||
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #cell_url="scope">
|
||||||
|
<el-tag size="small">{{ scope.row.url }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
|
</fs-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="downloadCenter">
|
||||||
|
import { ref, onMounted, inject, onBeforeUpdate } from 'vue';
|
||||||
|
|
||||||
|
import { GetPermission } from './api';
|
||||||
|
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||||
|
import { createCrudOptions } from './crud';
|
||||||
|
import PermissionComNew from './components/PermissionComNew/index.vue';
|
||||||
|
import _ from "lodash-es";
|
||||||
|
import { handleColumnPermission } from "/@/utils/columnPermission";
|
||||||
|
|
||||||
|
// crud组件的ref
|
||||||
|
const crudRef = ref();
|
||||||
|
// crud 配置的ref
|
||||||
|
const crudBinding = ref();
|
||||||
|
|
||||||
|
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||||
|
|
||||||
|
// 你的crud配置
|
||||||
|
const { crudOptions } = createCrudOptions({ crudExpose });
|
||||||
|
|
||||||
|
// 初始化crud配置
|
||||||
|
const { resetCrudOptions } = useCrud({
|
||||||
|
crudExpose,
|
||||||
|
crudOptions,
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面打开后获取列表数据
|
||||||
|
onMounted(async () => {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user