Merge remote-tracking branch 'origin/dev' into dev

pull/102/MERGE
猿小天 2023-08-11 20:47:56 +08:00
commit 22879cb170
23 changed files with 275 additions and 116 deletions

View File

@ -2,4 +2,5 @@
# python manage.py makemigrations # python manage.py makemigrations
# python manage.py migrate # python manage.py migrate
# python manage.py init -y # python manage.py init -y
gunicorn -c gunicorn_conf.py application.asgi:application #gunicorn -c gunicorn_conf.py application.asgi:application
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 4

View File

@ -318,29 +318,29 @@ class FileList(CoreModel):
# 保存到File model中 # 保存到File model中
instance = FileList() instance = FileList()
instance.name = file_name instance.name = file_name
instance.engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' instance.engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
instance.file_url = os.path.join(file_path, file_name) instance.file_url = os.path.join(file_path, file_name)
instance.mime_type = mime_type instance.mime_type = mime_type
instance.creator = request.user instance.creator = request.user
instance.modifier = request.user.id instance.modifier = request.user.id
instance.dept_belong_id = request.user.dept_id instance.dept_belong_id = request.user.dept_id
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup") file_backup = dispatch.get_system_config_values("file_storage.file_backup")
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
if file_backup: if file_backup:
instance.url = os.path.join(file_path.replace('media/', ''), file_name) instance.url = os.path.join(file_path.replace('media/', ''), file_name)
if file_engine == 'oss': if file_engine == 'oss':
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
file = File(open(os.path.join(BASE_DIR, file_path, file_name))) with open(os.path.join(BASE_DIR, file_path, file_name), 'rb') as file:
file_path = ali_oss_upload(file) file_path = ali_oss_upload(file, file_name=os.path.join(file_path.replace('media/', ''), file_name))
if file_path: if file_path:
instance.file_url = file_path instance.file_url = file_path
else: else:
raise ValueError("上传失败") raise ValueError("上传失败")
elif file_engine == 'cos': elif file_engine == 'cos':
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
file = File(open(os.path.join(BASE_DIR, file_path, file_name))) with open(os.path.join(BASE_DIR, file_path, file_name), 'rb') as file:
file_path = tencent_cos_upload(file) file_path = tencent_cos_upload(file, file_name=os.path.join(file_path.replace('media/', ''), file_name))
if file_path: if file_path:
instance.file_url = file_path instance.file_url = file_path
else: else:

View File

@ -215,38 +215,39 @@ class DataVViewSet(GenericViewSet):
CHINA_PROVINCES = [ CHINA_PROVINCES = [
{'name': '北京', 'code': '110000'}, {'name': '北京', 'code': '110000'},
{'name': '天津', 'code': '120000'}, {'name': '天津', 'code': '120000'},
{'name': '河北', 'code': '130000'}, {'name': '河北', 'code': '130000'},
{'name': '山西', 'code': '140000'}, {'name': '山西', 'code': '140000'},
{'name': '内蒙古', 'code': '150000'}, {'name': '内蒙古', 'code': '150000'},
{'name': '辽宁', 'code': '210000'}, {'name': '辽宁', 'code': '210000'},
{'name': '吉林', 'code': '220000'}, {'name': '吉林', 'code': '220000'},
{'name': '黑龙江', 'code': '230000'}, {'name': '黑龙江', 'code': '230000'},
{'name': '上海', 'code': '310000'}, {'name': '上海', 'code': '310000'},
{'name': '江苏', 'code': '320000'}, {'name': '江苏', 'code': '320000'},
{'name': '浙江', 'code': '330000'}, {'name': '浙江', 'code': '330000'},
{'name': '安徽', 'code': '340000'}, {'name': '安徽', 'code': '340000'},
{'name': '福建', 'code': '350000'}, {'name': '福建', 'code': '350000'},
{'name': '江西', 'code': '360000'}, {'name': '江西', 'code': '360000'},
{'name': '山东', 'code': '370000'}, {'name': '山东', 'code': '370000'},
{'name': '河南', 'code': '410000'}, {'name': '河南', 'code': '410000'},
{'name': '湖北', 'code': '420000'}, {'name': '湖北', 'code': '420000'},
{'name': '湖南', 'code': '430000'}, {'name': '湖南', 'code': '430000'},
{'name': '广东', 'code': '440000'}, {'name': '广东', 'code': '440000'},
{'name': '广西', 'code': '450000'}, {'name': '广西', 'code': '450000'},
{'name': '海南', 'code': '460000'}, {'name': '海南', 'code': '460000'},
{'name': '重庆', 'code': '500000'}, {'name': '重庆', 'code': '500000'},
{'name': '四川', 'code': '510000'}, {'name': '四川', 'code': '510000'},
{'name': '贵州', 'code': '520000'}, {'name': '贵州', 'code': '520000'},
{'name': '云南', 'code': '530000'}, {'name': '云南', 'code': '530000'},
{'name': '西藏', 'code': '540000'}, {'name': '西藏', 'code': '540000'},
{'name': '陕西', 'code': '610000'}, {'name': '陕西', 'code': '610000'},
{'name': '甘肃', 'code': '620000'}, {'name': '甘肃', 'code': '620000'},
{'name': '青海', 'code': '630000'}, {'name': '青海', 'code': '630000'},
{'name': '宁夏', 'code': '640000'}, {'name': '宁夏', 'code': '640000'},
{'name': '新疆', 'code': '650000'}, {'name': '新疆', 'code': '650000'},
{'name': '台湾', 'code': '710000'}, {'name': '台湾', 'code': '710000'},
{'name': '香港', 'code': '810000'}, {'name': '香港', 'code': '810000'},
{'name': '澳门', 'code': '820000'}, {'name': '澳门', 'code': '820000'},
{'name': '钓鱼岛', 'code': '900000'},
{'name': '未知区域', 'code': '000000'}, {'name': '未知区域', 'code': '000000'},
] ]
provinces = [x['name'] for x in CHINA_PROVINCES] provinces = [x['name'] for x in CHINA_PROVINCES]

