Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a2739774a | ||
|
|
012f3a2f63 | ||
|
|
64c5d505e7 | ||
|
|
3914432d9f | ||
|
|
cc6ac68223 | ||
|
|
05ee833fe5 | ||
|
|
ff736aae93 | ||
|
|
c0a68f91ca | ||
|
|
8d6abeb891 | ||
|
|
163e5eb2db | ||
|
|
e786f60cdd | ||
|
|
abe2db3c82 | ||
|
|
fa734dd479 | ||
|
|
6e9b94aed2 | ||
|
|
2a9f5be895 | ||
|
|
2ea34bfbd5 | ||
|
|
edbd6862a2 | ||
|
|
6c99a78821 | ||
|
|
3db048e650 | ||
|
|
e470a716e5 | ||
|
|
95bf503529 | ||
|
|
4e9155f09b | ||
|
|
3e60c5b5f9 | ||
|
|
b72fb5238f | ||
|
|
74d89c3968 | ||
|
|
5eab2b6e4f | ||
|
|
d2f14e41ad | ||
|
|
483863e238 | ||
|
|
ad95bea301 | ||
|
|
344f754fc7 | ||
|
|
749f5d80d2 | ||
|
|
ac3bfb6b80 | ||
|
|
ef48bdd0df | ||
|
|
a2e07a89e1 | ||
|
|
7b55a3db64 | ||
|
|
3c8b4b6ac0 | ||
|
|
e8212501e2 | ||
|
|
fa063a8611 | ||
|
|
b89f1671c3 | ||
|
|
c6c54d8013 | ||
|
|
0005d45d85 | ||
|
|
1052f6a07b | ||
|
|
5dcbae292a | ||
|
|
ed915aa2cb | ||
|
|
82a0ef612a | ||
|
|
8ea49866bc | ||
|
|
a0a7c25b18 | ||
|
|
f8c8f2963b | ||
|
|
5a980f3b54 | ||
|
|
c0be63afcd | ||
|
|
150b92163f | ||
|
|
3a25cdb53c | ||
|
|
45dcda0cc0 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,4 +4,6 @@
|
||||
|
||||
.history/
|
||||
.vscode/
|
||||
web/package-lock.json
|
||||
web/package-lock.json
|
||||
|
||||
*.bat
|
||||
@@ -8,25 +8,25 @@ https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.security.websocket import AllowedHostsOriginValidator
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
from channels.security.websocket import AllowedHostsOriginValidator
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||
|
||||
http_application = get_asgi_application()
|
||||
|
||||
from application.routing import websocket_urlpatterns
|
||||
from application.ws_routing import websocket_urlpatterns
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
"http": http_application,
|
||||
'websocket': AllowedHostsOriginValidator(
|
||||
AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns # 指明路由文件是devops/routing.py
|
||||
AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns # 指明路由文件是devops/routing.py
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
})
|
||||
|
||||
@@ -399,8 +399,12 @@ DICTIONARY_CONFIG = {}
|
||||
# ================================================= #
|
||||
# 租户共享app
|
||||
TENANT_SHARED_APPS = []
|
||||
# 普通租户独有app
|
||||
TENANT_EXCLUSIVE_APPS = []
|
||||
# 插件 urlpatterns
|
||||
PLUGINS_URL_PATTERNS = []
|
||||
# 所有模式有的
|
||||
SHARED_APPS = []
|
||||
# ********** 一键导入插件配置开始 **********
|
||||
# 例如:
|
||||
# from dvadmin_upgrade_center.settings import * # 升级中心
|
||||
|
||||
33
backend/application/sse_views.py
Normal file
33
backend/application/sse_views.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# views.py
|
||||
import time
|
||||
|
||||
import jwt
|
||||
from django.http import StreamingHttpResponse
|
||||
|
||||
from application import settings
|
||||
from dvadmin.system.models import MessageCenterTargetUser
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
def event_stream(user_id):
|
||||
last_sent_time = 0
|
||||
|
||||
while True:
|
||||
# 从 Redis 中获取最后数据库变更时间
|
||||
last_db_change_time = cache.get('last_db_change_time', 0)
|
||||
# 只有当数据库发生变化时才检查总数
|
||||
if last_db_change_time and last_db_change_time > last_sent_time:
|
||||
count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count()
|
||||
yield f"data: {count}\n\n"
|
||||
last_sent_time = time.time()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def sse_view(request):
|
||||
token = request.GET.get('token')
|
||||
decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
|
||||
user_id = decoded.get('user_id')
|
||||
response = StreamingHttpResponse(event_stream(user_id), content_type='text/event-stream')
|
||||
response['Cache-Control'] = 'no-cache'
|
||||
return response
|
||||
@@ -15,6 +15,7 @@ Including another URLconf
|
||||
"""
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path, include, re_path
|
||||
from django.views.static import serve
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
@@ -24,6 +25,7 @@ from rest_framework_simplejwt.views import (
|
||||
|
||||
from application import dispatch
|
||||
from application import settings
|
||||
from application.sse_views import sse_view
|
||||
from dvadmin.system.views.dictionary import InitDictionaryViewSet
|
||||
from dvadmin.system.views.login import (
|
||||
LoginView,
|
||||
@@ -40,6 +42,7 @@ dispatch.init_system_config()
|
||||
dispatch.init_dictionary()
|
||||
# =========== 初始化系统配置 =================
|
||||
|
||||
permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ]
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Snippets API",
|
||||
@@ -50,7 +53,7 @@ schema_view = get_schema_view(
|
||||
license=openapi.License(name="BSD License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=(permissions.IsAuthenticated,),
|
||||
permission_classes=permission_classes,
|
||||
generator_class=CustomOpenAPISchemaGenerator,
|
||||
)
|
||||
# 前端页面映射
|
||||
@@ -115,6 +118,8 @@ urlpatterns = (
|
||||
# 前端页面映射
|
||||
path('web/', web_view, name='web_view'),
|
||||
path('web/<path:filename>', serve_web_files, name='serve_web_files'),
|
||||
# sse
|
||||
path('sse/', sse_view, name='sse'),
|
||||
]
|
||||
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
|
||||
|
||||
@@ -180,4 +180,4 @@ def create_message_push(title: str, content: str, target_type: int = 0, target_u
|
||||
"type": "push.message",
|
||||
"json": {**message, 'unread': unread_count}
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -4,4 +4,4 @@ from application.websocketConfig import MegCenter
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
|
||||
]
|
||||
]
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import os
|
||||
|
||||
exclude = ["venv"] # 需要排除的文件目录
|
||||
exclude = ["venv", ".venv"] # 需要排除的文件目录
|
||||
for root, dirs, files in os.walk('.'):
|
||||
dirs[:] = list(set(dirs) - set(exclude))
|
||||
if 'migrations' in dirs:
|
||||
|
||||
@@ -4,3 +4,7 @@ from django.apps import AppConfig
|
||||
class SystemConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'dvadmin.system'
|
||||
|
||||
def ready(self):
|
||||
# 注册信号
|
||||
import dvadmin.system.signals # 确保路径正确
|
||||
|
||||
@@ -546,5 +546,50 @@
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "文件存储引擎",
|
||||
"value": "file_engine",
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 9,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "本地",
|
||||
"value": "local",
|
||||
"type": 0,
|
||||
"color": "primary",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "阿里云oss",
|
||||
"value": "oss",
|
||||
"type": 0,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "腾讯cos",
|
||||
"value": "cos",
|
||||
"type": 0,
|
||||
"color": "warning",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 3,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -11,326 +11,11 @@
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": null,
|
||||
"children": [
|
||||
{
|
||||
"name": "菜单管理",
|
||||
"icon": "iconfont icon-caidan",
|
||||
"sort": 1,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/menu",
|
||||
"component": "system/menu/index",
|
||||
"component_name": "menu",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 1,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "menu:Search",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "menu:Retrieve",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "查询所有",
|
||||
"value": "menu:SearchAll",
|
||||
"api": "/api/system/menu/get_all_menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "路由",
|
||||
"value": "menu:router",
|
||||
"api": "/api/system/menu/web_router/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "查询按钮权限",
|
||||
"value": "btn:Search",
|
||||
"api": "/api/system/menu_button/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "查询列权限",
|
||||
"value": "column:Search",
|
||||
"api": "/api/system/column/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "menu:Create",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "上移",
|
||||
"value": "menu:MoveUp",
|
||||
"api": "/api/system/menu/mode_up/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "下移",
|
||||
"value": "menu:MoveDown",
|
||||
"api": "/api/system/menu/mode_down/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "新增按钮权限",
|
||||
"value": "btn:Create",
|
||||
"api": "/api/system/menu_button/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "新增列权限",
|
||||
"value": "column:Create",
|
||||
"api": "/api/system/column/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "自动匹配列权限",
|
||||
"value": "column:Match",
|
||||
"api": "/api/system/column/auto_match_fields/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "menu:Update",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "修改按钮权限",
|
||||
"value": "btn:Update",
|
||||
"api": "/api/system/menu_button/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "编辑列权限",
|
||||
"value": "column:Update",
|
||||
"api": "/api/system/column/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "menu:Delete",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "删除按钮权限",
|
||||
"value": "btn:Delete",
|
||||
"api": "/api/system/menu_button/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "删除列权限",
|
||||
"value": "column:Delete",
|
||||
"api": "/api/system/column/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
],
|
||||
"menu_field": []
|
||||
},
|
||||
{
|
||||
"name": "部门管理",
|
||||
"icon": "ele-OfficeBuilding",
|
||||
"sort": 3,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/dept",
|
||||
"component": "system/dept/index",
|
||||
"component_name": "dept",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 1,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "dept:Search",
|
||||
"api": "/api/system/dept/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "dept:Retrieve",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "获取所有部门",
|
||||
"value": "dept:SearchAll",
|
||||
"api": "/api/system/dept/all_dept/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "部门顶部信息",
|
||||
"value": "dept:HeaderInfo",
|
||||
"api": "/api/system/dept/dept_info/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "dept:Create",
|
||||
"api": "/api/system/dept/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "上移",
|
||||
"value": "dept:MoveUp",
|
||||
"api": "/api/system/dept/mode_up/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "下移",
|
||||
"value": "dept:MoveDown",
|
||||
"api": "/api/system/dept/mode_down/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "dept:Update",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "dept:Delete",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
],
|
||||
"menu_field": []
|
||||
},
|
||||
{
|
||||
"name": "角色管理",
|
||||
"icon": "ele-ColdDrink",
|
||||
"sort": 4,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/role",
|
||||
"component": "system/role/index",
|
||||
"component_name": "role",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 1,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "role:Search",
|
||||
"api": "/api/system/role/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "role:Retrieve",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "权限配置",
|
||||
"value": "role:Permission",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "role:Create",
|
||||
"api": "/api/system/role/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "role:Update",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "保存",
|
||||
"value": "role:Save",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "role:Delete",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
],
|
||||
"menu_field": [
|
||||
{
|
||||
"field_name": "create_datetime",
|
||||
"title": "创建时间",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "creator",
|
||||
"title": "创建人",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "dept_belong_id",
|
||||
"title": "数据归属部门",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "description",
|
||||
"title": "描述",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "id",
|
||||
"title": "Id",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "key",
|
||||
"title": "权限字符",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "modifier",
|
||||
"title": "修改人",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "name",
|
||||
"title": "角色名称",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "sort",
|
||||
"title": "角色顺序",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "status",
|
||||
"title": "角色状态",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "update_datetime",
|
||||
"title": "修改时间",
|
||||
"model": "Role"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "用户管理",
|
||||
"icon": "iconfont icon-icon-",
|
||||
"sort": 6,
|
||||
"sort": 1,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/user",
|
||||
@@ -339,7 +24,6 @@
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 1,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
@@ -348,18 +32,24 @@
|
||||
"api": "/api/system/user/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "user:Retrieve",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "user:Create",
|
||||
"api": "/api/system/user/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "user:Update",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "user:Delete",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "导出",
|
||||
"value": "user:Export",
|
||||
@@ -373,10 +63,16 @@
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "user:Update",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 2
|
||||
"name": "获取导入模板",
|
||||
"value": "user:ImportTemplate",
|
||||
"api": "/api/system/user/import/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "批量更新模板",
|
||||
"value": "user:BatchUpdateTemplate",
|
||||
"api": "/api/system/user/update_template/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "重设密码",
|
||||
@@ -386,15 +82,9 @@
|
||||
},
|
||||
{
|
||||
"name": "重置密码",
|
||||
"value": "user:DefaultPassword",
|
||||
"value": "user:ResetDefaultPassword",
|
||||
"api": "/api/system/user/{id}/reset_to_default_password/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "user:Delete",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
],
|
||||
"menu_field": [
|
||||
@@ -475,6 +165,359 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "菜单管理",
|
||||
"icon": "iconfont icon-caidan",
|
||||
"sort": 2,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/menu",
|
||||
"component": "system/menu/index",
|
||||
"component_name": "menu",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "menu:Search",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "单例",
|
||||
"value": "menu:Retrieve",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "menu:Create",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "menu:Update",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "menu:Delete",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "查询所有",
|
||||
"value": "menu:SearchAll",
|
||||
"api": "/api/system/menu/get_all_menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "路由",
|
||||
"value": "menu:router",
|
||||
"api": "/api/system/menu/web_router/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "查询按钮",
|
||||
"value": "menu:SearchButton",
|
||||
"api": "/api/system/menu_button/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增按钮",
|
||||
"value": "menu:CreateButton",
|
||||
"api": "/api/system/menu_button/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑按钮",
|
||||
"value": "menu:UpdateButton",
|
||||
"api": "/api/system/menu_button/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除按钮",
|
||||
"value": "menu:DeleteButton",
|
||||
"api": "/api/system/menu_button/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "上移",
|
||||
"value": "menu:MoveUp",
|
||||
"api": "/api/system/menu/mode_up/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "下移",
|
||||
"value": "menu:MoveDown",
|
||||
"api": "/api/system/menu/mode_down/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "查询列权限",
|
||||
"value": "column:Search",
|
||||
"api": "/api/system/column/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增列权限",
|
||||
"value": "column:Create",
|
||||
"api": "/api/system/column/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑列权限",
|
||||
"value": "column:Update",
|
||||
"api": "/api/system/column/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除列权限",
|
||||
"value": "column:Delete",
|
||||
"api": "/api/system/column/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "自动匹配列权限",
|
||||
"value": "column:Match",
|
||||
"api": "/api/system/column/auto_match_fields/",
|
||||
"method": 1
|
||||
}
|
||||
],
|
||||
"menu_field": []
|
||||
},
|
||||
{
|
||||
"name": "部门管理",
|
||||
"icon": "ele-OfficeBuilding",
|
||||
"sort": 3,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/dept",
|
||||
"component": "system/dept/index",
|
||||
"component_name": "dept",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "dept:Search",
|
||||
"api": "/api/system/dept/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "dept:Retrieve",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "获取所有部门",
|
||||
"value": "dept:SearchAll",
|
||||
"api": "/api/system/dept/all_dept/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "部门顶部信息",
|
||||
"value": "dept:HeaderInfo",
|
||||
"api": "/api/system/dept/dept_info/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "dept:Create",
|
||||
"api": "/api/system/dept/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "上移",
|
||||
"value": "dept:MoveUp",
|
||||
"api": "/api/system/dept/mode_up/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "下移",
|
||||
"value": "dept:MoveDown",
|
||||
"api": "/api/system/dept/mode_down/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "dept:Update",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "dept:Delete",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
],
|
||||
"menu_field": []
|
||||
},
|
||||
{
|
||||
"name": "角色管理",
|
||||
"icon": "ele-ColdDrink",
|
||||
"sort": 4,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/role",
|
||||
"component": "system/role/index",
|
||||
"component_name": "role",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "role:Search",
|
||||
"api": "/api/system/role/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "单例",
|
||||
"value": "role:Retrieve",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "role:Create",
|
||||
"api": "/api/system/role/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "role:Update",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "role:Delete",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 3
|
||||
},
|
||||
{
|
||||
"name": "获取所有可授权数据范围的部门",
|
||||
"value": "role:AllDataRangeDept",
|
||||
"api": "/api/system/role_menu_button_permision/role_to_dept_all/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "获取所有可授权菜单",
|
||||
"value": "role:AllCanMenu",
|
||||
"api": "/api/system/role_menu_button_permision/get_role_menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "获取所有已授权用户",
|
||||
"value": "role:AllAuthorizedUser",
|
||||
"api": "/api/system/role/get_role_users/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "获取菜单所有可授权按钮",
|
||||
"value": "role:AllMenuButton",
|
||||
"api": "/api/system/role_menu_button_permision/get_role_menu_btn_field/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "授权菜单",
|
||||
"value": "role:SetMenu",
|
||||
"api": "/api/system/role_menu_button_permision/set_role_menu/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "授权菜单按钮",
|
||||
"value": "role:SetMenuButton",
|
||||
"api": "/api/system/role_menu_button_permision/set_role_menu_btn/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "授权数据范围",
|
||||
"value": "role:SetDataRange",
|
||||
"api": "/api/system/role_menu_button_permision/set_role_menu_btn_data_range/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "获取所有用户",
|
||||
"value": "role:AllUser",
|
||||
"api": "/api/system/user/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "授权用户予角色",
|
||||
"value": "role:SetUserRole",
|
||||
"api": "/api/system/role/{id}/set_role_users/",
|
||||
"method": 2
|
||||
}
|
||||
],
|
||||
"menu_field": [
|
||||
{
|
||||
"field_name": "create_datetime",
|
||||
"title": "创建时间",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "creator",
|
||||
"title": "创建人",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "dept_belong_id",
|
||||
"title": "数据归属部门",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "description",
|
||||
"title": "描述",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "id",
|
||||
"title": "Id",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "key",
|
||||
"title": "权限字符",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "modifier",
|
||||
"title": "修改人",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "name",
|
||||
"title": "角色名称",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "sort",
|
||||
"title": "角色顺序",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "status",
|
||||
"title": "角色状态",
|
||||
"model": "Role"
|
||||
},
|
||||
{
|
||||
"field_name": "update_datetime",
|
||||
"title": "修改时间",
|
||||
"model": "Role"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "消息中心",
|
||||
"icon": "iconfont icon-xiaoxizhongxin",
|
||||
@@ -690,35 +733,11 @@
|
||||
"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
|
||||
"value": "downloadCenter:Search",
|
||||
"api": "/api/system/download_center/"
|
||||
}
|
||||
]
|
||||
],
|
||||
"menu_field": []
|
||||
}
|
||||
],
|
||||
"menu_button": [],
|
||||
|
||||
@@ -235,5 +235,252 @@
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "文件存储配置",
|
||||
"key": "file_storage",
|
||||
"value": null,
|
||||
"sort": 0,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": null,
|
||||
"placeholder": null,
|
||||
"setting": null,
|
||||
"children": [
|
||||
{
|
||||
"title": "存储引擎",
|
||||
"key": "file_engine",
|
||||
"value": "local",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 4,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请选择存储引擎",
|
||||
"setting": "file_engine",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "文件是否备份",
|
||||
"key": "file_backup",
|
||||
"value": false,
|
||||
"sort": 2,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 9,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "启用云存储时,文件是否备份到本地",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "阿里云-AccessKey",
|
||||
"key": "aliyun_access_key",
|
||||
"value": null,
|
||||
"sort": 3,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入AccessKey",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "阿里云-Secret",
|
||||
"key": "aliyun_access_secret",
|
||||
"value": null,
|
||||
"sort": 4,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入Secret",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "阿里云-Endpoint",
|
||||
"key": "aliyun_endpoint",
|
||||
"value": null,
|
||||
"sort": 5,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入Endpoint",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "阿里云-上传路径",
|
||||
"key": "aliyun_path",
|
||||
"value": "/media/",
|
||||
"sort": 5,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入上传路径",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "阿里云-Bucket",
|
||||
"key": "aliyun_bucket",
|
||||
"value": null,
|
||||
"sort": 7,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入Bucket",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},{
|
||||
"title": "阿里云-cdn地址",
|
||||
"key": "aliyun_cdn_url",
|
||||
"value": null,
|
||||
"sort": 7,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入cdn地址",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "腾讯云-SecretId",
|
||||
"key": "tencent_secret_id",
|
||||
"value": null,
|
||||
"sort": 8,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入SecretId",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "腾讯云-SecretKey",
|
||||
"key": "tencent_secret_key",
|
||||
"value": null,
|
||||
"sort": 9,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入SecretKey",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "腾讯云-Region",
|
||||
"key": "tencent_region",
|
||||
"value": null,
|
||||
"sort": 10,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入Region",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "腾讯云-Bucket",
|
||||
"key": "tencent_bucket",
|
||||
"value": null,
|
||||
"sort": 11,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入Bucket",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"title": "腾讯云-上传路径",
|
||||
"key": "tencent_path",
|
||||
"value": "/media/",
|
||||
"sort": 12,
|
||||
"status": false,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"required": false,
|
||||
"message": "必填项不能为空"
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入上传路径",
|
||||
"setting": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -29,7 +29,7 @@ class Command(BaseCommand):
|
||||
def serializer_data(self, serializer, query_set: QuerySet):
|
||||
serializer = serializer(query_set, many=True)
|
||||
data = json.loads(json.dumps(serializer.data, ensure_ascii=False))
|
||||
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w') as f:
|
||||
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w',encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
return
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from application import dispatch
|
||||
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
|
||||
|
||||
from dvadmin3_flow.base_model import FlowBaseModel
|
||||
class Role(CoreModel,FlowBaseModel):
|
||||
|
||||
class Role(CoreModel):
|
||||
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
|
||||
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
|
||||
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
|
||||
@@ -63,6 +63,8 @@ class Users(CoreModel, AbstractUser):
|
||||
help_text="关联岗位")
|
||||
role = models.ManyToManyField(to="Role", blank=True, verbose_name="关联角色", db_constraint=False,
|
||||
help_text="关联角色")
|
||||
current_role = models.ForeignKey(to=Role, null=True, blank=True, db_constraint=False, on_delete=models.SET_NULL,
|
||||
verbose_name="当前登录角色", help_text="当前登录角色", related_name='current_role_set')
|
||||
dept = models.ForeignKey(
|
||||
to="Dept",
|
||||
verbose_name="所属部门",
|
||||
@@ -72,12 +74,26 @@ class Users(CoreModel, AbstractUser):
|
||||
blank=True,
|
||||
help_text="关联部门",
|
||||
)
|
||||
manage_dept = models.ManyToManyField(
|
||||
to="Dept",
|
||||
verbose_name="管理部门",
|
||||
db_constraint=False,
|
||||
blank=True,
|
||||
help_text="管理部门",
|
||||
related_name='manage_dept_set'
|
||||
)
|
||||
login_error_count = models.IntegerField(default=0, verbose_name="登录错误次数", help_text="登录错误次数")
|
||||
pwd_change_count = models.IntegerField(default=0,blank=True, verbose_name="密码修改次数", help_text="密码修改次数")
|
||||
objects = CustomUserManager()
|
||||
|
||||
def set_password(self, raw_password):
|
||||
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
||||
if raw_password:
|
||||
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.name == "":
|
||||
self.name = self.username
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_users"
|
||||
@@ -162,6 +178,23 @@ class Dept(CoreModel):
|
||||
cls.recursion_all_dept(ele.get("id"), dept_all_list, dept_list)
|
||||
return list(set(dept_list))
|
||||
|
||||
@classmethod
|
||||
def recursion_all_parent_dept(cls, dept_id: int, dept_list=None):
|
||||
"""
|
||||
递归获取部门的所有上级部门
|
||||
:param dept_id: 需要获取的id
|
||||
:param dept_list: 递归list
|
||||
:return:
|
||||
"""
|
||||
if dept_list is None:
|
||||
dept_list = [dept_id]
|
||||
current_dept = Dept.objects.filter(id=dept_id).values('parent').first()
|
||||
if current_dept and current_dept.get('parent'):
|
||||
parent_id = current_dept.get('parent')
|
||||
dept_list.append(parent_id)
|
||||
cls.recursion_all_parent_dept(parent_id, dept_list)
|
||||
return list(set(dept_list))
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_dept"
|
||||
verbose_name = "部门表"
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
from django.dispatch import Signal
|
||||
import time
|
||||
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.core.cache import cache
|
||||
from dvadmin.system.models import MessageCenterTargetUser
|
||||
|
||||
# 初始化信号
|
||||
pre_init_complete = Signal()
|
||||
detail_init_complete = Signal()
|
||||
@@ -10,3 +16,12 @@ post_tenants_init_complete = Signal()
|
||||
post_tenants_all_init_complete = Signal()
|
||||
# 租户创建完成信号
|
||||
tenants_create_complete = Signal()
|
||||
|
||||
# 全局变量用于标记最后修改时间
|
||||
last_db_change_time = time.time()
|
||||
|
||||
|
||||
@receiver(post_save, sender=MessageCenterTargetUser)
|
||||
@receiver(post_delete, sender=MessageCenterTargetUser)
|
||||
def update_last_change_time(sender, **kwargs):
|
||||
cache.set('last_db_change_time', time.time(), timeout=None) # 设置永不超时的键值对
|
||||
|
||||
@@ -44,6 +44,11 @@ class DownloadCenterViewSet(CustomModelViewSet):
|
||||
extra_filter_class = []
|
||||
|
||||
def get_queryset(self):
|
||||
# 判断是否是 Swagger 文档生成阶段,防止报错
|
||||
if getattr(self, 'swagger_fake_view', False):
|
||||
return self.queryset.model.objects.none()
|
||||
|
||||
# 正常请求下的逻辑
|
||||
if self.request.user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(creator=self.request.user)
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.conf import settings
|
||||
from django.db import connection
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from application import dispatch
|
||||
from dvadmin.system.models import FileList
|
||||
@@ -35,8 +36,8 @@ class FileSerializer(CustomModelSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
def create(self, validated_data):
|
||||
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
|
||||
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
|
||||
file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
|
||||
file_backup = dispatch.get_system_config_values("file_storage.file_backup")
|
||||
file = self.initial_data.get('file')
|
||||
file_size = file.size
|
||||
validated_data['name'] = str(file)
|
||||
@@ -52,15 +53,15 @@ class FileSerializer(CustomModelSerializer):
|
||||
if file_backup:
|
||||
validated_data['url'] = file
|
||||
if file_engine == 'oss':
|
||||
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
|
||||
file_path = ali_oss_upload(file)
|
||||
from dvadmin.utils.aliyunoss import ali_oss_upload
|
||||
file_path = ali_oss_upload(file, file_name=validated_data['name'])
|
||||
if file_path:
|
||||
validated_data['file_url'] = file_path
|
||||
else:
|
||||
raise ValueError("上传失败")
|
||||
elif file_engine == 'cos':
|
||||
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
|
||||
file_path = tencent_cos_upload(file)
|
||||
from dvadmin.utils.tencentcos import tencent_cos_upload
|
||||
file_path = tencent_cos_upload(file, file_name=validated_data['name'])
|
||||
if file_path:
|
||||
validated_data['file_url'] = file_path
|
||||
else:
|
||||
@@ -106,7 +107,7 @@ class FileViewSet(CustomModelViewSet):
|
||||
queryset = FileList.objects.all()
|
||||
serializer_class = FileSerializer
|
||||
filter_class = FileFilter
|
||||
permission_classes = []
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@action(methods=['GET'], detail=False)
|
||||
def get_all(self, request):
|
||||
|
||||
@@ -36,7 +36,7 @@ class MessageCenterSerializer(CustomModelSerializer):
|
||||
return serializer.data
|
||||
|
||||
def get_user_info(self, instance, parsed_query):
|
||||
if instance.target_type in (1,2,3):
|
||||
if instance.target_type in (1, 2, 3):
|
||||
return []
|
||||
users = instance.target_user.all()
|
||||
# You can do what ever you want in here
|
||||
@@ -108,7 +108,7 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
||||
return serializer.data
|
||||
|
||||
def get_user_info(self, instance, parsed_query):
|
||||
if instance.target_type in (1,2,3):
|
||||
if instance.target_type in (1, 2, 3):
|
||||
return []
|
||||
users = instance.target_user.all()
|
||||
# You can do what ever you want in here
|
||||
@@ -138,7 +138,6 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
def websocket_push(user_id, message):
|
||||
"""
|
||||
主动推送消息
|
||||
@@ -153,7 +152,6 @@ def websocket_push(user_id, message):
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
消息中心-新增-序列化器
|
||||
|
||||
@@ -90,6 +90,8 @@ class UserCreateSerializer(CustomModelSerializer):
|
||||
data = super().save(**kwargs)
|
||||
data.dept_belong_id = data.dept_id
|
||||
data.save()
|
||||
if not self.validated_data.get('manage_dept', None):
|
||||
data.manage_dept.add(data.dept_id)
|
||||
data.post.set(self.initial_data.get("post", []))
|
||||
return data
|
||||
|
||||
@@ -127,6 +129,8 @@ class UserUpdateSerializer(CustomModelSerializer):
|
||||
data = super().save(**kwargs)
|
||||
data.dept_belong_id = data.dept_id
|
||||
data.save()
|
||||
if not self.validated_data.get('manage_dept', None):
|
||||
data.manage_dept.add(data.dept_id)
|
||||
data.post.set(self.initial_data.get("post", []))
|
||||
return data
|
||||
|
||||
@@ -426,12 +430,9 @@ class UserViewSet(CustomModelViewSet):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
else:
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
# print(queryset.values('id','name','dept__id'))
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True, request=request)
|
||||
# print(serializer.data)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
62
backend/dvadmin/utils/aliyunoss.py
Normal file
62
backend/dvadmin/utils/aliyunoss.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import oss2
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from application import dispatch
|
||||
|
||||
|
||||
# 进度条
|
||||
# 当无法确定待上传的数据长度时,total_bytes的值为None。
|
||||
def percentage(consumed_bytes, total_bytes):
|
||||
if total_bytes:
|
||||
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
||||
print('\r{0}% '.format(rate), end='')
|
||||
|
||||
|
||||
def ali_oss_upload(file, file_name):
|
||||
"""
|
||||
阿里云OSS上传
|
||||
"""
|
||||
try:
|
||||
file.seek(0)
|
||||
file_read = file.read()
|
||||
except Exception as e:
|
||||
file_read = file
|
||||
if not file:
|
||||
raise ValidationError('请上传文件')
|
||||
# 转存到oss
|
||||
path_prefix = dispatch.get_system_config_values("file_storage.aliyun_path")
|
||||
if not path_prefix.endswith('/'):
|
||||
path_prefix = path_prefix + '/'
|
||||
if path_prefix.startswith('/'):
|
||||
path_prefix = path_prefix[1:]
|
||||
base_fil_name = f'{path_prefix}{file_name}'
|
||||
# 获取OSS配置
|
||||
# 获取的AccessKey
|
||||
access_key_id = dispatch.get_system_config_values("file_storage.aliyun_access_key")
|
||||
access_key_secret = dispatch.get_system_config_values("file_storage.aliyun_access_secret")
|
||||
auth = oss2.Auth(access_key_id, access_key_secret)
|
||||
# 这个是需要用特定的地址,不同地域的服务器地址不同,不要弄错了
|
||||
# 参考官网给的地址配置https://www.alibabacloud.com/help/zh/object-storage-service/latest/regions-and-endpoints#concept-zt4-cvy-5db
|
||||
endpoint = dispatch.get_system_config_values("file_storage.aliyun_endpoint")
|
||||
bucket_name = dispatch.get_system_config_values("file_storage.aliyun_bucket")
|
||||
if bucket_name.endswith(endpoint):
|
||||
bucket_name = bucket_name.replace(f'.{endpoint}', '')
|
||||
# 你的项目名称,类似于不同的项目上传的图片前缀url不同
|
||||
bucket = oss2.Bucket(auth, endpoint, bucket_name) # 项目名称
|
||||
# 生成外网访问的文件路径
|
||||
aliyun_cdn_url = dispatch.get_system_config_values("file_storage.aliyun_cdn_url")
|
||||
if aliyun_cdn_url:
|
||||
if aliyun_cdn_url.endswith('/'):
|
||||
aliyun_cdn_url = aliyun_cdn_url[1:]
|
||||
file_path = f"{aliyun_cdn_url}/{base_fil_name}"
|
||||
else:
|
||||
file_path = f"https://{bucket_name}.{endpoint}/{base_fil_name}"
|
||||
# 这个是阿里提供的SDK方法
|
||||
res = bucket.put_object(base_fil_name, file_read, progress_callback=percentage)
|
||||
# 如果上传状态是200 代表成功 返回文件外网访问路径
|
||||
if res.status == 200:
|
||||
return file_path
|
||||
else:
|
||||
return None
|
||||
@@ -15,15 +15,16 @@ import six
|
||||
from django.db import models
|
||||
from django.db.models import Q, F
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django_filters import utils, FilterSet
|
||||
from django_filters.constants import ALL_FIELDS
|
||||
from django_filters.filters import CharFilter, DateTimeFromToRangeFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters.utils import get_model_field
|
||||
from django_filters.utils import get_model_field, translate_validation, deprecate
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
from django_filters.conf import settings
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton
|
||||
from dvadmin.utils.models import CoreModel
|
||||
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton, Users
|
||||
from util.currency import recursion_down_fast
|
||||
|
||||
|
||||
class CoreModelFilterBankend(BaseFilterBackend):
|
||||
"""
|
||||
@@ -200,6 +201,86 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
return queryset.filter(dept_belong_id__in=list(set(dept_list)))
|
||||
|
||||
|
||||
class DataLevelPermissionsSubFilter(DataLevelPermissionsFilter):
|
||||
"""数据级权限过滤的子过滤器,过滤管理部门字段manage_dept"""
|
||||
|
||||
def _extracted_from_filter_queryset_33(self, request:Request, queryset, api, method):
|
||||
u:Users = request.user
|
||||
if u.is_superuser:
|
||||
return queryset
|
||||
# (0, "仅本人数据权限"),
|
||||
# (1, "本部门及以下数据权限"),
|
||||
# (2, "本部门数据权限"),
|
||||
# (3, "全部数据权限"),
|
||||
# (4, "自定数据权限")
|
||||
re_api = api
|
||||
_pk = request.parser_context["kwargs"].get('pk')
|
||||
if _pk: # 判断是否是单例查询
|
||||
re_api = re.sub(_pk,'{id}', api)
|
||||
role_id_list = request.user.role.values_list('id', flat=True)
|
||||
menu_button_ids = MenuButton.objects.filter(api=re_api,method=method).values_list('id', flat=True)
|
||||
role_permission_list = []
|
||||
if menu_button_ids:
|
||||
role_permission_list=RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_id_list,
|
||||
role__status=1,
|
||||
menu_button_id__in=menu_button_ids).values(
|
||||
'data_range'
|
||||
)
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_permission_list:
|
||||
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
||||
if ele.get("data_range") == 3:
|
||||
return queryset
|
||||
dataScope_list.append(ele.get("data_range"))
|
||||
dataScope_list = list(set(dataScope_list))
|
||||
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
|
||||
if 0 in dataScope_list:
|
||||
return queryset.filter(
|
||||
creator=request.user, dept_belong_id=u.dept_id
|
||||
)
|
||||
dept_list = []
|
||||
# 5. 自定数据权限 获取部门,根据部门过滤
|
||||
for ele in dataScope_list:
|
||||
if ele == 1:
|
||||
dept_list.append(u.dept_id)
|
||||
dept_list.extend(
|
||||
get_dept(
|
||||
u.dept_id,
|
||||
)
|
||||
)
|
||||
elif ele == 2:
|
||||
dept_list.append(u.dept_id)
|
||||
elif ele == 4:
|
||||
dept_ids = RoleMenuButtonPermission.objects.filter(
|
||||
role__in=role_id_list,
|
||||
role__status=1,
|
||||
data_range=4).values_list(
|
||||
'dept__id',flat=True
|
||||
)
|
||||
dept_list.extend(
|
||||
dept_ids
|
||||
)
|
||||
# 自己部门 交 管理部门
|
||||
if u.manage_dept.exists(): # 兼容旧数据
|
||||
for dept in u.manage_dept.all():
|
||||
dept_list.extend(recursion_down_fast(dept, 'parent', 'id'))
|
||||
else:
|
||||
dept_list = recursion_down_fast(u.dept, 'parent', 'id')
|
||||
dept_list = set(recursion_down_fast(u.dept)) & set(dept_list)
|
||||
# 自己创建的数据要能看到
|
||||
# 应对归属a管b、c等情况,如果自己创建数据则是a,不显式指定自己的数据就查不到
|
||||
if queryset.model._meta.model_name == 'dept':
|
||||
return queryset.filter(Q(id__in=dept_list) | Q(creator=u))
|
||||
return queryset.filter(Q(dept_belong_id__in=dept_list) | Q(creator=u))
|
||||
|
||||
|
||||
class DataLevelPermissionMargeFilter(DataLevelPermissionsFilter):
|
||||
def _extracted_from_filter_queryset_33(self, request, queryset, api, method):
|
||||
queryset = super()._extracted_from_filter_queryset_33(request, queryset, api, method)
|
||||
return DataLevelPermissionsSubFilter._extracted_from_filter_queryset_33(self, request, queryset, api, method)
|
||||
|
||||
|
||||
class CustomDjangoFilterBackend(DjangoFilterBackend):
|
||||
lookup_prefixes = {
|
||||
"^": "istartswith",
|
||||
@@ -240,14 +321,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
||||
|
||||
# TODO: remove assertion in 2.1
|
||||
if filterset_class is None and hasattr(view, "filter_class"):
|
||||
utils.deprecate(
|
||||
deprecate(
|
||||
"`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
|
||||
)
|
||||
filterset_class = getattr(view, "filter_class", None)
|
||||
|
||||
# TODO: remove assertion in 2.1
|
||||
if filterset_fields is None and hasattr(view, "filter_fields"):
|
||||
utils.deprecate(
|
||||
deprecate(
|
||||
"`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
|
||||
)
|
||||
self.filter_fields = getattr(view, "filter_fields", None)
|
||||
@@ -427,5 +508,5 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
||||
return queryset
|
||||
|
||||
if not filterset.is_valid() and self.raise_exception:
|
||||
raise utils.translate_validation(filterset.errors)
|
||||
raise translate_validation(filterset.errors)
|
||||
return filterset.qs
|
||||
|
||||
@@ -81,6 +81,26 @@ class SoftDeleteModel(models.Model):
|
||||
super().delete(using=using, *args, **kwargs)
|
||||
|
||||
|
||||
class CoreModelManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
is_deleted = getattr(self.model, 'is_soft_delete', False)
|
||||
flow_work_status = getattr(self.model, 'flow_work_status', False)
|
||||
queryset = super().get_queryset()
|
||||
if flow_work_status:
|
||||
queryset = queryset.filter(flow_work_status=1)
|
||||
if is_deleted:
|
||||
queryset = queryset.filter(is_deleted=False)
|
||||
return queryset
|
||||
def create(self,request: Request=None, **kwargs):
|
||||
data = {**kwargs}
|
||||
if request:
|
||||
request_user = request.user
|
||||
data["creator"] = request_user
|
||||
data["modifier"] = request_user.id
|
||||
data["dept_belong_id"] = request_user.dept_id
|
||||
# 调用父类的create方法执行实际的创建操作
|
||||
return super().create(**data)
|
||||
|
||||
class CoreModel(models.Model):
|
||||
"""
|
||||
核心标准抽象模型模型,可直接继承使用
|
||||
@@ -98,7 +118,8 @@ class CoreModel(models.Model):
|
||||
verbose_name="修改时间")
|
||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||
verbose_name="创建时间")
|
||||
|
||||
objects = CoreModelManager()
|
||||
all_objects = models.Manager()
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = '核心模型'
|
||||
|
||||
56
backend/dvadmin/utils/tencentcos.py
Normal file
56
backend/dvadmin/utils/tencentcos.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from application import dispatch
|
||||
from qcloud_cos import CosConfig
|
||||
from qcloud_cos import CosS3Client
|
||||
|
||||
|
||||
# 进度条
|
||||
# 当无法确定待上传的数据长度时,total_bytes的值为None。
|
||||
def percentage(consumed_bytes, total_bytes):
|
||||
if total_bytes:
|
||||
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
|
||||
print('\r{0}% '.format(rate), end='')
|
||||
|
||||
def tencent_cos_upload(file, file_name):
|
||||
try:
|
||||
file.seek(0)
|
||||
file_read = file.read()
|
||||
except Exception as e:
|
||||
file_read = file
|
||||
if not file:
|
||||
raise ValidationError('请上传文件')
|
||||
# 生成文件名
|
||||
path_prefix = dispatch.get_system_config_values("file_storage.tencent_path")
|
||||
if not path_prefix.endswith('/'):
|
||||
path_prefix = path_prefix + '/'
|
||||
if path_prefix.startswith('/'):
|
||||
path_prefix = path_prefix[1:]
|
||||
base_fil_name = f'{path_prefix}{file_name}'
|
||||
# 获取cos配置
|
||||
# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成
|
||||
secret_id = dispatch.get_system_config_values("file_storage.tencent_secret_id") # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
|
||||
secret_key = dispatch.get_system_config_values("file_storage.tencent_secret_key") # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
|
||||
region = dispatch.get_system_config_values("file_storage.tencent_region") # 替换为用户的 region,已创建桶归属的 region 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket # COS 支持的所有 region 列表参见https://cloud.tencent.com/document/product/436/6224
|
||||
bucket = dispatch.get_system_config_values("file_storage.tencent_bucket") # 要访问的桶名称
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
|
||||
client = CosS3Client(config)
|
||||
# 访问地址
|
||||
base_file_url = f'https://{bucket}.cos.{region}.myqcloud.com'
|
||||
# 生成外网访问的文件路径
|
||||
if base_file_url.endswith('/'):
|
||||
file_path = base_file_url + base_fil_name
|
||||
else:
|
||||
file_path = f'{base_file_url}/{base_fil_name}'
|
||||
# 这个是阿里提供的SDK方法 bucket是调用的4.1中配置的变量名
|
||||
try:
|
||||
response = client.put_object(
|
||||
Bucket=bucket,
|
||||
Body=file_read,
|
||||
Key=base_fil_name,
|
||||
EnableMD5=False
|
||||
)
|
||||
return file_path
|
||||
except:
|
||||
return None
|
||||
@@ -16,7 +16,7 @@ from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from dvadmin.utils.filters import DataLevelPermissionsFilter, CoreModelFilterBankend
|
||||
from dvadmin.utils.filters import CoreModelFilterBankend, DataLevelPermissionMargeFilter
|
||||
from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.permission import CustomPermission
|
||||
@@ -41,7 +41,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
update_serializer_class = None
|
||||
filter_fields = '__all__'
|
||||
search_fields = ()
|
||||
extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter]
|
||||
extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionMargeFilter]
|
||||
permission_classes = [CustomPermission]
|
||||
import_field_dict = {}
|
||||
export_field_label = {}
|
||||
@@ -152,3 +152,13 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
|
||||
return SuccessResponse(data=[], msg="删除成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到keys字段")
|
||||
|
||||
@action(methods=['post'], detail=False)
|
||||
def get_by_ids(self, request):
|
||||
"""通过IDS列表获取数据"""
|
||||
ids = request.data.get('ids', [])
|
||||
if ids and ids != ['']:
|
||||
queryset = self.get_queryset().filter(id__in=ids)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return DetailResponse(data=serializer.data)
|
||||
return DetailResponse(data=None)
|
||||
|
||||
@@ -7,26 +7,27 @@ djangorestframework==3.15.2
|
||||
django-restql==0.15.4
|
||||
django-simple-captcha==0.6.0
|
||||
django-timezone-field==7.0
|
||||
djangorestframework-simplejwt==5.3.1
|
||||
djangorestframework_simplejwt==5.4.0
|
||||
drf-yasg==1.21.7
|
||||
mysqlclient==2.2.0
|
||||
pypinyin==0.51.0
|
||||
ua-parser==0.18.0
|
||||
pyparsing==3.1.2
|
||||
openpyxl==3.1.5
|
||||
requests==2.32.3
|
||||
requests==2.32.4
|
||||
typing-extensions==4.12.2
|
||||
tzlocal==5.2
|
||||
channels==4.1.0
|
||||
channels-redis==4.2.0
|
||||
websockets==11.0.3
|
||||
user-agents==2.2.0
|
||||
six==1.16.0
|
||||
whitenoise==6.7.0
|
||||
psycopg2==2.9.9
|
||||
uvicorn==0.30.3
|
||||
gunicorn==22.0.0
|
||||
gunicorn==23.0.0
|
||||
gevent==24.2.1
|
||||
Pillow==10.4.0
|
||||
pyinstaller==6.9.0
|
||||
dvadmin3-celery==3.1.6
|
||||
dvadmin3-celery==3.1.6
|
||||
oss2==2.19.1
|
||||
cos-python-sdk-v5==1.9.37
|
||||
@@ -82,7 +82,7 @@
|
||||
<br>
|
||||
<h2>五、巨梦科技企业客户服务说明</h2>
|
||||
<h3>
|
||||
1、巨梦科技平台提供给多家企业客户使用,企业客户通过巨梦科技平台进行发布用户活动等。如果功夫企火星企业用户违反了隐私政策或发生其它侵犯用户权益的行为,用户可要求巨梦科技提供必要的配合与巨梦科技企业用户进行沟通和维权,维权过程中产生的费用由用户自行承担。</h3>
|
||||
1、巨梦科技平台提供给多家企业客户使用,企业客户通过巨梦科技平台进行发布用户活动等。如果企业用户违反了隐私政策或发生其它侵犯用户权益的行为,用户可要求巨梦科技提供必要的配合与巨梦科技企业用户进行沟通和维权,维权过程中产生的费用由用户自行承担。</h3>
|
||||
<br>
|
||||
<h2>六、其他事宜</h2>
|
||||
<h3>
|
||||
|
||||
78
backend/util/currency.py
Normal file
78
backend/util/currency.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from uuid import uuid4
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import connection
|
||||
from django.db.models import Model
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
def create_code(model,prefix):
|
||||
current_date = datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
code = f"{prefix}{current_date}" + str(uuid4().int)[:6]
|
||||
return code
|
||||
|
||||
def lock(key):
|
||||
if callable(key): # @lock
|
||||
def inner(*args, **kwargs):
|
||||
with cache.lock(key='lock'):
|
||||
return key(*args, **kwargs)
|
||||
inner.__name__ = key.__name__
|
||||
else: # @lock(key='aaa')
|
||||
def inner(func):
|
||||
def _inner(*args, **kwargs):
|
||||
with cache.lock(key=key):
|
||||
return func(*args, **kwargs)
|
||||
_inner.__name__ = func.__name__
|
||||
return _inner
|
||||
return inner
|
||||
|
||||
def recursion_down_fast(instance:Model, parent='parent', key='id') -> list[int]:
|
||||
"""向下递归instance的所有子级,且返回一维列表,使用sql优化,速度非常快"""
|
||||
if not instance:
|
||||
return []
|
||||
sql = f"""
|
||||
WITH RECURSIVE children AS (
|
||||
SELECT id, {key} AS param_{key} FROM {instance.__class__._meta.db_table} WHERE {parent}_id = %s UNION ALL
|
||||
SELECT a.id, a.{key} AS param_{key} FROM {instance.__class__._meta.db_table} a
|
||||
INNER JOIN children b ON a.{parent}_id = b.id
|
||||
) SELECT param_{key} FROM children;
|
||||
"""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, [getattr(instance, key)])
|
||||
data = cursor.fetchall()
|
||||
return [getattr(instance, key), *[i[0] for i in data]]
|
||||
|
||||
def recursion_up_fast(instance: Model, parent='parent', key='id') -> list[int]:
|
||||
"""向上递归instance的所有父级,使用sql优化,速度非常快"""
|
||||
if not instance:
|
||||
return []
|
||||
sql = f"""
|
||||
WITH RECURSIVE parents AS (
|
||||
SELECT id, {key} as param_{key}, {parent}_id FROM {instance.__class__._meta.db_table} WHERE id = %s UNION ALL
|
||||
SELECT a.id, a.{key} as param_{key}, a.{parent}_id FROM {instance.__class__._meta.db_table} a
|
||||
INNER JOIN parents b ON a.id = b.{parent}_id
|
||||
) SELECT param_{key} FROM parents;
|
||||
"""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, [getattr(instance, key)])
|
||||
data = cursor.fetchall()
|
||||
return [i[0] for i in data]
|
||||
|
||||
def recursion_up_joint(instance: Model, parent='parent', key='name', joint='/') -> str:
|
||||
"""向上递归instance所有父级并拼接需要的值,返回完整路径,使用sql优化,速度非常快"""
|
||||
if instance is None:
|
||||
return ''
|
||||
sql = f"""
|
||||
WITH RECURSIVE parents AS (
|
||||
SELECT id, {parent}_id, {key}::TEXT AS path FROM {instance.__class__._meta.db_table} WHERE {key} = %s AND id = %s UNION ALL
|
||||
SELECT a.id, a.{parent}_id, (a.{key} || '{joint}' || b.path)::TEXT FROM {instance.__class__._meta.db_table} a
|
||||
INNER JOIN parents b ON a.id = b.{parent}_id
|
||||
) SELECT path FROM parents where {parent}_id IS NULL;
|
||||
"""
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, [getattr(instance, key), instance.pk])
|
||||
data = cursor.fetchall()
|
||||
try:
|
||||
return data[0][0]
|
||||
except IndexError:
|
||||
raise Exception('找不到初始数据')
|
||||
@@ -2,7 +2,7 @@
|
||||
ENV = 'development'
|
||||
|
||||
# 本地环境接口地址
|
||||
VITE_API_URL = 'http://127.0.0.1:8001'
|
||||
VITE_API_URL = 'http://127.0.0.1:8000'
|
||||
|
||||
# 是否启用按钮权限
|
||||
VITE_PM_ENABLED = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "django-vue3-admin",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"description": "是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。\n django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台,权限粒度达到列级别,前后端分离,后端采用django + django-rest-framework,前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -8,7 +8,8 @@
|
||||
"build:dev": "vite build --mode development",
|
||||
"build": "vite build",
|
||||
"build:local": "vite build --mode local_prod",
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
|
||||
"build:flowH5": "vite build --config flowH5.config.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
@@ -18,6 +19,7 @@
|
||||
"@fast-crud/ui-interface": "^1.21.2",
|
||||
"@great-dream/dvadmin3-celery-web": "^3.1.3",
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@meetjs/vant4-kit": "^1.0.1",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
@@ -37,6 +39,7 @@
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-table2excel": "^1.1.2",
|
||||
"jsplumb": "^2.15.6",
|
||||
"less": "^4.3.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lunar-javascript": "^1.7.1",
|
||||
"mitt": "^3.0.1",
|
||||
@@ -47,17 +50,23 @@
|
||||
"print-js": "^1.6.0",
|
||||
"qrcodejs2-fixes": "^0.0.2",
|
||||
"qs": "^6.11.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"splitpanes": "^3.1.5",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"ts-md5": "^1.3.1",
|
||||
"upgrade": "^1.1.0",
|
||||
"vant": "^4.9.19",
|
||||
"vant4-kit": "^1.0.3",
|
||||
"vue": "^3.4.38",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-cropper": "^1.0.8",
|
||||
"vue-draggable-plus": "^0.6.0",
|
||||
"vue-grid-layout": "^3.0.0-beta1",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-qr": "^4.0.9",
|
||||
"vue-router": "^4.4.3",
|
||||
"vxe-table": "^4.6.18",
|
||||
"xe-utils": "^3.5.30"
|
||||
|
||||
@@ -20,6 +20,7 @@ import other from '/@/utils/other';
|
||||
import { Local, Session } from '/@/utils/storage';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import setIntroduction from '/@/utils/setIconfont';
|
||||
import websocket from '/@/utils/websocket';
|
||||
|
||||
// 引入组件
|
||||
const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
|
||||
@@ -35,7 +36,6 @@ const route = useRoute();
|
||||
const stores = useTagsViewRoutes();
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
import websocket from '/@/utils/websocket';
|
||||
const core = useCore();
|
||||
const router = useRouter();
|
||||
// 获取版本号
|
||||
@@ -94,61 +94,61 @@ onUnmounted(() => {
|
||||
});
|
||||
// 监听路由的变化,设置网站标题
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
other.useFavicon();
|
||||
if (!websocket.websocket) {
|
||||
//websockt 模块
|
||||
try {
|
||||
websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
() => route.path,
|
||||
() => {
|
||||
other.useTitle();
|
||||
other.useFavicon();
|
||||
if (!websocket.websocket) {
|
||||
//websockt 模块
|
||||
try {
|
||||
websocket.init(wsReceive)
|
||||
} catch (e) {
|
||||
console.log('websocket错误');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// websocket相关代码
|
||||
import { messageCenterStore } from '/@/stores/messageCenter';
|
||||
const wsReceive = (message: any) => {
|
||||
const data = JSON.parse(message.data);
|
||||
const { unread } = data;
|
||||
const messageCenter = messageCenterStore();
|
||||
messageCenter.setUnread(unread);
|
||||
if (data.contentType === 'SYSTEM') {
|
||||
ElNotification({
|
||||
title: '系统消息',
|
||||
message: data.content,
|
||||
type: 'success',
|
||||
position: 'bottom-right',
|
||||
duration: 5000,
|
||||
});
|
||||
} else if (data.contentType === 'Content') {
|
||||
ElMessageBox.confirm(data.content, data.notificationTitle, {
|
||||
confirmButtonText: data.notificationButton,
|
||||
const data = JSON.parse(message.data);
|
||||
const { unread } = data;
|
||||
const messageCenter = messageCenterStore();
|
||||
messageCenter.setUnread(unread);
|
||||
if (data.contentType === 'SYSTEM') {
|
||||
ElNotification({
|
||||
title: '系统消息',
|
||||
message: data.content,
|
||||
type: 'success',
|
||||
position: 'bottom-right',
|
||||
duration: 5000,
|
||||
});
|
||||
} else if (data.contentType === 'Content') {
|
||||
ElMessageBox.confirm(data.content, data.notificationTitle, {
|
||||
confirmButtonText: data.notificationButton,
|
||||
dangerouslyUseHTMLString: true,
|
||||
cancelButtonText: '关闭',
|
||||
type: 'info',
|
||||
closeOnClickModal: false,
|
||||
}).then(() => {
|
||||
ElMessageBox.close();
|
||||
const path = data.path;
|
||||
if (route.path === path) {
|
||||
core.bus.emit('onNewTask', { name: 'onNewTask' });
|
||||
} else {
|
||||
router.push({ path});
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
cancelButtonText: '关闭',
|
||||
type: 'info',
|
||||
closeOnClickModal: false,
|
||||
}).then(() => {
|
||||
ElMessageBox.close();
|
||||
const path = data.path;
|
||||
if (route.path === path) {
|
||||
core.bus.emit('onNewTask', { name: 'onNewTask' });
|
||||
} else {
|
||||
router.push({ path});
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
};
|
||||
onBeforeUnmount(() => {
|
||||
// 关闭连接
|
||||
websocket.close();
|
||||
// 关闭连接
|
||||
websocket.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
BIN
web/src/assets/home-bg.png
Normal file
BIN
web/src/assets/home-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
55
web/src/assets/iconfont/iconfont-01/iconfont.css
Normal file
55
web/src/assets/iconfont/iconfont-01/iconfont.css
Normal file
@@ -0,0 +1,55 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3882322 */
|
||||
src: url('iconfont.woff2?t=1676037377315') format('woff2'),
|
||||
url('iconfont.woff?t=1676037377315') format('woff'),
|
||||
url('iconfont.ttf?t=1676037377315') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-xiaoxizhongxin:before {
|
||||
content: "\e665";
|
||||
}
|
||||
|
||||
.icon-xitongshezhi:before {
|
||||
content: "\e7ba";
|
||||
}
|
||||
|
||||
.icon-caozuorizhi:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-guanlidenglurizhi:before {
|
||||
content: "\ea45";
|
||||
}
|
||||
|
||||
.icon-rizhi:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.icon-system:before {
|
||||
content: "\e684";
|
||||
}
|
||||
|
||||
.icon-Area:before {
|
||||
content: "\eaa2";
|
||||
}
|
||||
|
||||
.icon-file:before {
|
||||
content: "\e671";
|
||||
}
|
||||
|
||||
.icon-dict:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.icon-configure:before {
|
||||
content: "\e733";
|
||||
}
|
||||
|
||||
BIN
web/src/assets/iconfont/iconfont-01/iconfont.ttf
Normal file
BIN
web/src/assets/iconfont/iconfont-01/iconfont.ttf
Normal file
Binary file not shown.
BIN
web/src/assets/iconfont/iconfont-01/iconfont.woff
Normal file
BIN
web/src/assets/iconfont/iconfont-01/iconfont.woff
Normal file
Binary file not shown.
BIN
web/src/assets/iconfont/iconfont-01/iconfont.woff2
Normal file
BIN
web/src/assets/iconfont/iconfont-01/iconfont.woff2
Normal file
Binary file not shown.
427
web/src/assets/iconfont/iconfont-02/iconfont.css
Normal file
427
web/src/assets/iconfont/iconfont-02/iconfont.css
Normal file
@@ -0,0 +1,427 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2298093 */
|
||||
src: url('iconfont.woff2?t=1627014681704') format('woff2'),
|
||||
url('iconfont.woff?t=1627014681704') format('woff'),
|
||||
url('iconfont.ttf?t=1627014681704') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-diannao101:before {
|
||||
content: "\e670";
|
||||
}
|
||||
|
||||
.icon-diannao:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.icon-diannao1:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.icon-diannao-shuju:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.icon-shoujidiannao:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.icon-diannaobangong:before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.icon-LoggedinPC:before {
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.icon-barcode-qr:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.icon-zhongduancanshuchaxun:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.icon-shouye_dongtaihui:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.icon-putong:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.icon-dongtai:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.icon-wenducanshu-05:before {
|
||||
content: "\e634";
|
||||
}
|
||||
|
||||
.icon-zhongduancanshu:before {
|
||||
content: "\e63b";
|
||||
}
|
||||
|
||||
.icon-tongzhi1:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.icon-tongzhi2:before {
|
||||
content: "\e649";
|
||||
}
|
||||
|
||||
.icon-tongzhi3:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.icon-tongzhi4:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.icon-dianhua:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.icon-xianshimima:before {
|
||||
content: "\e63c";
|
||||
}
|
||||
|
||||
.icon-yincangmima:before {
|
||||
content: "\e63d";
|
||||
}
|
||||
|
||||
.icon-shuxing:before {
|
||||
content: "\e67a";
|
||||
}
|
||||
|
||||
.icon-juxingkaobei:before {
|
||||
content: "\e7a5";
|
||||
}
|
||||
|
||||
.icon-shuxingtu:before {
|
||||
content: "\e685";
|
||||
}
|
||||
|
||||
.icon-bolangneng:before {
|
||||
content: "\e745";
|
||||
}
|
||||
|
||||
.icon-bolangnengshiyanchang:before {
|
||||
content: "\e746";
|
||||
}
|
||||
|
||||
.icon--chaifenhang:before {
|
||||
content: "\e6d1";
|
||||
}
|
||||
|
||||
.icon--chaifenlie:before {
|
||||
content: "\e6d0";
|
||||
}
|
||||
|
||||
.icon-tupianyulan:before {
|
||||
content: "\e67e";
|
||||
}
|
||||
|
||||
.icon-15tupianyulan:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-728bianjiqi_zitidaxiao:before {
|
||||
content: "\e660";
|
||||
}
|
||||
|
||||
.icon-ziti:before {
|
||||
content: "\e7b1";
|
||||
}
|
||||
|
||||
.icon-font-size:before {
|
||||
content: "\eaef";
|
||||
}
|
||||
|
||||
.icon-tuodong:before {
|
||||
content: "\e6a8";
|
||||
}
|
||||
|
||||
.icon-zhongyingwen1:before {
|
||||
content: "\e7a3";
|
||||
}
|
||||
|
||||
.icon-fuhao-yingwen:before {
|
||||
content: "\e714";
|
||||
}
|
||||
|
||||
.icon-fuhao-zhongwen:before {
|
||||
content: "\e712";
|
||||
}
|
||||
|
||||
.icon-diqiu:before {
|
||||
content: "\e689";
|
||||
}
|
||||
|
||||
.icon-xingqiu:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.icon-diqiu1:before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.icon-huanjingxingqiu:before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
.icon-zidingyibuju:before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
.icon-dayin:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.icon-step:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.icon-30xuanzhongyuanxingfill:before {
|
||||
content: "\e677";
|
||||
}
|
||||
|
||||
.icon-shibai:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.icon-7_round_solid:before {
|
||||
content: "\e64d";
|
||||
}
|
||||
|
||||
.icon-6_round_solid:before {
|
||||
content: "\e64e";
|
||||
}
|
||||
|
||||
.icon-9_round_solid:before {
|
||||
content: "\e64f";
|
||||
}
|
||||
|
||||
.icon-1_round_solid:before {
|
||||
content: "\e650";
|
||||
}
|
||||
|
||||
.icon-5_round_solid:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.icon-2_round_solid:before {
|
||||
content: "\e654";
|
||||
}
|
||||
|
||||
.icon-0_round_solid:before {
|
||||
content: "\e655";
|
||||
}
|
||||
|
||||
.icon-3_round_solid:before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.icon-4_round_solid:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.icon-8_round_solid:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.icon-radio-off-full:before {
|
||||
content: "\ea6b";
|
||||
}
|
||||
|
||||
.icon-tongzhi:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.icon-ditu:before {
|
||||
content: "\e8bc";
|
||||
}
|
||||
|
||||
.icon-ico:before {
|
||||
content: "\e646";
|
||||
}
|
||||
|
||||
.icon-chazhaobiaodanliebiao:before {
|
||||
content: "\e76a";
|
||||
}
|
||||
|
||||
.icon-biaodan:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.icon-siweidaotu:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.icon-jiliandongxuanzeqi:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.icon-caijian:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-fuwenben:before {
|
||||
content: "\e7e4";
|
||||
}
|
||||
|
||||
.icon-fuwenbenkuang:before {
|
||||
content: "\e66f";
|
||||
}
|
||||
|
||||
.icon-shangchuan:before {
|
||||
content: "\e663";
|
||||
}
|
||||
|
||||
.icon-xuanzeqi:before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
||||
.icon-fangkuang:before {
|
||||
content: "\e642";
|
||||
}
|
||||
|
||||
.icon-gouxuan-weixuanzhong-xianxingfangkuang:before {
|
||||
content: "\e77b";
|
||||
}
|
||||
|
||||
.icon-shidu:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icon-yangan:before {
|
||||
content: "\e67d";
|
||||
}
|
||||
|
||||
.icon-wendu:before {
|
||||
content: "\e686";
|
||||
}
|
||||
|
||||
.icon-zaosheng:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.icon-jinridaiban:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.icon-AIshiyanshi:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.icon-shenqingkaiban:before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.icon-zhongyingwenqiehuan:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-zhongyingwen:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.icon-zhongyingzhuanhuan:before {
|
||||
content: "\e6a2";
|
||||
}
|
||||
|
||||
.icon-zhongyingwenyuyan:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.icon-shuju:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.icon-ico_shuju:before {
|
||||
content: "\e6ff";
|
||||
}
|
||||
|
||||
.icon-shuju1:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.icon-fuzhiyemian:before {
|
||||
content: "\e772";
|
||||
}
|
||||
|
||||
.icon-caozuo-wailian:before {
|
||||
content: "\e711";
|
||||
}
|
||||
|
||||
.icon-icon-:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.icon-gerenzhongxin:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.icon-caidan:before {
|
||||
content: "\e652";
|
||||
}
|
||||
|
||||
.icon-xitongshezhi:before {
|
||||
content: "\e69b";
|
||||
}
|
||||
|
||||
.icon-neiqianshujuchucun:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.icon-shouye:before {
|
||||
content: "\e653";
|
||||
}
|
||||
|
||||
.icon-quanxian:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.icon-zujian:before {
|
||||
content: "\e85e";
|
||||
}
|
||||
|
||||
.icon-crew_feature:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.icon-gongju:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.icon-skin:before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
.icon-shixinyuan:before {
|
||||
content: "\e669";
|
||||
}
|
||||
|
||||
.icon-webicon318:before {
|
||||
content: "\e6a9";
|
||||
}
|
||||
|
||||
.icon-dian:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.icon-fullscreen:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-tuichuquanping:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
BIN
web/src/assets/iconfont/iconfont-02/iconfont.ttf
Normal file
BIN
web/src/assets/iconfont/iconfont-02/iconfont.ttf
Normal file
Binary file not shown.
BIN
web/src/assets/iconfont/iconfont-02/iconfont.woff
Normal file
BIN
web/src/assets/iconfont/iconfont-02/iconfont.woff
Normal file
Binary file not shown.
BIN
web/src/assets/iconfont/iconfont-02/iconfont.woff2
Normal file
BIN
web/src/assets/iconfont/iconfont-02/iconfont.woff2
Normal file
Binary file not shown.
@@ -1,403 +1,402 @@
|
||||
<template>
|
||||
<div style="width: 100%; height: 100%;">
|
||||
<div class="selected-show" v-if="props.modelValue && props.selectable">
|
||||
<el-text>已选择:</el-text>
|
||||
<el-tag v-if="props.multiple" v-for="item in data" closable @close="handleTagClose(item)">
|
||||
{{ item.toLocaleDateString('en-CA') }}
|
||||
</el-tag>
|
||||
<el-tag v-else closable @close="handleTagClose(data)">{{ data?.toLocaleDateString('en-CA') }}</el-tag>
|
||||
<el-button v-if="props.modelValue" size="small" type="text" @click="clear">清空</el-button>
|
||||
<div style="width: 100%; height: 100%;">
|
||||
<div class="selected-show" v-if="props.modelValue && props.selectable">
|
||||
<el-text>已选择:</el-text>
|
||||
<el-tag v-if="props.multiple" v-for="item in data" closable @close="handleTagClose(item)">
|
||||
{{ item.toLocaleDateString('en-CA') }}
|
||||
</el-tag>
|
||||
<el-tag v-else closable @close="handleTagClose(data)">{{ data?.toLocaleDateString('en-CA') }}</el-tag>
|
||||
<el-button v-if="props.modelValue" size="small" type="text" @click="clear">清空</el-button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div>
|
||||
今天:<el-text size="large">{{ today.toLocaleDateString('en-CA') }}</el-text>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div>
|
||||
今天:<el-text size="large">{{ today.toLocaleDateString('en-CA') }}</el-text>
|
||||
</div>
|
||||
<!-- <div class="current-month">
|
||||
<!-- <div class="current-month">
|
||||
<el-tag size="large" type="primary">
|
||||
{{ currentCalendarDate.getFullYear() }}年{{ currentCalendarDate.getMonth() + 1 }}月
|
||||
</el-tag>
|
||||
</div> -->
|
||||
<div class="control-button" v-if="!(!!props.range && props.range[0] && props.range[1]) && props.showPageTurn">
|
||||
<el-button-group size="small" type="default" v-if="props.pageTurn">
|
||||
<el-popover trigger="click" width="160px">
|
||||
<template #reference>
|
||||
<el-button type="text" size="small">节假日设置</el-button>
|
||||
</template>
|
||||
<el-switch v-model="showHoliday" active-text="显示节日" inactive-text="关闭节日" inline-prompt />
|
||||
<el-checkbox v-model="showLunarHoliday" label="农历节日" />
|
||||
<el-checkbox v-model="showJieQi" label="节气" />
|
||||
<el-checkbox v-model="showDetailedHoliday" label="更多节日" />
|
||||
</el-popover>
|
||||
<el-button icon="DArrowLeft" @click="turnToPreY">上年</el-button>
|
||||
<el-button icon="ArrowLeft" @click="turnToPreM">上月</el-button>
|
||||
<el-button @click="turnToToday">今天</el-button>
|
||||
<el-button icon="ArrowRight" @click="turnToNextM">下月</el-button>
|
||||
<el-button icon="DArrowRight" @click="turnToNextY">下年</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider style="margin: 4px;" />
|
||||
<div class="calender">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="calender-header" v-for="item, ind in ['日', '一', '二', '三', '四', '五', '六']" :key="ind">
|
||||
{{ item }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="week in calendarList">
|
||||
<td class="calender-td" v-for="item in week">
|
||||
<div class="calender-cell" :data-date="item.date.toLocaleDateString('en-CA')" :class="{
|
||||
'no-current-month': item.date.getMonth() !== currentCalendarDate.getMonth(),
|
||||
'today': item.date.toDateString() === today.toDateString(),
|
||||
'selected': item.selected,
|
||||
'disabled': item.disabled,
|
||||
}" @mouseenter="onCalenderCellHover" @mouseleave="onCalenderCellUnhover"
|
||||
@click="(e: MouseEvent) => item.disabled ? null : onCalenderCellClick(e)">
|
||||
<div class="calender-cell-header calender-cell-line">
|
||||
<span>{{ item.date.getDate() }}</span>
|
||||
<span v-if="item.selected">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 1024 1024">
|
||||
<path fill="currentColor"
|
||||
d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="calender-cell-body calender-cell-line">
|
||||
<slot name="cell-body" v-bind="item">
|
||||
</slot>
|
||||
</div>
|
||||
<div class="calender-cell-footer calender-cell-line">
|
||||
<span>{{ item.holiday || ' ' }}</span>
|
||||
<el-text v-if="item.date.toDateString() === today.toDateString()" type="danger">今天</el-text>
|
||||
</div>
|
||||
<!-- {{ item }} -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="watermark" v-if="props.watermark" :style="watermarkPositionMap[props.watermarkPosition]">
|
||||
{{ (currentCalendarDate.toLocaleDateString('en-CA').split('-').slice(0, 2)).join('-') }}
|
||||
</div>
|
||||
<div class="control-button" v-if="!(!!props.range && props.range[0] && props.range[1]) && props.showPageTurn">
|
||||
<el-button-group size="small" type="default" v-if="props.pageTurn">
|
||||
<el-popover trigger="click" width="160px">
|
||||
<template #reference>
|
||||
<el-button type="text" size="small">节假日设置</el-button>
|
||||
</template>
|
||||
<el-switch v-model="showHoliday" active-text="显示节日" inactive-text="关闭节日" inline-prompt />
|
||||
<el-checkbox v-model="showLunarHoliday" label="农历节日" />
|
||||
<el-checkbox v-model="showJieQi" label="节气" />
|
||||
<el-checkbox v-model="showDetailedHoliday" label="更多节日" />
|
||||
</el-popover>
|
||||
<el-button icon="DArrowLeft" @click="turnToPreY">上年</el-button>
|
||||
<el-button icon="ArrowLeft" @click="turnToPreM">上月</el-button>
|
||||
<el-button @click="turnToToday">今天</el-button>
|
||||
<el-button icon="ArrowRight" @click="turnToNextM">下月</el-button>
|
||||
<el-button icon="DArrowRight" @click="turnToNextY">下年</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUi } from '@fast-crud/fast-crud';
|
||||
import { ref, defineProps, PropType, watch, computed, onMounted } from 'vue';
|
||||
import Holidays from 'date-holidays';
|
||||
import Lunar from 'lunar-javascript';
|
||||
const LUNAR = Lunar.Lunar; // 农历
|
||||
const SOLAR = Lunar.Solar; // 阳历
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {},
|
||||
// 日期多选
|
||||
multiple: { type: Boolean, default: false },
|
||||
// 日期范围
|
||||
range: { type: Object as PropType<[Date, Date]> },
|
||||
// 可以翻页
|
||||
pageTurn: { type: Boolean, default: true },
|
||||
// 跨页选择
|
||||
crossPage: { type: Boolean, default: false },
|
||||
// 显示年月水印和水印位置
|
||||
watermark: { type: Boolean, default: true },
|
||||
watermarkPosition: { type: Object as PropType<PositionType>, default: 'bottom-right' },
|
||||
// 显示翻页控件
|
||||
showPageTurn: { type: Boolean, default: true },
|
||||
// 是否可选
|
||||
selectable: { type: Boolean, default: true },
|
||||
// 验证日期是否有效
|
||||
validDate: { type: Object as PropType<ValidDateFunc>, default: () => ((d: Date) => true) }
|
||||
});
|
||||
type ValidDateFunc = (d: Date) => boolean;
|
||||
type PositionType = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center' | 'center-left' | 'center-right' | 'center-top';
|
||||
const today = new Date();
|
||||
const showHoliday = ref<boolean>(true); // 显示节日
|
||||
const showDetailedHoliday = ref<boolean>(false); // 显示详细的国际节日
|
||||
const showJieQi = ref<boolean>(true); // 显示节气
|
||||
const showLunarHoliday = ref<boolean>(true); // 显示农历节日
|
||||
const watermarkPositionMap: { [key: string]: any } = {
|
||||
'top-left': { top: '40px', left: 0, transformOrigin: '0 0' },
|
||||
'top-right': { top: '40px', right: 0, transformOrigin: '100% 0' },
|
||||
'bottom-left': { bottom: 0, left: 0, transformOrigin: '0 100%' },
|
||||
'bottom-right': { bottom: 0, right: 0, transformOrigin: '100% 100%' },
|
||||
'center': { top: '50%', left: '50%', transformOrigin: '50% 50%', transform: 'translate(-50%, -50%) scale(10)' },
|
||||
'center-left': { top: '50%', left: 0, transformOrigin: '0 50%' },
|
||||
'center-right': { top: '50%', right: 0, transformOrigin: '100% 50%' },
|
||||
'center-top': { top: 0, left: '50%', transformOrigin: '50% 0', transform: 'translate(-50%, 40px) scale(10)' },
|
||||
'center-bottom': { bottom: 0, left: '50%', transformOrigin: '50% 100%', transform: 'translate(-50%, 0) scale(10)' },
|
||||
};
|
||||
// 获取当月第一周的第一天(包括上个月)
|
||||
const calendarFirstDay = (current: Date = new Date()) => {
|
||||
let today = new Date(current); // 指定天
|
||||
let firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); // 月初天
|
||||
let weekOfFirstDay = firstDayOfMonth.getDay(); // 周几,0日
|
||||
if (weekOfFirstDay === 0) return new Date(firstDayOfMonth); // 是周日则直接返回
|
||||
let firstDayOfWeek = new Date(firstDayOfMonth);
|
||||
// 月初减去周几,不+1是因为从日历周日开始
|
||||
firstDayOfWeek.setDate(firstDayOfMonth.getDate() - weekOfFirstDay);
|
||||
return new Date(firstDayOfWeek);
|
||||
};
|
||||
// 获取当月最后一周的最后一天(包括下个月)
|
||||
const calendarLastDay = (current: Date = new Date()) => {
|
||||
let today = new Date(current); // 指定天
|
||||
let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1); // 月末天
|
||||
lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1);
|
||||
let weekOfFirstDay = lastDayOfMonth.getDay();
|
||||
if (weekOfFirstDay === 6) return new Date(lastDayOfMonth); // 是周六则直接返回
|
||||
let lastDayOfWeek = new Date(lastDayOfMonth);
|
||||
// 月末加剩下周几,要-1是因为日历到周六结束
|
||||
lastDayOfWeek.setDate(lastDayOfMonth.getDate() + (7 - weekOfFirstDay - 1));
|
||||
return new Date(lastDayOfWeek);
|
||||
};
|
||||
const generateDateList = (startDate: Date, endDate: Date): Date[] => { // 生成日期列表
|
||||
let dates = [];
|
||||
let s = new Date(startDate);
|
||||
let e = new Date(endDate);
|
||||
while (s <= e) {
|
||||
dates.push(new Date(s));
|
||||
s.setDate(s.getDate() + 1);
|
||||
}
|
||||
return dates;
|
||||
};
|
||||
// 日历当前页范围
|
||||
interface CalendarCell {
|
||||
date: Date;
|
||||
selected: boolean;
|
||||
disabled: boolean;
|
||||
currentMonth: boolean;
|
||||
holiday: string;
|
||||
};
|
||||
const currentCalendarDate = ref<Date>(new Date());
|
||||
const calendarList = computed(() => {
|
||||
let dates = (!!props.range && props.range[0] && props.range[1]) ?
|
||||
generateDateList(props.range[0], props.range[1]) :
|
||||
generateDateList(calendarFirstDay(currentCalendarDate.value), calendarLastDay(currentCalendarDate.value));
|
||||
let proce_dates: CalendarCell[] = dates.map((value) => {
|
||||
let solarDate = SOLAR.fromDate(value);
|
||||
let lunarDate = solarDate.getLunar();
|
||||
let solarHolidays: string[] = solarDate.getFestivals(); // 国历节日
|
||||
let lunarHolidays: string[] = lunarDate.getFestivals(); // 农历节日
|
||||
let jieQi: string = lunarDate.getJieQi(); // 节气
|
||||
// 农历节日、国际节日、节气三选一
|
||||
let holiday = showHoliday.value ? ((showLunarHoliday.value ? lunarHolidays[0] : '') || (showJieQi.value ? jieQi : '') ||
|
||||
(showDetailedHoliday.value ? solarHolidays[0] : yearHolidays.value[value.toLocaleDateString('en-CA')])) : ''; // yearHolidays国际的
|
||||
return {
|
||||
date: value,
|
||||
selected: props.multiple ?
|
||||
(data.value as Date[]).findIndex((v) => v.toLocaleDateString('en-CA') === value.toLocaleDateString('en-CA')) !== -1 :
|
||||
data.value?.toLocaleDateString('en-CA') === value.toLocaleDateString('en-CA'),
|
||||
disabled: !props.validDate(value),
|
||||
currentMonth: value.getMonth() === currentCalendarDate.value.getMonth(),
|
||||
// 农历节日、节气、法定日三选一
|
||||
holiday: holiday
|
||||
}
|
||||
});
|
||||
let res: CalendarCell[][] = [];
|
||||
for (let i = 0; i < 6; i++) res.push(proce_dates.slice(i * 7, (i + 1) * 7));
|
||||
return res;
|
||||
});
|
||||
// 控件
|
||||
const turnToPreM = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear(), currentCalendarDate.value.getMonth() - 1, 1);
|
||||
const turnToNextM = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear(), currentCalendarDate.value.getMonth() + 1, 1);
|
||||
const turnToPreY = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear() - 1, currentCalendarDate.value.getMonth(), 1);
|
||||
const turnToNextY = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear() + 1, currentCalendarDate.value.getMonth(), 1);
|
||||
const turnToToday = () => currentCalendarDate.value = new Date();
|
||||
// 如果禁止跨页则跨页时清空选择
|
||||
watch(
|
||||
() => currentCalendarDate.value,
|
||||
(v, ov) => props.crossPage ? {} :
|
||||
(v.toLocaleDateString('en-CA') === ov.toLocaleDateString('en-CA') ? {} : clear())
|
||||
);
|
||||
// 单元格事件
|
||||
const onCalenderCellHover = ({ target }: MouseEvent) => (target as HTMLElement).classList.add('onhover');
|
||||
const onCalenderCellUnhover = ({ target }: MouseEvent) => (target as HTMLElement).classList.remove('onhover');
|
||||
const onCalenderCellClick = (e: MouseEvent) => {
|
||||
if (!props.selectable) return;
|
||||
let strValue = (e.target as HTMLElement).dataset.date as string;
|
||||
if (strValue === undefined) return;
|
||||
let value = new Date(strValue);
|
||||
if (props.multiple) {
|
||||
let d = (data.value as Date[]).map((v) => v.toLocaleDateString('en-CA'));
|
||||
let ind = d.findIndex((v) => v === strValue);
|
||||
if (ind === -1) d.push(strValue);
|
||||
else d.splice(ind, 1);
|
||||
onDataChange(d);
|
||||
}
|
||||
// 这里阻止了点击取消选中,需要通过tag的x来取消
|
||||
else (data.value?.toLocaleDateString('en-CA') === strValue ? {} : onDataChange(value));
|
||||
};
|
||||
// 选择回显
|
||||
const handleTagClose = (d: Date) => {
|
||||
let strValue = d.toLocaleDateString('en-CA');
|
||||
if (props.multiple) {
|
||||
let d = (data.value as Date[]).map((v) => v.toLocaleDateString('en-CA'));
|
||||
d.splice(d.findIndex((v) => v === strValue), 1);
|
||||
onDataChange(d);
|
||||
}
|
||||
else onDataChange(null);
|
||||
};
|
||||
// 节假日
|
||||
const holidays = new Holidays('CN');
|
||||
const yearHolidays = computed(() => {
|
||||
let h = holidays.getHolidays(currentCalendarDate.value.getFullYear());
|
||||
let proce_h: { [key: string]: string } = {};
|
||||
let _h: string[] = [];
|
||||
for (let i of h) {
|
||||
let d = i.date.split(' ')[0];
|
||||
let hn = i.name.split(' ')[0];
|
||||
if (_h.includes(hn)) continue;
|
||||
proce_h[d] = hn;
|
||||
_h.push(hn);
|
||||
}
|
||||
return proce_h
|
||||
});
|
||||
|
||||
// fs-crud部分
|
||||
const data = ref<any>();
|
||||
const emit = defineEmits(['update:modelValue', 'onSave', 'onClose', 'onClosed']);
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val === undefined) data.value = props.multiple ? [] : null;
|
||||
else data.value = props.multiple ? (val as Date[]).map((v: Date) => new Date(v)) : val;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const { ui } = useUi();
|
||||
const formValidator = ui.formItem.injectFormItemContext();
|
||||
const onDataChange = (value: any) => {
|
||||
emit('update:modelValue', value);
|
||||
formValidator.onChange();
|
||||
formValidator.onBlur();
|
||||
};
|
||||
const reset = () => { // 重置日历
|
||||
currentCalendarDate.value = new Date();
|
||||
onDataChange(props.multiple ? [] : null);
|
||||
};
|
||||
const clear = () => onDataChange(props.multiple ? [] : null); // 清空数据
|
||||
defineExpose({
|
||||
data,
|
||||
onDataChange,
|
||||
reset,
|
||||
clear,
|
||||
showHoliday,
|
||||
showDetailedHoliday,
|
||||
showJieQi,
|
||||
showLunarHoliday
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.selected-show {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
<el-divider style="margin: 4px;" />
|
||||
<div class="calender">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="calender-header" v-for="item, ind in ['日', '一', '二', '三', '四', '五', '六']" :key="ind">
|
||||
{{ item }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="week in calendarList">
|
||||
<td class="calender-td" v-for="item in week">
|
||||
<div class="calender-cell" :data-date="item.date.toLocaleDateString('en-CA')" :class="{
|
||||
'no-current-month': item.date.getMonth() !== currentCalendarDate.getMonth(),
|
||||
'today': item.date.toDateString() === today.toDateString(),
|
||||
'selected': item.selected,
|
||||
'disabled': item.disabled,
|
||||
}" @mouseenter="onCalenderCellHover" @mouseleave="onCalenderCellUnhover"
|
||||
@click="(e: MouseEvent) => item.disabled ? null : onCalenderCellClick(e)">
|
||||
<div class="calender-cell-header calender-cell-line">
|
||||
<span>{{ item.date.getDate() }}</span>
|
||||
<span v-if="item.selected">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 1024 1024">
|
||||
<path fill="currentColor"
|
||||
d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496z">
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="calender-cell-body calender-cell-line">
|
||||
<slot name="cell-body" v-bind="item">
|
||||
</slot>
|
||||
</div>
|
||||
<div class="calender-cell-footer calender-cell-line">
|
||||
<span>{{ item.holiday || ' ' }}</span>
|
||||
<el-text v-if="item.date.toDateString() === today.toDateString()" type="danger">今天</el-text>
|
||||
</div>
|
||||
<!-- {{ item }} -->
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="watermark" v-if="props.watermark" :style="watermarkPositionMap[props.watermarkPosition]">
|
||||
{{ (currentCalendarDate.toLocaleDateString('en-CA').split('-').slice(0, 2)).join('-') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUi } from '@fast-crud/fast-crud';
|
||||
import { ref, defineProps, PropType, watch, computed, onMounted } from 'vue';
|
||||
import Holidays from 'date-holidays';
|
||||
import Lunar from 'lunar-javascript';
|
||||
const LUNAR = Lunar.Lunar; // 农历
|
||||
const SOLAR = Lunar.Solar; // 阳历
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {},
|
||||
// 日期多选
|
||||
multiple: { type: Boolean, default: false },
|
||||
// 日期范围
|
||||
range: { type: Object as PropType<[Date, Date]> },
|
||||
// 可以翻页
|
||||
pageTurn: { type: Boolean, default: true },
|
||||
// 跨页选择
|
||||
crossPage: { type: Boolean, default: false },
|
||||
// 显示年月水印和水印位置
|
||||
watermark: { type: Boolean, default: true },
|
||||
watermarkPosition: { type: Object as PropType<PositionType>, default: 'bottom-right' },
|
||||
// 显示翻页控件
|
||||
showPageTurn: { type: Boolean, default: true },
|
||||
// 是否可选
|
||||
selectable: { type: Boolean, default: true },
|
||||
// 验证日期是否有效
|
||||
validDate: { type: Object as PropType<ValidDateFunc>, default: () => ((d: Date) => true) }
|
||||
});
|
||||
type ValidDateFunc = (d: Date) => boolean;
|
||||
type PositionType = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center' | 'center-left' | 'center-right' | 'center-top';
|
||||
const today = new Date();
|
||||
const showHoliday = ref<boolean>(true); // 显示节日
|
||||
const showDetailedHoliday = ref<boolean>(false); // 显示详细的国际节日
|
||||
const showJieQi = ref<boolean>(true); // 显示节气
|
||||
const showLunarHoliday = ref<boolean>(true); // 显示农历节日
|
||||
const watermarkPositionMap: { [key: string]: any } = {
|
||||
'top-left': { top: '40px', left: 0, transformOrigin: '0 0' },
|
||||
'top-right': { top: '40px', right: 0, transformOrigin: '100% 0' },
|
||||
'bottom-left': { bottom: 0, left: 0, transformOrigin: '0 100%' },
|
||||
'bottom-right': { bottom: 0, right: 0, transformOrigin: '100% 100%' },
|
||||
'center': { top: '50%', left: '50%', transformOrigin: '50% 50%', transform: 'translate(-50%, -50%) scale(10)' },
|
||||
'center-left': { top: '50%', left: 0, transformOrigin: '0 50%' },
|
||||
'center-right': { top: '50%', right: 0, transformOrigin: '100% 50%' },
|
||||
'center-top': { top: 0, left: '50%', transformOrigin: '50% 0', transform: 'translate(-50%, 40px) scale(10)' },
|
||||
'center-bottom': { bottom: 0, left: '50%', transformOrigin: '50% 100%', transform: 'translate(-50%, 0) scale(10)' },
|
||||
};
|
||||
// 获取当月第一周的第一天(包括上个月)
|
||||
const calendarFirstDay = (current: Date = new Date()) => {
|
||||
let today = new Date(current); // 指定天
|
||||
let firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); // 月初天
|
||||
let weekOfFirstDay = firstDayOfMonth.getDay(); // 周几,0日
|
||||
if (weekOfFirstDay === 0) return new Date(firstDayOfMonth); // 是周日则直接返回
|
||||
let firstDayOfWeek = new Date(firstDayOfMonth);
|
||||
// 月初减去周几,不+1是因为从日历周日开始
|
||||
firstDayOfWeek.setDate(firstDayOfMonth.getDate() - weekOfFirstDay);
|
||||
return new Date(firstDayOfWeek);
|
||||
};
|
||||
// 获取当月最后一周的最后一天(包括下个月)
|
||||
const calendarLastDay = (current: Date = new Date()) => {
|
||||
let today = new Date(current); // 指定天
|
||||
let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 1); // 月末天
|
||||
lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1);
|
||||
let weekOfFirstDay = lastDayOfMonth.getDay();
|
||||
if (weekOfFirstDay === 6) return new Date(lastDayOfMonth); // 是周六则直接返回
|
||||
let lastDayOfWeek = new Date(lastDayOfMonth);
|
||||
// 月末加剩下周几,要-1是因为日历到周六结束
|
||||
lastDayOfWeek.setDate(lastDayOfMonth.getDate() + (7 - weekOfFirstDay - 1));
|
||||
return new Date(lastDayOfWeek);
|
||||
};
|
||||
const generateDateList = (startDate: Date, endDate: Date): Date[] => { // 生成日期列表
|
||||
let dates = [];
|
||||
let s = new Date(startDate);
|
||||
let e = new Date(endDate);
|
||||
while (s <= e) {
|
||||
dates.push(new Date(s));
|
||||
s.setDate(s.getDate() + 1);
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
return dates;
|
||||
};
|
||||
// 日历当前页范围
|
||||
interface CalendarCell {
|
||||
date: Date;
|
||||
selected: boolean;
|
||||
disabled: boolean;
|
||||
currentMonth: boolean;
|
||||
holiday: string;
|
||||
};
|
||||
const currentCalendarDate = ref<Date>(new Date());
|
||||
const calendarList = computed(() => {
|
||||
let dates = (!!props.range && props.range[0] && props.range[1]) ?
|
||||
generateDateList(props.range[0], props.range[1]) :
|
||||
generateDateList(calendarFirstDay(currentCalendarDate.value), calendarLastDay(currentCalendarDate.value));
|
||||
let proce_dates: CalendarCell[] = dates.map((value) => {
|
||||
let solarDate = SOLAR.fromDate(value);
|
||||
let lunarDate = solarDate.getLunar();
|
||||
let solarHolidays: string[] = solarDate.getFestivals(); // 国历节日
|
||||
let lunarHolidays: string[] = lunarDate.getFestivals(); // 农历节日
|
||||
let jieQi: string = lunarDate.getJieQi(); // 节气
|
||||
// 农历节日、国际节日、节气三选一
|
||||
let holiday = showHoliday.value ? ((showLunarHoliday.value ? lunarHolidays[0] : '') || (showJieQi.value ? jieQi : '') ||
|
||||
(showDetailedHoliday.value ? solarHolidays[0] : yearHolidays.value[value.toLocaleDateString('en-CA')])) : ''; // yearHolidays国际的
|
||||
return {
|
||||
date: value,
|
||||
selected: props.multiple ?
|
||||
(data.value as Date[]).findIndex((v) => v.toLocaleDateString('en-CA') === value.toLocaleDateString('en-CA')) !== -1 :
|
||||
data.value?.toLocaleDateString('en-CA') === value.toLocaleDateString('en-CA'),
|
||||
disabled: !props.validDate(value),
|
||||
currentMonth: value.getMonth() === currentCalendarDate.value.getMonth(),
|
||||
// 农历节日、节气、法定日三选一
|
||||
holiday: holiday
|
||||
}
|
||||
});
|
||||
let res: CalendarCell[][] = [];
|
||||
for (let i = 0; i < 6; i++) res.push(proce_dates.slice(i * 7, (i + 1) * 7));
|
||||
return res;
|
||||
});
|
||||
// 控件
|
||||
const turnToPreM = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear(), currentCalendarDate.value.getMonth() - 1, 1);
|
||||
const turnToNextM = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear(), currentCalendarDate.value.getMonth() + 1, 1);
|
||||
const turnToPreY = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear() - 1, currentCalendarDate.value.getMonth(), 1);
|
||||
const turnToNextY = () => currentCalendarDate.value = new Date(currentCalendarDate.value.getFullYear() + 1, currentCalendarDate.value.getMonth(), 1);
|
||||
const turnToToday = () => currentCalendarDate.value = new Date();
|
||||
// 如果禁止跨页则跨页时清空选择
|
||||
watch(
|
||||
() => currentCalendarDate.value,
|
||||
(v, ov) => props.crossPage ? {} :
|
||||
(v.toLocaleDateString('en-CA') === ov.toLocaleDateString('en-CA') ? {} : clear())
|
||||
);
|
||||
// 单元格事件
|
||||
const onCalenderCellHover = ({ target }: MouseEvent) => (target as HTMLElement).classList.add('onhover');
|
||||
const onCalenderCellUnhover = ({ target }: MouseEvent) => (target as HTMLElement).classList.remove('onhover');
|
||||
const onCalenderCellClick = (e: MouseEvent) => {
|
||||
if (!props.selectable) return;
|
||||
let strValue = (e.target as HTMLElement).dataset.date as string;
|
||||
if (strValue === undefined) return;
|
||||
let value = new Date(strValue);
|
||||
if (props.multiple) {
|
||||
let d = (data.value as Date[]).map((v) => v.toLocaleDateString('en-CA'));
|
||||
let ind = d.findIndex((v) => v === strValue);
|
||||
if (ind === -1) d.push(strValue);
|
||||
else d.splice(ind, 1);
|
||||
onDataChange(d);
|
||||
}
|
||||
|
||||
.calender {
|
||||
// 这里阻止了点击取消选中,需要通过tag的x来取消
|
||||
else (data.value?.toLocaleDateString('en-CA') === strValue ? {} : onDataChange(value));
|
||||
};
|
||||
// 选择回显
|
||||
const handleTagClose = (d: Date) => {
|
||||
let strValue = d.toLocaleDateString('en-CA');
|
||||
if (props.multiple) {
|
||||
let d = (data.value as Date[]).map((v) => v.toLocaleDateString('en-CA'));
|
||||
d.splice(d.findIndex((v) => v === strValue), 1);
|
||||
onDataChange(d);
|
||||
}
|
||||
else onDataChange(null);
|
||||
};
|
||||
// 节假日
|
||||
const holidays = new Holidays('CN');
|
||||
const yearHolidays = computed(() => {
|
||||
let h = holidays.getHolidays(currentCalendarDate.value.getFullYear());
|
||||
let proce_h: { [key: string]: string } = {};
|
||||
let _h: string[] = [];
|
||||
for (let i of h) {
|
||||
let d = i.date.split(' ')[0];
|
||||
let hn = i.name.split(' ')[0];
|
||||
if (_h.includes(hn)) continue;
|
||||
proce_h[d] = hn;
|
||||
_h.push(hn);
|
||||
}
|
||||
return proce_h
|
||||
});
|
||||
|
||||
// fs-crud部分
|
||||
const data = ref<any>();
|
||||
const emit = defineEmits(['update:modelValue', 'onSave', 'onClose', 'onClosed']);
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val === undefined) data.value = props.multiple ? [] : null;
|
||||
else data.value = props.multiple ? (val as Date[]).map((v: Date) => new Date(v)) : val;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const { ui } = useUi();
|
||||
const formValidator = ui.formItem.injectFormItemContext();
|
||||
const onDataChange = (value: any) => {
|
||||
emit('update:modelValue', value);
|
||||
formValidator.onChange();
|
||||
formValidator.onBlur();
|
||||
};
|
||||
const reset = () => { // 重置日历
|
||||
currentCalendarDate.value = new Date();
|
||||
onDataChange(props.multiple ? [] : null);
|
||||
};
|
||||
const clear = () => onDataChange(props.multiple ? [] : null); // 清空数据
|
||||
defineExpose({
|
||||
data,
|
||||
onDataChange,
|
||||
reset,
|
||||
clear,
|
||||
showHoliday,
|
||||
showDetailedHoliday,
|
||||
showJieQi,
|
||||
showLunarHoliday
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.selected-show {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.calender {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
|
||||
.watermark {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
transform: scale(10);
|
||||
color: #aaa;
|
||||
opacity: 0.1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
table {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
|
||||
.watermark {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
transform: scale(10);
|
||||
color: #aaa;
|
||||
opacity: 0.1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.calender-header {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.calender-td {
|
||||
border: 1px solid #eee;
|
||||
width: calc(100% / 7);
|
||||
}
|
||||
|
||||
.calender-cell {
|
||||
min-height: 96px;
|
||||
min-width: 100px;
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
|
||||
&.today {
|
||||
color: var(--el-color-warning) !important;
|
||||
background-color: var(--el-color-warning-light-9) !important;
|
||||
}
|
||||
|
||||
table {
|
||||
position: relative;
|
||||
|
||||
&.onhover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.calender-header {
|
||||
padding: 16px 0;
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #bbb;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.calender-td {
|
||||
border: 1px solid #eee;
|
||||
width: calc(100% / 7);
|
||||
|
||||
&.no-current-month {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.calender-cell {
|
||||
min-height: 96px;
|
||||
min-width: 100px;
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
|
||||
&.today {
|
||||
color: var(--el-color-warning) !important;
|
||||
background-color: var(--el-color-warning-light-9) !important;
|
||||
|
||||
&.selected {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.calender-cell-line {
|
||||
min-height: 0px;
|
||||
|
||||
&.calender-cell-header {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.onhover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
&.calender-cell-body {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
color: #bbb;
|
||||
background: none;
|
||||
}
|
||||
|
||||
&.no-current-month {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.calender-cell-line {
|
||||
min-height: 0px;
|
||||
|
||||
&.calender-cell-header {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.calender-cell-body {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.calender-cell-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.calender-cell-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div style="display: inline-block">
|
||||
<el-button size="default" type="success" @click="handleImport()">
|
||||
<el-button type="success" @click="handleImport()">
|
||||
<slot>导入</slot>
|
||||
</el-button>
|
||||
<el-dialog :title="props.upload.title" v-model="uploadShow" width="400px" append-to-body>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
class="tableSelector"
|
||||
multiple
|
||||
:collapseTags="props.tableConfig.collapseTags"
|
||||
@remove-tag="removeTag"
|
||||
v-model="data"
|
||||
placeholder="请选择"
|
||||
@visible-change="visibleChange"
|
||||
@@ -29,9 +28,9 @@
|
||||
max-height="200"
|
||||
height="200"
|
||||
:highlight-current-row="!props.tableConfig.isMultiple"
|
||||
@selection-change="handleSelectionChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
@select="handleSelectionChange"
|
||||
@selectAll="handleSelectionChange"
|
||||
@selectAll="handleSelectionChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<el-table-column v-if="props.tableConfig.isMultiple" fixed type="selection" reserve-selection width="55" />
|
||||
@@ -59,34 +58,36 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, defineProps, onMounted, reactive, ref, watch} from 'vue';
|
||||
import { computed, defineProps, onMounted, reactive, ref, watch } from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import { request } from '/@/utils/service';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array || String || Number,
|
||||
default: () => []
|
||||
},
|
||||
type: Array || String || Number,
|
||||
default: () => [],
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default:{
|
||||
url: null,
|
||||
label: null, //显示值
|
||||
value: null, //数据值
|
||||
isTree: false,
|
||||
lazy: true,
|
||||
size:'default',
|
||||
load: () => {},
|
||||
data: [], //默认数据
|
||||
isMultiple: false, //是否多选
|
||||
collapseTags:false,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
||||
columns: [], //每一项对应的列表项
|
||||
},
|
||||
},
|
||||
type: Object,
|
||||
default: {
|
||||
url: null,
|
||||
label: null, //显示值
|
||||
value: null, //数据值
|
||||
isTree: false,
|
||||
lazy: true,
|
||||
size: 'default',
|
||||
load: () => {},
|
||||
data: [], //默认数据
|
||||
isMultiple: false, //是否多选
|
||||
collapseTags: false,
|
||||
treeProps: { children: 'children', hasChildren: 'hasChildren' },
|
||||
columns: [], //每一项对应的列表项
|
||||
},
|
||||
},
|
||||
displayLabel: {},
|
||||
} as any);
|
||||
console.log(props.tableConfig);
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
// tableRef
|
||||
const tableRef = ref();
|
||||
@@ -137,6 +138,8 @@ const handleCurrentChange = (val: any) => {
|
||||
*/
|
||||
const getDict = async () => {
|
||||
const url = props.tableConfig.url;
|
||||
console.log(url);
|
||||
|
||||
const params = {
|
||||
page: pageConfig.page,
|
||||
limit: pageConfig.limit,
|
||||
@@ -162,29 +165,25 @@ const getDict = async () => {
|
||||
|
||||
// 获取节点值
|
||||
const getNodeValues = () => {
|
||||
console.log(props.tableConfig.url);
|
||||
|
||||
request({
|
||||
url:props.tableConfig.valueUrl,
|
||||
method:'post',
|
||||
data:{ids:props.modelValue}
|
||||
}).then(res=>{
|
||||
if(res.data.length>0){
|
||||
data.value = res.data.map((item:any)=>{
|
||||
return item[props.tableConfig.label]
|
||||
})
|
||||
|
||||
tableRef.value!.clearSelection()
|
||||
res.data.forEach((row) => {
|
||||
tableRef.value!.toggleRowSelection(
|
||||
row,
|
||||
true,
|
||||
false
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
url: props.tableConfig.url,
|
||||
method: 'post',
|
||||
data: { ids: props.modelValue },
|
||||
}).then((res) => {
|
||||
if (res.data.length > 0) {
|
||||
data.value = res.data.map((item: any) => {
|
||||
return item[props.tableConfig.label];
|
||||
});
|
||||
|
||||
tableRef.value!.clearSelection();
|
||||
res.data.forEach((row) => {
|
||||
tableRef.value!.toggleRowSelection(row, true, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 下拉框展开/关闭
|
||||
@@ -205,12 +204,11 @@ const handlePageChange = (page: any) => {
|
||||
getDict();
|
||||
};
|
||||
|
||||
onMounted(()=>{
|
||||
setTimeout(()=>{
|
||||
getNodeValues()
|
||||
},1000)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// setTimeout(() => {
|
||||
// getNodeValues();
|
||||
// }, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
|
||||
<img :src="siteLogo" class="layout-logo-medium-img" />
|
||||
<span style="font-size: x-large">{{ getSystemConfig['login.site_title'] || themeConfig.globalTitle }}</span>
|
||||
<span style="font-size: x-large; ">{{ getSystemConfig['login.site_title'] || themeConfig.globalTitle }}</span>
|
||||
</div>
|
||||
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
||||
<img :src="siteLogo" class="layout-logo-size-img" />
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb-user pr15" :style="{ flex: layoutUserFlexNum }">
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||
<el-icon :title="$t('message.user.title2')">
|
||||
<ele-Search />
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>
|
||||
@@ -28,11 +33,7 @@
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||
<el-icon :title="$t('message.user.title2')">
|
||||
<ele-Search />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
|
||||
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
|
||||
</div>
|
||||
@@ -60,14 +61,14 @@
|
||||
<div>
|
||||
<span v-if="!isSocketOpen" class="online-status-span">
|
||||
<el-popconfirm
|
||||
width="250"
|
||||
ref="onlinePopoverRef"
|
||||
:confirm-button-text="$t('message.user.retry')"
|
||||
:icon="InfoFilled"
|
||||
trigger="hover"
|
||||
icon-color="#626AEF"
|
||||
:title="$t('message.user.onlinePrompt')"
|
||||
@confirm="onlineConfirmEvent"
|
||||
width="250"
|
||||
ref="onlinePopoverRef"
|
||||
:confirm-button-text="$t('message.user.retry')"
|
||||
:icon="InfoFilled"
|
||||
trigger="hover"
|
||||
icon-color="#626AEF"
|
||||
:title="$t('message.user.onlinePrompt')"
|
||||
@confirm="onlineConfirmEvent"
|
||||
>
|
||||
<template #reference>
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
@@ -77,13 +78,12 @@
|
||||
</el-popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link">
|
||||
<span v-if="isSocketOpen">
|
||||
<el-badge is-dot class="item" :class="{'online-status': isSocketOpen,'online-down':!isSocketOpen}">
|
||||
<img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
</el-badge>
|
||||
</span>
|
||||
<!-- <el-badge is-dot class="item online-status">-->
|
||||
<!-- <img :src="userInfos.avatar || headerImage" class="layout-navbars-breadcrumb-user-link-photo mr5" />-->
|
||||
<!-- </el-badge>-->
|
||||
{{ userInfos.username === '' ? 'common' : userInfos.username }}
|
||||
<el-icon class="el-icon--right">
|
||||
<ele-ArrowDown />
|
||||
@@ -115,8 +115,8 @@ import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
import headerImage from '/@/assets/img/headerImage.png';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import websocket from '/@/utils/websocket';
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
// 引入组件
|
||||
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue'));
|
||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue'));
|
||||
@@ -152,13 +152,14 @@ const { isSocketOpen } = storeToRefs(useUserInfo());
|
||||
const onlinePopoverRef = ref()
|
||||
const onlineConfirmEvent = () => {
|
||||
if (!isSocketOpen.value) {
|
||||
websocket.is_reonnect = true
|
||||
websocket.reconnect_current = 1
|
||||
websocket.reconnect()
|
||||
websocket.is_reonnect = true
|
||||
websocket.reconnect_current = 1
|
||||
websocket.reconnect()
|
||||
}
|
||||
// 手动隐藏弹出
|
||||
unref(onlinePopoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
|
||||
// 全屏点击时
|
||||
const onScreenfullClick = () => {
|
||||
if (!screenfull.isEnabled) {
|
||||
@@ -237,8 +238,10 @@ const onLanguageChange = (lang: string) => {
|
||||
initI18nOrSize('globalI18n', 'disabledI18n');
|
||||
};
|
||||
// 初始化组件大小/i18n
|
||||
const initI18nOrSize = (value: string, attr: string) => {
|
||||
state[attr] = Local.get('themeConfig')[value];
|
||||
const initI18nOrSize = (value: string, attr: keyof typeof state) => {
|
||||
const themeConfig = Local.get('themeConfig') as { [key: string]: any } | null;
|
||||
const configValue = ((themeConfig && themeConfig[value]) as string) || '';
|
||||
state[attr] = configValue as unknown as never;
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -246,12 +249,41 @@ onMounted(() => {
|
||||
initI18nOrSize('globalComponentSize', 'disabledSize');
|
||||
initI18nOrSize('globalI18n', 'disabledI18n');
|
||||
}
|
||||
getMessageCenterCount();
|
||||
});
|
||||
|
||||
//消息中心的未读数量
|
||||
import { messageCenterStore } from '/@/stores/messageCenter';
|
||||
import {getBaseURL} from "/@/utils/baseUrl";
|
||||
import { getBaseURL } from '/@/utils/baseUrl';
|
||||
const messageCenter = messageCenterStore();
|
||||
let eventSource: EventSource | null = null; // 存储 EventSource 实例
|
||||
const token = Session.get('token');
|
||||
const isConnected = ref(false); // 标志变量,记录是否已连接过
|
||||
const getMessageCenterCount = () => {
|
||||
// 创建 EventSource 实例并连接到后端 SSE 端点
|
||||
eventSource = new EventSource(`${getBaseURL()}sse/?token=${token}`); // 替换为你的后端地址
|
||||
// 首次连接成功时打印一次
|
||||
eventSource.onopen = function () {
|
||||
if (!isConnected.value) {
|
||||
console.log('SSE 首次连接成功');
|
||||
isConnected.value = true; // 设置标志为已连接
|
||||
}
|
||||
};
|
||||
// 监听消息事件
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log(event.data);
|
||||
|
||||
messageCenter.setUnread(+event.data); // 更新总记录数
|
||||
};
|
||||
|
||||
// 错误处理
|
||||
eventSource.onerror = function (err) {
|
||||
console.error('SSE 错误:', err);
|
||||
if (eventSource !== null && eventSource.readyState === EventSource.CLOSED) {
|
||||
console.log('连接已关闭');
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -298,29 +330,29 @@ const messageCenter = messageCenterStore();
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
.online-status{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #18bc9c;
|
||||
}
|
||||
}
|
||||
.online-down{
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #979b9c;
|
||||
}
|
||||
}
|
||||
.online-status {
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #18bc9c;
|
||||
}
|
||||
}
|
||||
.online-down {
|
||||
cursor: pointer;
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
top: 30px;
|
||||
font-size: 14px;
|
||||
left: 5px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
padding: 0;
|
||||
background-color: #979b9c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div class="layout-navbars-breadcrumb-user-news">
|
||||
<div class="head-box">
|
||||
<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
|
||||
<!-- <div class="head-box-btn" v-if="state.newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>-->
|
||||
|
||||
<!-- <div class="head-box-btn" v-if="state.newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div> -->
|
||||
</div>
|
||||
<div class="content-box">
|
||||
<template v-if="state.newsList.length > 0">
|
||||
@@ -21,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutBreadcrumbUserNews">
|
||||
import { reactive,onBeforeMount,ref,onMounted } from 'vue';
|
||||
import { reactive, onBeforeMount, ref, onMounted } from 'vue';
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
@@ -33,27 +34,27 @@ const onAllReadClick = () => {
|
||||
state.newsList = [];
|
||||
};
|
||||
// 前往通知中心点击
|
||||
import {useRouter } from "vue-router";
|
||||
const route = useRouter()
|
||||
import { useRouter } from 'vue-router';
|
||||
const route = useRouter();
|
||||
const onGoToGiteeClick = () => {
|
||||
route.push('/messageCenter')
|
||||
route.push('/messageCenter');
|
||||
};
|
||||
//获取最新消息
|
||||
import { request } from "/@/utils/service";
|
||||
const getLastMsg= ()=>{
|
||||
request({
|
||||
url: '/api/system/message_center/get_newest_msg/',
|
||||
method: 'get',
|
||||
params: {}
|
||||
}).then((res:any) => {
|
||||
const { data } = res
|
||||
state.newsList= [data]
|
||||
})
|
||||
}
|
||||
onMounted(()=>{
|
||||
getLastMsg()
|
||||
})
|
||||
|
||||
import { request } from '/@/utils/service';
|
||||
const getLastMsg = () => {
|
||||
request({
|
||||
url: '/api/system/message_center/get_newest_msg/',
|
||||
method: 'get',
|
||||
params: {},
|
||||
}).then((res: any) => {
|
||||
const { data } = res;
|
||||
if (data) state.newsList = [data];
|
||||
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
getLastMsg();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -591,10 +591,13 @@ watch(
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-tagsview {
|
||||
background-color: var(--el-color-white);
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
height: 45px;
|
||||
border-radius: 8px;
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
@@ -602,7 +605,7 @@ watch(
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 34px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--el-text-color-regular);
|
||||
@@ -610,7 +613,7 @@ watch(
|
||||
white-space: nowrap;
|
||||
padding: 0 15px;
|
||||
&-li {
|
||||
height: 26px;
|
||||
height: 30px;
|
||||
line-height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -93,7 +93,7 @@ const initElMenuOffsetLeft = () => {
|
||||
nextTick(() => {
|
||||
let els = <HTMLElement>document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
||||
if (!els) return false;
|
||||
elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
|
||||
// elMenuHorizontalScrollRef.value.$refs.wrapRef.scrollLeft = els.offsetLeft;
|
||||
});
|
||||
};
|
||||
// 路由过滤递归函数
|
||||
|
||||
@@ -25,6 +25,8 @@ import fontAwesome470 from 'e-icon-picker/icon/fontawesome/font-awesome.v4.7.0.j
|
||||
import eIconList from 'e-icon-picker/icon/default-icon/eIconList.js';
|
||||
import iconfont from '/@/assets/iconfont/iconfont.json'; //引入json文件
|
||||
import '/@/assets/iconfont/iconfont.css'; //引入css
|
||||
import '/@/assets/iconfont/iconfont-01/iconfont.css'; //引入css
|
||||
import '/@/assets/iconfont/iconfont-02/iconfont.css'; //引入css
|
||||
// 自动注册插件
|
||||
import { scanAndInstallPlugins } from '/@/views/plugins/index';
|
||||
import VXETable from 'vxe-table'
|
||||
|
||||
@@ -98,10 +98,22 @@ export function formatTwoStageRoutes(arr: any) {
|
||||
|
||||
const frameOutRoutes = staticRoutes.map(item => item.path)
|
||||
|
||||
const checkToken = ()=>{
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const _oauth2_token = urlParams.get('_oauth2_token');
|
||||
if (_oauth2_token) {
|
||||
Session.set('token', _oauth2_token);
|
||||
const cleanUrl = window.location.href.split('?')[0];
|
||||
window.history.replaceState({}, '', cleanUrl);
|
||||
useUserInfo(pinia).setUserInfos();
|
||||
|
||||
}
|
||||
}
|
||||
// 路由加载前
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// 检查浏览器本地版本与线上版本是否一致,判断是否需要刷新页面进行更新
|
||||
await checkVersion()
|
||||
checkToken()
|
||||
NProgress.configure({showSpinner: false});
|
||||
if (to.meta.title) NProgress.start();
|
||||
const token = Session.get('token');
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
* 定义接口来定义对象的类型
|
||||
* `stores` 全部类型定义在这里
|
||||
*/
|
||||
import {useFrontendMenuStore} from "/@/stores/frontendMenu";
|
||||
import { useFrontendMenuStore } from "/@/stores/frontendMenu";
|
||||
|
||||
// 用户信息
|
||||
export interface UserInfosState {
|
||||
id: '',
|
||||
avatar: string;
|
||||
is_superuser: boolean,
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
mobile: string;
|
||||
gender: string;
|
||||
pwd_change_count:null|number;
|
||||
pwd_change_count: null | number;
|
||||
dept_info: {
|
||||
dept_id: number;
|
||||
dept_name: string;
|
||||
@@ -107,9 +109,9 @@ export interface ConfigStates {
|
||||
|
||||
export interface FrontendMenu {
|
||||
arrayRouter: Array<any>;
|
||||
treeRouter:Array<any>;
|
||||
treeRouter: Array<any>;
|
||||
|
||||
frameOutRoutes:Array<any>;
|
||||
frameOutRoutes: Array<any>;
|
||||
|
||||
frameInRoutes:Array<any>;
|
||||
frameInRoutes: Array<any>;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
* 全局主题
|
||||
*/
|
||||
// 默认 primary 主题颜色
|
||||
primary: '#409eff',
|
||||
primary: "#193755",
|
||||
// 是否开启深色模式
|
||||
isIsDark: false,
|
||||
|
||||
@@ -26,9 +26,9 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
* 顶栏设置
|
||||
*/
|
||||
// 默认顶栏导航背景颜色
|
||||
topBar: '#ffffff',
|
||||
topBar: "#f8f8f8",
|
||||
// 默认顶栏导航字体颜色
|
||||
topBarColor: '#606266',
|
||||
topBarColor: "#000000",
|
||||
// 是否开启顶栏背景颜色渐变
|
||||
isTopBarColorGradual: false,
|
||||
|
||||
@@ -36,11 +36,11 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
* 菜单设置
|
||||
*/
|
||||
// 默认菜单导航背景颜色
|
||||
menuBar: '#334054',
|
||||
menuBar: "#f8f8f8",
|
||||
// 默认菜单导航字体颜色
|
||||
menuBarColor: '#eaeaea',
|
||||
menuBarColor: "#000000",
|
||||
// 默认菜单高亮背景色
|
||||
menuBarActiveColor: 'rgba(0, 0, 0, 0.2)',
|
||||
menuBarActiveColor: "rgba(0, 48, 255, 0.38)",
|
||||
// 是否开启菜单背景颜色渐变
|
||||
isMenuBarColorGradual: false,
|
||||
|
||||
@@ -48,9 +48,9 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
* 分栏设置
|
||||
*/
|
||||
// 默认分栏菜单背景颜色
|
||||
columnsMenuBar: '#334054',
|
||||
columnsMenuBar:"#334054",
|
||||
// 默认分栏菜单字体颜色
|
||||
columnsMenuBarColor: '#e6e6e6',
|
||||
columnsMenuBarColor: "#e6e6e6",
|
||||
// 是否开启分栏菜单背景颜色渐变
|
||||
isColumnsMenuBarColorGradual: false,
|
||||
// 是否开启分栏菜单鼠标悬停预加载(预览菜单)
|
||||
|
||||
@@ -12,6 +12,7 @@ import headerImage from '/@/assets/img/headerImage.png';
|
||||
export const useUserInfo = defineStore('userInfo', {
|
||||
state: (): UserInfosStates => ({
|
||||
userInfos: {
|
||||
id:'',
|
||||
avatar: '',
|
||||
username: '',
|
||||
name: '',
|
||||
@@ -19,6 +20,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
mobile: '',
|
||||
gender: '',
|
||||
pwd_change_count:null,
|
||||
is_superuser: false,
|
||||
dept_info: {
|
||||
dept_id: 0,
|
||||
dept_name: '',
|
||||
@@ -37,6 +39,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
this.userInfos.pwd_change_count = count;
|
||||
},
|
||||
async updateUserInfos(userInfos:any) {
|
||||
this.userInfos.id = userInfos.id;
|
||||
this.userInfos.username = userInfos.name;
|
||||
this.userInfos.avatar = userInfos.avatar;
|
||||
this.userInfos.name = userInfos.name;
|
||||
@@ -46,6 +49,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
this.userInfos.dept_info = userInfos.dept_info;
|
||||
this.userInfos.role_info = userInfos.role_info;
|
||||
this.userInfos.pwd_change_count = userInfos.pwd_change_count;
|
||||
this.userInfos.is_superuser = userInfos.is_superuser;
|
||||
Session.set('userInfo', this.userInfos);
|
||||
},
|
||||
async setUserInfos() {
|
||||
@@ -54,6 +58,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
this.userInfos = Session.get('userInfo');
|
||||
} else {
|
||||
let userInfos: any = await this.getApiUserInfo();
|
||||
this.userInfos.id = userInfos.id;
|
||||
this.userInfos.username = userInfos.data.name;
|
||||
this.userInfos.avatar = userInfos.data.avatar;
|
||||
this.userInfos.name = userInfos.data.name;
|
||||
@@ -63,6 +68,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
this.userInfos.dept_info = userInfos.data.dept_info;
|
||||
this.userInfos.role_info = userInfos.data.role_info;
|
||||
this.userInfos.pwd_change_count = userInfos.data.pwd_change_count;
|
||||
this.userInfos.is_superuser = userInfos.data.is_superuser;
|
||||
Session.set('userInfo', this.userInfos);
|
||||
}
|
||||
},
|
||||
@@ -74,6 +80,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
url: '/api/system/user/user_info/',
|
||||
method: 'get',
|
||||
}).then((res:any)=>{
|
||||
this.userInfos.id = res.data.id;
|
||||
this.userInfos.username = res.data.name;
|
||||
this.userInfos.avatar = (res.data.avatar && getBaseURL(res.data.avatar)) || headerImage;
|
||||
this.userInfos.name = res.data.name;
|
||||
@@ -83,6 +90,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
this.userInfos.dept_info = res.data.dept_info;
|
||||
this.userInfos.role_info = res.data.role_info;
|
||||
this.userInfos.pwd_change_count = res.data.pwd_change_count;
|
||||
this.userInfos.is_superuser = res.data.is_superuser;
|
||||
Session.set('userInfo', this.userInfos);
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'mixins/index.scss';
|
||||
@use 'mixins/index.scss' as index;
|
||||
|
||||
/* Button 按钮
|
||||
------------------------------- */
|
||||
@@ -29,7 +29,7 @@
|
||||
.el-form {
|
||||
// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
|
||||
.el-form-item:last-of-type {
|
||||
margin-bottom: 22px !important;
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
// 修复行内表单最后一个 el-form-item 位置下移问题
|
||||
&.el-form--inline {
|
||||
@@ -38,12 +38,12 @@
|
||||
}
|
||||
.el-form-item--default.el-form-item:last-of-type,
|
||||
.el-form-item--small.el-form-item:last-of-type {
|
||||
margin-bottom: 18px !important;
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
}
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
|
||||
.el-form-item .el-form-item__label .el-icon {
|
||||
margin-right: 0px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +76,16 @@
|
||||
width: 220px;
|
||||
}
|
||||
.el-menu-item {
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
height: 46px !important;
|
||||
line-height: 46px !important;
|
||||
border-radius:12px;
|
||||
}
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 46px !important;
|
||||
line-height: 46px !important;
|
||||
color: var(--next-bg-menuBarColor);
|
||||
border-radius:12px;
|
||||
}
|
||||
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
|
||||
.el-menu--collapse {
|
||||
@@ -100,7 +104,7 @@
|
||||
.el-sub-menu .iconfont,
|
||||
.el-menu-item .fa,
|
||||
.el-sub-menu .fa {
|
||||
@include generalIcon;
|
||||
@include index.generalIcon;
|
||||
}
|
||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
|
||||
.el-menu-item.is-active,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.icon-selector-warp-title {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
left: 15px;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './app.scss';
|
||||
@import 'common/transition.scss';
|
||||
@import './other.scss';
|
||||
@import './element.scss';
|
||||
@import './media/media.scss';
|
||||
@import './waves.scss';
|
||||
@import './dark.scss';
|
||||
@import './fa/css/font-awesome.min.css';
|
||||
@use './app.scss';
|
||||
@use 'common/transition.scss';
|
||||
@use './other.scss';
|
||||
@use './element.scss';
|
||||
@use './media/media.scss';
|
||||
@use './waves.scss';
|
||||
@use './dark.scss';
|
||||
@use './fa/css/font-awesome.min.css';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.big-data-down-left {
|
||||
width: 100% !important;
|
||||
flex-direction: unset !important;
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
/* 页面宽度大于768px小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (min-width: $sm) and (max-width: $lg) {
|
||||
@media screen and (min-width: index.$sm) and (max-width: index.$lg) {
|
||||
.chart-warp-bottom {
|
||||
.big-data-down-left {
|
||||
width: 50% !important;
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
/* 页面宽度小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $lg) {
|
||||
@media screen and (max-width: index.$lg) {
|
||||
.chart-warp-top {
|
||||
.up-left {
|
||||
display: none;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.el-cascader__dropdown.el-popper {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
// 时间选择器适配
|
||||
.el-date-range-picker {
|
||||
width: 100vw;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于800px
|
||||
------------------------------- */
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.error {
|
||||
.error-flex {
|
||||
flex-direction: column-reverse !important;
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
/* 页面宽度大于768px小于992px
|
||||
------------------------------- */
|
||||
@media screen and (min-width: $sm) and (max-width: $md) {
|
||||
@media screen and (min-width: index.$sm) and (max-width: index.$md) {
|
||||
.error {
|
||||
.error-flex {
|
||||
padding-left: 30px !important;
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
/* 页面宽度小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $lg) {
|
||||
@media screen and (max-width: index.$lg) {
|
||||
.error {
|
||||
.error-flex {
|
||||
padding: 0 30px;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.el-form-item__label {
|
||||
width: 100% !important;
|
||||
text-align: left !important;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.home-media,
|
||||
.home-media-sm {
|
||||
margin-top: 15px;
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
/* 页面宽度小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $lg) {
|
||||
@media screen and (max-width: index.$lg) {
|
||||
.home-media-lg {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
@media screen and (max-width: index.$xs) {
|
||||
// MessageBox 弹框
|
||||
.el-message-box {
|
||||
width: 80% !important;
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
// Breadcrumb 面包屑
|
||||
.layout-navbars-breadcrumb-hide {
|
||||
display: none;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $lg) and (min-width: $xs) {
|
||||
@media screen and (max-width: index.$lg) and (min-width: index.$xs) {
|
||||
.login-container {
|
||||
.login-left {
|
||||
.login-left-img {
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.login-container {
|
||||
.login-left {
|
||||
display: none;
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
/* 页面宽度小于375px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $us) {
|
||||
@media screen and (max-width: index.$us) {
|
||||
.login-container {
|
||||
.login-right {
|
||||
.login-right-warp {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
@import './login.scss';
|
||||
@import './error.scss';
|
||||
@import './layout.scss';
|
||||
@import './personal.scss';
|
||||
@import './tagsView.scss';
|
||||
@import './home.scss';
|
||||
@import './chart.scss';
|
||||
@import './form.scss';
|
||||
@import './scrollbar.scss';
|
||||
@import './pagination.scss';
|
||||
@import './dialog.scss';
|
||||
@import './cityLinkage.scss';
|
||||
@import './date.scss';
|
||||
@use './login.scss';
|
||||
@use './error.scss';
|
||||
@use './layout.scss';
|
||||
@use './personal.scss';
|
||||
@use './tagsView.scss';
|
||||
@use './home.scss';
|
||||
@use './chart.scss';
|
||||
@use './form.scss';
|
||||
@use './scrollbar.scss';
|
||||
@use './pagination.scss';
|
||||
@use './dialog.scss';
|
||||
@use './cityLinkage.scss';
|
||||
@use './date.scss';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.el-pager,
|
||||
.el-pagination__jump {
|
||||
display: none !important;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.personal-info {
|
||||
padding-left: 0 !important;
|
||||
margin-top: 15px;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
// 滚动条的宽度
|
||||
::-webkit-scrollbar {
|
||||
width: 3px !important;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.tags-view-form {
|
||||
.tags-view-form-col {
|
||||
margin-bottom: 20px;
|
||||
|
||||
@@ -172,20 +172,20 @@ function createRequestFunction(service: any) {
|
||||
return function (config: any) {
|
||||
const configDefault = {
|
||||
headers: {
|
||||
'Content-Type': get(config, 'headers.Content-Type', 'application/json'),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 5000,
|
||||
baseURL: getBaseURL(),
|
||||
data: {},
|
||||
};
|
||||
|
||||
Object.assign(configDefault, config);
|
||||
// const token = userStore.getToken;
|
||||
const token = Session.get('token');
|
||||
if (token != null) {
|
||||
// @ts-ignore
|
||||
configDefault.headers.Authorization = 'JWT ' + token;
|
||||
}
|
||||
return service(Object.assign(configDefault, config));
|
||||
return service(configDefault);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 字体图标 url
|
||||
const cssCdnUrlList: Array<string> = [
|
||||
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
|
||||
'//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
|
||||
// '//at.alicdn.com/t/font_2298093_y6u00apwst.css',
|
||||
// '//at.alicdn.com/t/c/font_3882322_9ah7y8m9175.css', //dvadmin3项目用icon
|
||||
//'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'
|
||||
];
|
||||
// 第三方 js url
|
||||
|
||||
@@ -1,54 +1,96 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<el-row :gutter="15" class="home-card-one mb15">
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
v-for="(v, k) in homeOne"
|
||||
:key="k"
|
||||
:class="{ 'home-media home-media-lg': k > 1, 'home-media-sm': k === 1 }"
|
||||
<div class="home-container">
|
||||
<div style="margin: 15px; font-size: 16px; font-weight: 700">
|
||||
欢迎回来,{{ userInfo.userInfos.name }}
|
||||
<span style="font-size: 12px; color: grey"> 这里是您的工作台,请愉快的工作吧!</span>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-row :gutter="15" class="home-card-one mb15">
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
v-for="(v, k) in homeOne"
|
||||
:key="k"
|
||||
:class="{ 'home-media home-media-lg': k > 1, 'home-media-sm': k === 1 }"
|
||||
>
|
||||
<div class="home-card-item flex">
|
||||
<div class="home-card-item flex" style="padding: 0;">
|
||||
<div class="flex-margin flex w100" :class="` home-one-animation${k}`">
|
||||
<div class="flex-auto">
|
||||
<span class="font30">{{ v.num1 }}</span>
|
||||
<span class="ml5 font16" :style="{ color: v.color1 }">{{ v.num2 }}%</span>
|
||||
<div class="mt10">{{ v.num3 }}</div>
|
||||
<div class="home-card-item-icon flex" style="margin: 10px;" :style="{ background: `var(${v.color2})` }">
|
||||
<i class="flex-margin font24" :class="v.num4" :style="{ color: `var(${v.color3})` }"></i>
|
||||
</div>
|
||||
<div class="home-card-item-icon flex" :style="{ background: `var(${v.color2})` }">
|
||||
<i class="flex-margin font32" :class="v.num4" :style="{ color: `var(${v.color3})` }"></i>
|
||||
<div class="flex-auto">
|
||||
<span class="font24">{{ v.num1 }}</span>
|
||||
<span class="ml5 font14" :style="{ color: v.color1 }">{{ v.num2 }}%</span>
|
||||
<div class="mt10">{{ v.num3 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="15" class="home-card-two mb15">
|
||||
<el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16">
|
||||
<el-row :gutter="15" class="home-card-two mb15">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
||||
<div class="home-card-item">
|
||||
<div style="height: 100%" ref="homeLineRef"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="15" class="home-card-three">
|
||||
<el-col :span="24" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="home-media">
|
||||
<div class="home-card-item">
|
||||
<div style="height: 100%" ref="homeLineRef"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8" class="home-media">
|
||||
<div class="home-card-item">
|
||||
<div style="height: 100%" ref="homePieRef"></div>
|
||||
<div style="height: 100%" ref="homeBarRef"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="15" class="home-card-three">
|
||||
<el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8">
|
||||
<div class="home-card-item">
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div style="margin-left: 15px">
|
||||
<el-row :gutter="100" class="home-card-one mb15">
|
||||
<el-col :span="24">
|
||||
<div class="home-card-item flex" style="padding: 0; width:350px; height:200px">
|
||||
<img :src="HomeBg" style="padding: 0; margin: 0;">
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="100" class="home-card-one mb15">
|
||||
<el-col :span="24" >
|
||||
<div class="home-card-item" style=" width:350px; height: 100%; max-height: 320px">
|
||||
<div class="home-card-item-title">消息通知
|
||||
<button type="button" class="el-button" style=" float: right; border-color: transparent; margin-top: -2px;" @click="msgMore">
|
||||
<span>更多
|
||||
<i class="el-icon fs-icon fs-button-icon-right"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352m0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288m0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448m0 64a160.19 160.19 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160"></path></svg></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(v, k) in newsInfoList" :key="k" class="personal-info-li flex-margin flex w100" >
|
||||
<div class="home-card-item-icon flex" style="margin: 5px;" :style="{ background: `#f8f8f8` }">
|
||||
<i class="flex-margin font24" :class="`fa fa-commenting-o`" :style="{ color: `#5d8b22` }"></i>
|
||||
</div>
|
||||
<div class="flex-auto" style="margin-top: 10px">
|
||||
<span class="font14">[{{ v.creator_name }}]</span>
|
||||
<span style=" color: grey; float: right; font-style:italic;"> {{ v.create_datetime }} </span>
|
||||
<div class="text-container"> {{ v.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="100" class="home-card-one mb15">
|
||||
<el-col :span="24">
|
||||
<div class="home-card-item" style=" width:350px; height: 100%">
|
||||
<div class="home-card-item-title">快捷导航工具</div>
|
||||
<div class="home-monitor">
|
||||
<div class="flex-warp">
|
||||
<div class="flex-warp-item" v-for="(v, k) in homeThree" :key="k">
|
||||
<div class="flex-warp-item-box" :class="`home-animation${k}`">
|
||||
<div class="flex-margin">
|
||||
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
|
||||
<span class="pl5">{{ v.label }}</span>
|
||||
<div class="mt10">{{ v.value }}</div>
|
||||
<div class="home-card-item-icon flex" style="margin: 20px;" :style="{ background: '#f8f8f8' }">
|
||||
<i class="flex-margin font24" :class="v.icon" :style="{ color: v.iconColor}"></i>
|
||||
</div>
|
||||
<span class="pl20" :style="{ fontSize: 'clamp(0.875rem, 2vw, 1rem)' }">{{ v.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,13 +98,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16" class="home-media">
|
||||
<div class="home-card-item">
|
||||
<div style="height: 100%" ref="homeBarRef"></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -71,6 +111,7 @@ import * as echarts from 'echarts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import HomeBg from '/@/assets/home-bg.png';
|
||||
|
||||
let global: any = {
|
||||
homeChartOne: null,
|
||||
@@ -79,9 +120,21 @@ let global: any = {
|
||||
dispose: [null, '', undefined],
|
||||
};
|
||||
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import {useRouter} from "vue-router";
|
||||
import * as api from "/@/views/system/personal/api";
|
||||
|
||||
// 定义消息类型
|
||||
interface NewsItem {
|
||||
creator_name: string;
|
||||
create_datetime: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'home',
|
||||
setup() {
|
||||
const userInfo = useUserInfo();
|
||||
const homeLineRef = ref();
|
||||
const homePieRef = ref();
|
||||
const homeBarRef = ref();
|
||||
@@ -89,7 +142,11 @@ export default defineComponent({
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
|
||||
const router = useRouter(); // 将router移到组件级别
|
||||
const defaultNewsItems: NewsItem[] = [];
|
||||
|
||||
const state = reactive({
|
||||
newsInfoList: [...defaultNewsItems] as NewsItem[],
|
||||
homeOne: [
|
||||
{
|
||||
num1: '125,12',
|
||||
@@ -109,15 +166,6 @@ export default defineComponent({
|
||||
color2: '--next-color-success-lighter',
|
||||
color3: '--el-color-success',
|
||||
},
|
||||
{
|
||||
num1: '125,65',
|
||||
num2: '+17.32',
|
||||
num3: '年度计划信息',
|
||||
num4: 'iconfont icon-zaosheng',
|
||||
color1: '#6690F9',
|
||||
color2: '--next-color-warning-lighter',
|
||||
color3: '--el-color-warning',
|
||||
},
|
||||
{
|
||||
num1: '520,43',
|
||||
num2: '-10.01',
|
||||
@@ -131,57 +179,48 @@ export default defineComponent({
|
||||
homeThree: [
|
||||
{
|
||||
icon: 'iconfont icon-yangan',
|
||||
label: '浅粉红',
|
||||
value: '2.1%OBS/M',
|
||||
iconColor: '#F72B3F',
|
||||
label: '用户管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-wendu',
|
||||
label: '深红(猩红)',
|
||||
value: '30℃',
|
||||
iconColor: '#91BFF8',
|
||||
label: '部门管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-shidu',
|
||||
label: '淡紫红',
|
||||
value: '57%RH',
|
||||
iconColor: '#88D565',
|
||||
label: '权限管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-shidu',
|
||||
label: '弱紫罗兰红',
|
||||
value: '107w',
|
||||
iconColor: '#88D565',
|
||||
label: '日志管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-zaosheng',
|
||||
label: '中紫罗兰红',
|
||||
value: '57DB',
|
||||
iconColor: '#FBD4A0',
|
||||
label: '菜单管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-zaosheng',
|
||||
label: '紫罗兰',
|
||||
value: '57PV',
|
||||
iconColor: '#FBD4A0',
|
||||
label: '消息中心',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-zaosheng',
|
||||
label: '暗紫罗兰',
|
||||
value: '517Cpd',
|
||||
iconColor: '#FBD4A0',
|
||||
label: '接口管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-zaosheng',
|
||||
label: '幽灵白',
|
||||
value: '12kg',
|
||||
iconColor: '#FBD4A0',
|
||||
label: '下载中心',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-zaosheng',
|
||||
label: '海军蓝',
|
||||
value: '64fm',
|
||||
iconColor: '#FBD4A0',
|
||||
label: '系统管理',
|
||||
iconColor: 'gray',
|
||||
},
|
||||
],
|
||||
myCharts: [],
|
||||
@@ -506,6 +545,7 @@ export default defineComponent({
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
initEchartsResize();
|
||||
getMsg(); // 确保组件挂载时立即获取消息列表
|
||||
});
|
||||
// 由于页面缓存原因,keep-alive
|
||||
onActivated(() => {
|
||||
@@ -542,11 +582,47 @@ export default defineComponent({
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
// 获取消息列表
|
||||
const getMsg = (): void => {
|
||||
// 先重置为默认数据
|
||||
state.newsInfoList = [...defaultNewsItems];
|
||||
|
||||
// 尝试从API获取最新数据
|
||||
api.GetSelfReceive({}).then((res: any) => {
|
||||
const { data } = res || {};
|
||||
// 严格检查返回数据的有效性
|
||||
if (data && Array.isArray(data) && data.length > 0) {
|
||||
try {
|
||||
// 安全地进行类型转换并更新状态
|
||||
state.newsInfoList = data.map((item: any): NewsItem => ({
|
||||
creator_name: String(item.creator_name || '未知用户'),
|
||||
create_datetime: String(item.create_datetime || ''),
|
||||
title: String(item.title || '')
|
||||
}));
|
||||
} catch (typeError) {
|
||||
console.error('数据类型转换失败:', typeError);
|
||||
// 类型转换失败时保持默认数据
|
||||
}
|
||||
}
|
||||
}).catch((error: Error) => {
|
||||
console.error('获取消息列表失败,保持默认数据:', error);
|
||||
// 错误时保持已设置的默认数据
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转消息中心
|
||||
const msgMore = (): void => {
|
||||
router.push({ path: '/messageCenter' });
|
||||
};
|
||||
|
||||
return {
|
||||
homeLineRef,
|
||||
homePieRef,
|
||||
homeBarRef,
|
||||
...toRefs(state),
|
||||
homeLineRef,
|
||||
homePieRef,
|
||||
homeBarRef,
|
||||
userInfo,
|
||||
...toRefs(state),
|
||||
HomeBg,
|
||||
msgMore,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -554,6 +630,20 @@ export default defineComponent({
|
||||
|
||||
<style scoped lang="scss">
|
||||
$homeNavLengh: 8;
|
||||
.text-container {
|
||||
width: 420px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.text-container:hover {
|
||||
animation: scrollText 5s linear infinite;
|
||||
}
|
||||
@keyframes scrollText {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
.home-container {
|
||||
overflow: hidden;
|
||||
.home-card-one,
|
||||
@@ -561,21 +651,21 @@ $homeNavLengh: 8;
|
||||
.home-card-three {
|
||||
.home-card-item {
|
||||
width: 100%;
|
||||
height: 130px;
|
||||
border-radius: 4px;
|
||||
height: 120px;
|
||||
transition: all ease 0.3s;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
background: var(--el-color-white);
|
||||
color: var(--el-text-color-primary);
|
||||
border: 1px solid var(--next-border-color-light);
|
||||
border-radius: 24px;
|
||||
&:hover {
|
||||
box-shadow: 0 2px 12px var(--next-color-dark-hover);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
&-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
border-radius: 100%;
|
||||
flex-shrink: 1;
|
||||
i {
|
||||
@@ -583,13 +673,15 @@ $homeNavLengh: 8;
|
||||
}
|
||||
}
|
||||
&-title {
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.home-card-one {
|
||||
left:15px;
|
||||
right: 15px;
|
||||
@for $i from 0 through 3 {
|
||||
.home-one-animation#{$i} {
|
||||
opacity: 0;
|
||||
@@ -602,6 +694,9 @@ $homeNavLengh: 8;
|
||||
}
|
||||
.home-card-two,
|
||||
.home-card-three {
|
||||
position: relative;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
.home-card-item {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
@@ -626,7 +721,7 @@ $homeNavLengh: 8;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
@for $i from 0 through $homeNavLengh {
|
||||
@for $i from 0 through 3 {
|
||||
.home-animation#{$i} {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
@@ -641,3 +736,4 @@ $homeNavLengh: 8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -28,3 +28,10 @@ export function getUserInfo() {
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function getBackends() {
|
||||
return request({
|
||||
url: '/api/dvadmin3_social_oauth2/backend/get_login_backend/',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
@@ -187,8 +187,8 @@ export default defineComponent({
|
||||
// 初始化登录成功时间问候语
|
||||
let currentTimeInfo = currentTime.value;
|
||||
// 登录成功,跳到转首页
|
||||
const pwd_change_count = userInfos.value.pwd_change_count
|
||||
if(pwd_change_count>0){
|
||||
const pwd_change_count = userInfos.value.pwd_change_count ?? 0
|
||||
if(pwd_change_count > 0){
|
||||
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
||||
if (route.query?.redirect) {
|
||||
router.push({
|
||||
@@ -234,16 +234,37 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 定义error-num动画的关键帧
|
||||
@keyframes error-num {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
|
||||
// 为输入框添加圆角和设置字体大小
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
// 设置输入框文字大小
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 12px !important; // Element Plus large尺寸的默认字体大小
|
||||
}
|
||||
|
||||
@for $i from 1 through 4 {
|
||||
.login-animation#{$i} {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: calc($i/10) + s;
|
||||
animation-delay: #{$i/10}s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +274,7 @@ export default defineComponent({
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #909399;
|
||||
color: #909397;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +283,7 @@ export default defineComponent({
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
letter-spacing: 5px;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.login-content-submit {
|
||||
@@ -269,6 +291,7 @@ export default defineComponent({
|
||||
letter-spacing: 2px;
|
||||
font-weight: 800;
|
||||
margin-top: 15px;
|
||||
border-radius:8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -239,6 +239,15 @@ export default defineComponent({
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
|
||||
// 为输入框添加圆角和设置字体大小
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
// 设置输入框文字大小
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 12px !important; // Element Plus large尺寸的默认字体大小
|
||||
}
|
||||
|
||||
@for $i from 1 through 5 {
|
||||
.login-animation#{$i} {
|
||||
opacity: 0;
|
||||
@@ -264,6 +273,7 @@ export default defineComponent({
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
letter-spacing: 5px;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.login-content-submit {
|
||||
@@ -271,6 +281,7 @@ export default defineComponent({
|
||||
letter-spacing: 2px;
|
||||
font-weight: 800;
|
||||
margin-top: 15px;
|
||||
border-radius:8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
139
web/src/views/system/login/component/oauth2.vue
Normal file
139
web/src/views/system/login/component/oauth2.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="other-fast-way" v-if="backends.length">
|
||||
<div class="fast-title"><span>其他快速方式登录</span></div>
|
||||
<ul class="fast-list">
|
||||
<li v-for="(v, k) in backends" :key="v">
|
||||
<a @click.once="handleOAuth2LoginClick(v)" style="width: 50px;color: #18bc9c">
|
||||
<img :src="v.icon" :alt="v.app_name" />
|
||||
<p>{{ v.app_name }}</p>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, reactive, toRefs } from 'vue';
|
||||
import * as loginApi from '../api';
|
||||
import { OAuth2Backend } from '/@/views/system/login/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'loginOAuth2',
|
||||
setup() {
|
||||
const handleOAuth2LoginClick = (backend: OAuth2Backend) => {
|
||||
history.replaceState(null, '', location.pathname + location.search);
|
||||
window.location.href = backend.authentication_url + '?next=' + window.location.href;
|
||||
};
|
||||
const state = reactive({
|
||||
handleOAuth2LoginClick: handleOAuth2LoginClick,
|
||||
backends: [],
|
||||
});
|
||||
const getBackends = async () => {
|
||||
loginApi.getBackends().then((ret: any) => {
|
||||
state.backends = ret.data;
|
||||
});
|
||||
};
|
||||
// const handleTreeClick = (record: MenuTreeItemType) => {
|
||||
// menuButtonRef.value?.handleRefreshTable(record);
|
||||
// menuFieldRef.value?.handleRefreshTable(record)
|
||||
// };
|
||||
|
||||
onMounted(() => {
|
||||
// getBackends();
|
||||
});
|
||||
return {
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
@for $i from 1 through 4 {
|
||||
.login-animation#{$i} {
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: calc($i/10) + s;
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-code {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.login-content-submit {
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 300;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.login-msg {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
.other-fast-way {
|
||||
//height: 240px;
|
||||
position: relative;
|
||||
|
||||
z-index: 1;
|
||||
//display: flex;
|
||||
//align-items: center;
|
||||
//justify-content: center;
|
||||
.fast-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
span {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
.fast-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
li {
|
||||
margin-left: 20px;
|
||||
opacity: 0;
|
||||
animation-name: error-num;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 0.1s;
|
||||
a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 35px;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,8 +5,6 @@
|
||||
<img :src="siteLogo" />
|
||||
<div class="login-left-logo-text">
|
||||
<span>{{ getSystemConfig['login.site_title'] || getThemeConfig.globalViceTitle }}</span>
|
||||
<span class="login-left-logo-text-msg" style="margin-top: 5px;">{{
|
||||
getSystemConfig['login.site_name'] || getThemeConfig.globalViceTitleMsg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,7 +14,9 @@
|
||||
<!-- <span class="login-right-warp-two"></span>-->
|
||||
<div class="login-right-warp-mian">
|
||||
<div class="login-right-warp-main-title">
|
||||
{{userInfos.pwd_change_count===0?'初次登录修改密码':'欢迎登录'}}
|
||||
<span>{{getSystemConfig['login.site_name'] || getThemeConfig.globalViceTitleMsg }}</span>
|
||||
<br>
|
||||
<span>{{userInfos.pwd_change_count===0?'初次登录请修改密码':'欢迎登录'}}</span>
|
||||
</div>
|
||||
<div class="login-right-warp-main-form">
|
||||
<div v-if="!state.isScan">
|
||||
@@ -34,7 +34,9 @@
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
</div>
|
||||
<!-- <Scan v-if="state.isScan" />-->
|
||||
<OAuth2 />
|
||||
|
||||
<!-- <Scan v-if="state.isScan" />-->
|
||||
<!-- <div class="login-content-main-sacn" @click="state.isScan = !state.isScan">-->
|
||||
<!-- <i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>-->
|
||||
<!-- <div class="login-content-main-sacn-delta"></div>-->
|
||||
@@ -45,10 +47,10 @@
|
||||
</div>
|
||||
|
||||
<div class="login-authorization z-10">
|
||||
<p>Copyright © {{ getSystemConfig['login.copyright'] || '2021-2024 北京信码新创科技有限公司' }} 版权所有</p>
|
||||
<p>Copyright © {{ getSystemConfig['login.copyright'] || '2021-2025 django-vue-admin.com' }} 版权所有</p>
|
||||
<p class="la-other" style="margin-top: 5px;">
|
||||
<a href="https://beian.miit.gov.cn" target="_blank">{{ getSystemConfig['login.keep_record'] ||
|
||||
'京ICP备2021031018号' }}</a>
|
||||
'晋ICP备18005113号-3' }}</a>
|
||||
|
|
||||
<a :href="getSystemConfig['login.help_url'] ? getSystemConfig['login.help_url'] : '#'"
|
||||
target="_blank">帮助</a>
|
||||
@@ -81,6 +83,8 @@ const Account = defineAsyncComponent(() => import('/@/views/system/login/compone
|
||||
const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
|
||||
const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'));
|
||||
const ChangePwd = defineAsyncComponent(() => import('/@/views/system/login/component/changePwd.vue'));
|
||||
const OAuth2 = defineAsyncComponent(() => import('/@/views/system/login/component/oauth2.vue'));
|
||||
|
||||
import _ from "lodash-es";
|
||||
import {useUserInfo} from "/@/stores/userInfo";
|
||||
const { userInfos } = storeToRefs(useUserInfo());
|
||||
@@ -164,14 +168,11 @@ onMounted(() => {
|
||||
|
||||
span {
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
font-size: 24px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.login-left-logo-text-msg {
|
||||
font-size: 12px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,10 +277,10 @@ onMounted(() => {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
|
||||
.login-right-warp-main-title {
|
||||
height: 130px;
|
||||
line-height: 130px;
|
||||
font-size: 32px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
letter-spacing: 3px;
|
||||
@@ -343,7 +344,7 @@ onMounted(() => {
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
||||
8
web/src/views/system/login/types.ts
Normal file
8
web/src/views/system/login/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
export interface OAuth2Backend {
|
||||
app_name: string;
|
||||
backend_name: string;
|
||||
icon: string;
|
||||
authentication_url: string;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('btn:Create')
|
||||
show: auth('menu:CreateButton')
|
||||
},
|
||||
batchAdd: {
|
||||
show: true,
|
||||
@@ -108,10 +108,10 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
|
||||
edit: {
|
||||
icon: '',
|
||||
type: 'primary',
|
||||
show: auth('btn:Update')
|
||||
show: auth('menu:UpdateButton')
|
||||
},
|
||||
remove: {
|
||||
show: auth('btn:Delete')
|
||||
show: auth('menu:DeleteButton')
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -254,6 +254,9 @@ const handleSubmit = () => {
|
||||
let res;
|
||||
menuBtnLoading.value = true;
|
||||
if (menuFormData.id) {
|
||||
if (menuFormData.parent == undefined) {
|
||||
menuFormData.parent = null
|
||||
}
|
||||
res = await UpdateObj(menuFormData);
|
||||
} else {
|
||||
res = await AddObj(menuFormData);
|
||||
|
||||
@@ -131,10 +131,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
data: [
|
||||
{ value: 0, label: '按用户' },
|
||||
{ value: 1, label: '按角色' },
|
||||
{
|
||||
value: 2,
|
||||
label: '按部门',
|
||||
},
|
||||
{ value: 2, label: '按部门' },
|
||||
{ value: 3, label: '通知公告' },
|
||||
],
|
||||
}),
|
||||
@@ -142,14 +139,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
component: {
|
||||
optionName: 'el-radio-button',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '必选项',
|
||||
// @ts-ignore
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
rules: [{ required: true, message: '必选项', trigger: ['blur', 'change'] }],
|
||||
},
|
||||
},
|
||||
target_user: {
|
||||
@@ -191,10 +181,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
}),
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '必填项',
|
||||
},
|
||||
{ required: true, message: '必填项' },
|
||||
],
|
||||
},
|
||||
column: {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<el-col :xs="24" :sm="24" class="personal-item mb6">
|
||||
<div class="personal-item-label">角色:</div>
|
||||
<div class="personal-item-value">
|
||||
<el-tag v-for="(item, index) in state.personalForm.role_info" :key="index" style="margin-right: 5px;">{{ item.name }}</el-tag>
|
||||
<el-tag v-for="(item, index) in state.personalForm.role_info" :key="index" style="margin-right: 5px">{{ item.name }}</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -84,10 +84,10 @@
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="性别">
|
||||
<el-select v-model="state.personalForm.gender" placeholder="请选择性别" clearable class="w100">
|
||||
<!-- <el-option label="男" :value="1"></el-option>-->
|
||||
<!-- <el-option label="女" :value="0"></el-option>-->
|
||||
<!-- <el-option label="保密" :value="2"></el-option>-->
|
||||
<el-option v-for="(item,index) in genderList" :key="index" :label="item.label" :value="item.value"></el-option>
|
||||
<!-- <el-option label="男" :value="1"></el-option>-->
|
||||
<!-- <el-option label="女" :value="0"></el-option>-->
|
||||
<!-- <el-option label="保密" :value="2"></el-option>-->
|
||||
<el-option v-for="(item, index) in genderList" :key="index" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -181,8 +181,8 @@ import { Session } from '/@/utils/storage';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import {dictionary} from "/@/utils/dictionary";
|
||||
import {Md5} from "ts-md5";
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { Md5 } from 'ts-md5';
|
||||
const router = useRouter();
|
||||
|
||||
// 头像裁剪组件
|
||||
@@ -237,7 +237,7 @@ const genderList = ref();
|
||||
const getUserInfo = function () {
|
||||
api.GetUserInfo({}).then((res: any) => {
|
||||
const { data } = res;
|
||||
genderList.value = dictionary('gender')
|
||||
genderList.value = dictionary('gender');
|
||||
state.personalForm.avatar = data.avatar || '';
|
||||
state.personalForm.username = data.username || '';
|
||||
state.personalForm.name = data.name || '';
|
||||
@@ -335,10 +335,10 @@ const settingPassword = () => {
|
||||
if (valid) {
|
||||
api.UpdatePassword(userPasswordInfo).then((res: any) => {
|
||||
ElMessage.success('密码修改成功');
|
||||
setTimeout(() => {
|
||||
Session.remove('token');
|
||||
router.push('/login');
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
Session.remove('token');
|
||||
router.push('/login');
|
||||
}, 1000);
|
||||
});
|
||||
} else {
|
||||
// 校验失败
|
||||
@@ -369,7 +369,7 @@ const uploadImg = (data: any) => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/theme/mixins/index.scss';
|
||||
@use '/@/theme/mixins/index.scss' as mixins;
|
||||
.personal {
|
||||
.personal-user {
|
||||
height: 130px;
|
||||
@@ -400,7 +400,7 @@ const uploadImg = (data: any) => {
|
||||
padding: 0 15px;
|
||||
.personal-title {
|
||||
font-size: 18px;
|
||||
@include text-ellipsis(1);
|
||||
@include mixins.text-ellipsis(1);
|
||||
}
|
||||
.personal-item {
|
||||
display: flex;
|
||||
@@ -408,10 +408,10 @@ const uploadImg = (data: any) => {
|
||||
font-size: 13px;
|
||||
.personal-item-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
@include mixins.text-ellipsis(1);
|
||||
}
|
||||
.personal-item-value {
|
||||
@include text-ellipsis(1);
|
||||
@include mixins.text-ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,7 +436,7 @@ const uploadImg = (data: any) => {
|
||||
padding-bottom: 10px;
|
||||
.personal-info-li-title {
|
||||
display: inline-block;
|
||||
@include text-ellipsis(1);
|
||||
@include mixins.text-ellipsis(1);
|
||||
color: var(--el-text-color-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -518,7 +518,7 @@ const uploadImg = (data: any) => {
|
||||
}
|
||||
.personal-edit-safe-item-left-value {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
@include mixins.text-ellipsis(1);
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
highlight-current
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:check-on-click-leaf="false"
|
||||
>
|
||||
</el-tree>
|
||||
</template>
|
||||
|
||||
@@ -75,7 +75,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
show: auth('role:AuthorizedAdd'),
|
||||
show: auth('role:SetMenu'),
|
||||
click: (ctx: any) => {
|
||||
context!.subUserRef.value.dialog = true;
|
||||
nextTick(() => {
|
||||
@@ -91,7 +91,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
//固定右侧
|
||||
fixed: 'left',
|
||||
width: 120,
|
||||
show: auth('role:AuthorizedDel'),
|
||||
show: auth('role:SetMenu'),
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
@@ -115,7 +115,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
title: "选择",
|
||||
form: { show: false},
|
||||
column: {
|
||||
show: auth('role:AuthorizedDel'),
|
||||
show: auth('role:SetMenu'),
|
||||
type: "selection",
|
||||
align: "center",
|
||||
width: "55px",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</template>
|
||||
<template #pagination-left>
|
||||
<el-tooltip content="批量删除所选择的用户权限">
|
||||
<el-button v-show="selectedRowsCount > 0 && auth('role:AuthorizedDel')" type="danger" @click="multipleDel" :icon="Delete">批量删除</el-button>
|
||||
<el-button v-show="selectedRowsCount > 0 && auth('role:SetMenu')" type="danger" @click="multipleDel" :icon="Delete">批量删除</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
|
||||
@@ -48,7 +48,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: computed(() => {
|
||||
if (auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch')){
|
||||
if (auth('role:AllAuthorizedUser') || auth('role:AllCanMenu')){
|
||||
return 420;
|
||||
}
|
||||
return 320;
|
||||
@@ -66,7 +66,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
assignment: {
|
||||
type: 'primary',
|
||||
text: '授权用户',
|
||||
show: auth('role:AuthorizedAdd') || auth('role:AuthorizedSearch'),
|
||||
show: auth('role:AllAuthorizedUser'),
|
||||
click: (ctx: any) => {
|
||||
const { row } = ctx;
|
||||
context!.RoleUserDrawer.handleDrawerOpen(row);
|
||||
@@ -79,7 +79,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
permission: {
|
||||
type: 'primary',
|
||||
text: '权限配置',
|
||||
show: auth('role:Permission'),
|
||||
show: auth('role:SetMenu'),
|
||||
click: (clickContext: any): void => {
|
||||
const { row } = clickContext;
|
||||
context.RoleDrawer.handleDrawerOpen(row);
|
||||
@@ -181,7 +181,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
value: dictionary('button_status_bool'),
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ import { computed } from "vue";
|
||||
import { Md5 } from 'ts-md5';
|
||||
import { commonCrudConfig } from "/@/utils/commonCrud";
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { exportData } from "./api";
|
||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery) => {
|
||||
return await api.GetList(query);
|
||||
@@ -81,16 +82,16 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
title: "导出",//鼠标停留显示的信息
|
||||
show: auth('user:Export'),
|
||||
click: (ctx: any) => ElMessageBox.confirm(
|
||||
'确定重设密码吗?', '提示',
|
||||
'确定导出数据吗?', '提示',
|
||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||
).then(() => resetToDefaultPasswordRequest(ctx.row))
|
||||
).then(() => exportData(ctx.row))
|
||||
}
|
||||
}
|
||||
},
|
||||
rowHandle: {
|
||||
//固定右侧
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
width: 220,
|
||||
buttons: {
|
||||
view: {
|
||||
show: false,
|
||||
@@ -105,19 +106,15 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
type: 'text',
|
||||
show: auth('user:Delete'),
|
||||
},
|
||||
custom: {
|
||||
text: '重设密码',
|
||||
resetDefaultPwd: {
|
||||
text: '重置密码',
|
||||
type: 'text',
|
||||
show: auth('user:ResetPassword'),
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
content: '重设密码',
|
||||
},
|
||||
//@ts-ignore
|
||||
click: (ctx: any) => {
|
||||
const { row } = ctx;
|
||||
resetToDefaultPasswordRequest(row)
|
||||
},
|
||||
iconRight: 'Setting',
|
||||
show: auth('user:ResetDefaultPassword'),
|
||||
click: (ctx: any) => ElMessageBox.confirm(
|
||||
'确定重置为系统默认密码吗?', '提示',
|
||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||
).then(() => resetToDefaultPasswordRequest(ctx.row))
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -208,10 +205,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
},
|
||||
dept: {
|
||||
title: '部门',
|
||||
search: {
|
||||
disabled: true,
|
||||
},
|
||||
title: '所属部门',
|
||||
type: 'dict-tree',
|
||||
dict: dict({
|
||||
isTree: true,
|
||||
@@ -220,7 +214,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
label: 'name'
|
||||
}),
|
||||
column: {
|
||||
minWidth: 200, //最小列宽
|
||||
minWidth: 300, //最小列宽
|
||||
formatter({ value, row, index }) {
|
||||
return row.dept_name_all
|
||||
}
|
||||
@@ -246,6 +240,39 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
},
|
||||
},
|
||||
manage_dept: {
|
||||
title: '管理部门',
|
||||
type: 'dict-tree',
|
||||
dict: dict({
|
||||
isTree: true,
|
||||
url: '/api/system/dept/all_dept/',
|
||||
value: 'id',
|
||||
label: 'name'
|
||||
}),
|
||||
column: {
|
||||
minWidth: 300
|
||||
},
|
||||
form: {
|
||||
value: [],
|
||||
component: {
|
||||
filterable: true,
|
||||
multiple: true,
|
||||
placeholder: '请选择',
|
||||
clearable: true,
|
||||
collapseTags: true,
|
||||
maxCollapseTags: 2,
|
||||
collapseTagsTooltip: true,
|
||||
props: {
|
||||
checkStrictly: true,
|
||||
props: {
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
},
|
||||
},
|
||||
},
|
||||
helper: '不选则默认为所属部门',
|
||||
},
|
||||
},
|
||||
role: {
|
||||
title: '角色',
|
||||
search: {
|
||||
@@ -381,6 +408,9 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
dict: dict({
|
||||
data: dictionary('button_status_bool'),
|
||||
}),
|
||||
form: {
|
||||
value: true
|
||||
}
|
||||
},
|
||||
avatar: {
|
||||
title: '头像',
|
||||
@@ -395,8 +425,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
||||
},
|
||||
...commonCrudConfig({
|
||||
dept_belong_id: {
|
||||
form: true,
|
||||
table: true
|
||||
form: false,
|
||||
table: false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import vue from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
import { defineConfig, loadEnv, ConfigEnv } from 'vite';
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
// import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import { generateVersionFile } from "/@/utils/upgrade";
|
||||
|
||||
const pathResolve = (dir: string) => {
|
||||
@@ -22,7 +22,7 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
|
||||
// 当Vite构建时,生成版本文件
|
||||
generateVersionFile()
|
||||
return {
|
||||
plugins: [vue(), vueJsx(), vueSetupExtend()],
|
||||
plugins: [vue(), /* vueJsx(), */ vueSetupExtend()],
|
||||
root: process.cwd(),
|
||||
resolve: { alias },
|
||||
base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH,
|
||||
|
||||
Reference in New Issue
Block a user