commit
1310e206a5
|
@ -30,8 +30,6 @@ system_url.register(r'system_config', SystemConfigViewSet)
|
||||||
system_url.register(r'message_center',MessageCenterViewSet)
|
system_url.register(r'message_center',MessageCenterViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('user/export/', UserViewSet.as_view({'post': 'export_data', })),
|
|
||||||
path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})),
|
|
||||||
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
|
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
|
||||||
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
|
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
|
||||||
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
|
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
|
||||||
|
|
|
@ -54,25 +54,39 @@ class LoginSerializer(TokenObtainPairSerializer):
|
||||||
登录的序列化器:
|
登录的序列化器:
|
||||||
重写djangorestframework-simplejwt的序列化器
|
重写djangorestframework-simplejwt的序列化器
|
||||||
"""
|
"""
|
||||||
|
|
||||||
captcha = serializers.CharField(
|
captcha = serializers.CharField(
|
||||||
max_length=6, required=False, allow_null=True, allow_blank=True
|
max_length=6, required=False, allow_null=True, allow_blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Users
|
model = Users
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
default_error_messages = {"no_active_account": _("账号/密码错误")}
|
class LoginView(TokenObtainPairView):
|
||||||
|
"""
|
||||||
|
登录接口
|
||||||
|
"""
|
||||||
|
serializer_class = LoginSerializer
|
||||||
|
permission_classes = []
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# username可能携带的不止是用户名,可能还是用户的其它唯一标识 手机号 邮箱
|
||||||
|
username = request.data.get('username',None)
|
||||||
|
if username is None:
|
||||||
|
return ErrorResponse(msg="账号不能为空")
|
||||||
|
password = request.data.get('password',None)
|
||||||
|
if password is None:
|
||||||
|
return ErrorResponse(msg="密码不能为空")
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
captcha = self.initial_data.get("captcha", None)
|
|
||||||
if dispatch.get_system_config_values("base.captcha_state"):
|
if dispatch.get_system_config_values("base.captcha_state"):
|
||||||
|
captcha = request.data.get('captcha', None)
|
||||||
|
captchaKey = request.data.get('captchaKey', None)
|
||||||
|
if captchaKey is None:
|
||||||
|
return ErrorResponse(msg="验证码不能为空")
|
||||||
if captcha is None:
|
if captcha is None:
|
||||||
raise CustomValidationError("验证码不能为空")
|
raise CustomValidationError("验证码不能为空")
|
||||||
self.image_code = CaptchaStore.objects.filter(
|
self.image_code = CaptchaStore.objects.filter(
|
||||||
id=self.initial_data["captchaKey"]
|
id=captchaKey
|
||||||
).first()
|
).first()
|
||||||
five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
|
five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
|
||||||
if self.image_code and five_minute_ago > self.image_code.expiration:
|
if self.image_code and five_minute_ago > self.image_code.expiration:
|
||||||
|
@ -87,34 +101,36 @@ class LoginSerializer(TokenObtainPairSerializer):
|
||||||
else:
|
else:
|
||||||
self.image_code and self.image_code.delete()
|
self.image_code and self.image_code.delete()
|
||||||
raise CustomValidationError("图片验证码错误")
|
raise CustomValidationError("图片验证码错误")
|
||||||
data = super().validate(attrs)
|
try:
|
||||||
data["name"] = self.user.name
|
# 手动通过 user 签发 jwt-token
|
||||||
data["userId"] = self.user.id
|
user = Users.objects.get(username=username)
|
||||||
data["avatar"] = self.user.avatar
|
except:
|
||||||
dept = getattr(self.user, 'dept', None)
|
return ErrorResponse(msg='该账号未注册')
|
||||||
|
# 获得用户后,校验密码并签发token
|
||||||
|
if not user.check_password(password):
|
||||||
|
return ErrorResponse(msg='密码错误')
|
||||||
|
result = {
|
||||||
|
"name":user.name,
|
||||||
|
"userId":user.id,
|
||||||
|
"avatar":user.avatar,
|
||||||
|
}
|
||||||
|
dept = getattr(user, 'dept', None)
|
||||||
if dept:
|
if dept:
|
||||||
data['dept_info'] = {
|
result['dept_info'] = {
|
||||||
'dept_id': dept.id,
|
'dept_id': dept.id,
|
||||||
'dept_name': dept.name,
|
'dept_name': dept.name,
|
||||||
'dept_key': dept.key
|
'dept_key': dept.key
|
||||||
}
|
}
|
||||||
role = getattr(self.user, 'role', None)
|
role = getattr(user, 'role', None)
|
||||||
if role:
|
if role:
|
||||||
data['role_info'] = role.values('id', 'name', 'key')
|
result['role_info'] = role.values('id', 'name', 'key')
|
||||||
request = self.context.get("request")
|
refresh = LoginSerializer.get_token(user)
|
||||||
request.user = self.user
|
result["refresh"] = str(refresh)
|
||||||
|
result["access"] = str(refresh.access_token)
|
||||||
# 记录登录日志
|
# 记录登录日志
|
||||||
|
request.user = user
|
||||||
save_login_log(request=request)
|
save_login_log(request=request)
|
||||||
return {"code": 2000, "msg": "请求成功", "data": data}
|
return DetailResponse(data=result,msg="获取成功")
|
||||||
|
|
||||||
|
|
||||||
class LoginView(TokenObtainPairView):
|
|
||||||
"""
|
|
||||||
登录接口
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer_class = LoginSerializer
|
|
||||||
permission_classes = []
|
|
||||||
|
|
||||||
|
|
||||||
class LoginTokenSerializer(TokenObtainPairSerializer):
|
class LoginTokenSerializer(TokenObtainPairSerializer):
|
||||||
|
|
|
@ -34,3 +34,4 @@ class LoginLogViewSet(CustomModelViewSet):
|
||||||
queryset = LoginLog.objects.all()
|
queryset = LoginLog.objects.all()
|
||||||
serializer_class = LoginLogSerializer
|
serializer_class = LoginLogSerializer
|
||||||
extra_filter_backends = []
|
extra_filter_backends = []
|
||||||
|
ordering_fields = ['create_datetime']
|
||||||
|
|
|
@ -185,11 +185,11 @@ class MenuViewSet(CustomModelViewSet):
|
||||||
parent = params.get('parent', None)
|
parent = params.get('parent', None)
|
||||||
if params:
|
if params:
|
||||||
if parent:
|
if parent:
|
||||||
queryset = self.queryset.filter(status=1, parent=parent)
|
queryset = self.queryset.filter(parent=parent)
|
||||||
else:
|
else:
|
||||||
queryset = self.queryset.filter(status=1)
|
queryset = self.queryset
|
||||||
else:
|
else:
|
||||||
queryset = self.queryset.filter(status=1, parent__isnull=True)
|
queryset = self.queryset.filter(parent__isnull=True)
|
||||||
queryset = self.filter_queryset(queryset)
|
queryset = self.filter_queryset(queryset)
|
||||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
|
|
|
@ -86,7 +86,9 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
||||||
user_id = self.request.user.id
|
user_id = self.request.user.id
|
||||||
message_center_id = instance.id
|
message_center_id = instance.id
|
||||||
queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id,users_id=user_id).first()
|
queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id,users_id=user_id).first()
|
||||||
|
if queryset:
|
||||||
return queryset.is_read
|
return queryset.is_read
|
||||||
|
return False
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MessageCenter
|
model = MessageCenter
|
||||||
|
@ -121,12 +123,12 @@ class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||||
users = initial_data.get('target_user', [])
|
users = initial_data.get('target_user', [])
|
||||||
if target_type in [1]: # 按角色
|
if target_type in [1]: # 按角色
|
||||||
target_role = initial_data.get('target_role',[])
|
target_role = initial_data.get('target_role',[])
|
||||||
users = Users.objects.exclude(is_deleted=True).filter(role__id__in=target_role).values_list('id', flat=True)
|
users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True)
|
||||||
if target_type in [2]: # 按部门
|
if target_type in [2]: # 按部门
|
||||||
target_dept = initial_data.get('target_dept',[])
|
target_dept = initial_data.get('target_dept',[])
|
||||||
users = Users.objects.exclude(is_deleted=True).filter(dept__id__in=target_dept).values_list('id', flat=True)
|
users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True)
|
||||||
if target_type in [3]: # 系统通知
|
if target_type in [3]: # 系统通知
|
||||||
users = Users.objects.exclude(is_deleted=True).values_list('id', flat=True)
|
users = Users.objects.values_list('id', flat=True)
|
||||||
targetuser_data = []
|
targetuser_data = []
|
||||||
for user in users:
|
for user in users:
|
||||||
targetuser_data.append({
|
targetuser_data.append({
|
||||||
|
@ -211,6 +213,6 @@ class MessageCenterViewSet(CustomModelViewSet):
|
||||||
queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('create_datetime').last()
|
queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('create_datetime').last()
|
||||||
data = None
|
data = None
|
||||||
if queryset:
|
if queryset:
|
||||||
serializer = MessageCenterTargetUserListSerializer(queryset, many=False, request=request)
|
serializer = MessageCenterTargetUserListSerializer(queryset.messagecenter, many=False, request=request)
|
||||||
data = serializer.data
|
data = serializer.data
|
||||||
return DetailResponse(data=data, msg="获取成功")
|
return DetailResponse(data=data, msg="获取成功")
|
||||||
|
|
|
@ -219,10 +219,12 @@ class ExportUserProfileSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class UserProfileImportSerializer(CustomModelSerializer):
|
class UserProfileImportSerializer(CustomModelSerializer):
|
||||||
|
password = serializers.CharField(required=True, max_length=50, error_messages={"required": "登录密码不能为空"})
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
data = super().save(**kwargs)
|
data = super().save(**kwargs)
|
||||||
password = hashlib.new(
|
password = hashlib.new(
|
||||||
"md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")
|
"md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8")
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
data.set_password(password)
|
data.set_password(password)
|
||||||
data.save()
|
data.save()
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.core import paginator
|
from django.core import paginator
|
||||||
from django.core.paginator import Paginator as DjangoPaginator
|
from django.core.paginator import Paginator as DjangoPaginator, InvalidPage
|
||||||
from rest_framework.pagination import PageNumberPagination
|
from rest_framework.pagination import PageNumberPagination
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
@ -21,13 +21,52 @@ class CustomPagination(PageNumberPagination):
|
||||||
max_page_size = 999
|
max_page_size = 999
|
||||||
django_paginator_class = DjangoPaginator
|
django_paginator_class = DjangoPaginator
|
||||||
|
|
||||||
|
def paginate_queryset(self, queryset, request, view=None):
|
||||||
|
"""
|
||||||
|
Paginate a queryset if required, either returning a
|
||||||
|
page object, or `None` if pagination is not configured for this view.
|
||||||
|
"""
|
||||||
|
empty = True
|
||||||
|
|
||||||
|
page_size = self.get_page_size(request)
|
||||||
|
if not page_size:
|
||||||
|
return None
|
||||||
|
|
||||||
|
paginator = self.django_paginator_class(queryset, page_size)
|
||||||
|
page_number = request.query_params.get(self.page_query_param, 1)
|
||||||
|
if page_number in self.last_page_strings:
|
||||||
|
page_number = paginator.num_pages
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.page = paginator.page(page_number)
|
||||||
|
except InvalidPage as exc:
|
||||||
|
|
||||||
|
# msg = self.invalid_page_message.format(
|
||||||
|
# page_number=page_number, message=str(exc)
|
||||||
|
# )
|
||||||
|
# raise NotFound(msg)
|
||||||
|
empty = False
|
||||||
|
pass
|
||||||
|
|
||||||
|
if paginator.num_pages > 1 and self.template is not None:
|
||||||
|
# The browsable API should display pagination controls.
|
||||||
|
self.display_page_controls = True
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
if not empty:
|
||||||
|
self.page = []
|
||||||
|
|
||||||
|
return list(self.page)
|
||||||
def get_paginated_response(self, data):
|
def get_paginated_response(self, data):
|
||||||
code = 2000
|
code = 2000
|
||||||
msg = 'success'
|
msg = 'success'
|
||||||
res = {
|
res = {
|
||||||
"page": int(self.get_page_number(self.request, paginator)) or 1,
|
"page": int(self.get_page_number(self.request, paginator)) or 1,
|
||||||
"total": self.page.paginator.count,
|
"total": self.page.paginator.count if self.page else 0,
|
||||||
"limit": int(self.get_page_size(self.request)) or 10,
|
"limit": int(self.get_page_size(self.request)) or 10,
|
||||||
|
"is_next": self.page.has_next(),
|
||||||
|
"is_previous": self.page.has_previous(),
|
||||||
"data": data
|
"data": data
|
||||||
}
|
}
|
||||||
if not data:
|
if not data:
|
||||||
|
@ -38,6 +77,6 @@ class CustomPagination(PageNumberPagination):
|
||||||
return Response(OrderedDict([
|
return Response(OrderedDict([
|
||||||
('code', code),
|
('code', code),
|
||||||
('msg', msg),
|
('msg', msg),
|
||||||
# ('total',self.page.paginator.count),
|
|
||||||
('data', res),
|
('data', res),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
"china-division": "^2.4.0",
|
"china-division": "^2.4.0",
|
||||||
"core-js": "^3.4.3",
|
"core-js": "^3.4.3",
|
||||||
"cropperjs": "^1.5.6",
|
"cropperjs": "^1.5.6",
|
||||||
"d2-crud-plus": "^2.13.1",
|
"d2-crud-plus": "^2.17.9",
|
||||||
"d2-crud-x": "^2.13.1",
|
"d2-crud-x": "^2.17.9",
|
||||||
"d2p-extends": "^2.13.1",
|
"d2p-extends": "^2.17.9",
|
||||||
"dayjs": "^1.8.17",
|
"dayjs": "^1.8.17",
|
||||||
"echarts": "^5.1.2",
|
"echarts": "^5.1.2",
|
||||||
"el-phone-number-input": "^1.1.5",
|
"el-phone-number-input": "^1.1.5",
|
||||||
|
@ -42,6 +42,7 @@
|
||||||
"sortablejs": "^1.10.1",
|
"sortablejs": "^1.10.1",
|
||||||
"ua-parser-js": "^0.7.20",
|
"ua-parser-js": "^0.7.20",
|
||||||
"vue": "2.7.14",
|
"vue": "2.7.14",
|
||||||
|
"vue-grid-layout": "^2.4.0",
|
||||||
"vue-i18n": "^8.15.1",
|
"vue-i18n": "^8.15.1",
|
||||||
"vue-infinite-scroll": "^2.0.2",
|
"vue-infinite-scroll": "^2.0.2",
|
||||||
"vue-router": "^3.6.5",
|
"vue-router": "^3.6.5",
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default {
|
||||||
that.$refs.upload.clearFiles()
|
that.$refs.upload.clearFiles()
|
||||||
// 是否更新已经存在的用户数据
|
// 是否更新已经存在的用户数据
|
||||||
return request({
|
return request({
|
||||||
url: util.baseURL() + that.api + 'import_data/',
|
url: that.api + 'import_data/',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
url: response.data.url
|
url: response.data.url
|
||||||
|
|
|
@ -62,7 +62,15 @@ Vue.use(d2CrudPlus, {
|
||||||
page: { // page接口返回的数据结构配置,
|
page: { // page接口返回的数据结构配置,
|
||||||
request: {
|
request: {
|
||||||
current: 'page',
|
current: 'page',
|
||||||
size: 'limit'
|
size: 'limit',
|
||||||
|
orderAsc (query, value) {
|
||||||
|
const field = query.orderProp
|
||||||
|
if (value) {
|
||||||
|
query.ordering = field
|
||||||
|
} else {
|
||||||
|
query.ordering = `-${field}`
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
response: {
|
response: {
|
||||||
current: 'page', // 当前页码 ret.data.current
|
current: 'page', // 当前页码 ret.data.current
|
||||||
|
|
|
@ -97,6 +97,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 授权后可以删除-->
|
||||||
|
<div class="dvadmin-auth">
|
||||||
|
<span>Powered by Django-Vue-Admin</span>
|
||||||
|
<el-divider direction="vertical"></el-divider>
|
||||||
|
<span>Copyright dvadmin团队</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -208,4 +214,24 @@ export default {
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
// 注册主题
|
// 注册主题
|
||||||
@import "~@/assets/style/theme/register.scss";
|
@import "~@/assets/style/theme/register.scss";
|
||||||
|
|
||||||
|
// 授权样式
|
||||||
|
.dvadmin-auth{
|
||||||
|
font-size: 0.8em;
|
||||||
|
position: absolute;
|
||||||
|
top: 50vh;
|
||||||
|
right: -9vw;
|
||||||
|
text-align: center;
|
||||||
|
color: #888888;
|
||||||
|
background-image: linear-gradient(to left, #d3d3d3, #989898, #888888, #363636,#888888,#989898,#d3d3d3);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
-webkit-background-size: 200% 100%;
|
||||||
|
animation: bgp 6s infinite linear;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
@-webkit-keyframes bgp {
|
||||||
|
0% {background-position: 0 0; }
|
||||||
|
100% {background-position: -100% 0; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<el-card shadow="hover" header="关于项目" class="item-background">
|
||||||
|
<p>高性能 / 精致 / 优雅。基于Vue2 + Element-UI 的中后台前端解决方案,如果喜欢就点个星星支持一下。</p>
|
||||||
|
<p>
|
||||||
|
<a href='https://liqianglog.gitee.io/django-vue-admin' target="_blank">
|
||||||
|
<img src='https://liqianglog.gitee.io/django-vue-admin/badge/star.svg?theme=dark' alt='star' style="vertical-align: middle">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
title: '关于项目',
|
||||||
|
icon: 'el-icon-setting',
|
||||||
|
description: '点个星星支持一下',
|
||||||
|
height: 20,
|
||||||
|
width: 8,
|
||||||
|
minH: 20,
|
||||||
|
minW: 4,
|
||||||
|
isResizable: true,
|
||||||
|
data () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item-background p {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { markRaw } from 'vue'
|
||||||
|
|
||||||
|
const resultComps = {}
|
||||||
|
const requireComponent = require.context(
|
||||||
|
'./', // 在当前目录下查找
|
||||||
|
false, // 不遍历子文件夹
|
||||||
|
/\.vue$/ // 正则匹配 以 .vue结尾的文件
|
||||||
|
)
|
||||||
|
requireComponent.keys().forEach(fileName => {
|
||||||
|
const comp = requireComponent(fileName)
|
||||||
|
resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, '$1')] = comp.default
|
||||||
|
})
|
||||||
|
export default markRaw(resultComps)
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<el-card shadow="hover" header="时钟" class="item-background">
|
||||||
|
<div class="time">
|
||||||
|
<h2>{{ time }}</h2>
|
||||||
|
<p>{{ day }}</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: '时钟',
|
||||||
|
icon: 'el-icon-alarm-clock',
|
||||||
|
description: '演示部件效果',
|
||||||
|
height: 17,
|
||||||
|
minH: 17,
|
||||||
|
width: 8,
|
||||||
|
minW: 4,
|
||||||
|
isResizable: true,
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
time: '',
|
||||||
|
day: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.showTime()
|
||||||
|
setInterval(() => {
|
||||||
|
this.showTime()
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showTime () {
|
||||||
|
this.time = dayjs().format('hh:mm:ss')
|
||||||
|
this.day = dayjs().format('YYYY年MM月DD日')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item-background {
|
||||||
|
background: linear-gradient(to right, #8E54E9, #4776E6);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time h2 {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time p {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<el-card shadow="hover" header="版本信息">
|
||||||
|
<div style="height: 70px;text-align: center;">
|
||||||
|
<h2 style="margin-top: 5px;">Dvadmin</h2>
|
||||||
|
<p style="margin-top: 5px;">最新版本 {{ ver }}</p>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 5px;">
|
||||||
|
<el-button type="primary" plain round @click="golog">更新日志</el-button>
|
||||||
|
<el-button type="primary" plain round @click="gogit">gitee</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
title: '版本信息',
|
||||||
|
icon: 'el-icon-monitor',
|
||||||
|
description: '当前项目版本信息',
|
||||||
|
height: 22,
|
||||||
|
width: 8,
|
||||||
|
minH: 22,
|
||||||
|
minW: 4,
|
||||||
|
isResizable: true,
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
ver: 'loading...'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.getVer()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getVer () {
|
||||||
|
this.ver = 'v2.0.9'
|
||||||
|
},
|
||||||
|
golog () {
|
||||||
|
window.open('https://gitee.com/liqianglog/django-vue-admin/releases')
|
||||||
|
},
|
||||||
|
gogit () {
|
||||||
|
window.open('https://gitee.com/liqianglog/django-vue-admin')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<el-card shadow="hover" header="欢迎">
|
||||||
|
<div class="welcome">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="/image/django-vue-admin.png">
|
||||||
|
<h2>欢迎体验 Dvadmin</h2>
|
||||||
|
</div>
|
||||||
|
<div class="tips">
|
||||||
|
<div class="tips-item">
|
||||||
|
<div class="tips-item-icon">
|
||||||
|
<i class="el-icon-menu"></i>
|
||||||
|
</div>
|
||||||
|
<div class="tips-item-message">这里是项目控制台,你可以点击右上方的“自定义”按钮来添加移除或者移动部件。</div>
|
||||||
|
</div>
|
||||||
|
<div class="tips-item">
|
||||||
|
<div class="tips-item-icon">
|
||||||
|
<i class="el-icon-star-on"></i>
|
||||||
|
</div>
|
||||||
|
<div class="tips-item-message">热爱Python和Vue,打造一个低代码开源平台,并且持续着。</div>
|
||||||
|
</div>
|
||||||
|
<div class="tips-item">
|
||||||
|
<div class="tips-item-icon">
|
||||||
|
<i class="el-icon-milk-tea"></i>
|
||||||
|
</div>
|
||||||
|
<div class="tips-item-message">项目目的:让前端和后端工作更快乐</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<el-button type="primary" icon="el-icon-check" size="large" @click="godoc">文档</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
title: '欢迎',
|
||||||
|
icon: 'el-icon-present',
|
||||||
|
description: '项目特色以及文档链接',
|
||||||
|
height: 50,
|
||||||
|
minH: 50,
|
||||||
|
width: 8,
|
||||||
|
minW: 4,
|
||||||
|
isResizable: true,
|
||||||
|
data () {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
godoc () {
|
||||||
|
window.open('https://www.django-vue-admin.com/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.welcome {
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .logo img {
|
||||||
|
vertical-align: bottom;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .logo h2 {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: normal;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 7.5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-item-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-right: 20px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background: rgba(180, 180, 180, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-item-message {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
text-align: center;
|
||||||
|
margin: 40px 0 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,313 +1,382 @@
|
||||||
<template>
|
<template>
|
||||||
<d2-container>
|
<d2-container>
|
||||||
<div class="page-header">
|
<div class="component-header">
|
||||||
|
<div class="set-btn-class">
|
||||||
<el-avatar src="https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640" class="user-avatar">
|
<el-button v-if="customizing" type="primary" icon="el-icon-check" round @click="save">完成</el-button>
|
||||||
|
<el-button v-else type="primary" icon="el-icon-edit" round @click="custom">自定义</el-button>
|
||||||
</el-avatar>
|
|
||||||
<div class="title">
|
|
||||||
<h1>早安, DVAdmin, 开始您一天的工作吧!</h1>
|
|
||||||
<span> 今日晴,20℃ - 32℃! </span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="customizing" class="all-component-class">
|
||||||
|
<el-card style="margin-bottom: 20px">
|
||||||
|
<div slot="header">
|
||||||
|
<i class="el-icon-circle-plus"></i>
|
||||||
|
<span>添加部件</span>
|
||||||
|
<el-button-group style="float: right">
|
||||||
|
<el-button type="primary" size="mini" @click="backDefaul()">恢复默认</el-button>
|
||||||
|
<el-button type="danger" size="mini" @click="close()">关闭</el-button>
|
||||||
|
</el-button-group>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="widgets-list">
|
||||||
<el-row :gutter="20">
|
<div v-if="myCompsList.length<=0" class="widgets-list-nodata">
|
||||||
<el-col :span="12">
|
<el-empty description="没有部件啦" :image-size="60"></el-empty>
|
||||||
<el-card class="box-card">
|
|
||||||
<div slot="header" class="clearfix">
|
|
||||||
<span>友情链接</span>
|
|
||||||
|
|
||||||
<el-button style="float: right; padding: 3px 0" type="text">
|
|
||||||
<el-link href="https://bbs.django-vue-admin.com" target="_blank" type="primary">更多</el-link>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
<el-row>
|
<div v-for="item in myCompsList" :key="item.title" class="widgets-list-item" @drag="onDrag($event,item)"
|
||||||
<el-col :span="8" v-for="({name,imageUrl,slogan,link},index) in projects" :key="index">
|
@dragend="onDragend($event,item)" draggable="true"
|
||||||
<el-card shadow="hover" style="padding: 0">
|
unselectable="on">
|
||||||
<div class="project-detail">
|
<el-card style="width: 300px">
|
||||||
<div>
|
<div slot="header">
|
||||||
<a :href="link" target="_blank">
|
<i :class="item.icon"></i>
|
||||||
<img :src="imageUrl" alt="">
|
<span> {{ item.title }}</span>
|
||||||
<span v-text="name" class="name"></span>
|
<el-button type="primary" style="float: right;" size="mini" @click="push(item)">添加</el-button>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-text="slogan" class="slogan" :title="slogan"></div>
|
<div class="item-info">
|
||||||
</div>
|
<p>{{ item.description }}</p>
|
||||||
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
</el-col>
|
|
||||||
|
|
||||||
<el-col :span="12">
|
|
||||||
<div class="grid-content bg-purple">
|
|
||||||
|
|
||||||
<el-card class="box-card" >
|
|
||||||
<div slot="header" class="clearfix">
|
|
||||||
<span>快捷导航</span>
|
|
||||||
</div>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="8" v-for="({name,icon,route,color},index) of navigators" :key="index" style="padding: 0">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<div style="display: flex;align-items: center;flex-direction: column;cursor: pointer" @click="()=>{gotoRoute(route)}">
|
|
||||||
<d2-icon-svg :name="icon" style="width: 25px;height: 25px;" :style="{fill:color}"/>
|
|
||||||
<div style="text-align: center;font-size: 12px;margin-top: 20px" v-text="name"></div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-card class="box-card" style="margin-top: 25px">
|
|
||||||
<div class="work">
|
|
||||||
<d2-icon-svg name="work" style="margin-left: 50%;transform: translateX(-50%);height: 216px"/>
|
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="widgets" ref="widgets">
|
||||||
|
<div :class="['widgets-wrapper',customizing?'widgets-wrapper-bg':'']">
|
||||||
|
<div v-if="nowCompsList.length<=0" class="no-widgets">
|
||||||
|
<el-empty image="img/no-widgets.svg" description="没有部件啦" :image-size="280"></el-empty>
|
||||||
|
</div>
|
||||||
|
<grid-layout
|
||||||
|
ref="gridlayout"
|
||||||
|
:layout.sync="layout"
|
||||||
|
:col-num="24"
|
||||||
|
:row-height="1"
|
||||||
|
:is-draggable="customizing"
|
||||||
|
:vertical-compact="false"
|
||||||
|
:use-css-transforms="true"
|
||||||
|
:autoSize="true"
|
||||||
|
>
|
||||||
|
<grid-item v-for="(item, index) in layout"
|
||||||
|
:static="item.static"
|
||||||
|
:x="item.x"
|
||||||
|
:y="item.y"
|
||||||
|
:w="item.w"
|
||||||
|
:h="item.h"
|
||||||
|
:i="item.i"
|
||||||
|
:key="index"
|
||||||
|
:isResizable="customizing"
|
||||||
|
|
||||||
|
>
|
||||||
|
<div v-if="customizing" class="customize-overlay">
|
||||||
|
<el-button class="close" type="danger" plain icon="el-icon-close" size="small"
|
||||||
|
@click="remove(index)"></el-button>
|
||||||
|
<label>
|
||||||
|
<i :class="allComps[item.element].icon"></i>
|
||||||
|
{{ allComps[item.element].title }}</label>
|
||||||
|
</div>
|
||||||
|
<component :class="customizing?'set-component-bg':''" :is="allComps[item.element]"></component>
|
||||||
|
</grid-item>
|
||||||
|
</grid-layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</d2-container>
|
</d2-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { use } from 'echarts/core'
|
import draggable from 'vuedraggable'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import allComps from './components'
|
||||||
import { PieChart } from 'echarts/charts'
|
import util from '@/libs/util'
|
||||||
import {
|
import VueGridLayout from 'vue-grid-layout'
|
||||||
TitleComponent,
|
import XEUtils from 'xe-utils'
|
||||||
TooltipComponent,
|
const mouseXY = { x: null, y: null }
|
||||||
LegendComponent
|
const DragPos = { x: 0, y: 0, w: 1, h: 1, i: null }
|
||||||
} from 'echarts/components'
|
|
||||||
|
|
||||||
use([
|
|
||||||
CanvasRenderer,
|
|
||||||
PieChart,
|
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
|
||||||
LegendComponent
|
|
||||||
])
|
|
||||||
export default {
|
export default {
|
||||||
name: 'workbench',
|
components: {
|
||||||
|
draggable,
|
||||||
|
GridLayout: VueGridLayout.GridLayout,
|
||||||
|
GridItem: VueGridLayout.GridItem
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
projects: [
|
customizing: false,
|
||||||
{
|
allComps: allComps,
|
||||||
name: '官方文档',
|
selectLayout: [],
|
||||||
imageUrl: '/image/django-vue-admin.png',
|
defaultGrid: {
|
||||||
slogan: 'Django-Vue-Admin 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。',
|
// 默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6]
|
||||||
link: 'http://django-vue-admin.com'
|
layout: [12, 6, 6],
|
||||||
|
// 小组件分布,com取值:views/home/components 文件名
|
||||||
|
copmsList: [
|
||||||
|
['welcome'],
|
||||||
|
['about', 'ver'],
|
||||||
|
['time', 'progress']
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
defaultLayout: [],
|
||||||
name: '官方论坛',
|
layout: [
|
||||||
imageUrl: '/image/django-vue-admin.png',
|
// { x: 0, y: 0, w: 2, h: 2, i: '0', element: 'welcome' },
|
||||||
slogan: 'Django-Vue-Admin 官方论坛',
|
// { x: 2, y: 0, w: 2, h: 4, i: '1', element: 'about' },
|
||||||
link: 'http://bbs.django-vue-admin.com'
|
// { x: 4, y: 0, w: 2, h: 5, i: '2', element: 'time' },
|
||||||
},
|
// { x: 6, y: 0, w: 2, h: 3, i: '3', element: 'progress' }
|
||||||
{
|
|
||||||
name: 'D2admin',
|
|
||||||
imageUrl: '/image/d2-pub.png',
|
|
||||||
slogan: 'D2Admin (opens new window)是一个完全 开源免费 的企业中后台产品前端集成方案,使用最新的前端技术栈,' +
|
|
||||||
'小于 60kb 的本地首屏 js 加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助力管理系统快速开发。',
|
|
||||||
link: 'https://d2.pub/zh'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'SimpleUi',
|
|
||||||
imageUrl: '/image/simple-ui.png',
|
|
||||||
slogan: '一个基于Django Admin的现代化主题。',
|
|
||||||
link: 'https://simpleui.72wo.com/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '若依',
|
|
||||||
imageUrl: '/image/ruoyi.png',
|
|
||||||
slogan: '基于SpringBoot、Shiro、Mybatis的权限后台管理系统。',
|
|
||||||
link: 'http://ruoyi.vip/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Gin-Vue-Admin',
|
|
||||||
imageUrl: '/image/gin-vue-admin.png',
|
|
||||||
slogan: '使用gin+vue进行极速开发的全栈后台管理系统。',
|
|
||||||
link: 'https://www.gin-vue-admin.com/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DCM',
|
|
||||||
imageUrl: '/image/django-comment-migrate.png',
|
|
||||||
slogan: '这是一个Django model注释迁移的app',
|
|
||||||
link: 'https://github.com/starryrbs/django-comment-migrate'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Jetbrains',
|
|
||||||
imageUrl: '/image/jetbrains.jpeg',
|
|
||||||
slogan: '我们构建我们的软件,让您可以享受构建自己的软件的乐趣',
|
|
||||||
link: 'https://www.jetbrains.com/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Django',
|
|
||||||
imageUrl: '/image/django.png',
|
|
||||||
slogan: '有期限的完美主义者的网络框架。',
|
|
||||||
link: 'https://github.com/django/django'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
navigators: [
|
|
||||||
{
|
|
||||||
name: '控制台',
|
|
||||||
icon: 'home',
|
|
||||||
route: {
|
|
||||||
name: 'index'
|
|
||||||
},
|
|
||||||
color: 'rgb(31, 218, 202);'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '部门管理',
|
|
||||||
icon: 'department',
|
|
||||||
route: {
|
|
||||||
name: 'dept'
|
|
||||||
},
|
|
||||||
color: 'rgb(225, 133, 37);'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '角色管理',
|
|
||||||
icon: 'role',
|
|
||||||
route: {
|
|
||||||
name: 'role'
|
|
||||||
},
|
|
||||||
color: 'rgb(191, 12, 44);'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '菜单管理',
|
|
||||||
icon: 'menu',
|
|
||||||
route: {
|
|
||||||
name: 'menu'
|
|
||||||
},
|
|
||||||
color: 'rgb(63, 178, 127);'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '用户管理',
|
|
||||||
icon: 'user',
|
|
||||||
route: {
|
|
||||||
name: 'user'
|
|
||||||
},
|
|
||||||
color: 'rgb(191, 12, 44);'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '日志管理',
|
|
||||||
icon: 'log',
|
|
||||||
route: {
|
|
||||||
name: 'operationLog'
|
|
||||||
},
|
|
||||||
color: 'rgb(0, 216, 255);'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chartData: {
|
|
||||||
columns: ['日期', '销售额'],
|
|
||||||
rows: [
|
|
||||||
{ 日期: '1月1日', 销售额: 123 },
|
|
||||||
{ 日期: '1月2日', 销售额: 1223 },
|
|
||||||
{ 日期: '1月3日', 销售额: 2123 },
|
|
||||||
{ 日期: '1月4日', 销售额: 4123 },
|
|
||||||
{ 日期: '1月5日', 销售额: 3123 },
|
|
||||||
{ 日期: '1月6日', 销售额: 7123 }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.layout = JSON.parse(util.cookies.get('grid-layout') || JSON.stringify(this.defaultLayout))
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
document.addEventListener('dragover', function (e) {
|
||||||
|
mouseXY.x = e.clientX
|
||||||
|
mouseXY.y = e.clientY
|
||||||
|
}, false)
|
||||||
|
this.$emit('on-mounted')
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
allCompsList () {
|
||||||
|
var allCompsList = []
|
||||||
|
for (var key in this.allComps) {
|
||||||
|
allCompsList.push({
|
||||||
|
key: key,
|
||||||
|
title: allComps[key].title,
|
||||||
|
icon: allComps[key].icon,
|
||||||
|
height: allComps[key].height,
|
||||||
|
width: allComps[key].width,
|
||||||
|
maxH: allComps[key].maxH || Infinity,
|
||||||
|
maxW: allComps[key].maxW || Infinity,
|
||||||
|
isResizable: allComps[key].isResizable || null,
|
||||||
|
description: allComps[key].description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return allCompsList
|
||||||
|
},
|
||||||
|
myCompsList () {
|
||||||
|
return this.allCompsList
|
||||||
|
},
|
||||||
|
nowCompsList () {
|
||||||
|
return this.allCompsList
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
gotoRoute (route) {
|
// 开启自定义
|
||||||
this.$router.push(route)
|
custom () {
|
||||||
|
this.customizing = true
|
||||||
|
const oldWidth = this.$refs.widgets.offsetWidth
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const scale = this.$refs.widgets.offsetWidth / oldWidth
|
||||||
|
this.$refs.widgets.style.setProperty('transform', `scale(${scale})`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 设置布局
|
||||||
|
setLayout (layout) {
|
||||||
|
// 暂定
|
||||||
|
},
|
||||||
|
getLayoutElementNumber (elementName) {
|
||||||
|
// var index = 0
|
||||||
|
// this.layout.map(res => {
|
||||||
|
// if (elementName === res.element) {
|
||||||
|
// index += 1
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// return index + 1
|
||||||
|
return elementName + this.layout.length
|
||||||
|
},
|
||||||
|
// 追加
|
||||||
|
push (item) {
|
||||||
|
console.log(1, item)
|
||||||
|
this.layout.push({
|
||||||
|
x: 6,
|
||||||
|
y: 0,
|
||||||
|
w: item.width,
|
||||||
|
h: item.height,
|
||||||
|
isResizable: item.isResizable || null,
|
||||||
|
i: this.getLayoutElementNumber(item.key),
|
||||||
|
element: item.key
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 删除组件
|
||||||
|
remove (index) {
|
||||||
|
this.layout.splice(index, 1)
|
||||||
|
},
|
||||||
|
// 保存
|
||||||
|
save () {
|
||||||
|
console.log(this.layout)
|
||||||
|
this.customizing = false
|
||||||
|
this.$refs.widgets.style.removeProperty('transform')
|
||||||
|
util.cookies.set('grid-layout', this.layout)
|
||||||
|
},
|
||||||
|
// 恢复默认
|
||||||
|
backDefaul () {
|
||||||
|
this.customizing = false
|
||||||
|
this.$refs.widgets.style.removeProperty('transform')
|
||||||
|
this.layout = JSON.parse(JSON.stringify(this.defaultLayout))
|
||||||
|
util.cookies.remove('grid-layout')
|
||||||
|
},
|
||||||
|
// 关闭
|
||||||
|
close () {
|
||||||
|
this.customizing = false
|
||||||
|
this.$refs.widgets.style.removeProperty('transform')
|
||||||
|
},
|
||||||
|
// 拖拽事件
|
||||||
|
onDrag (e, item) {
|
||||||
|
const { key, width, height } = item
|
||||||
|
const parentRect = this.$refs.widgets.getBoundingClientRect()
|
||||||
|
let mouseInGrid = false
|
||||||
|
if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
|
||||||
|
mouseInGrid = true
|
||||||
|
}
|
||||||
|
const cloneLayout = XEUtils.clone(this.layout, true)
|
||||||
|
if (mouseInGrid === true && (cloneLayout.findIndex(item => item.i === this.getLayoutElementNumber(key)) === -1)) {
|
||||||
|
// this.layout.push({
|
||||||
|
// x: (this.layout.length * 2) % (this.colNum || 12),
|
||||||
|
// y: this.layout.length + (this.colNum || 12), // puts it at the bottom
|
||||||
|
// w: width,
|
||||||
|
// h: height,
|
||||||
|
// i: this.getLayoutElementNumber(key),
|
||||||
|
// element: key
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
const index = this.layout.findIndex(item => item.i === this.getLayoutElementNumber(key))
|
||||||
|
if (index !== -1) {
|
||||||
|
try {
|
||||||
|
this.$refs.gridlayout.$children[this.layout.length].$refs.item.style.display = 'none'
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
const el = this.$refs.gridlayout.$children[index]
|
||||||
|
el.dragging = { top: mouseXY.y - parentRect.top, left: mouseXY.x - parentRect.left }
|
||||||
|
const new_pos = el.calcXY(mouseXY.y - parentRect.top, mouseXY.x - parentRect.left)
|
||||||
|
if (mouseInGrid === true) {
|
||||||
|
this.$refs.gridlayout.dragEvent('dragstart', this.getLayoutElementNumber(key), new_pos.x, new_pos.y, 1, 1)
|
||||||
|
DragPos.i = String(index)
|
||||||
|
DragPos.x = this.layout[index].x
|
||||||
|
DragPos.y = this.layout[index].y
|
||||||
|
}
|
||||||
|
if (mouseInGrid === false) {
|
||||||
|
this.$refs.gridlayout.dragEvent('dragend', this.getLayoutElementNumber(key), new_pos.x, new_pos.y, 1, 1)
|
||||||
|
this.layout = this.layout.filter(obj => obj.i !== this.getLayoutElementNumber(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragend (e, item) {
|
||||||
|
const { key, width, height } = item
|
||||||
|
const parentRect = this.$refs.widgets.getBoundingClientRect()
|
||||||
|
let mouseInGrid = false
|
||||||
|
if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
|
||||||
|
mouseInGrid = true
|
||||||
|
}
|
||||||
|
if (mouseInGrid === true) {
|
||||||
|
this.layout.push({
|
||||||
|
x: DragPos.x,
|
||||||
|
y: DragPos.y,
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
i: this.getLayoutElementNumber(key),
|
||||||
|
element: key
|
||||||
|
})
|
||||||
|
this.$refs.gridlayout.dragEvent('dragend', this.getLayoutElementNumber(key), DragPos.x, DragPos.y, 1, 1)
|
||||||
|
this.layout = this.layout.filter(obj => obj.i !== this.getLayoutElementNumber(key))
|
||||||
|
// UNCOMMENT below if you want to add a grid-item
|
||||||
|
/*
|
||||||
|
this.layout.push({
|
||||||
|
x: DragPos.x,
|
||||||
|
y: DragPos.y,
|
||||||
|
w: 1,
|
||||||
|
h: 1,
|
||||||
|
i: DragPos.i,
|
||||||
|
});
|
||||||
|
this.$refs.gridLayout.dragEvent('dragend', DragPos.i, DragPos.x,DragPos.y,1,1);
|
||||||
|
try {
|
||||||
|
this.$refs.gridLayout.$children[this.layout.length].$refs.item.style.display="block";
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.component-header {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
position: sticky;
|
||||||
|
top: -20px;
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
$userAvatarLength: 72px;
|
.set-btn-class {
|
||||||
|
float: right;
|
||||||
.page-header{
|
z-index: 99;
|
||||||
box-sizing: border-box;
|
margin-bottom: 10px;
|
||||||
padding: 16px;
|
|
||||||
.user-avatar{
|
|
||||||
width: $userAvatarLength;
|
|
||||||
height: $userAvatarLength;
|
|
||||||
line-height: $userAvatarLength;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title{
|
.all-component-class {
|
||||||
display: inline-block;
|
clear: right;
|
||||||
padding: 0 0 0 15px;
|
|
||||||
position: relative;
|
|
||||||
top: -5px;
|
|
||||||
|
|
||||||
h1{
|
.widgets-list {
|
||||||
font-size: 1.125rem;
|
display: flex;
|
||||||
font-weight: 500;
|
justify-content: space-between;
|
||||||
line-height: 1.75rem;
|
overflow-x: scroll;
|
||||||
}
|
padding-bottom: 10px;
|
||||||
span{
|
|
||||||
font-size: 14px;
|
.widgets-list-item {
|
||||||
color: rgba(0,0,0,.45);
|
margin-right: 10px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.widgets-list-item:last-child {
|
||||||
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.project-detail{
|
.widgets-wrapper-bg {
|
||||||
color: rgba(0,0,0,.45);
|
background: rgba(180, 180, 180, .2);
|
||||||
height: 65px;
|
min-height: 500px;
|
||||||
img {
|
}
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
}
|
|
||||||
.name{
|
|
||||||
margin-left: 1rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
display: inline-block;
|
|
||||||
color: rgba(0,0,0,.85);
|
|
||||||
position: relative;
|
|
||||||
top: -5px;
|
|
||||||
}
|
|
||||||
.slogan{
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 5px 0;
|
|
||||||
overflow:hidden;
|
|
||||||
text-overflow:ellipsis;
|
|
||||||
white-space:nowrap;
|
|
||||||
}
|
|
||||||
.team{
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.activity{
|
.widgets-wrapper .sortable-ghost {
|
||||||
padding: 0;
|
opacity: 0.5;
|
||||||
.activity-avatar{
|
}
|
||||||
width: 40px;
|
|
||||||
|
.set-component-bg{
|
||||||
|
background:rgba(255, 255, 255, 0.5);
|
||||||
|
border: 1px solid rgba(0,0,0,.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 5px;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-overlay label {
|
||||||
|
background: #409EFF;
|
||||||
|
color: #fff;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
padding: 0 30px;
|
||||||
}
|
border-radius: 40px;
|
||||||
.activity-detail{
|
font-size: 18px;
|
||||||
padding: 10px;
|
display: flex;
|
||||||
line-height: 15px;
|
align-items: center;
|
||||||
font-size: 14px;
|
justify-content: center;
|
||||||
color: rgba(0,0,0,.85);
|
cursor: move;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.chart {
|
|
||||||
height: 408px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-divider--horizontal{
|
.customize-overlay label i {
|
||||||
margin: 4px 0;
|
margin-right: 15px;
|
||||||
background: 0 0;
|
font-size: 24px;
|
||||||
border-top: 1px solid #e8eaec;
|
}
|
||||||
|
|
||||||
|
.customize-overlay .close {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customize-overlay .close:focus, .customize-overlay .close:hover {
|
||||||
|
background: #76B1F9;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
.el-card, .el-message {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -29,34 +29,35 @@ export const crudOptions = (vm) => {
|
||||||
iconLoaded: 'el-icon-loading' // 美化loading图标
|
iconLoaded: 'el-icon-loading' // 美化loading图标
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rowHandle: {
|
// rowHandle: {
|
||||||
show: false,
|
// show: false,
|
||||||
width: 140,
|
// width: 140,
|
||||||
view: {
|
// view: {
|
||||||
thin: true,
|
// thin: true,
|
||||||
text: '',
|
// text: '',
|
||||||
show: false,
|
// show: false,
|
||||||
disabled () {
|
// disabled () {
|
||||||
return !vm.hasPermissions('Retrieve')
|
// return !vm.hasPermissions('Retrieve')
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
edit: {
|
// edit: {
|
||||||
thin: true,
|
// thin: true,
|
||||||
text: '',
|
// text: '',
|
||||||
show: false,
|
// show: false,
|
||||||
disabled () {
|
// disabled () {
|
||||||
return !vm.hasPermissions('Update')
|
// return !vm.hasPermissions('Update')
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
remove: {
|
// remove: {
|
||||||
thin: true,
|
// thin: true,
|
||||||
text: '',
|
// text: '',
|
||||||
show: false,
|
// show: false,
|
||||||
disabled () {
|
// disabled () {
|
||||||
return !vm.hasPermissions('Delete')
|
// return !vm.hasPermissions('Delete')
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
|
rowHandle: false,
|
||||||
viewOptions: {
|
viewOptions: {
|
||||||
componentType: 'form'
|
componentType: 'form'
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,13 +15,7 @@ export const crudOptions = (vm) => {
|
||||||
return !vm.hasPermissions('Retrieve')
|
return !vm.hasPermissions('Retrieve')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
edit: {
|
edit: false,
|
||||||
thin: true,
|
|
||||||
text: '',
|
|
||||||
disabled () {
|
|
||||||
return !vm.hasPermissions('Update')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remove: {
|
remove: {
|
||||||
thin: true,
|
thin: true,
|
||||||
text: '',
|
text: '',
|
||||||
|
|
|
@ -4,12 +4,11 @@ export const crudOptions = (vm) => {
|
||||||
compact: true
|
compact: true
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
tableType: 'vxe-table',
|
// tableType: 'vxe-table',
|
||||||
rowKey: true, // 必须设置,true or false
|
// rowKey: true, // 必须设置,true or false
|
||||||
rowId: 'id',
|
rowId: 'id',
|
||||||
height: '100%', // 表格高度100%, 使用toolbar必须设置
|
height: '100%', // 表格高度100%, 使用toolbar必须设置
|
||||||
highlightCurrentRow: false
|
highlightCurrentRow: false,
|
||||||
|
|
||||||
},
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
|
@ -284,7 +283,8 @@ export const crudOptions = (vm) => {
|
||||||
title: '登录时间',
|
title: '登录时间',
|
||||||
key: 'create_datetime',
|
key: 'create_datetime',
|
||||||
width: 160,
|
width: 160,
|
||||||
type: 'datetime'
|
type: 'datetime',
|
||||||
|
sortable: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,7 @@ export const crudOptions = (vm) => {
|
||||||
edit: {
|
edit: {
|
||||||
thin: true,
|
thin: true,
|
||||||
text: '',
|
text: '',
|
||||||
show () {
|
show: false,
|
||||||
return vm.tabActivted !== 'receive'
|
|
||||||
},
|
|
||||||
disabled () {
|
disabled () {
|
||||||
return !vm.hasPermissions('Update')
|
return !vm.hasPermissions('Update')
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ export const crudOptions = (vm) => {
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
tableType: 'vxe-table',
|
// tableType: 'vxe-table',
|
||||||
rowKey: true,
|
//rowKey: true,
|
||||||
rowId: 'id'
|
rowId: 'id'
|
||||||
},
|
},
|
||||||
selectionRow: {
|
selectionRow: {
|
||||||
|
@ -149,6 +149,7 @@ export const crudOptions = (vm) => {
|
||||||
{
|
{
|
||||||
title: '姓名',
|
title: '姓名',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
sortable: 'custom',
|
||||||
minWidth: 90,
|
minWidth: 90,
|
||||||
search: {
|
search: {
|
||||||
disabled: false
|
disabled: false
|
||||||
|
|
Loading…
Reference in New Issue