View File

@ -1,17 +1,20 @@
import base64
import datetime import datetime
import hashlib import hashlib
import json import json
import os import os
import random import random
from pathlib import PurePosixPath
from django.http import HttpResponse from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action from rest_framework.decorators import action
from application.settings import BASE_DIR from application.settings import BASE_DIR
from application import dispatch from application import dispatch, settings
from dvadmin.system.models import FileList from dvadmin.system.models import FileList, media_file_name
from dvadmin.system.views.ueditor_settings import ueditor_upload_settings, ueditor_settings from dvadmin.system.views.ueditor_settings import ueditor_upload_settings, ueditor_settings
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.string_util import format_bytes from dvadmin.utils.string_util import format_bytes
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -21,6 +24,16 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True) url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance): 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())
if instance.file_url:
return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}"
return (f'{prefix}/media/{str(instance.url)}')
return instance.file_url or (f'media/{str(instance.url)}') return instance.file_url or (f'media/{str(instance.url)}')
class Meta: class Meta:
@ -28,8 +41,8 @@ class FileSerializer(CustomModelSerializer):
fields = "__all__" fields = "__all__"
def create(self, validated_data): def create(self, validated_data):
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup") file_backup = dispatch.get_system_config_values("file_storage.file_backup")
file = self.initial_data.get('file') file = self.initial_data.get('file')
file_size = file.size file_size = file.size
validated_data['name'] = file.name validated_data['name'] = file.name
@ -44,14 +57,18 @@ class FileSerializer(CustomModelSerializer):
validated_data['url'] = file validated_data['url'] = file
if file_engine == 'oss': if file_engine == 'oss':
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
file_path = ali_oss_upload(file) h = validated_data['md5sum']
basename, ext = os.path.splitext(file.name)
file_path = ali_oss_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower()))
if file_path: if file_path:
validated_data['file_url'] = file_path validated_data['file_url'] = file_path
else: else:
raise ValueError("上传失败") raise ValueError("上传失败")
elif file_engine == 'cos': elif file_engine == 'cos':
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
file_path = tencent_cos_upload(file) h = validated_data['md5sum']
basename, ext = os.path.splitext(file.name)
file_path = tencent_cos_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower()))
if file_path: if file_path:
validated_data['file_url'] = file_path validated_data['file_url'] = file_path
else: else:
@ -83,6 +100,12 @@ class FileViewSet(CustomModelViewSet):
filter_fields = ['name', ] filter_fields = ['name', ]
permission_classes = [] permission_classes = []
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return DetailResponse(data=serializer.data, msg="新增成功")
@csrf_exempt @csrf_exempt
@action(methods=["GET", "POST"], detail=False, permission_classes=[]) @action(methods=["GET", "POST"], detail=False, permission_classes=[])
def ueditor(self, request): def ueditor(self, request):
@ -138,16 +161,17 @@ class FileViewSet(CustomModelViewSet):
# 涂鸦功能上传处理 # 涂鸦功能上传处理
def save_scrawl_file(self, request, file_path, file_name): def save_scrawl_file(self, request, file_path, file_name):
import base64 import base64
instance = None
try: try:
content = request.data.get(ueditor_upload_settings.get("scrawlFieldName", "upfile")) content = request.data.get(ueditor_upload_settings.get("scrawlFieldName", "upfile"))
f = open(os.path.join(BASE_DIR, file_path, file_name), 'wb') f = open(os.path.join(BASE_DIR, file_path, file_name), 'wb')
f.write(base64.b64decode(content)) f.write(base64.b64decode(content))
f.close() f.close()
state = "SUCCESS" state = "SUCCESS"
FileList.save_file(request, file_path, file_name,mime_type='image/png') instance = FileList.save_file(request, file_path, file_name, mime_type='image/png')
except Exception as e: except Exception as e:
state = f"写入图片文件错误:{e}" state = f"写入图片文件错误:{e}"
return state return state, instance
def upload_file(self, request): def upload_file(self, request):
"""上传文件""" """上传文件"""
@ -213,20 +237,21 @@ class FileViewSet(CustomModelViewSet):
# 取得输出文件的路径 # 取得输出文件的路径
format_file_name, output_path = self.get_output_path(path_format_var) format_file_name, output_path = self.get_output_path(path_format_var)
# 所有检测完成后写入文件 # 所有检测完成后写入文件
file_instance = None
if state == "SUCCESS": if state == "SUCCESS":
if action == "uploadscrawl": if action == "uploadscrawl":
state = self.save_scrawl_file(request, file_path=output_path, state, file_instance = self.save_scrawl_file(request, file_path=output_path,
file_name=format_file_name) file_name=format_file_name)
else: else:
file = request.FILES.get(upload_field_name, None) file = request.FILES.get(upload_field_name, None)
# 保存到文件中如果保存错误需要返回ERROR # 保存到文件中如果保存错误需要返回ERROR
state = self.save_upload_file(file, os.path.join(BASE_DIR, output_path, format_file_name)) state = self.save_upload_file(file, os.path.join(BASE_DIR, output_path, format_file_name))
# 保存到附件管理中 # 保存到附件管理中
FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type) file_instance = FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type)
# 返回数据 # 返回数据
return_info = { return_info = {
'url': os.path.join(output_path, format_file_name), # 保存后的文件名称 'url': file_instance.file_url if file_instance else os.path.join(output_path, format_file_name), # 保存后的文件名称
'original': upload_file_name, # 原始文件名 'original': upload_file_name, # 原始文件名
'type': upload_original_ext, 'type': upload_original_ext,
'state': state, # 上传状态成功时返回SUCCESS,其他任何值将原样返回至图片上传框中 'state': state, # 上传状态成功时返回SUCCESS,其他任何值将原样返回至图片上传框中

View File

@ -249,16 +249,16 @@ class UserViewSet(CustomModelViewSet):
serializer_class = UserSerializer serializer_class = UserSerializer
create_serializer_class = UserCreateSerializer create_serializer_class = UserCreateSerializer
update_serializer_class = UserUpdateSerializer update_serializer_class = UserUpdateSerializer
# filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"] filter_fields = ["^name", "~username", "^mobile", "is_active", "dept", "user_type", "$dept__name"]
filter_fields = { # filter_fields = {
"name": ["icontains"], # "name": ["icontains"],
"mobile": ["icontains"], # "mobile": ["iregex"],
"username": ["icontains"], # "username": ["icontains"],
"gender": ["icontains"], # "is_active": ["icontains"],
"is_active": ["icontains"], # "dept": ["exact"],
"dept": ["exact"], # "user_type": ["exact"],
"user_type": ["exact"], # "dept__name": ["icontains"],
} # }
search_fields = ["username", "name", "gender", "dept__name", "role__name"] search_fields = ["username", "name", "gender", "dept__name", "role__name"]
# 导出 # 导出
export_field_label = { export_field_label = {

View File

@ -150,6 +150,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
"$": "iregex", "$": "iregex",
"~": "icontains", "~": "icontains",
} }
filter_fields = "__all__"
def construct_search(self, field_name, lookup_expr=None): def construct_search(self, field_name, lookup_expr=None):
lookup = self.lookup_prefixes.get(field_name[0]) lookup = self.lookup_prefixes.get(field_name[0])
@ -157,14 +158,16 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
field_name = field_name[1:] field_name = field_name[1:]
else: else:
lookup = lookup_expr lookup = lookup_expr
if lookup:
if field_name.endswith(lookup): if field_name.endswith(lookup):
return field_name return field_name
return LOOKUP_SEP.join([field_name, lookup]) return LOOKUP_SEP.join([field_name, lookup])
return field_name
def find_filter_lookups(self, orm_lookups, search_term_key): def find_filter_lookups(self, orm_lookups, search_term_key):
for lookup in orm_lookups: for lookup in orm_lookups:
# if lookup.find(search_term_key) >= 0: # if lookup.find(search_term_key) >= 0:
new_lookup = lookup.split("__")[0] new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup
# 修复条件搜索错误 bug # 修复条件搜索错误 bug
if new_lookup == search_term_key: if new_lookup == search_term_key:
return lookup return lookup
@ -189,7 +192,13 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
utils.deprecate( utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__ "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
) )
filterset_fields = getattr(view, "filter_fields", None) self.filter_fields = getattr(view, "filter_fields", None)
if isinstance(self.filter_fields, (list, tuple)):
filterset_fields = [
field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields
]
else:
filterset_fields = self.filter_fields
if filterset_class: if filterset_class:
filterset_model = filterset_class._meta.model filterset_model = filterset_class._meta.model
@ -327,18 +336,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return queryset return queryset
if filterset.__class__.__name__ == "AutoFilterSet": if filterset.__class__.__name__ == "AutoFilterSet":
queryset = filterset.queryset queryset = filterset.queryset
orm_lookups = [] filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields
for search_field in filterset.filters: orm_lookup_dict = dict(
if isinstance(filterset.filters[search_field], CharFilter): zip(
orm_lookups.append( [field for field in filter_fields],
self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr) [filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()],
) )
else: )
orm_lookups.append(search_field) orm_lookups = [
self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items()
]
# print(orm_lookups)
conditions = [] conditions = []
queries = [] queries = []
for search_term_key in filterset.data.keys(): for search_term_key in filterset.data.keys():
orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key) orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key)
# print(search_term_key, orm_lookup)
if not orm_lookup: if not orm_lookup:
continue continue
query = Q(**{orm_lookup: filterset.data[search_term_key]}) query = Q(**{orm_lookup: filterset.data[search_term_key]})

View File

@ -118,7 +118,6 @@ class SoftDeleteModel(models.Model):
] ]
relations["self"] = f"{tree_model_field[0]}_id" if len(tree_model_field) == 1 else None relations["self"] = f"{tree_model_field[0]}_id" if len(tree_model_field) == 1 else None
relations["foreign"] = related_fields relations["foreign"] = related_fields
print(f"{relations=}", flush=True)
return relations return relations
def _is_cascade(self, relation): def _is_cascade(self, relation):

View File

@ -3,7 +3,7 @@ certifi==2021.5.30
chardet==4.0.0 chardet==4.0.0
coreapi==2.3.3 coreapi==2.3.3
coreschema==0.0.4 coreschema==0.0.4
Django==3.2.12 Django==3.2.19
django-comment-migrate==0.1.7 django-comment-migrate==0.1.7
django-cors-headers==3.10.1 django-cors-headers==3.10.1
django-filter==22.1 django-filter==22.1
@ -11,8 +11,8 @@ django-ranged-response==0.2.0
django-redis==5.2.0 django-redis==5.2.0
django-restql==0.15.3 django-restql==0.15.3
django-simple-captcha==0.5.17 django-simple-captcha==0.5.17
django-tenants==3.4.8 django-tenants==3.5.0
django-timezone-field==4.2.3 django-timezone-field==5.0
djangorestframework==3.14.0 djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2 djangorestframework-simplejwt==5.2.2
packaging==23.0 packaging==23.0
@ -29,7 +29,7 @@ pyparsing==2.4.7
pyPEG2==2.15.2 pyPEG2==2.15.2
pypinyin==0.48.0 pypinyin==0.48.0
pytz==2021.1 pytz==2021.1
requests==2.28.2 requests==2.28.0
ruamel.yaml==0.17.10 ruamel.yaml==0.17.10
ruamel.yaml.clib==0.2.4 ruamel.yaml.clib==0.2.4
six==1.16.0 six==1.16.0

View File

@ -6,4 +6,6 @@ VUE_APP_TITLE=企业级后台管理系统
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 后端接口地址及端口(域名) # 后端接口地址及端口(域名)
VUE_APP_API = "http://127.0.0.1:8000" VUE_APP_API = "http://127.0.0.1:8000"
VUE_APP_VERSION = '2.0.4'
# 文件存储引擎
VUE_APP_FILE_ENGINE = 'local' # oss、cos、local

View File

@ -13,3 +13,5 @@ VUE_APP_SCOURCE_LINK=FALSE
VUE_APP_PUBLIC_PATH=/ VUE_APP_PUBLIC_PATH=/
# 启用权限管理 # 启用权限管理
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 文件存储引擎
VUE_APP_FILE_ENGINE = 'local' # oss、cos、local

View File

@ -13,3 +13,5 @@ VUE_APP_SCOURCE_LINK=FALSE
VUE_APP_PUBLIC_PATH=/ VUE_APP_PUBLIC_PATH=/
# 启用权限管理 # 启用权限管理
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 文件存储引擎
VUE_APP_FILE_ENGINE = 'local' # oss、cos、local

View File

@ -56,6 +56,9 @@
<div style="text-align: center" v-if="_elProps.listType === 'avatar'"> <div style="text-align: center" v-if="_elProps.listType === 'avatar'">
<img style="max-width: 100%;" :src="dialogImageUrl" alt=""> <img style="max-width: 100%;" :src="dialogImageUrl" alt="">
</div> </div>
<div style="text-align: center" v-else-if="_elProps.listType === 'picture-card'">
<img style="max-width: 100%;" :src="dialogImageUrl" alt="">
</div>
<div style="text-align: center" v-else> <div style="text-align: center" v-else>
<div id="player" v-if="dialogVisible"> <div id="player" v-if="dialogVisible">
<div class="player-container"> <div class="player-container">

View File

@ -15,7 +15,7 @@
<el-row style="margin-bottom: 0" :gutter="5" v-for="(field, index) in currentForm.data" :key="index"> <el-row style="margin-bottom: 0" :gutter="5" v-for="(field, index) in currentForm.data" :key="index">
<el-col :span="elProps.index.span" v-if="elProps.index"> <el-col :span="elProps.index.span" v-if="elProps.index">
<div style="text-align: center"> <div style="text-align: center">
{{index+1}} {{ index + 1 }}
</div> </div>
</el-col> </el-col>
<el-col :span="elProps.fields[key].span" v-for="(_,key) in elProps.fields" :key="key"> <el-col :span="elProps.fields[key].span" v-for="(_,key) in elProps.fields" :key="key">
@ -34,20 +34,23 @@
:value="item.value"> :value="item.value">
</el-option> </el-option>
</el-select> </el-select>
<el-input-number style="width: 100%" v-else-if="elProps.fields[key].type === 'number'" controls-position="right" v-model="field[key]"></el-input-number> <el-input-number style="width: 100%" v-else-if="elProps.fields[key].type === 'number'"
<div v-else-if="elProps.fields[key].type === 'image'" style="height: 30px;width: 30px;"> controls-position="right" v-model="field[key]"></el-input-number>
<d2p-file-uploader v-model="field[key]" :elProps="elProps.fields[key].elProps || { listType: 'picture-card', accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif', limit: 1 }"></d2p-file-uploader> <div class="d2p-images" v-else-if="elProps.fields[key].type === 'image'">
<d2p-file-uploader v-model="field[key]"
:elProps="elProps.fields[key].elProps || { listType: 'picture-card', accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif', limit: 1 }"></d2p-file-uploader>
</div> </div>
<!-- 富文本 --> <!-- 富文本 -->
<span v-else-if="elProps.fields[key].type === 'ueditor'"> <span v-else-if="elProps.fields[key].type === 'ueditor'">
<values-popover v-model="field[key]" :elProps="{ type: 'ueditor' }" @previewClick="previewClick(index,key)"></values-popover> <values-popover v-model="field[key]" :elProps="{ type: 'ueditor' }"
@previewClick="previewClick(index,key)"></values-popover>
</span> </span>
<!-- 多对多 --> <!-- 多对多 -->
<span v-else-if="elProps.fields[key].type === 'many_to_many'"> <span v-else-if="elProps.fields[key].type === 'many_to_many'">
<values-popover <values-popover
v-model="field[key]" v-model="field[key]"
:dict="elProps.fields[key].dict" :dict="elProps.fields[key].dict"
:elProps="{ type: elProps.fields[key].value?.type || 'strList', rowKey: elProps.fields[key].value?.rowKey || 'title', label: elProps.value?.title || '答复选项内容' }" :elProps="{ type: elProps.fields[key].value?.type || 'manyToMany', rowKey: elProps.fields[key].value?.rowKey || 'title', label: elProps.value?.title || '答复选项内容' }"
@listClick="manyToManyClick(index,key)"> @listClick="manyToManyClick(index,key)">
</values-popover> </values-popover>
</span> </span>
@ -56,8 +59,10 @@
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<el-form-item> <el-form-item>
<el-button @click.prevent="topDomain(index)" :disabled="index === 0" type="primary" circle icon="el-icon-top"></el-button> <el-button @click.prevent="topDomain(index)" :disabled="index === 0" type="primary" circle
<el-button @click.prevent="bottomDomain(index)" :disabled="index === currentForm.data.length - 1" type="primary" circle icon="el-icon-bottom"></el-button> icon="el-icon-top"></el-button>
<el-button @click.prevent="bottomDomain(index)" :disabled="index === currentForm.data.length - 1"
type="primary" circle icon="el-icon-bottom"></el-button>
<el-button @click.prevent="removeDomain(index)" type="danger" circle icon="el-icon-delete"></el-button> <el-button @click.prevent="removeDomain(index)" type="danger" circle icon="el-icon-delete"></el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -102,6 +107,7 @@
</template> </template>
<script> <script>
import util from '@/libs/util.js' import util from '@/libs/util.js'
export default { export default {
name: 'foreignKeyCrudForm', name: 'foreignKeyCrudForm',
model: { model: {
@ -213,21 +219,21 @@ export default {
ueditorConfig: { ueditorConfig: {
serverUrl: util.baseURL() + 'api/system/file/ueditor/', serverUrl: util.baseURL() + 'api/system/file/ueditor/',
headers: { Authorization: 'JWT ' + util.cookies.get('token') }, headers: { Authorization: 'JWT ' + util.cookies.get('token') },
imageUrlPrefix: util.baseURL(), imageUrlPrefix: util.baseFileURL(),
// //
scrawlUrlPrefix: util.baseURL(), scrawlUrlPrefix: util.baseFileURL(),
// //
snapscreenUrlPrefix: util.baseURL(), snapscreenUrlPrefix: util.baseFileURL(),
// //
catcherUrlPrefix: util.baseURL(), catcherUrlPrefix: util.baseFileURL(),
// 访 // 访
videoUrlPrefix: util.baseURL(), videoUrlPrefix: util.baseFileURL(),
// 访 // 访
fileUrlPrefix: util.baseURL(), fileUrlPrefix: util.baseFileURL(),
// //
imageManagerUrlPrefix: util.baseURL(), imageManagerUrlPrefix: util.baseFileURL(),
// //
fileManagerUrlPrefix: util.baseURL() fileManagerUrlPrefix: util.baseFileURL()
// ueditor // ueditor
// http://fex.baidu.com/ueditor/#start-config // http://fex.baidu.com/ueditor/#start-config
}, },
@ -346,8 +352,37 @@ export default {
} }
} }
</script> </script>
<style scoped> <style lang="scss">
::v-deep .d2p-file-uploader .el-upload--picture-card{ .d2p-images {
height: 30px;
width: 30px;
.el-upload-list__item-thumbnail {
height: 60px !important;
width: 60px !important;
}
.el-upload-list__item {
width: 60px !important;
height: 60px !important;
line-height: 0 !important;
img {
height: 60px !important;
width: 60px !important;
}
}
.el-upload-list__item-actions {
height: 60px !important;
width: 60px !important;
line-height: 0 !important;
}
}
</style>
<style lang="scss" scoped>
::v-deep .d2p-file-uploader .el-upload--picture-card {
width: 50px; width: 50px;
height: 50px; height: 50px;
} }

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="#999">
<path opacity=".25" d="M16 0 A16 16 0 0 0 16 32 A16 16 0 0 0 16 0 M16 4 A12 12 0 0 1 16 28 A12 12 0 0 1 16 4"/>
<path d="M16 0 A16 16 0 0 1 32 16 L28 16 A12 12 0 0 0 16 4z">
<animateTransform attributeName="transform" type="rotate" from="0 16 16" to="360 16 16" dur="0.8s" repeatCount="indefinite" />
</path>
</svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@ -40,7 +40,18 @@
<el-table-column fixed type="index" label="#" width="50"/> <el-table-column fixed type="index" label="#" width="50"/>
<span v-for="(item,index) in _elProps.tableConfig.columns" :key="index" > <span v-for="(item,index) in _elProps.tableConfig.columns" :key="index" >
<el-table-column :prop="item.prop" :label="item.label" :width="item.width" <el-table-column :prop="item.prop" :label="item.label" :width="item.width"
v-if="item.show !== false"/> v-if="item.show !== false">
<template slot-scope="scope">
<span v-if="item.type === 'image'">
<el-image :src="baseURL + scope.row[item.prop]" style="height: 30px;width: 30px;">
<div slot="placeholder" class="image-slot">
<img src="./loading-spin.svg">
</div>
</el-image>
</span>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
</span> </span>
</el-table> </el-table>
<el-pagination style="margin-top: 10px;max-width: 200px" background <el-pagination style="margin-top: 10px;max-width: 200px" background
@ -80,6 +91,7 @@
import { request } from '@/api/service' import { request } from '@/api/service'
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
import { d2CrudPlus } from 'd2-crud-plus' import { d2CrudPlus } from 'd2-crud-plus'
import util from '@/libs/util'
export default { export default {
name: 'selector-table-input', name: 'selector-table-input',
@ -147,7 +159,8 @@ export default {
search: null, search: null,
tableData: [], tableData: [],
multipleSelection: [], multipleSelection: [],
collapseTags: false collapseTags: false,
baseURL: util.baseURL()
} }
}, },
computed: { computed: {

View File

@ -306,7 +306,7 @@ export default {
}, },
itemClosed (item) { itemClosed (item) {
const newNodes = lodash.without(this.selected, item) const newNodes = lodash.without(this.selected, item)
console.log('new value', item, newNodes) // console.log('new value', item, newNodes)
this.$set(this, 'selected', newNodes) this.$set(this, 'selected', newNodes)
this.doValueInputChanged(newNodes) this.doValueInputChanged(newNodes)
}, },

View File

@ -36,6 +36,30 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
<div v-if="elProps.type === 'manyToMany'">
<el-popover
placement="right"
width="300"
trigger="hover"
v-if="value.length > 0"
@show="showEvents"
@hide="show=false">
<el-descriptions class="margin-top" :column="1" size="mini" border>
<el-descriptions-item v-for="(item,index) in value" :key="index" labelStyle="width: 60px;">
<template slot="label">
选项{{ index + 1 }}
</template>
{{ item[dict.label] }}
</el-descriptions-item>
</el-descriptions>
<el-button type="primary" plain size="mini" slot="reference" @click="listClick"><span> {{ value.length }} {{ elProps.unit }}</span>
</el-button>
</el-popover>
<el-button v-else type="primary" plain size="mini" slot="reference" @click="listClick"><span> {{
value.length
}} {{ elProps.unit }}</span>
</el-button>
</div>
<div v-else-if="elProps.type === 'ueditor'"> <div v-else-if="elProps.type === 'ueditor'">
<el-popover <el-popover
placement="right" placement="right"

View File

@ -1,4 +1,5 @@
import util from '@/libs/util.js' import util from '@/libs/util.js'
export default { export default {
'image-uploader': { 'image-uploader': {
form: { component: { name: 'd2p-file-uploader', props: { elProps: { listType: 'picture-card', accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif' } } } }, form: { component: { name: 'd2p-file-uploader', props: { elProps: { listType: 'picture-card', accept: '.png,.jpeg,.jpg,.ico,.bmp,.gif' } } } },
@ -244,21 +245,21 @@ export default {
config: { config: {
serverUrl: util.baseURL() + 'api/system/file/ueditor/', serverUrl: util.baseURL() + 'api/system/file/ueditor/',
headers: { Authorization: 'JWT ' + util.cookies.get('token') }, headers: { Authorization: 'JWT ' + util.cookies.get('token') },
imageUrlPrefix: util.baseURL(), imageUrlPrefix: util.baseFileURL(),
// 涂鸦图片上传 // 涂鸦图片上传
scrawlUrlPrefix: util.baseURL(), scrawlUrlPrefix: util.baseFileURL(),
// 截图工具上传 // 截图工具上传
snapscreenUrlPrefix: util.baseURL(), snapscreenUrlPrefix: util.baseFileURL(),
// 抓取远程图片路径前缀 // 抓取远程图片路径前缀
catcherUrlPrefix: util.baseURL(), catcherUrlPrefix: util.baseFileURL(),
// 视频访问路径前缀 // 视频访问路径前缀
videoUrlPrefix: util.baseURL(), videoUrlPrefix: util.baseFileURL(),
// 文件访问路径前缀 // 文件访问路径前缀
fileUrlPrefix: util.baseURL(), fileUrlPrefix: util.baseFileURL(),
// 列出指定目录下的图片 // 列出指定目录下的图片
imageManagerUrlPrefix: util.baseURL(), imageManagerUrlPrefix: util.baseFileURL(),
// 列出指定目录下的文件 // 列出指定目录下的文件
fileManagerUrlPrefix: util.baseURL() fileManagerUrlPrefix: util.baseFileURL()
// 传入ueditor的配置 // 传入ueditor的配置
// 文档参考 http://fex.baidu.com/ueditor/#start-config // 文档参考 http://fex.baidu.com/ueditor/#start-config
} }

View File

@ -3,7 +3,6 @@ import db from './util.db'
import log from './util.log' import log from './util.log'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import filterParams from './util.params' import filterParams from './util.params'
const util = { const util = {
cookies, cookies,
db, db,
@ -63,6 +62,12 @@ util.baseURL = function () {
return baseURL return baseURL
} }
util.baseFileURL = function () {
if (process.env.VUE_APP_FILE_ENGINE && (process.env.VUE_APP_FILE_ENGINE === 'oss' || process.env.VUE_APP_FILE_ENGINE === 'cos')) {
return ''
}
return util.baseURL()
}
util.wsBaseURL = function () { util.wsBaseURL = function () {
var baseURL = process.env.VUE_APP_API var baseURL = process.env.VUE_APP_API
var param = baseURL.split('/')[3] || '' var param = baseURL.split('/')[3] || ''

View File

@ -56,11 +56,11 @@ router.beforeEach(async (to, from, next) => {
}) })
await store.dispatch('d2admin/user/set', res.data, { root: true }) await store.dispatch('d2admin/user/set', res.data, { root: true })
await store.dispatch('d2admin/account/load') await store.dispatch('d2admin/account/load')
await store.dispatch('d2admin/permission/load', routes)
store.dispatch('d2admin/dept/load')
store.dispatch('d2admin/settings/init') store.dispatch('d2admin/settings/init')
} }
if (!store.state.d2admin.menu || store.state.d2admin.menu.aside.length === 0) { if (!store.state.d2admin.menu || store.state.d2admin.menu.aside.length === 0) {
await store.dispatch('d2admin/permission/load', routes)
await store.dispatch('d2admin/dept/load')
// 动态添加路由 // 动态添加路由
getMenu().then(ret => { getMenu().then(ret => {
// 校验路由是否有效 // 校验路由是否有效
@ -87,8 +87,11 @@ router.beforeEach(async (to, from, next) => {
} else { } else {
const childrenPath = window.qiankunActiveRule || [] const childrenPath = window.qiankunActiveRule || []
if (to.name) { if (to.name) {
if (to.meta.openInNewWindow && (from.query.newWindow && to.query.newWindow !== '1' || from.path === '/')) {
to.query.newWindow = '1'
}
// name 属性说明是主应用的路由 // name 属性说明是主应用的路由
if (to.meta.openInNewWindow && !to.query.newWindow) { if (to.meta.openInNewWindow && !to.query.newWindow && !from.query.newWindow && from.path !== '/') {
// 在新窗口中打开路由 // 在新窗口中打开路由
const { href } = router.resolve({ const { href } = router.resolve({
path: to.path + '?newWindow=1' path: to.path + '?newWindow=1'

View File

@ -97,6 +97,11 @@ export const crudOptions = (vm) => {
// label: 'name', // 数据字典中label字段的属性名 // label: 'name', // 数据字典中label字段的属性名
// children: 'children' // 数据字典中children字段的属性名 // children: 'children' // 数据字典中children字段的属性名
// }, // },
valueResolve (row, key) {
if (row.pcode === null) {
row.pcode = undefined
}
},
form: { form: {
component: { component: {
showAllLevels: false, // 仅显示最后一级 showAllLevels: false, // 仅显示最后一级

View File

@ -82,7 +82,9 @@ export const crudOptions = (vm) => {
}, },
type: 'select', type: 'select',
dict: { dict: {
data: vm.dictionary('system_button') data: vm.dictionary('system_button'),
label: 'label',
value: 'label'
}, },
form: { form: {
rules: [ // 表单校验规则 rules: [ // 表单校验规则
@ -107,7 +109,7 @@ export const crudOptions = (vm) => {
// console.log('component.dictOptions', component.dictOptions) // console.log('component.dictOptions', component.dictOptions)
const obj = component.dictOptions.find(item => { const obj = component.dictOptions.find(item => {
// console.log(item.label, value) // console.log(item.label, value)
return item.value === value return item.label === value
}) })
if (obj && obj.value) { if (obj && obj.value) {
form.name = obj.label form.name = obj.label

View File

@ -92,6 +92,23 @@ export const crudOptions = (vm) => {
disabled: true disabled: true
} }
}, },
{
title: '部门名称',
key: 'dept__name',
treeNode: true, // 设置为树形列
search: {
disabled: false,
component: {
props: {
clearable: true
}
}
},
show: false,
form: {
disabled: true
}
},
{ {
title: '账号', title: '账号',
key: 'username', key: 'username',
@ -174,7 +191,7 @@ export const crudOptions = (vm) => {
title: '部门', title: '部门',
key: 'dept', key: 'dept',
search: { search: {
disabled: true disabled: false
}, },
minWidth: 140, minWidth: 140,
type: 'tree-selector', type: 'tree-selector',