2025-03-27 17:47:37 vscode task自动提交

This commit is contained in:
etoai 2025-03-27 17:47:37 +08:00
parent 6992f2c737
commit e5b74c9ad7
103 changed files with 15276 additions and 3173 deletions

View File

@ -21,7 +21,7 @@
4. 设置数据库不区分大小写修改配置文件my.cnf 的[mysqld]中增加 lower_case_table_names = 1 4. 设置数据库不区分大小写修改配置文件my.cnf 的[mysqld]中增加 lower_case_table_names = 1
5. 安装依赖环境 5. 安装依赖环境
pip install -r requirements_linux.txt -i https://mirrors.aliyun.com/pypi/simple/ pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
6. 数据初始化使用sql脚本直接导入本sql脚本使用的是SQL_Front5.3工具配合mysql5.7数据库导出的) 6. 数据初始化使用sql脚本直接导入本sql脚本使用的是SQL_Front5.3工具配合mysql5.7数据库导出的)
sql脚本位置backend/lyadmin_pro.sql sql脚本位置backend/lyadmin_pro.sql

View File

@ -325,7 +325,7 @@ X_FRAME_OPTIONS = 'SAMEORIGIN'#SAMEORIGIN允许同源iframe嵌套、 DENY不允
# "accept-encoding", # "accept-encoding",
# "lypage" # "lypage"
# ) # )
CORS_EXPOSE_HEADERS = ['Content-Disposition'] # Content-Disposition 头部添加到 Access-Control-Expose-Headers 中,允许客户端 JavaScript 访问该头部
# ================================================= # # ================================================= #
# ********************* 日志配置 ******************* # # ********************* 日志配置 ******************* #
# ================================================= # # ================================================= #
@ -441,7 +441,7 @@ REST_FRAMEWORK = {
# 'user': '60/minute' #已登录用户每分钟可以请求60次 # 'user': '60/minute' #已登录用户每分钟可以请求60次
# }, # },
'EXCEPTION_HANDLER': 'utils.exception.CustomExceptionHandler', # 自定义的异常处理 'EXCEPTION_HANDLER': 'utils.exception.CustomExceptionHandler', # 自定义的异常处理
#线上部署正式环境关闭web接口测试页面 # #线上部署正式环境关闭web接口测试页面
# 'DEFAULT_RENDERER_CLASSES':( # 'DEFAULT_RENDERER_CLASSES':(
# 'rest_framework.renderers.JSONRenderer', # 'rest_framework.renderers.JSONRenderer',
# ), # ),
@ -557,6 +557,7 @@ API_MODEL_MAP = {
"/api/token/": "登录模块", "/api/token/": "登录模块",
"/api/super/operate/":"前端API关闭开启", "/api/super/operate/":"前端API关闭开启",
"/api/platformsettings/uploadplatformimg/":"图片上传", "/api/platformsettings/uploadplatformimg/":"图片上传",
"/api/system/fileManage/":"文件管理"
} }
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

View File

@ -41,6 +41,10 @@ from apps.lyTiktokUnion.views.douyinMsgWebhook import DouyinAllianceDarenOrderWe
from apps.lyTiktokUnion.views.doudianCallback import * from apps.lyTiktokUnion.views.doudianCallback import *
from apps.lyTiktokUnion.views.dySystemAccountViews import DouyinSytemDarenCodeCallbackView from apps.lyTiktokUnion.views.dySystemAccountViews import DouyinSytemDarenCodeCallbackView
#文件管理
from mysystem.views.file_manage import RYFileMediaView,RYGetFileDownloadView
#字典信息数据获取 #字典信息数据获取
from mysystem.views.dictionary import GetDictionaryInfoView,GetDictionaryAllView from mysystem.views.dictionary import GetDictionaryInfoView,GetDictionaryAllView
#app下载页 #app下载页
@ -98,6 +102,10 @@ urlpatterns = [
path('api/lytiktokunion/', include('apps.lyTiktokUnion.urls')), path('api/lytiktokunion/', include('apps.lyTiktokUnion.urls')),
path('api/workflow/', include('apps.lyworkflow.urls')), path('api/workflow/', include('apps.lyworkflow.urls')),
#文件管理
path('api/fileMedia/', RYFileMediaView.as_view(), name='file_media'),
path('api/download/', RYGetFileDownloadView.as_view(), name='download'),
# ========================================================================================= # # ========================================================================================= #
# ********************************** 前端微服务API用户接口************************************ # # ********************************** 前端微服务API用户接口************************************ #
# ========================================================================================= # # ========================================================================================= #

View File

@ -13,9 +13,9 @@ system_url = routers.SimpleRouter()
system_url.register(r'messagetemplate', MyMessageTemplateViewSet) system_url.register(r'messagetemplate', MyMessageTemplateViewSet)
system_url.register(r'messagenotice', MyMessageViewSet) system_url.register(r'messagenotice', MyMessageViewSet)
urlpatterns = [ urlpatterns = [
path('messagenotice/ownmsg/',MyMessageViewSet.as_view({'get':'get_own_receive'}), name='获取自己的消息列表'),
path('messagenotice/delownmsg/',MyMessageViewSet.as_view({'post':'del_own_receive'}), name='删除自己的消息'),
path('messagenotice/readownmsg/',MyMessageViewSet.as_view({'post':'read_own_receive'}), name='设置自己的消息已读'),
] ]
urlpatterns += system_url.urls urlpatterns += system_url.urls

View File

@ -12,6 +12,8 @@ from rest_framework.permissions import IsAuthenticated
from utils.pagination import CustomPagination from utils.pagination import CustomPagination
from django.db import transaction from django.db import transaction
import datetime import datetime
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
# ================================================= # # ================================================= #
# ************** 后台消息中心 view ************** # # ************** 后台消息中心 view ************** #
@ -67,6 +69,20 @@ class MyMessageSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
# exclude = ['password'] # exclude = ['password']
def websocket_push(user_id, message):
"""
主动推送消息
"""
room_group_name = "notifications_user_" + str(user_id)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
room_group_name,
{
"type": "push.message",
"json": message
}
)
class MyMessageCreateUpdateSerializer(CustomModelSerializer): class MyMessageCreateUpdateSerializer(CustomModelSerializer):
""" """
消息公告 -序列化器 消息公告 -序列化器
@ -98,6 +114,9 @@ class MyMessageCreateUpdateSerializer(CustomModelSerializer):
targetuser_instance = MyMessageUserSerializer(data=targetuser_data, many=True, request=self.request) targetuser_instance = MyMessageUserSerializer(data=targetuser_data, many=True, request=self.request)
targetuser_instance.is_valid(raise_exception=True) targetuser_instance.is_valid(raise_exception=True)
targetuser_instance.save() targetuser_instance.save()
for user in users:
unread_nums = MyMessageUser.objects.filter(revuserid_id=user, is_read=False,is_delete=False).count()
websocket_push(user, message={"sender": 'system', "msg_type": 'SYS',"content": '您有一条新消息~', "unread": unread_nums})
return data return data
class Meta: class Meta:
@ -106,6 +125,28 @@ class MyMessageCreateUpdateSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
# exclude = ['password'] # exclude = ['password']
class MyMessageListSerializer(CustomModelSerializer):
"""
用户消息列表序列化器-序列化器
"""
class Meta:
model = MyMessage
fields = ("id","target_type","msg_title","msg_content")
read_only_fields = ["id"]
class MyMessageUserListSerializer(CustomModelSerializer):
"""
目标用户序列化器-序列化器
"""
messageid = MyMessageListSerializer()
class Meta:
model = MyMessageUser
fields = "__all__"
read_only_fields = ["id"]
class MyMessageViewSet(CustomModelViewSet): class MyMessageViewSet(CustomModelViewSet):
""" """
后台消息公告 接口: 后台消息公告 接口:
@ -117,6 +158,39 @@ class MyMessageViewSet(CustomModelViewSet):
filterset_fields = ('msg_title',) filterset_fields = ('msg_title',)
search_fields = ('msg_title',) search_fields = ('msg_title',)
def get_own_receive(self, request):
"""
获取自己的接收消息
"""
own_id = self.request.user.id
queryset = MyMessageUser.objects.filter(revuserid_id=own_id)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = MyMessageUserListSerializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data)
serializer = MyMessageUserListSerializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def del_own_receive(self, request):
"""
删除自己的接收消息
"""
reqData = get_parameter_dic(request)
id = reqData.get("id")
own_id = self.request.user.id
MyMessageUser.objects.filter(id=id,revuserid_id=own_id).delete()
return SuccessResponse(msg="删除成功")
def read_own_receive(self, request):
"""
设置自己的接收消息已读
"""
reqData = get_parameter_dic(request)
id = reqData.get("id")
own_id = self.request.user.id
MyMessageUser.objects.filter(id=id,revuserid_id=own_id,is_read=False).update(is_read=True,read_at=datetime.datetime.now())
return SuccessResponse(msg="删除成功")
def send_sys_template_message(revuser,code): def send_sys_template_message(revuser,code):
""" """
发送系统模板消息给用户一对一 发送系统模板消息给用户一对一

View File

@ -0,0 +1,60 @@
import json
from asgiref.sync import sync_to_async, async_to_sync
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer
@database_sync_to_async
def _get_message_unread_count(user):
"""获取用户的未读消息数量"""
from apps.lymessages.models import MyMessageUser
count = MyMessageUser.objects.filter(revuserid=user, is_read=False, is_delete=False).count()
return count or 0
def set_message(sender, msg_type, msg, unread=0):
text = {
'sender': sender,
'msg_type': msg_type,
'content': msg,
'unread': unread
}
return text
class NotificationConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
try:
self.user = self.scope.get('user',None)
room_name = "user_" + str(self.user.id)
self.room_group_name = f'notifications_{room_name}'
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept("JWTLYADMIN")
unread_count = await _get_message_unread_count(self.user)
if unread_count == 0:
await self.send_json(set_message('system', 'SYS', f'{self.user.name},你好!消息通知已上线!'))
else:
await self.send_json(set_message('system', 'SYS', "请查看您的未读消息~",unread=unread_count))
except Exception as e:
await self.close()
async def receive(self, text_data=None, bytes_data=None, **kwargs):
pass
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
try:
await self.close(close_code)
except Exception:
pass
async def push_message(self, event):
message = event['json']
await self.send(text_data=json.dumps(message))

View File

@ -6,7 +6,9 @@
from django.urls import re_path,path from django.urls import re_path,path
from . import consumers from . import consumers
from apps.lymessages.wsmessage import NotificationConsumer
websocket_urlpatterns = [ websocket_urlpatterns = [
re_path(r'^ws/webssh/$', consumers.TerminalConsumer.as_asgi()), re_path(r'^ws/webssh/$', consumers.TerminalConsumer.as_asgi()),
re_path(r'^ws/msg/$', NotificationConsumer.as_asgi()),
] ]

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
{% load i18n static %}
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="{% static 'images/favicon.2554488.lllxs2download-app.ico' %}" type="image/x-icon" />
<title>APP下载</title>
<link rel="stylesheet" href="{% static 'css/index.2554488.lllxs2download-app.css' %}" />
</head>
<body>
<div class="page">
<!-- 丝带 -->
<div class="pattern left">
<img src="{% static 'images/download_pattern_left.2554488.lllxs2download-app.png' %}" />
</div>
<div class="pattern right">
<img src="{% static 'images/download_pattern_right.2554488.lllxs2download-app.png' %}" />
</div>
<!-- 内容 -->
<div class="wrap">
{% if data.logo %}
<img class="logo" src="{{ data.logo }}" />
{% else %}
<img class="logo" src="{% static 'images/icon_logo.2554488.lllxs2download-app.png' %}" />
{% endif %}
<div class="platform">
<img class="icon-env ios" src="{% static 'images/icon_ios_1.2554488.lllxs2download-app.png' %}" />
<img class="icon-env android" src="{% static 'images/icon_android_1.2554488.lllxs2download-app.png' %}" />
<span class="app-name">{{ data.name }}APP下载</span>
</div>
<div class="download-button">点击下载</div>
<div id="lyandroidurl" style="display:none;">{{ data.apkurl }}</div>
{# <div class="open-tips">已安装?立即打开</div>#}
<div class="browser-tips">
温馨提示:请在移动端(手机)浏览器打开此页面
</div>
</div>
<!-- 提示信息@即将开放 -->
<div class="dialog">
<div class="dialog-content">即将开放</div>
</div>
<!-- 提示信息@微信环境 -->
<img class="tips" src="{% static 'images/tips.2554488.lllxs2download-app.png' %}" />
</div>
<script src="{% static 'js/utils.2554488.lllxs2download-app.js' %}"></script>
<script src="{% static 'js/index.2554488.lllxs2download-app.js' %}"></script>
</body>
</html>

View File

@ -0,0 +1,176 @@
@charset "utf-8";
/* 动画 */
@keyframes debounce {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-2px);
}
20%,
40%,
60%,
80% {
transform: translateX(2px);
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body,
.page {
height: 100%;
}
.page {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.pattern {
width: 18%;
position: absolute;
top: 0;
}
.pattern img {
width: 100%;
}
.pattern.left {
left: 0;
}
.pattern.right {
right: 0;
}
.wrap {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.logo {
width: 100px;
height: 100px;
border-radius: 12px;
margin-bottom: 20px;
}
.platform {
display: flex;
justify-content: center;
align-items: center;
}
.platform .icon-env {
width: 22px;
height: 22px;
margin-right: 10px;
display: none;
}
.platform .icon-env.show {
display: block;
}
.platform .app-name {
font-size: 16px;
font-weight: 800;
line-height: 22px;
color: #505556;
letter-spacing: 1px;
}
.download-button {
width: 211px;
height: 40px;
background: #32b2a7;
margin-top: 17px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 22px;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
/* 已安装提示 */
.open-tips {
margin-top: 20px;
font-size: 15px;
font-weight: bold;
line-height: 21px;
color: #fb4545;
display: none;
cursor: pointer;
}
.open-tips.show {
display: block;
}
.browser-tips {
font-size: 12px;
margin-top: 20px;
color: #999999;
letter-spacing: 2px;
display: none;
}
.browser-tips.show {
display: block;
}
/* 微信环境提示 */
.tips {
display: none;
width: 160px;
position: absolute;
right: 6px;
top: 0;
}
.tips.show {
display: block;
}
.tips.ani {
animation: debounce 1s;
}
/* 即将开放 */
.dialog {
width: 100%;
height: 100%;
display: none;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
}
.dialog.show {
display: flex;
}
.dialog-content {
background: rgba(0, 0, 0, 0.7);
padding: 4px 15px;
border-radius: 3px;
color: #fff;
font-size: 12px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@ -0,0 +1,86 @@
(function () {
// 1. 获取必要的DOM元素
var oTips = document.querySelector(".tips");
var oBrowserTips = document.querySelector(".browser-tips");
var oEnvImgForiOS = document.querySelector(".icon-env.ios");
var oEnvImgForAndroid = document.querySelector(".icon-env.android");
var oButtonDownload = document.querySelector(".download-button");
var oButtonOpenApp = document.querySelector(".open-tips");
var oDialog = document.querySelector(".dialog");
// 2. 常量值
var env = getEnv();
var isAnimating = false;
var SCHEME_URI = "d-point-life://www.lybbn.com/switch?index=0";
var downloadUrlForiOS = "https://itunes.apple.com/cn/app/id12346546xx";
var downloadUrlForAndroid = document.getElementById("lyandroidurl").innerText;
// 3. 控制默认显示
switch (env) {
// 微信环境,提示浏览器打开
case "weixin":
oTips.classList.add("show");
break;
// iOS环境展示图标和下载按钮
case "ios":
oEnvImgForiOS.classList.add("show");
// oButtonOpenApp.classList.add("show");
break;
// Android环境展示图标和下载按钮
case "android":
oEnvImgForAndroid.classList.add("show");
// oButtonOpenApp.classList.add("show");
break;
case "unknown":
oBrowserTips.classList.add("show");
break;
}
// 4. 事件 --- 唤醒APP
// oButtonOpenApp.addEventListener(
// "click",
// function () {
// if (["ios", "android"].indexOf(env) !== -1) {
// window.location.href = SCHEME_URI;
// }
// },
// false
// );
// 5. 事件 --- 下载APP
oButtonDownload.addEventListener(
"click",
function () {
switch (env) {
case "weixin":
if (isAnimating) {
return;
}
isAnimating = true;
oTips.classList.add("ani");
var t = setTimeout(function () {
isAnimating = false;
oTips.classList.remove("ani");
clearTimeout(t);
}, 1000);
break;
case "android":
window.location.href = downloadUrlForAndroid;
break;
case "ios":
window.location.href = downloadUrlForiOS;
// # 即将开放提示
/*
oDialog.classList.add("show");
var t = setTimeout(function () {
oDialog.classList.remove("show");
clearTimeout(t);
}, 1000);*/
break;
case "unknown":
window.location.href = downloadUrlForAndroid;
// alert("请在移动端(手机)浏览器打开此页面");
break;
}
},
false
);
})();

View File

@ -0,0 +1,25 @@
function getEnv() {
var _userAgent = window.navigator.userAgent;
if (/MicroMessenger/i.test(_userAgent)) {
return "weixin";
} else if (/Linux|Android/i.test(_userAgent)) {
return "android";
} else if (/iPhone/i.test(_userAgent)) {
return "ios";
} else {
return "unknown";
}
}
function setEnvImg(element, env) {
switch (env) {
case "android":
element.src = "images/icon_android_1.2554488.lllxs2download-app.png";
break;
case "ios":
element.src = "images/icon_ios_1.2554488.lllxs2download-app.png";
break;
default:
}
}

View File

@ -0,0 +1,20 @@
<html>
<head>
<meta charset="utf-8">
<title>django-vue-lyadmin启动成功</title>
<style type="text/css">
html, body {
overflow: hidden;
margin: auto;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<iframe src="https://doc.lybbn.cn/articles/%E6%96%87%E6%A1%A3/%E7%BA%BF%E4%B8%8A%E9%83%A8%E7%BD%B2.html" width="100%" height="100%" frameborder="0">
<p>您的浏览器不支持 iframe 标签。</p>
</iframe>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

View File

@ -16,7 +16,7 @@
} }
}</style><script>var _hmt = _hmt || []; }</style><script>var _hmt = _hmt || [];
var hmid = "33e0b6798fd8809c21ef51bc99e3149e"; var hmid = "33e0b6798fd8809c21ef51bc99e3149e";
(function () { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?" + hmid; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })();</script><script defer="defer" src="static/js/echarts.f8cde3b5.js"></script><script defer="defer" src="static/js/tinymce.3973fba9.js"></script><script defer="defer" src="static/js/elicons.61c4f779.js"></script><script defer="defer" src="static/js/modules.be4f7764.js"></script><script defer="defer" src="static/js/app.e6d3bb8a.js"></script><link href="static/css/modules.8c9e63f3.css" rel="stylesheet"><link href="static/css/app.e8b4186e.css" rel="stylesheet"></head><body><noscript><strong>Sorry django-vue-lyadmin (dvlyadmin_pro) doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><script>var dark = window.localStorage.getItem('siteTheme'); (function () { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?" + hmid; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })();</script><script defer="defer" src="static/js/vendors.43bd63f4.js"></script><script defer="defer" src="static/js/app.b5863ad2.js"></script><link href="static/css/vendors.25c1fb87.css" rel="stylesheet"><link href="static/css/app.193d5b92.css" rel="stylesheet"></head><body><noscript><strong>Sorry django-vue-lyadmin (dvlyadmin_pro) doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><script>var dark = window.localStorage.getItem('siteTheme');
if(dark && dark=="dark"){ if(dark && dark=="dark"){
document.documentElement.classList.add("dark") document.documentElement.classList.add("dark")
}</script><div id="app" class="lyadmin"><div class="app-loading"><div class="app-loading__logo"><img src="static/img/logo.png"/></div><div class="app-loading__loader"></div><div class="app-loading__title">加载中</div></div></div></body></html> }</script><div id="app" class="lyadmin"><div class="app-loading"><div class="app-loading__logo"><img src="static/img/logo.png"/></div><div class="app-loading__loader"></div><div class="app-loading__title">加载中</div></div></div></body></html>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1 @@
"use strict";(self.webpackChunkdjango_vue_lyadmin_pro=self.webpackChunkdjango_vue_lyadmin_pro||[]).push([[839],{96839:function(e,t,r){r.r(t),r.d(t,{default:function(){return m}});var s=r(61431);const o={class:"ly-cropper"},p={class:"ly-cropper__img"},a=["src"],i={class:"ly-cropper__preview"},c={class:"ly-cropper__preview__img",ref:"preview"};var l=r(65643),n=r.n(l),d={props:{src:{type:String,default:""},compress:{type:Number,default:1},aspectRatio:{type:Number,default:NaN}},data(){return{crop:null}},watch:{aspectRatio(e){this.crop.setAspectRatio(e)}},mounted(){this.init()},methods:{init(){this.crop=new(n())(this.$refs.img,{viewMode:2,dragMode:"move",responsive:!1,aspectRatio:this.aspectRatio,preview:this.$refs.preview})},setAspectRatio(e){this.crop.setAspectRatio(e)},getCropData(e,t="image/jpeg"){e(this.crop.getCroppedCanvas().toDataURL(t,this.compress))},getCropBlob(e,t="image/jpeg"){this.crop.getCroppedCanvas().toBlob((t=>{e(t)}),t,this.compress)},getCropFile(e,t="fileName.jpg",r="image/jpeg"){this.crop.getCroppedCanvas().toBlob((s=>{let o=new File([s],t,{type:r});e(o)}),r,this.compress)}}},m=(0,r(66262).A)(d,[["render",function(e,t,r,l,n,d){return(0,s.openBlock)(),(0,s.createElementBlock)("div",o,[(0,s.createElementVNode)("div",p,[(0,s.createElementVNode)("img",{src:r.src,ref:"img"},null,8,a)]),(0,s.createElementVNode)("div",i,[t[0]||(t[0]=(0,s.createElementVNode)("h4",null,"图像预览",-1)),(0,s.createElementVNode)("div",c,null,512)])])}],["__scopeId","data-v-2d8252e6"]])}}]);

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,150 @@
/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
* @license MIT */
/*!
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
JSZip uses the library pako released under the MIT license :
https://github.com/nodeca/pako/blob/main/LICENSE
*/
/*!
* core-base v10.0.6
* (c) 2025 kazuya kawaguchi
* Released under the MIT License.
*/
/*!
* shared v10.0.6
* (c) 2025 kazuya kawaguchi
* Released under the MIT License.
*/
/*!
* vue-i18n v10.0.6
* (c) 2025 kazuya kawaguchi
* Released under the MIT License.
*/
/*!
* vue-router v4.5.0
* (c) 2024 Eduardo San Martin Morote
* @license MIT
*/
/*!
* Cropper.js v1.6.2
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2024-04-21T07:43:05.335Z
*/
/*!
* clipboard.js v2.0.11
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/
/*!
* html2canvas 1.4.1 <https://html2canvas.hertzen.com>
* Copyright (c) 2022 Niklas von Hertzen <https://hertzen.com>
* Released under MIT License
*/
/*!
* pinia v2.3.1
* (c) 2025 Eduardo San Martin Morote
* @license MIT
*/
/*!
* VueCodemirror v6.1.1
* Copyright (c) Surmon. All rights reserved.
* Released under the MIT License.
* Surmon
*/
/*! #__NO_SIDE_EFFECTS__ */
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/*! Element Plus Icons Vue v2.3.1 */
/*! Element Plus v2.9.6 */
/**
* Checks if an event is supported in the current execution environment.
*
* NOTE: This will not work correctly for non-generic events such as `change`,
* `reset`, `load`, `error`, and `select`.
*
* Borrows from Modernizr.
*
* @param {string} eventNameSuffix Event name, e.g. "click".
* @param {?boolean} capture Check if the capture phase is supported.
* @return {boolean} True if the event is supported.
* @internal
* @license Modernizr 3.0.0pre (Custom Build) | MIT
*/
/**
* @vue/runtime-core v3.5.13
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
/**
* @vue/runtime-dom v3.5.13
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
/**
* @vue/shared v3.5.13
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
/**!
* Sortable 1.15.6
* @author RubaXa <trash@rubaxa.org>
* @author owenm <owen23355@gmail.com>
* @license MIT
*/

Binary file not shown.

View File

@ -16,6 +16,8 @@ echo -e "!!!若安装异常请手动删除venv虚拟环境目录!!!"
echo -e "============================================" echo -e "============================================"
py_path="/usr/local/lyadmin" py_path="/usr/local/lyadmin"
cpu_core=$(cat /proc/cpuinfo|grep processor|wc -l)
Return_Error(){ Return_Error(){
echo '================================================='; echo '=================================================';
printf '\033[1;31;40m%b\033[0m\n' "$@"; printf '\033[1;31;40m%b\033[0m\n' "$@";
@ -51,7 +53,7 @@ Install_Openssl111() {
rm -f openssl-${opensslVersion}.tar.gz rm -f openssl-${opensslVersion}.tar.gz
cd /tmp/openssl-${opensslVersion} cd /tmp/openssl-${opensslVersion}
./config --prefix=/usr/local/openssl111 zlib-dynamic ./config --prefix=/usr/local/openssl111 zlib-dynamic
make -j${cpuCore} -C /tmp/openssl-${opensslVersion}/ make -j${cpu_core} -C /tmp/openssl-${opensslVersion}/
make install -C /tmp/openssl-${opensslVersion}/ make install -C /tmp/openssl-${opensslVersion}/
echo "/usr/local/openssl111/lib" >>/etc/ld.so.conf.d/openssl111.conf echo "/usr/local/openssl111/lib" >>/etc/ld.so.conf.d/openssl111.conf
ldconfig ldconfig
@ -81,7 +83,7 @@ Install_Python(){
if [ "$(printf '%s\n' "1.1.1" "$openssl_version" | sort -V | head -n1)" = "1.1.1" ]; then if [ "$(printf '%s\n' "1.1.1" "$openssl_version" | sort -V | head -n1)" = "1.1.1" ]; then
Show_Text_With_Ellipsis "检测到OpenSSL版本为${openssl_version}正在使用此环境" 0.3 Show_Text_With_Ellipsis "检测到OpenSSL版本为${openssl_version}正在使用此环境" 0.3
sys_openssl_path=$(which openssl) sys_openssl_path=$(which openssl)
sys_openssl_dir=$(dirname "$openssl_path") sys_openssl_dir=$(dirname "$sys_openssl_path")
WITH_SSL="--with-openssl=${sys_openssl_dir}" WITH_SSL="--with-openssl=${sys_openssl_dir}"
else else
Show_Text_With_Ellipsis "检测到OpenSSL版本为${openssl_version}正在安装支持Python${py_version}版本的openssl" 0.3 Show_Text_With_Ellipsis "检测到OpenSSL版本为${openssl_version}正在安装支持Python${py_version}版本的openssl" 0.3
@ -138,7 +140,7 @@ else
source ${PROJECT_ROOT}/venv/bin/activate source ${PROJECT_ROOT}/venv/bin/activate
# 安装 Django (可选) # 安装 Django (可选)
pip install -r ${PROJECT_ROOT}/requirements_linux.txt -i https://pypi.tuna.tsinghua.edu.cn/simple pip install -r ${PROJECT_ROOT}/requirements_linux.txt -i https://mirrors.aliyun.com/pypi/simple/
echo "requrements.txt依赖安装完成" echo "requrements.txt依赖安装完成"
fi fi

File diff suppressed because one or more lines are too long

View File

@ -87,7 +87,11 @@ if object.{key}:
{"id": 21, "name": "菜单配置", "value": "MenuConfig", }, {"id": 21, "name": "菜单配置", "value": "MenuConfig", },
{"id": 22, "name": "审核", "value": "Audit", }, {"id": 22, "name": "审核", "value": "Audit", },
{"id": 23, "name": "修改排序", "value": "EditSort", }, {"id": 23, "name": "修改排序", "value": "EditSort", },
{"id": 24, "name": "同步", "value": "Sync", }, {"id": 24, "name": "同步", "value": "Sync"},
{"id": 25, "name": "管理", "value": "Manage"},
{"id": 26, "name": "下载", "value": "Download"},
{"id": 27, "name": "上传", "value": "Upload"},
{"id": 28, "name": "Token", "value": "Token"},
] ]
self.save(Button, self.button_data, "权限表标识") self.save(Button, self.button_data, "权限表标识")
@ -97,55 +101,57 @@ if object.{key}:
初始化菜单表 初始化菜单表
""" """
self.menu_data = [ self.menu_data = [
{"id": 7,"icon": "","name": "轮播图设置","sort": 1,"is_link": 0,"web_path": "carouselSettingsimg","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 3}, {"id": 7,"icon": "","name": "轮播图设置","sort": 1,"is_link": 0,"web_path": "carouselSettingsimg","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 3},
{"id": 12,"icon": "","name": "部门管理","sort": 1,"is_link": 0,"web_path": "departmentManage","component": "system/dept","component_name": "dept","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 20}, {"id": 12,"icon": "","name": "部门管理","sort": 1,"is_link": 0,"web_path": "departmentManage","component": "system/dept","component_name": "dept","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 20},
{"id": 14,"icon": "","name": "操作日志","sort": 1,"is_link": 0,"web_path": "journalManage","component": "system/log/operationLog","component_name": "operationLog","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 22}, {"id": 14,"icon": "","name": "操作日志","sort": 1,"is_link": 0,"web_path": "journalManage","component": "system/log/operationLog","component_name": "operationLog","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 22},
{"id": 23,"icon": "DataLine","name": "DashBoard","sort": 1,"is_link": 0,"web_path": "analysis","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": None}, {"id": 23,"icon": "DataLine","name": "DashBoard","sort": 1,"is_link": 0,"web_path": "analysis","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": None},
{"id": 30,"icon": "","name": "商品管理","sort": 1,"is_link": 0,"web_path": "goodsManage","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 29}, {"id": 30,"icon": "","name": "商品管理","sort": 1,"is_link": 0,"web_path": "goodsManage","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 29},
{"id": 36,"icon": "Edit","name": "代码生成","sort": 1,"is_link": 0,"web_path": "lycodeGenerate","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 35}, {"id": 36,"icon": "Edit","name": "代码生成","sort": 1,"is_link": 0,"web_path": "lycodeGenerate","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 35},
{"id": 15,"icon": "","name": "菜单管理","sort": 2,"is_link": 0,"web_path": "menuManage","component": "system/menu","component_name": "menu","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 20}, {"id": 15,"icon": "","name": "菜单管理","sort": 2,"is_link": 0,"web_path": "menuManage","component": "system/menu","component_name": "menu","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 20},
{"id": 16,"icon": "","name": "按钮配置","sort": 2,"is_link": 0,"web_path": "buttonConfig","component": "system/ menuButton","component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 0,"parent": 20}, {"id": 16,"icon": "","name": "按钮配置","sort": 2,"is_link": 0,"web_path": "buttonConfig","component": "system/ menuButton","component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 0,"parent_id": 20},
{"id": 31,"icon": "","name": "商品分类","sort": 2,"is_link": 0,"web_path": "goodsType","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 29}, {"id": 31,"icon": "","name": "商品分类","sort": 2,"is_link": 0,"web_path": "goodsType","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 29},
{"id": 34,"icon": "Cpu","name": "表单构建","sort": 2,"is_link": 0,"web_path": "lyFormBuilders","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 35}, {"id": 34,"icon": "Cpu","name": "表单构建","sort": 2,"is_link": 0,"web_path": "lyFormBuilders","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 35},
{"id": 40,"icon": "DataAnalysis","name": "数据面板","sort": 2,"is_link": 0,"web_path": "lyDataPanel","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": None}, {"id": 40,"icon": "DataAnalysis","name": "数据面板","sort": 2,"is_link": 0,"web_path": "lyDataPanel","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": None},
{"id": 1,"icon": "avatar","name": "管理员管理","sort": 3,"is_link": 0,"web_path": "adminManage","component": None,"component_name": None,"status": 1,"isautopm": 1,"cache": 0,"visible": 1,"parent": None}, {"id": 1,"icon": "avatar","name": "管理员管理","sort": 3,"is_link": 0,"web_path": "adminManage","component": None,"component_name": None,"status": 1,"isautopm": 1,"cache": 0,"visible": 1,"parent_id": None},
{"id": 17,"icon": "","name": "角色管理","sort": 3,"is_link": 0,"web_path": "roleManage","component": "system/role","component_name": "role","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"creator": None,"parent": 20}, {"id": 17,"icon": "","name": "角色管理","sort": 3,"is_link": 0,"web_path": "roleManage","component": "system/role","component_name": "role","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"creator": None,"parent_id": 20},
{"id": 43,"icon": "CopyDocument","name": "表单模板","sort": 3,"is_link": 0,"web_path": "lyFormBuilderTemplate","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 35}, {"id": 43,"icon": "CopyDocument","name": "表单模板","sort": 3,"is_link": 0,"web_path": "lyFormBuilderTemplate","component": None,"component_name": None,"status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 35},
{"id": 18,"icon": "","name": "权限管理","sort": 4,"is_link": 0,"web_path": "authorityManage","component": "system/rolePermission","component_name": "rolePermission","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent": 20}, {"id": 18,"icon": "","name": "权限管理","sort": 4,"is_link": 0,"web_path": "authorityManage","component": "system/rolePermission","component_name": "rolePermission","status": 1,"isautopm": 0,"cache": 0,"visible": 1,"parent_id": 20},
{"id":2,"icon":"user-filled","name":"用户管理","sort":5,"is_link":0,"web_path":"userManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":5}, {"id":2,"icon":"user-filled","name":"用户管理","sort":5,"is_link":0,"web_path":"userManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":5},
{"id":8,"icon":"","name":"通知公告","sort":5,"is_link":0,"web_path":"messagNotice","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":3}, {"id":8,"icon":"","name":"通知公告","sort":5,"is_link":0,"web_path":"messagNotice","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":3},
{"id":5,"icon":"UserFilled","name":"用户管理","sort":6,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":5,"icon":"UserFilled","name":"用户管理","sort":6,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":19,"icon":"","name":"按钮管理","sort":6,"is_link":0,"web_path":"buttonManage","component":"system/button","component_name":"buttonManage","status":1,"isautopm":0,"cache":0,"visible":0,"parent":20}, {"id":19,"icon":"","name":"按钮管理","sort":6,"is_link":0,"web_path":"buttonManage","component":"system/button","component_name":"buttonManage","status":1,"isautopm":0,"cache":0,"visible":0,"parent_id":20},
{"id":4,"icon":"user-filled","name":"用户管理CRUD","sort":7,"is_link":0,"web_path":"userManageCrud","component":None,"component_name":None,"status":1,"isautopm":1,"cache":0,"visible":1,"parent":5}, {"id":4,"icon":"user-filled","name":"用户管理CRUD","sort":7,"is_link":0,"web_path":"userManageCrud","component":None,"component_name":None,"status":1,"isautopm":1,"cache":0,"visible":1,"parent_id":5},
{"id":10,"icon":"","name":"意见反馈","sort":8,"is_link":0,"web_path":"userFeekback","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":3}, {"id":10,"icon":"","name":"意见反馈","sort":8,"is_link":0,"web_path":"userFeekback","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":3},
{"id":37,"icon":"","name":"字典管理","sort":8,"is_link":0,"web_path":"sysDictionary","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":20}, {"id":37,"icon":"","name":"字典管理","sort":8,"is_link":0,"web_path":"sysDictionary","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":20},
{"id":3,"icon":"platform","name":"基础管理","sort":9,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":3,"icon":"platform","name":"基础管理","sort":9,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":11,"icon":"","name":"APP版本管理","sort":10,"is_link":0,"web_path":"lyAppVersion","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":3}, {"id":11,"icon":"","name":"APP版本管理","sort":10,"is_link":0,"web_path":"lyAppVersion","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":3},
{"id":26,"icon":"","name":"服务监控","sort":10,"is_link":0,"web_path":"server","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":25}, {"id":26,"icon":"","name":"服务监控","sort":10,"is_link":0,"web_path":"server","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":25},
{"id":48,"icon":"","name":"运费配置","sort":10,"is_link":0,"web_path":"freightConfigManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":29}, {"id":48,"icon":"","name":"运费配置","sort":10,"is_link":0,"web_path":"freightConfigManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":29},
{"id":50,"icon":"","name":"流程设计器","sort":10,"is_link":0,"web_path":"lyWorkflowDesign","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":49}, {"id":50,"icon":"","name":"流程设计器","sort":10,"is_link":0,"web_path":"lyWorkflowDesign","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":49},
{"id":9,"icon":"","name":"参数设置","sort":12,"is_link":0,"web_path":"platformSettingsother","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":3}, {"id":9,"icon":"","name":"参数设置","sort":12,"is_link":0,"web_path":"platformSettingsother","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":3},
{"id":6,"icon":"","name":"系统配置","sort":15,"is_link":0,"web_path":"systemConfig","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":3}, {"id":6,"icon":"","name":"系统配置","sort":15,"is_link":0,"web_path":"systemConfig","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":3},
{"id":24,"icon":"","name":"计划任务","sort":20,"is_link":0,"web_path":"crontab","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":25}, {"id":24,"icon":"","name":"计划任务","sort":20,"is_link":0,"web_path":"crontab","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":25},
{"id":41,"icon":"","name":"系统日志","sort":20,"is_link":0,"web_path":"lySystemLogs","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":22}, {"id":41,"icon":"","name":"系统日志","sort":20,"is_link":0,"web_path":"lySystemLogs","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":22},
{"id":27,"icon":"","name":"终端服务","sort":30,"is_link":0,"web_path":"terminal","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":25}, {"id":27,"icon":"","name":"终端服务","sort":30,"is_link":0,"web_path":"terminal","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":25},
{"id":32,"icon":"","name":"商城订单","sort":30,"is_link":0,"web_path":"mallOrderManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":29}, {"id":32,"icon":"","name":"商城订单","sort":30,"is_link":0,"web_path":"mallOrderManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":29},
{"id":33,"icon":"","name":"财务流水","sort":40,"is_link":0,"web_path":"financeStatisticsGoods","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":29}, {"id":33,"icon":"","name":"财务流水","sort":40,"is_link":0,"web_path":"financeStatisticsGoods","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":29},
{"id":45,"icon":"","name":"Redis监控","sort":40,"is_link":0,"web_path":"lyredis","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":25}, {"id":45,"icon":"","name":"Redis监控","sort":40,"is_link":0,"web_path":"lyredis","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":25},
{"id":44,"icon":"","name":"老师管理","sort":87,"is_link":0,"web_path":"lyFormBuilderteacherManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":35}, {"id":44,"icon":"","name":"老师管理","sort":87,"is_link":0,"web_path":"lyFormBuilderteacherManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":35},
{"id":28,"icon":"","name":"地区管理","sort":88,"is_link":0,"web_path":"areaManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":20}, {"id":28,"icon":"","name":"地区管理","sort":88,"is_link":0,"web_path":"areaManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":20},
{"id":38,"icon":"","name":"学生管理","sort":98,"is_link":0,"web_path":"lyAutoCodeStudentManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":35}, {"id":38,"icon":"","name":"学生管理","sort":98,"is_link":0,"web_path":"lyAutoCodeStudentManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":35},
{"id":29,"icon":"GoodsFilled","name":"商城管理","sort":180,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":29,"icon":"GoodsFilled","name":"商城管理","sort":180,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":49,"icon":"Connection","name":"流程管理","sort":186,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":49,"icon":"Connection","name":"流程管理","sort":186,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":42,"icon":"Coordinate","name":"功能大全","sort":188,"is_link":0,"web_path":"lyFunctionSets","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":42,"icon":"Coordinate","name":"功能大全","sort":188,"is_link":0,"web_path":"lyFunctionSets","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":21,"icon":"user","name":"个人中心","sort":866,"is_link":0,"web_path":"personalCenter","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":46}, {"id":46,"icon":"Aim","name":"个人中心","sort":866,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":46,"icon":"Aim","name":"个人中心","sort":866,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":21,"icon":"user","name":"个人中心","sort":866,"is_link":0,"web_path":"personalCenter","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":46},
{"id":25,"icon":"TrendCharts","name":"系统监控","sort":888,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":25,"icon":"TrendCharts","name":"系统监控","sort":888,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":47,"icon":"User","name":"个人中心新版","sort":900,"is_link":0,"web_path":"userCenter","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":46}, {"id":47,"icon":"User","name":"个人中心新版","sort":900,"is_link":0,"web_path":"userCenter","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":46},
{"id":35,"icon":"Box","name":"系统工具","sort":980,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":35,"icon":"Box","name":"系统工具","sort":980,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":20,"icon":"tools","name":"系统管理","sort":990,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":20,"icon":"tools","name":"系统管理","sort":990,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":22,"icon":"info-filled","name":"日志管理","sort":999,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":None}, {"id":22,"icon":"info-filled","name":"日志管理","sort":999,"is_link":0,"web_path":"","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":None},
{"id":39,"icon":"Opportunity","name":"关于系统","sort":1000,"is_link":0,"web_path":"lyabout","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent":20} {"id":39,"icon":"Opportunity","name":"关于系统","sort":1000,"is_link":0,"web_path":"lyabout","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":20},
{"id":59,"icon":"Message","name":"我的消息","sort":6,"is_link":0,"web_path":"myMessage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":3},
{"id":60,"icon":"FolderOpened","name":"文件管理","sort":60,"is_link":0,"web_path":"sysFileManage","component":None,"component_name":None,"status":1,"isautopm":0,"cache":0,"visible":1,"parent_id":25},
] ]
self.save(Menu, self.menu_data, "菜单表") self.save(Menu, self.menu_data, "菜单表")
@ -154,195 +160,202 @@ if object.{key}:
初始化菜单权限表 初始化菜单权限表
""" """
self.menu_button_data = [ self.menu_button_data = [
{"id":1,"name":"编辑","value":"Update","api":"/api/platformsettings/lunboimg/{id}/","method":2,"menu":7}, {"id":1,"name":"编辑","value":"Update","api":"/api/platformsettings/lunboimg/{id}/","method":2,"menu_id":7},
{"id":2,"name":"编辑","value":"Update","api":"/api/platformsettings/other/{id}/","method":2,"menu":9}, {"id":2,"name":"编辑","value":"Update","api":"/api/platformsettings/other/{id}/","method":2,"menu_id":9},
{"id":3,"name":"编辑","value":"Update","api":"/api/system/button/{id}/","method":2,"menu":19}, {"id":3,"name":"编辑","value":"Update","api":"/api/system/button/{id}/","method":2,"menu_id":19},
{"id":4,"name":"编辑","value":"Update","api":"/api/system/menu/{id}/","method":2,"menu":15}, {"id":4,"name":"编辑","value":"Update","api":"/api/system/menu/{id}/","method":2,"menu_id":15},
{"id":5,"name":"编辑","value":"Update","api":"/api/system/dept/{id}/","method":2,"menu":12}, {"id":5,"name":"编辑","value":"Update","api":"/api/system/dept/{id}/","method":2,"menu_id":12},
{"id":6,"name":"修改密码","value":"Changepassword","api":"/api/system/user/change_password/{id}/","method":2,"menu":21}, {"id":6,"name":"修改密码","value":"Changepassword","api":"/api/system/user/change_password/{id}/","method":2,"menu_id":21},
{"id":7,"name":"编辑","value":"Update","api":"/api/users/users/{id}/","method":2,"menu":2}, {"id":7,"name":"编辑","value":"Update","api":"/api/users/users/{id}/","method":2,"menu_id":2},
{"id":8,"name":"编辑","value":"Update","api":"/api/system/user/{id}/","method":2,"menu":1}, {"id":8,"name":"编辑","value":"Update","api":"/api/system/user/{id}/","method":2,"menu_id":1},
{"id":9,"name":"编辑","value":"Update","api":"/api/system/menu_button/{id}/","method":2,"menu":16}, {"id":9,"name":"编辑","value":"Update","api":"/api/system/menu_button/{id}/","method":2,"menu_id":16},
{"id":10,"name":"编辑","value":"Update","api":"/api/system/role/{id}/","method":2,"menu":17}, {"id":10,"name":"编辑","value":"Update","api":"/api/system/role/{id}/","method":2,"menu_id":17},
{"id":11,"name":"编辑","value":"Update","api":"/api/system/user/user_info/","method":2,"menu":21}, {"id":11,"name":"编辑","value":"Update","api":"/api/system/user/user_info/","method":2,"menu_id":21},
{"id":12,"name":"编辑","value":"Update","api":"/api/system/operation_log/{id}/","method":2,"menu":14}, {"id":12,"name":"编辑","value":"Update","api":"/api/system/operation_log/{id}/","method":2,"menu_id":14},
{"id":13,"name":"编辑","value":"Update","api":"/api/messages/messagenotice/{id}/","method":2,"menu":8}, {"id":13,"name":"编辑","value":"Update","api":"/api/messages/messagenotice/{id}/","method":2,"menu_id":8},
{"id":14,"name":"查询","value":"Search","api":"/api/platformsettings/lunboimg/","method":0,"menu":7}, {"id":14,"name":"查询","value":"Search","api":"/api/platformsettings/lunboimg/","method":0,"menu_id":7},
{"id":15,"name":"查询","value":"Search","api":"/api/platformsettings/other/","method":0,"menu":9}, {"id":15,"name":"查询","value":"Search","api":"/api/platformsettings/other/","method":0,"menu_id":9},
{"id":16,"name":"查询","value":"Search","api":"/api/system/role/","method":0,"menu":17}, {"id":16,"name":"查询","value":"Search","api":"/api/system/role/","method":0,"menu_id":17},
{"id":17,"name":"查询","value":"Search","api":"/api/system/user/","method":0,"menu":1}, {"id":17,"name":"查询","value":"Search","api":"/api/system/user/","method":0,"menu_id":1},
{"id":18,"name":"查询","value":"Search","api":"/api/system/user/user_info/","method":0,"menu":21}, {"id":18,"name":"查询","value":"Search","api":"/api/system/user/user_info/","method":0,"menu_id":21},
{"id":19,"name":"查询","value":"Search","api":"/api/system/operation_log/","method":0,"menu":14}, {"id":19,"name":"查询","value":"Search","api":"/api/system/operation_log/","method":0,"menu_id":14},
{"id":20,"name":"查询","value":"Search","api":"/api/system/menu/","method":0,"menu":15}, {"id":20,"name":"查询","value":"Search","api":"/api/system/menu/","method":0,"menu_id":15},
{"id":21,"name":"查询","value":"Search","api":"/api/users/users/","method":0,"menu":2}, {"id":21,"name":"查询","value":"Search","api":"/api/users/users/","method":0,"menu_id":2},
{"id":22,"name":"查询","value":"Search","api":"/api/system/menu_button/","method":0,"menu":16}, {"id":22,"name":"查询","value":"Search","api":"/api/system/menu_button/","method":0,"menu_id":16},
{"id":23,"name":"查询","value":"Search","api":"/api/system/dept/","method":0,"menu":12}, {"id":23,"name":"查询","value":"Search","api":"/api/system/dept/","method":0,"menu_id":12},
{"id":24,"name":"查询","value":"Search","api":"/api/system/button/","method":0,"menu":19}, {"id":24,"name":"查询","value":"Search","api":"/api/system/button/","method":0,"menu_id":19},
{"id":25,"name":"查询","value":"Search","api":"/api/messages/messagenotice/","method":0,"menu":8}, {"id":25,"name":"查询","value":"Search","api":"/api/messages/messagenotice/","method":0,"menu_id":8},
{"id":26,"name":"新增","value":"Create","api":"/api/platformsettings/lunboimg/","method":1,"menu":7}, {"id":26,"name":"新增","value":"Create","api":"/api/platformsettings/lunboimg/","method":1,"menu_id":7},
{"id":27,"name":"新增","value":"Create","api":"/api/platformsettings/other/","method":1,"menu":9}, {"id":27,"name":"新增","value":"Create","api":"/api/platformsettings/other/","method":1,"menu_id":9},
{"id":28,"name":"新增","value":"Create","api":"/api/system/operation_log/","method":1,"menu":14}, {"id":28,"name":"新增","value":"Create","api":"/api/system/operation_log/","method":1,"menu_id":14},
{"id":29,"name":"新增","value":"Create","api":"/api/system/dept/","method":1,"menu":12}, {"id":29,"name":"新增","value":"Create","api":"/api/system/dept/","method":1,"menu_id":12},
{"id":30,"name":"新增","value":"Create","api":"/api/system/button/","method":1,"menu":19}, {"id":30,"name":"新增","value":"Create","api":"/api/system/button/","method":1,"menu_id":19},
{"id":31,"name":"新增","value":"Create","api":"/api/system/role/","method":1,"menu":17}, {"id":31,"name":"新增","value":"Create","api":"/api/system/role/","method":1,"menu_id":17},
{"id":32,"name":"新增","value":"Create","api":"/api/system/user/","method":1,"menu":1}, {"id":32,"name":"新增","value":"Create","api":"/api/system/user/","method":1,"menu_id":1},
{"id":33,"name":"新增","value":"Create","api":"/api/users/users/","method":1,"menu":2}, {"id":33,"name":"新增","value":"Create","api":"/api/users/users/","method":1,"menu_id":2},
{"id":34,"name":"新增","value":"Create","api":"/api/system/menu/","method":1,"menu":15}, {"id":34,"name":"新增","value":"Create","api":"/api/system/menu/","method":1,"menu_id":15},
{"id":35,"name":"新增","value":"Create","api":"/api/system/menu_button/","method":1,"menu":16}, {"id":35,"name":"新增","value":"Create","api":"/api/system/menu_button/","method":1,"menu_id":16},
{"id":36,"name":"新增","value":"Create","api":"/api/messages/messagenotice/","method":1,"menu":8}, {"id":36,"name":"新增","value":"Create","api":"/api/messages/messagenotice/","method":1,"menu_id":8},
{"id":37,"name":"单例","value":"Retrieve","api":"/api/platformsettings/lunboimg/{id}/","method":0,"menu":7}, {"id":37,"name":"单例","value":"Retrieve","api":"/api/platformsettings/lunboimg/{id}/","method":0,"menu_id":7},
{"id":38,"name":"单例","value":"Retrieve","api":"/api/platformsettings/other/{id}/","method":0,"menu":9}, {"id":38,"name":"单例","value":"Retrieve","api":"/api/platformsettings/other/{id}/","method":0,"menu_id":9},
{"id":39,"name":"单例","value":"Retrieve","api":"/api/users/users/{id}/","method":0,"menu":2}, {"id":39,"name":"单例","value":"Retrieve","api":"/api/users/users/{id}/","method":0,"menu_id":2},
{"id":40,"name":"单例","value":"Retrieve","api":"/api/system/button/{id}/","method":0,"menu":19}, {"id":40,"name":"单例","value":"Retrieve","api":"/api/system/button/{id}/","method":0,"menu_id":19},
{"id":41,"name":"单例","value":"Retrieve","api":"/api/system/dept/{id}/","method":0,"menu":12}, {"id":41,"name":"单例","value":"Retrieve","api":"/api/system/dept/{id}/","method":0,"menu_id":12},
{"id":42,"name":"单例","value":"Retrieve","api":"/api/system/operation_log/{id}/","method":0,"menu":14}, {"id":42,"name":"单例","value":"Retrieve","api":"/api/system/operation_log/{id}/","method":0,"menu_id":14},
{"id":43,"name":"单例","value":"Retrieve","api":"/api/system/role/{id}/","method":0,"menu":17}, {"id":43,"name":"单例","value":"Retrieve","api":"/api/system/role/{id}/","method":0,"menu_id":17},
{"id":44,"name":"单例","value":"Retrieve","api":"/api/system/user/{id}/","method":0,"menu":1}, {"id":44,"name":"单例","value":"Retrieve","api":"/api/system/user/{id}/","method":0,"menu_id":1},
{"id":45,"name":"单例","value":"Retrieve","api":"/api/system/menu/{id}/","method":0,"menu":15}, {"id":45,"name":"单例","value":"Retrieve","api":"/api/system/menu/{id}/","method":0,"menu_id":15},
{"id":46,"name":"单例","value":"Retrieve","api":"/api/system/menu_button/{id}/","method":0,"menu":16}, {"id":46,"name":"单例","value":"Retrieve","api":"/api/system/menu_button/{id}/","method":0,"menu_id":16},
{"id":47,"name":"单例","value":"Retrieve","api":"/api/messages/messagenotice/{id}/","method":0,"menu":8}, {"id":47,"name":"单例","value":"Retrieve","api":"/api/messages/messagenotice/{id}/","method":0,"menu_id":8},
{"id":48,"name":"单例","value":"Retrieve","api":"/api/system/role_id_to_menu/{id}/","method":0,"menu":18}, {"id":48,"name":"单例","value":"Retrieve","api":"/api/system/role_id_to_menu/{id}/","method":0,"menu_id":18},
{"id":49,"name":"删除","value":"Delete","api":"/api/platformsettings/lunboimg/{id}/","method":3,"menu":7}, {"id":49,"name":"删除","value":"Delete","api":"/api/platformsettings/lunboimg/{id}/","method":3,"menu_id":7},
{"id":50,"name":"删除","value":"Delete","api":"/api/platformsettings/other/{id}/","method":3,"menu":9}, {"id":50,"name":"删除","value":"Delete","api":"/api/platformsettings/other/{id}/","method":3,"menu_id":9},
{"id":51,"name":"删除","value":"Delete","api":"/api/system/user/{id}/","method":3,"menu":1}, {"id":51,"name":"删除","value":"Delete","api":"/api/system/user/{id}/","method":3,"menu_id":1},
{"id":52,"name":"删除","value":"Delete","api":"/api/system/role/{id}/","method":3,"menu":17}, {"id":52,"name":"删除","value":"Delete","api":"/api/system/role/{id}/","method":3,"menu_id":17},
{"id":53,"name":"删除","value":"Delete","api":"/api/system/menu_button/{id}/","method":3,"menu":16}, {"id":53,"name":"删除","value":"Delete","api":"/api/system/menu_button/{id}/","method":3,"menu_id":16},
{"id":54,"name":"删除","value":"Delete","api":"/api/system/button/{id}/","method":3,"menu":19}, {"id":54,"name":"删除","value":"Delete","api":"/api/system/button/{id}/","method":3,"menu_id":19},
{"id":55,"name":"删除","value":"Delete","api":"/api/system/menu/{id}/","method":3,"menu":15}, {"id":55,"name":"删除","value":"Delete","api":"/api/system/menu/{id}/","method":3,"menu_id":15},
{"id":56,"name":"删除","value":"Delete","api":"/api/system/operation_log/{id}/","method":3,"menu":14}, {"id":56,"name":"删除","value":"Delete","api":"/api/system/operation_log/{id}/","method":3,"menu_id":14},
{"id":57,"name":"删除","value":"Delete","api":"/api/system/dept/{id}/","method":3,"menu":12}, {"id":57,"name":"删除","value":"Delete","api":"/api/system/dept/{id}/","method":3,"menu_id":12},
{"id":58,"name":"删除","value":"Delete","api":"/api/users/users/{id}/","method":3,"menu":2}, {"id":58,"name":"删除","value":"Delete","api":"/api/users/users/{id}/","method":3,"menu_id":2},
{"id":59,"name":"删除","value":"Delete","api":"/api/messages/messagenotice/{id}/","method":3,"menu":8}, {"id":59,"name":"删除","value":"Delete","api":"/api/messages/messagenotice/{id}/","method":3,"menu_id":8},
{"id":61,"name":"禁用","value":"Disable","api":"/api/users/users/disableuser/{id}/","method":2,"menu":2}, {"id":61,"name":"禁用","value":"Disable","api":"/api/users/users/disableuser/{id}/","method":2,"menu_id":2},
{"id":62,"name":"编辑","value":"Update","api":"/api/system/user/{id}/","method":2,"menu":4}, {"id":62,"name":"编辑","value":"Update","api":"/api/system/user/{id}/","method":2,"menu_id":4},
{"id":63,"name":"禁用","value":"Disable","api":"/api/users/users/disableuser/{id}/","method":2,"menu":4}, {"id":63,"name":"禁用","value":"Disable","api":"/api/users/users/disableuser/{id}/","method":2,"menu_id":4},
{"id":64,"name":"查询","value":"Search","api":"/api/system/user/","method":0,"menu":4}, {"id":64,"name":"查询","value":"Search","api":"/api/system/user/","method":0,"menu_id":4},
{"id":65,"name":"新增","value":"Create","api":"/api/system/user/","method":1,"menu":4}, {"id":65,"name":"新增","value":"Create","api":"/api/system/user/","method":1,"menu_id":4},
{"id":68,"name":"查询","value":"Search","api":"","method":0,"menu":23}, {"id":68,"name":"查询","value":"Search","api":"","method":0,"menu_id":23},
{"id":70,"name":"编辑","value":"Update","api":"/api/crontab/periodictask/{id}/","method":2,"menu":24}, {"id":70,"name":"编辑","value":"Update","api":"/api/crontab/periodictask/{id}/","method":2,"menu_id":24},
{"id":71,"name":"禁用","value":"Disable","api":"/api/crontab/periodictask/enabled/{id}/","method":2,"menu":24}, {"id":71,"name":"禁用","value":"Disable","api":"/api/crontab/periodictask/enabled/{id}/","method":2,"menu_id":24},
{"id":72,"name":"查询","value":"Search","api":"/api/crontab/periodictask/","method":0,"menu":24}, {"id":72,"name":"查询","value":"Search","api":"/api/crontab/periodictask/","method":0,"menu_id":24},
{"id":76,"name":"查询","value":"Search","api":"/api/monitor/getsysteminfo/","method":0,"menu":26}, {"id":76,"name":"查询","value":"Search","api":"/api/monitor/getsysteminfo/","method":0,"menu_id":26},
{"id":78,"name":"查询","value":"Search","api":"/api/terminal/terminal/","method":0,"menu":27}, {"id":78,"name":"查询","value":"Search","api":"/api/terminal/terminal/","method":0,"menu_id":27},
{"id":66,"name":"单例","value":"Retrieve","api":"/api/system/user/{id}/","method":0,"menu":4}, {"id":66,"name":"单例","value":"Retrieve","api":"/api/system/user/{id}/","method":0,"menu_id":4},
{"id":69,"name":"单例","value":"Retrieve","api":"","method":0,"menu":23}, {"id":69,"name":"单例","value":"Retrieve","api":"","method":0,"menu_id":23},
{"id":74,"name":"单例","value":"Retrieve","api":"/api/crontab/periodictask/{id}/","method":0,"menu":24}, {"id":74,"name":"单例","value":"Retrieve","api":"/api/crontab/periodictask/{id}/","method":0,"menu_id":24},
{"id":82,"name":"编辑","value":"Update","api":"/api/terminal/terminal/{id}/","method":2,"menu":27}, {"id":82,"name":"编辑","value":"Update","api":"/api/terminal/terminal/{id}/","method":2,"menu_id":27},
{"id":83,"name":"编辑","value":"Update","api":"/api/address/area/{id}/","method":2,"menu":28}, {"id":83,"name":"编辑","value":"Update","api":"/api/address/area/{id}/","method":2,"menu_id":28},
{"id":89,"name":"编辑","value":"Update","api":"/api/mall/goodsspu/{id}/","method":2,"menu":30}, {"id":89,"name":"编辑","value":"Update","api":"/api/mall/goodsspu/{id}/","method":2,"menu_id":30},
{"id":96,"name":"编辑","value":"Update","api":"/api/mall/goodstype/{id}/","method":2,"menu":31}, {"id":96,"name":"编辑","value":"Update","api":"/api/mall/goodstype/{id}/","method":2,"menu_id":31},
{"id":100,"name":"编辑","value":"Update","api":"/api/mall/goodsorder/{id}/","method":2,"menu":32}, {"id":100,"name":"编辑","value":"Update","api":"/api/mall/goodsorder/{id}/","method":2,"menu_id":32},
{"id":168,"name":"菜单配置","value":"MenuConfig","api":"/api/lyformbuilder/lyformbuilder/editMenu/","method":1,"menu":43}, {"id":168,"name":"菜单配置","value":"MenuConfig","api":"/api/lyformbuilder/lyformbuilder/editMenu/","method":1,"menu_id":43},
{"id":153,"name":"获取模型","value":"GetModels","api":"/api/platformsettings/sysconfig/get_models_info_list/","method":0,"menu":36}, {"id":153,"name":"获取模型","value":"GetModels","api":"/api/platformsettings/sysconfig/get_models_info_list/","method":0,"menu_id":36},
{"id":154,"name":"获取模型","value":"GetModels","api":"/api/platformsettings/sysconfig/get_models_info_list/","method":0,"menu":34}, {"id":154,"name":"获取模型","value":"GetModels","api":"/api/platformsettings/sysconfig/get_models_info_list/","method":0,"menu_id":34},
{"id":117,"name":"编辑","value":"Update","api":"/api/platformsettings/sysconfig/{id}/","method":2,"menu":6}, {"id":117,"name":"编辑","value":"Update","api":"/api/platformsettings/sysconfig/{id}/","method":2,"menu_id":6},
{"id":121,"name":"编辑","value":"Update","api":"/api/autocode/autocode/{id}/","method":2,"menu":36}, {"id":121,"name":"编辑","value":"Update","api":"/api/autocode/autocode/{id}/","method":2,"menu_id":36},
{"id":129,"name":"编辑","value":"Update","api":"/api/system/dictionary/{id}/","method":2,"menu":37}, {"id":129,"name":"编辑","value":"Update","api":"/api/system/dictionary/{id}/","method":2,"menu_id":37},
{"id":130,"name":"编辑","value":"Update","api":"/api/system/appversion/{id}/","method":2,"menu":11}, {"id":130,"name":"编辑","value":"Update","api":"/api/system/appversion/{id}/","method":2,"menu_id":11},
{"id":137,"name":"编辑","value":"Update","api":"/api/autocode/StudentManage/{id}/","method":2,"menu":38}, {"id":137,"name":"编辑","value":"Update","api":"/api/autocode/StudentManage/{id}/","method":2,"menu_id":38},
{"id":147,"name":"编辑","value":"Update","api":"","method":2,"menu":39}, {"id":147,"name":"编辑","value":"Update","api":"","method":2,"menu_id":39},
{"id":152,"name":"编辑","value":"Update","api":"","method":2,"menu":40}, {"id":152,"name":"编辑","value":"Update","api":"","method":2,"menu_id":40},
{"id":162,"name":"编辑","value":"Update","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":2,"menu":34}, {"id":162,"name":"编辑","value":"Update","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":2,"menu_id":34},
{"id":174,"name":"编辑","value":"Update","api":"/api/lyformbuilder/teacherManage/{id}/","method":2,"menu":44}, {"id":174,"name":"编辑","value":"Update","api":"/api/lyformbuilder/teacherManage/{id}/","method":2,"menu_id":44},
{"id":177,"name":"编辑","value":"Update","api":"/api/system/user/user_info/","method":2,"menu":47}, {"id":177,"name":"编辑","value":"Update","api":"/api/system/user/user_info/","method":2,"menu_id":47},
{"id":183,"name":"编辑","value":"Update","api":"/api/mall/freightcfg/{id}/","method":2,"menu":48}, {"id":183,"name":"编辑","value":"Update","api":"/api/mall/freightcfg/{id}/","method":2,"menu_id":48},
{"id":188,"name":"编辑","value":"Update","api":"","method":2,"menu":50}, {"id":188,"name":"编辑","value":"Update","api":"","method":2,"menu_id":50},
{"id":109,"name":"统计","value":"Statistics","api":"/api/mall/goodsorder/orderstatistics/","method":0,"menu":32}, {"id":109,"name":"统计","value":"Statistics","api":"/api/mall/goodsorder/orderstatistics/","method":0,"menu_id":32},
{"id":111,"name":"统计","value":"Statistics","api":"/api/mall/goodsforderinfo/orderstatistics/","method":0,"menu":33}, {"id":111,"name":"统计","value":"Statistics","api":"/api/mall/goodsforderinfo/orderstatistics/","method":0,"menu_id":33},
{"id":77,"name":"终端","value":"Terminal","api":"/ws/webssh/","method":5,"menu":27}, {"id":77,"name":"终端","value":"Terminal","api":"/ws/webssh/","method":5,"menu_id":27},
{"id":176,"name":"立即执行","value":"Execute","api":"/api/crontab/periodictask/exectask/","method":1,"menu":24}, {"id":176,"name":"立即执行","value":"Execute","api":"/api/crontab/periodictask/exectask/","method":1,"menu_id":24},
{"id":108,"name":"禁用","value":"Disable","api":"/api/mall/goodsspu/islaunched/{id}/","method":2,"menu":30}, {"id":108,"name":"禁用","value":"Disable","api":"/api/mall/goodsspu/islaunched/{id}/","method":2,"menu_id":30},
{"id":84,"name":"查询","value":"Search","api":"/api/address/area/area_root/","method":0,"menu":28}, {"id":84,"name":"查询","value":"Search","api":"/api/address/area/area_root/","method":0,"menu_id":28},
{"id":90,"name":"查询","value":"Search","api":"/api/mall/goodsspu/","method":0,"menu":30}, {"id":90,"name":"查询","value":"Search","api":"/api/mall/goodsspu/","method":0,"menu_id":30},
{"id":94,"name":"查询","value":"Search","api":"/api/mall/goodstype/","method":0,"menu":31}, {"id":94,"name":"查询","value":"Search","api":"/api/mall/goodstype/","method":0,"menu_id":31},
{"id":101,"name":"查询","value":"Search","api":"/api/mall/goodsorder/","method":0,"menu":32}, {"id":101,"name":"查询","value":"Search","api":"/api/mall/goodsorder/","method":0,"menu_id":32},
{"id":102,"name":"查询","value":"Search","api":"/api/mall/goodsforderinfo/","method":0,"menu":33}, {"id":102,"name":"查询","value":"Search","api":"/api/mall/goodsforderinfo/","method":0,"menu_id":33},
{"id":105,"name":"查询","value":"Search","api":"/api/platformsettings/userfeeckback/","method":0,"menu":10}, {"id":105,"name":"查询","value":"Search","api":"/api/platformsettings/userfeeckback/","method":0,"menu_id":10},
{"id":118,"name":"查询","value":"Search","api":"/api/platformsettings/sysconfig/","method":0,"menu":6}, {"id":118,"name":"查询","value":"Search","api":"/api/platformsettings/sysconfig/","method":0,"menu_id":6},
{"id":124,"name":"查询","value":"Search","api":"/api/autocode/autocode/","method":0,"menu":36}, {"id":124,"name":"查询","value":"Search","api":"/api/autocode/autocode/","method":0,"menu_id":36},
{"id":128,"name":"查询","value":"Search","api":"/api/system/dictionary/","method":0,"menu":37}, {"id":128,"name":"查询","value":"Search","api":"/api/system/dictionary/","method":0,"menu_id":37},
{"id":132,"name":"查询","value":"Search","api":"/api/system/appversion/","method":0,"menu":11}, {"id":132,"name":"查询","value":"Search","api":"/api/system/appversion/","method":0,"menu_id":11},
{"id":140,"name":"查询","value":"Search","api":"/api/autocode/StudentManage/","method":0,"menu":38}, {"id":140,"name":"查询","value":"Search","api":"/api/autocode/StudentManage/","method":0,"menu_id":38},
{"id":143,"name":"查询","value":"Search","api":"","method":0,"menu":39}, {"id":143,"name":"查询","value":"Search","api":"","method":0,"menu_id":39},
{"id":151,"name":"查询","value":"Search","api":"","method":0,"menu":40}, {"id":151,"name":"查询","value":"Search","api":"","method":0,"menu_id":40},
{"id":157,"name":"查询","value":"Search","api":"/api/system/operation_log/systemlog/","method":0,"menu":41}, {"id":157,"name":"查询","value":"Search","api":"/api/system/operation_log/systemlog/","method":0,"menu_id":41},
{"id":159,"name":"查询","value":"Search","api":"/api/platformsettings/other/functionSets/","method":0,"menu":42}, {"id":159,"name":"查询","value":"Search","api":"/api/platformsettings/other/functionSets/","method":0,"menu_id":42},
{"id":161,"name":"查询","value":"Search","api":"/api/lyformbuilder/lyformbuilder/","method":0,"menu":43}, {"id":161,"name":"查询","value":"Search","api":"/api/lyformbuilder/lyformbuilder/","method":0,"menu_id":43},
{"id":173,"name":"查询","value":"Search","api":"/api/lyformbuilder/teacherManage/","method":0,"menu":44}, {"id":173,"name":"查询","value":"Search","api":"/api/lyformbuilder/teacherManage/","method":0,"menu_id":44},
{"id":175,"name":"查询","value":"Search","api":"/api/monitor/monitor/getredisinfo/","method":0,"menu":45}, {"id":175,"name":"查询","value":"Search","api":"/api/monitor/monitor/getredisinfo/","method":0,"menu_id":45},
{"id":178,"name":"查询","value":"Search","api":"/api/system/user/user_info/","method":0,"menu":47}, {"id":178,"name":"查询","value":"Search","api":"/api/system/user/user_info/","method":0,"menu_id":47},
{"id":184,"name":"查询","value":"Search","api":"/api/mall/freightcfg/","method":0,"menu":48}, {"id":184,"name":"查询","value":"Search","api":"/api/mall/freightcfg/","method":0,"menu_id":48},
{"id":189,"name":"查询","value":"Search","api":"","method":0,"menu":50}, {"id":189,"name":"查询","value":"Search","api":"","method":0,"menu_id":50},
{"id":112,"name":"日志","value":"Logs","api":"/api/crontab/taskresult/","method":0,"menu":24}, {"id":112,"name":"日志","value":"Logs","api":"/api/crontab/taskresult/","method":0,"menu_id":24},
{"id":180,"name":"日志","value":"Logs","api":"/api/system/operation_log/getOwnerLogs/","method":0,"menu":47}, {"id":180,"name":"日志","value":"Logs","api":"/api/system/operation_log/getOwnerLogs/","method":0,"menu_id":47},
{"id":73,"name":"新增","value":"Create","api":"/api/crontab/periodictask/","method":1,"menu":24}, {"id":73,"name":"新增","value":"Create","api":"/api/crontab/periodictask/","method":1,"menu_id":24},
{"id":79,"name":"新增","value":"Create","api":"/api/terminal/terminal/","method":1,"menu":27}, {"id":79,"name":"新增","value":"Create","api":"/api/terminal/terminal/","method":1,"menu_id":27},
{"id":85,"name":"新增","value":"Create","api":"/api/address/area/","method":1,"menu":28}, {"id":85,"name":"新增","value":"Create","api":"/api/address/area/","method":1,"menu_id":28},
{"id":88,"name":"新增","value":"Create","api":"/api/mall/goodsspu/","method":1,"menu":30}, {"id":88,"name":"新增","value":"Create","api":"/api/mall/goodsspu/","method":1,"menu_id":30},
{"id":95,"name":"新增","value":"Create","api":"/api/mall/goodstype/","method":1,"menu":31}, {"id":95,"name":"新增","value":"Create","api":"/api/mall/goodstype/","method":1,"menu_id":31},
{"id":114,"name":"新增","value":"Create","api":"/api/platformsettings/sysconfig/","method":1,"menu":6}, {"id":114,"name":"新增","value":"Create","api":"/api/platformsettings/sysconfig/","method":1,"menu_id":6},
{"id":120,"name":"新增","value":"Create","api":"/api/autocode/autocode/","method":1,"menu":36}, {"id":120,"name":"新增","value":"Create","api":"/api/autocode/autocode/","method":1,"menu_id":36},
{"id":125,"name":"新增","value":"Create","api":"/api/system/dictionary/","method":1,"menu":37}, {"id":125,"name":"新增","value":"Create","api":"/api/system/dictionary/","method":1,"menu_id":37},
{"id":134,"name":"新增","value":"Create","api":"/api/system/appversion/","method":1,"menu":11}, {"id":134,"name":"新增","value":"Create","api":"/api/system/appversion/","method":1,"menu_id":11},
{"id":139,"name":"新增","value":"Create","api":"/api/autocode/StudentManage/","method":1,"menu":38}, {"id":139,"name":"新增","value":"Create","api":"/api/autocode/StudentManage/","method":1,"menu_id":38},
{"id":144,"name":"新增","value":"Create","api":"","method":1,"menu":39}, {"id":144,"name":"新增","value":"Create","api":"","method":1,"menu_id":39},
{"id":149,"name":"新增","value":"Create","api":"","method":1,"menu":40}, {"id":149,"name":"新增","value":"Create","api":"","method":1,"menu_id":40},
{"id":163,"name":"新增","value":"Create","api":"/api/lyformbuilder/lyformbuilder/","method":1,"menu":34}, {"id":163,"name":"新增","value":"Create","api":"/api/lyformbuilder/lyformbuilder/","method":1,"menu_id":34},
{"id":170,"name":"新增","value":"Create","api":"/api/lyformbuilder/teacherManage/","method":1,"menu":44}, {"id":170,"name":"新增","value":"Create","api":"/api/lyformbuilder/teacherManage/","method":1,"menu_id":44},
{"id":181,"name":"新增","value":"Create","api":"/api/mall/freightcfg/","method":1,"menu":48}, {"id":181,"name":"新增","value":"Create","api":"/api/mall/freightcfg/","method":1,"menu_id":48},
{"id":186,"name":"新增","value":"Create","api":"","method":1,"menu":50}, {"id":186,"name":"新增","value":"Create","api":"","method":1,"menu_id":50},
{"id":136,"name":"挂载同步","value":"MountSync","api":"/api/autocode/autocode/generatemount/","method":1,"menu":36}, {"id":136,"name":"挂载同步","value":"MountSync","api":"/api/autocode/autocode/generatemount/","method":1,"menu_id":36},
{"id":156,"name":"导出","value":"Export","api":"/api/mall/goodsspu/export/","method":1,"menu":30}, {"id":156,"name":"导出","value":"Export","api":"/api/mall/goodsspu/export/","method":1,"menu_id":30},
{"id":158,"name":"导出","value":"Export","api":"/api/autocode/StudentManage/export/","method":1,"menu":38}, {"id":158,"name":"导出","value":"Export","api":"/api/autocode/StudentManage/export/","method":1,"menu_id":38},
{"id":172,"name":"导出","value":"Export","api":"/api/lyformbuilder/teacherManage/export/","method":1,"menu":44}, {"id":172,"name":"导出","value":"Export","api":"/api/lyformbuilder/teacherManage/export/","method":1,"menu_id":44},
{"id":142,"name":"同步数据库","value":"Syncdb","api":"/api/autocode/autocode/syncdb/","method":1,"menu":36}, {"id":142,"name":"同步数据库","value":"Syncdb","api":"/api/autocode/autocode/syncdb/","method":1,"menu_id":36},
{"id":166,"name":"同步数据库","value":"Syncdb","api":"/api/lyformbuilder/lyformbuilder/syncdb/","method":1,"menu":43}, {"id":166,"name":"同步数据库","value":"Syncdb","api":"/api/lyformbuilder/lyformbuilder/syncdb/","method":1,"menu_id":43},
{"id":165,"name":"同步挂载","value":"MountSync","api":"/api/lyformbuilder/lyformbuilder/generatemount/","method":1,"menu":43}, {"id":165,"name":"同步挂载","value":"MountSync","api":"/api/lyformbuilder/lyformbuilder/generatemount/","method":1,"menu_id":43},
{"id":110,"name":"发货","value":"Deliver","api":"/api/mall/goodsorder/sendoutgoods/","method":1,"menu":32}, {"id":110,"name":"发货","value":"Deliver","api":"/api/mall/goodsorder/sendoutgoods/","method":1,"menu_id":32},
{"id":80,"name":"单例","value":"Retrieve","api":"/api/terminal/terminal/{id}/","method":0,"menu":27}, {"id":80,"name":"单例","value":"Retrieve","api":"/api/terminal/terminal/{id}/","method":0,"menu_id":27},
{"id":86,"name":"单例","value":"Retrieve","api":"/api/address/area/{id}/","method":0,"menu":28}, {"id":86,"name":"单例","value":"Retrieve","api":"/api/address/area/{id}/","method":0,"menu_id":28},
{"id":91,"name":"单例","value":"Retrieve","api":"/api/mall/goodsspu/{id}/","method":0,"menu":30}, {"id":91,"name":"单例","value":"Retrieve","api":"/api/mall/goodsspu/{id}/","method":0,"menu_id":30},
{"id":93,"name":"单例","value":"Retrieve","api":"/api/mall/goodstype/{id}/","method":0,"menu":31}, {"id":93,"name":"单例","value":"Retrieve","api":"/api/mall/goodstype/{id}/","method":0,"menu_id":31},
{"id":98,"name":"单例","value":"Retrieve","api":"/api/mall/goodsorder/{id}/","method":0,"menu":32}, {"id":98,"name":"单例","value":"Retrieve","api":"/api/mall/goodsorder/{id}/","method":0,"menu_id":32},
{"id":103,"name":"单例","value":"Retrieve","api":"/api/mall/goodsforderinfo/{id}/","method":0,"menu":33}, {"id":103,"name":"单例","value":"Retrieve","api":"/api/mall/goodsforderinfo/{id}/","method":0,"menu_id":33},
{"id":106,"name":"单例","value":"Retrieve","api":"/api/platformsettings/userfeeckback/{id}/","method":0,"menu":10}, {"id":106,"name":"单例","value":"Retrieve","api":"/api/platformsettings/userfeeckback/{id}/","method":0,"menu_id":10},
{"id":115,"name":"单例","value":"Retrieve","api":"/api/platformsettings/sysconfig/{id}/","method":0,"menu":6}, {"id":115,"name":"单例","value":"Retrieve","api":"/api/platformsettings/sysconfig/{id}/","method":0,"menu_id":6},
{"id":122,"name":"单例","value":"Retrieve","api":"/api/autocode/autocode/{id}/","method":0,"menu":36}, {"id":122,"name":"单例","value":"Retrieve","api":"/api/autocode/autocode/{id}/","method":0,"menu_id":36},
{"id":126,"name":"单例","value":"Retrieve","api":"/api/system/dictionary/{id}/","method":0,"menu":37}, {"id":126,"name":"单例","value":"Retrieve","api":"/api/system/dictionary/{id}/","method":0,"menu_id":37},
{"id":133,"name":"单例","value":"Retrieve","api":"/api/system/appversion/{id}/","method":0,"menu":11}, {"id":133,"name":"单例","value":"Retrieve","api":"/api/system/appversion/{id}/","method":0,"menu_id":11},
{"id":141,"name":"单例","value":"Retrieve","api":"/api/autocode/StudentManage/{id}/","method":0,"menu":38}, {"id":141,"name":"单例","value":"Retrieve","api":"/api/autocode/StudentManage/{id}/","method":0,"menu_id":38},
{"id":146,"name":"单例","value":"Retrieve","api":"","method":0,"menu":39}, {"id":146,"name":"单例","value":"Retrieve","api":"","method":0,"menu_id":39},
{"id":150,"name":"单例","value":"Retrieve","api":"","method":0,"menu":40}, {"id":150,"name":"单例","value":"Retrieve","api":"","method":0,"menu_id":40},
{"id":164,"name":"单例","value":"Retrieve","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":0,"menu":43}, {"id":164,"name":"单例","value":"Retrieve","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":0,"menu_id":43},
{"id":167,"name":"单例","value":"Retrieve","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":0,"menu":34}, {"id":167,"name":"单例","value":"Retrieve","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":0,"menu_id":34},
{"id":169,"name":"单例","value":"Retrieve","api":"/api/lyformbuilder/teacherManage/{id}/","method":0,"menu":44}, {"id":169,"name":"单例","value":"Retrieve","api":"/api/lyformbuilder/teacherManage/{id}/","method":0,"menu_id":44},
{"id":185,"name":"区域查询","value":"AreaSearch","api":"/api/mall/freightc/getAllSelect/","method":0,"menu":48}, {"id":185,"name":"区域查询","value":"AreaSearch","api":"/api/mall/freightc/getAllSelect/","method":0,"menu_id":48},
{"id":67,"name":"删除","value":"Delete","api":"/api/system/user/{id}/","method":3,"menu":4}, {"id":67,"name":"删除","value":"Delete","api":"/api/system/user/{id}/","method":3,"menu_id":4},
{"id":75,"name":"删除","value":"Delete","api":"/api/crontab/periodictask/{id}/","method":3,"menu":24}, {"id":75,"name":"删除","value":"Delete","api":"/api/crontab/periodictask/{id}/","method":3,"menu_id":24},
{"id":81,"name":"删除","value":"Delete","api":"/api/terminal/terminal/{id}/","method":3,"menu":27}, {"id":81,"name":"删除","value":"Delete","api":"/api/terminal/terminal/{id}/","method":3,"menu_id":27},
{"id":87,"name":"删除","value":"Delete","api":"/api/address/area/{id}/","method":3,"menu":28}, {"id":87,"name":"删除","value":"Delete","api":"/api/address/area/{id}/","method":3,"menu_id":28},
{"id":92,"name":"删除","value":"Delete","api":"/api/mall/goodsspu/{id}/","method":3,"menu":30}, {"id":92,"name":"删除","value":"Delete","api":"/api/mall/goodsspu/{id}/","method":3,"menu_id":30},
{"id":97,"name":"删除","value":"Delete","api":"/api/mall/goodstype/{id}/","method":3,"menu":31}, {"id":97,"name":"删除","value":"Delete","api":"/api/mall/goodstype/{id}/","method":3,"menu_id":31},
{"id":99,"name":"删除","value":"Delete","api":"/api/mall/goodsorder/{id}/","method":3,"menu":32}, {"id":99,"name":"删除","value":"Delete","api":"/api/mall/goodsorder/{id}/","method":3,"menu_id":32},
{"id":104,"name":"删除","value":"Delete","api":"/api/mall/goodsforderinfo/{id}/","method":3,"menu":33}, {"id":104,"name":"删除","value":"Delete","api":"/api/mall/goodsforderinfo/{id}/","method":3,"menu_id":33},
{"id":107,"name":"删除","value":"Delete","api":"/api/platformsettings/userfeeckback/{id}/","method":3,"menu":10}, {"id":107,"name":"删除","value":"Delete","api":"/api/platformsettings/userfeeckback/{id}/","method":3,"menu_id":10},
{"id":116,"name":"删除","value":"Delete","api":"/api/platformsettings/sysconfig/{id}/","method":3,"menu":6}, {"id":116,"name":"删除","value":"Delete","api":"/api/platformsettings/sysconfig/{id}/","method":3,"menu_id":6},
{"id":123,"name":"删除","value":"Delete","api":"/api/autocode/autocode/{id}/","method":3,"menu":36}, {"id":123,"name":"删除","value":"Delete","api":"/api/autocode/autocode/{id}/","method":3,"menu_id":36},
{"id":127,"name":"删除","value":"Delete","api":"/api/system/dictionary/{id}/","method":3,"menu":37}, {"id":127,"name":"删除","value":"Delete","api":"/api/system/dictionary/{id}/","method":3,"menu_id":37},
{"id":131,"name":"删除","value":"Delete","api":"/api/system/appversion/{id}/","method":3,"menu":11}, {"id":131,"name":"删除","value":"Delete","api":"/api/system/appversion/{id}/","method":3,"menu_id":11},
{"id":138,"name":"删除","value":"Delete","api":"/api/autocode/StudentManage/{id}/","method":3,"menu":38}, {"id":138,"name":"删除","value":"Delete","api":"/api/autocode/StudentManage/{id}/","method":3,"menu_id":38},
{"id":145,"name":"删除","value":"Delete","api":"","method":3,"menu":39}, {"id":145,"name":"删除","value":"Delete","api":"","method":3,"menu_id":39},
{"id":148,"name":"删除","value":"Delete","api":"","method":3,"menu":40}, {"id":148,"name":"删除","value":"Delete","api":"","method":3,"menu_id":40},
{"id":160,"name":"删除","value":"Delete","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":3,"menu":43}, {"id":160,"name":"删除","value":"Delete","api":"/api/lyformbuilder/lyformbuilder/{id}/","method":3,"menu_id":43},
{"id":171,"name":"删除","value":"Delete","api":"/api/lyformbuilder/teacherManage/{id}/","method":3,"menu":44}, {"id":171,"name":"删除","value":"Delete","api":"/api/lyformbuilder/teacherManage/{id}/","method":3,"menu_id":44},
{"id":182,"name":"删除","value":"Delete","api":"/api/mall/freightcfg/{id}/","method":3,"menu":48}, {"id":182,"name":"删除","value":"Delete","api":"/api/mall/freightcfg/{id}/","method":3,"menu_id":48},
{"id":187,"name":"删除","value":"Delete","api":"","method":3,"menu":50}, {"id":187,"name":"删除","value":"Delete","api":"","method":3,"menu_id":50},
{"id":179,"name":"修改密码","value":"Changepassword","api":"/api/system/user/change_password/{id}/","method":2,"menu":47}, {"id":179,"name":"修改密码","value":"Changepassword","api":"/api/system/user/change_password/{id}/","method":2,"menu_id":47},
{"id":60,"name":"保存","value":"Save","api":"/api/system/permission/{id}/","method":2,"menu":18}, {"id":60,"name":"保存","value":"Save","api":"/api/system/permission/{id}/","method":2,"menu_id":18},
{"id":119,"name":"保存","value":"Save","api":"/api/platformsettings/sysconfig/save_content/{id}/","method":2,"menu":6}, {"id":119,"name":"保存","value":"Save","api":"/api/platformsettings/sysconfig/save_content/{id}/","method":2,"menu_id":6},
{"id":113,"name":"任务列表","value":"Tasklist","api":"/api/crontab/periodictask/tasklist/","method":0,"menu":24}, {"id":113,"name":"任务列表","value":"Tasklist","api":"/api/crontab/periodictask/tasklist/","method":0,"menu_id":24},
{"id":135,"name":"代码预览","value":"PreCode","api":"/api/autocode/autocode/previewcode/","method":0,"menu":36}, {"id":135,"name":"代码预览","value":"PreCode","api":"/api/autocode/autocode/previewcode/","method":0,"menu_id":36},
{"id":155,"name":"代码预览","value":"PreCode","api":"/api/lyformbuilder/lyformbuilder/previewcodejson/","method":1,"menu":34} {"id":155,"name":"代码预览","value":"PreCode","api":"/api/lyformbuilder/lyformbuilder/previewcodejson/","method":1,"menu_id":34},
{"id":238,"name":"查询","value":"Search","api":"/api/messages/messagenotice/ownmsg/","method":0,"menu_id":59},
{"id":241,"name":"单例","value":"Retrieve","api":"/api/messages/messagenotice/readownmsg/","method":1,"menu_id":59},
{"id":242,"name":"删除","value":"Delete","api":"/api/messages/messagenotice/delownmsg/","method":1,"menu_id":59},
{"id":249,"name":"管理","value":"Manage","api":"/api/system/fileManage/","method":1,"menu_id":60},
{"id":250,"name":"上传","value":"Upload","api":"/api/system/fileManage/upload/","method":1,"menu_id":60},
{"id":251,"name":"下载","value":"Download","api":"/api/system/fileManage/download/","method":1,"menu_id":60},
{"id":252,"name":"Token","value":"Token","api":"/api/system/fileManage/getToken/","method":1,"menu_id":60},
] ]
self.save(MenuButton, self.menu_button_data, "菜单权限表") self.save(MenuButton, self.menu_button_data, "菜单权限表")
@ -359,7 +372,7 @@ if object.{key}:
{"id": 2, "name": "普通用户", "key": "public", "sort": 2, "status": 1, {"id": 2, "name": "普通用户", "key": "public", "sort": 2, "status": 1,
"admin": 0, "data_range": 4, "admin": 0, "data_range": 4,
"dept": [1], "dept": [1],
"menu": [21, 46, 47], "menu": [21, 46, 47,59],
"permission": [] "permission": []
}, },
#自定义 #自定义

View File

@ -16,6 +16,7 @@ from mysystem.views.user import UserViewSet
from mysystem.views.dictionary import DictionaryViewSet from mysystem.views.dictionary import DictionaryViewSet
from mysystem.views.appversion import AppVersionViewSet from mysystem.views.appversion import AppVersionViewSet
from mysystem.views.sysfiles import FileManageViewSet,FileGroupViewSet from mysystem.views.sysfiles import FileManageViewSet,FileGroupViewSet
from mysystem.views.file_manage import RYFileManageView,RYFileDownloadView,RYFileTokenView,RYFileUploadView
system_url = routers.SimpleRouter() system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet) system_url.register(r'menu', MenuViewSet)
@ -43,5 +44,9 @@ urlpatterns = [
re_path('operation_log/deletealllogs/',OperationLogViewSet.as_view({'delete':'deletealllogs'})), re_path('operation_log/deletealllogs/',OperationLogViewSet.as_view({'delete':'deletealllogs'})),
path('operation_log/systemlog/',OperationLogViewSet.as_view({'get':'system_logs'})), path('operation_log/systemlog/',OperationLogViewSet.as_view({'get':'system_logs'})),
path('operation_log/getOwnerLogs/',OperationLogViewSet.as_view({'get':'getOwnerLogs'})), path('operation_log/getOwnerLogs/',OperationLogViewSet.as_view({'get':'getOwnerLogs'})),
path('fileManage/', RYFileManageView.as_view(), name='文件操作'),
path('fileManage/download/', RYFileDownloadView.as_view(), name='文件下载'),
path('fileManage/getToken/', RYFileTokenView.as_view(), name='文件token'),
path('fileManage/upload/', RYFileUploadView.as_view(), name='文件上传'),
] ]
urlpatterns += system_url.urls urlpatterns += system_url.urls

View File

@ -0,0 +1,495 @@
#!/bin/python
#coding: utf-8
# +-------------------------------------------------------------------
# | system: 如意面板
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# | Date: 2024-02-29
# +-------------------------------------------------------------------
# | EditDate: 2024-02-29
# +-------------------------------------------------------------------
# ------------------------------
# 文件管理
# ------------------------------
import os,re
import time
from math import ceil
from rest_framework.views import APIView
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
from utils.permission import CustomPermission
from utils.common import get_parameter_dic,WriteFile,ast_convert,RunCommand
from utils.security.files import list_files_in_directory,get_directory_size,delete_file,delete_dir,create_file,create_dir,rename_file,copy_file,copy_dir,move_file
from utils.security.files import get_filedir_attribute,batch_operate,get_filename_ext,auto_detect_file_language
from utils.security.no_delete_list import check_in_black_list
from utils.common import ly_md5 as md5
import platform
from django.http import FileResponse,StreamingHttpResponse
from django.utils.encoding import escape_uri_path
import mimetypes
from utils.streamingmedia_response import stream_video
from django.conf import settings
from django.http import HttpResponse
is_windows = True if platform.system() == 'Windows' else False
def getIndexPath():
if is_windows:
return "c:/"
else:
return "/tmp"
#返回nginx 404 错误页,降低信息泄露风险,提升安全性
def ResponseNginx404(state = 404):
html_content = '''<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.21.1</center>
</body>
</html>'''
return HttpResponse(html_content, content_type='text/html',status=state,charset='utf-8')
def get_type_name(type):
c_type_name = ""
if type == "copy":
c_type_name = "复制"
elif type == "move":
c_type_name = "移动"
elif type == "zip":
c_type_name = "压缩"
elif type == "unzip":
c_type_name = "解压"
return c_type_name
class RYFileManageView(APIView):
"""
get:
文件管理
post:
文件管理
"""
permission_classes = [IsAuthenticated,CustomPermission]
authentication_classes = [JWTAuthentication]
def post(self, request):
reqData = get_parameter_dic(request)
action = reqData.get("action","")
is_windows = True if platform.system() == 'Windows' else False
#路径处理
path = reqData.get("path",getIndexPath())
if path == "default":
path = getIndexPath()
if not path:#根目录
if is_windows:
path = ""
else:
path = "/"
if is_windows:#windows 路径处理
path = path.replace("\\", "/")
#接口逻辑处理
if action == "list_dir":
containSub = reqData.get("containSub",False)
isDir = reqData.get("isDir",False)#只显示目录
search = reqData.get("search","")
if search:
search = search.strip().lower()
order = reqData.get("order","")
sort = reqData.get("sort","name")
is_reverse = True if (order and order == "desc") else False
data_info = list_files_in_directory(dst_path=path,sort=sort,is_windows=is_windows,is_reverse=is_reverse,search=search,containSub=containSub,isDir=isDir)
data_dirs = data_info.get('data',[])
page = int(reqData.get("page",1))
limit = int(reqData.get("limit",100))
#一次最大条数限制
limit = 3000 if limit > 3000 else limit
total_nums = data_info['total_nums']
file_nums = data_info['file_nums']
dir_nums = data_info['dir_nums']
total_pages = ceil(total_nums / limit)
if page > total_pages:
page = total_pages
page = 1 if page<1 else page
# 根据分页参数对结果进行切片
start_idx = (page - 1) * limit
end_idx = start_idx + limit
paginated_data = data_dirs[start_idx:end_idx]
if not is_windows:
# directory, filename = os.path.split(path)
if path == "/":
directory_dict_list = []
else:
directory_list = path.split(os.path.sep)
directory_dict_list = [{'name': directory_list[i-1], 'url': os.sep.join(directory_list[:i])} for i in range(1,len(directory_list)+1)]
else:
# 去掉盘符部分
drive_name = path.split(':')[0]
if drive_name:
drive_name.lower()
try:
path_without_drive =path.split(':')[1] if drive_name else None
except:
return ErrorResponse(msg="路径错误:%s"%path)
# 按斜杠分割路径
if not path_without_drive or path_without_drive == "/":
directory_list = []
directory_dict_list = []
else:
directory_list = path_without_drive.strip('/').strip('\\').split('/')
# 获取每个名称的路径
path_list = ['/'.join(directory_list[:i+1]) for i in range(len(directory_list))]
directory_dict_list = [{'name': name, 'url': drive_name+":/"+path_list[i]} for i, name in enumerate(directory_list)]
if drive_name:
directory_dict_list.insert(0, {'name':drive_name+"",'url':drive_name+":/"})
else:
directory_dict_list.insert(0, {'name':drive_name,'url':""})
data = {
'data':paginated_data,
'path':path,
'paths':directory_dict_list,
'file_nums':file_nums,
'dir_nums':dir_nums,
'is_windows':is_windows
}
return SuccessResponse(data=data,total=total_nums,page=page,limit=limit)
elif action == "get_filedir_attribute":
data = get_filedir_attribute(path,is_windows=is_windows)
if not data:
return ErrorResponse(msg="目标不存在/或不支持查看")
return DetailResponse(data=data)
elif action == "calc_size":
size = get_directory_size(path)
data = {"size":size}
return DetailResponse(data=data)
elif action == "batch_operate":
reqData['path'] = path
c_type = reqData.get('type',None)
c_type_name = get_type_name(c_type)
isok,msg,code,data = batch_operate(param=reqData,is_windows=is_windows)
if not isok:
return ErrorResponse(code=code,msg=msg,data=data)
return DetailResponse(msg=msg,data=data)
elif action == "create_file":
filename = reqData.get("filename","")
if not filename:
return ErrorResponse(msg="请填写文件名")
path = path+"/"+filename
create_file(path=path,is_windows=is_windows)
return DetailResponse(msg="创建成功")
elif action == "create_dir":
dirname = reqData.get("dirname","")
if not dirname:
return ErrorResponse(msg="请填写目录名")
path = path+"/"+dirname
create_dir(path=path,is_windows=is_windows)
return DetailResponse(msg="创建成功")
elif action == "delete_file":
delete_file(path=path,is_windows=is_windows)
return DetailResponse(msg="删除成功")
elif action == "delete_dir":
delete_dir(path=path,is_windows=is_windows)
return DetailResponse(msg="删除成功")
elif action == "rename_file":
sname = reqData.get("sname","")
dname = reqData.get("dname","")
if not sname or not dname:
return ErrorResponse(msg="参数错误")
sPath = path+"/"+sname
dPath = path+"/"+dname
rename_file(sPath=sPath,dPath=dPath,is_windows=is_windows)
return DetailResponse(msg="重命名成功")
elif action == "copy_file":
sPath = reqData.get("spath","")
name = reqData.get("name","")
cover = reqData.get("cover",False)
if not sPath or not name:
return ErrorResponse(msg="参数错误")
dPath = path+"/"+name
if not cover:
if os.path.exists(dPath):
return ErrorResponse(code=4050,msg="目标存在相同文件")
copy_file(sPath=sPath,dPath=dPath,is_windows=is_windows)
return DetailResponse(msg="复制成功")
elif action == "copy_dir":
sPath = reqData.get("spath","")
name = reqData.get("name","")
cover = reqData.get("cover",False)
if not sPath or not name:
return ErrorResponse(msg="参数错误")
dPath = path+"/"+name
if not cover:
if os.path.exists(dPath):
return ErrorResponse(code=4050,msg="目标存在相同目录")
copy_dir(sPath=sPath,dPath=dPath,is_windows=is_windows,cover=cover)
return DetailResponse(msg="复制成功")
elif action == "move_file":
spath = reqData.get("spath","")
name = reqData.get("name","")
cover = reqData.get("cover",False)
if not spath or not name:
return ErrorResponse(msg="参数错误")
dPath = path+"/"+name
if not cover:
if os.path.exists(dPath):
return ErrorResponse(code=4050,msg="目标存在相同目录/文件")
move_file(sPath=spath,dPath=dPath,is_windows=is_windows,cover=cover)
return DetailResponse(msg="移动成功")
elif action == "read_file_body":
ext = get_filename_ext(path)
if ext in ['msi','psd','dll','sys','gz', 'zip', 'rar','7z', 'bz2', 'exe', 'db','sqlite','sqlite3','.mdb', 'pdf', 'doc', 'xls', 'docx', 'xlsx', 'ppt','pptx','mp4','flv','avi', 'png', 'gif', 'jpg', 'jpeg', 'bmp', 'icon', 'ico', 'pyc','class', 'so', 'pyd']:
return ErrorResponse(msg="该文件不支持在线编辑")
if not os.path.exists(path):
return ErrorResponse(msg="该文件不存在")
size = os.path.getsize(path)
if size>3145728:#大于3M不建议在线编辑
return ErrorResponse(msg="文件过大,不支持在线编辑")
content = ""
encoding = "utf-8"
try:
with open(path, 'r', encoding="utf-8", errors='ignore') as file:
content = file.read()
except PermissionError as e:
return ErrorResponse(msg="文件被占用,暂无法打开")
except OSError as e:
return ErrorResponse(msg="操作系统错误,暂无法打开")
except:
try:
with open(path, 'r', encoding="GBK", errors='ignore') as file:
content = file.read()
encoding = "GBK"
except PermissionError as e:
return ErrorResponse(msg="文件被占用,暂无法打开")
except OSError as e:
return ErrorResponse(msg="操作系统错误,暂无法打开")
except Exception as e:
return ErrorResponse(msg="文件编码不兼容")
data = {
'st_mtime':str(int(os.stat(path).st_mtime)),
'content':content,
'size':size,
'encoding':encoding,
'language':auto_detect_file_language(path)
}
return DetailResponse(data=data,msg="获取成功")
elif action == "save_file_body":
content = reqData.get("content",None)
st_mtime = reqData.get("st_mtime",None)
force = reqData.get("force",False)
if not force and st_mtime and not st_mtime == str(int(os.stat(path).st_mtime)):
return ErrorResponse(code=4050,msg="在线文件可能发生变动,是否继续保存")
WriteFile(path,content)
return DetailResponse({'st_mtime':str(int(os.stat(path).st_mtime))},msg="保存成功")
elif action == "set_file_access":
user = reqData.get("user",None)
group = reqData.get("group",None)
access = reqData.get("access",False)
issub = reqData.get("issub",True)
if not user or not group:return ErrorResponse(msg="所属组/者不能为空")
if not os.path.exists(path):
return ErrorResponse(msg="路径不存在")
numeric_pattern = re.compile(r'^[0-7]{3}$')
if not numeric_pattern.match(str(access)):return ErrorResponse(msg="权限格式错误")
if is_windows:
return ErrorResponse(msg="windows目前还不支持此功能")
if check_in_black_list(path=path,is_windows=is_windows):
return ErrorResponse(msg="该目录不能设置权限")
issub_str ="-R" if issub else ""
command = f"chmod {issub_str} {access} {path}"
res,err = RunCommand(command)
if err:
return ErrorResponse(msg=err)
command1 = f"chown {issub_str} {user}:{group} {path}"
res1,err1 =RunCommand(command1)
if err1:
return ErrorResponse(msg=err1)
return DetailResponse({'st_mtime':str(int(os.stat(path).st_mtime))},msg="设置成功")
data = {}
return DetailResponse(data=data)
class RYGetFileDownloadView(APIView):
"""
get:
根据文件token进行文件下载
"""
permission_classes = []
authentication_classes = []
def get(self, request):
reqData = get_parameter_dic(request)
filename = reqData.get("filename",None)
token = reqData.get("token",None)
expires = reqData.get("expires",0)
isok,msg = validate_file_token(filename=filename,expires=expires,token=token)
if not isok:
return ResponseNginx404()
if not filename:
return ErrorResponse(msg="参数错误")
if not os.path.exists(filename):
return ErrorResponse(msg="文件不存在")
if not os.path.isfile(filename):
return ErrorResponse(msg="参数错误")
file_size = os.path.getsize(filename)
content_type, encoding = mimetypes.guess_type(filename)
content_type = content_type or 'application/octet-stream'
response = StreamingHttpResponse(open(filename, 'rb'), content_type=content_type)
response['Content-Disposition'] = f'attachment;filename="{escape_uri_path(os.path.basename(filename))}"'
response['Content-Length'] = file_size
return response
class RYFileDownloadView(APIView):
"""
post:
文件下载
"""
permission_classes = [IsAuthenticated,CustomPermission]
authentication_classes = [JWTAuthentication]
def post(self, request):
reqData = get_parameter_dic(request)
filename = reqData.get("filename",None)
if not filename:
return ErrorResponse(msg="参数错误")
if not os.path.exists(filename):
return ErrorResponse(msg="文件不存在")
if not os.path.isfile(filename):
return ErrorResponse(msg="参数错误")
file_size = os.path.getsize(filename)
response = FileResponse(open(filename, 'rb'))
response['content_type'] = "application/octet-stream"
response['Content-Disposition'] = f'attachment;filename="{escape_uri_path(os.path.basename(filename))}"'
# response['Content-Disposition'] = f'attachment; filename="{filename}"'
response['Content-Length'] = file_size # 设置文件大小
return response
def generate_file_token(filename,expire=None):
secret_key = settings.SECRET_KEY
if not expire:
expire = 43200
expire_time = int(time.time()) + expire # 设置过期时间
# 生成安全签名 token
signature = md5(f"{filename}-{expire_time}-{secret_key}")
return {
'token':signature,
'expires':expire_time,
'filename':filename
}
def validate_file_token(filename,expires,token):
expires = int(expires)
current_time = int(time.time())
secret_key = settings.SECRET_KEY
# 校验安全签名 token
expected_signature = md5(f"{filename}-{expires}-{secret_key}")
if token == expected_signature:
if current_time <= expires:
return True,"ok"
else:
return False,"token已过期"
else:
return False,"无效的token"
class RYFileTokenView(APIView):
"""
post:
获取文件访问token
"""
permission_classes = [IsAuthenticated,CustomPermission]
authentication_classes = [JWTAuthentication]
def post(self, request):
reqData = get_parameter_dic(request)
filename = reqData.get("filename",None)
if not filename:
return ErrorResponse(msg="参数错误")
if not os.path.exists(filename):
return ErrorResponse(msg="文件不存在")
data = generate_file_token(filename=filename)
return DetailResponse(data=data)
class RYFileMediaView(APIView):
"""
get:
媒体文件
"""
permission_classes = []
authentication_classes = []
def get(self, request):
reqData = get_parameter_dic(request)
filename = reqData.get("filename",None)
token = reqData.get("token",None)
expires = reqData.get("expires",0)
isok,msg = validate_file_token(filename=filename,expires=expires,token=token)
if not isok:
return ResponseNginx404()
if not filename:
return ErrorResponse(msg="参数错误")
if not os.path.exists(filename):
return ErrorResponse(msg="文件不存在")
if not os.path.isfile(filename):
return ErrorResponse(msg="参数错误")
content_type, encoding = mimetypes.guess_type(filename)
content_type = content_type or 'application/octet-stream'
if content_type in ['video/mp4','video/ogg', 'video/flv', 'video/avi', 'video/wmv', 'video/rmvb','audio/mp3','audio/x-m4a','audio/mpeg','audio/ogg']:
response = stream_video(request, filename)#支持视频流媒体播放
return response
return ErrorResponse(msg="限制只能媒体文件")
class RYFileUploadView(APIView):
"""
post:
文件上传(支持分片断点续传)
"""
permission_classes = [IsAuthenticated,CustomPermission]
authentication_classes = [JWTAuthentication]
throttle_classes=[]
def post(self, request):
reqData = get_parameter_dic(request)
file = request.FILES.get('lyfile',None)
filechunk_name = reqData.get('lyfilechunk',None)
path = reqData.get("path",None)
if not path:
return ErrorResponse(msg="参数错误")
if path[-1] == '/':
path = path[:-1]
if file:
save_path = path+"/"+file.name
if path and not os.path.exists(path):
os.makedirs(path)
with open(save_path, 'wb+') as destination:
for ck in file.chunks():
destination.write(ck)
return DetailResponse(data=None,msg="上传成功")
elif filechunk_name:
chunk = reqData.get('chunk')
chunkIndex = int(reqData.get('chunkIndex',0))
chunkCount = int(reqData.get('chunkCount',0))
if not chunkCount:
return ErrorResponse(msg="参数错误")
if path and not os.path.exists(path):
os.makedirs(path)
# 保存文件分片
with open(f'{path}/{filechunk_name}.part{chunkIndex}', 'wb') as destination:
for content in chunk.chunks():
destination.write(content)
# 如果所有分片上传完毕,合并文件
if chunkIndex == chunkCount - 1:
with open(f'{path}/{filechunk_name}', 'ab') as final_destination:
for i in range(chunkCount):
with open(f'{path}/{filechunk_name}.part{i}', 'rb') as part_file:
final_destination.write(part_file.read())
os.remove(f'{path}/{filechunk_name}.part{i}') # 删除临时分片文件
return DetailResponse(data=None,msg="上传成功")
return DetailResponse(data=None,msg=f'分片{chunkIndex}上传成功')
else:
return ErrorResponse(msg="参数错误")

View File

@ -22,6 +22,7 @@ from django_redis import get_redis_connection
from django.conf import settings from django.conf import settings
from config import IS_SINGLE_TOKEN,LOGIN_ERROR_RETRY_TIMES,LOGIN_ERROR_RETRY_TIMEOUT from config import IS_SINGLE_TOKEN,LOGIN_ERROR_RETRY_TIMES,LOGIN_ERROR_RETRY_TIMEOUT
from django.core.cache import cache from django.core.cache import cache
from utils.common import current_os
class CaptchaView(APIView): class CaptchaView(APIView):
""" """
@ -131,6 +132,7 @@ class LoginSerializer(TokenObtainPairSerializer):
role_list = user.role.values_list('id',"name") role_list = user.role.values_list('id',"name")
role_names_list = [i[1] for i in role_list] role_names_list = [i[1] for i in role_list]
role_names = ','.join(role_names_list) role_names = ','.join(role_names_list)
data['current_os'] = current_os
data['avatar'] = self.user.avatar data['avatar'] = self.user.avatar
data['role_names'] = role_names data['role_names'] = role_names
data['name'] = self.user.name data['name'] = self.user.name

BIN
backend/qqwry.dat Normal file

Binary file not shown.

View File

@ -69,3 +69,6 @@ eventlet
openpyxl openpyxl
python-barcode python-barcode
qqwry-py3 qqwry-py3
chardet
natsort
pywin32 ; sys_platform == 'win32'

View File

@ -72,3 +72,6 @@ gunicorn
gevent gevent
uwsgi uwsgi
qqwry-py3 qqwry-py3
chardet
natsort
pywin32 ; sys_platform == 'win32'

View File

@ -93,3 +93,6 @@ xlwt==1.3.0
zope.interface==5.4.0 zope.interface==5.4.0
openpyxl openpyxl
python-barcode python-barcode
chardet
natsort
pywin32 ; sys_platform == 'win32'

View File

@ -11,7 +11,8 @@
# ------------------------------ # ------------------------------
# 公用方法 # 公用方法
# ------------------------------ # ------------------------------
import shutil
import platform
import os,re import os,re
import random import random
import time import time
@ -23,14 +24,17 @@ import ast
import base64 import base64
import hashlib import hashlib
import json import json
import subprocess
from application.settings import SECRET_KEY from application.settings import SECRET_KEY
import chardet
current_os = platform.system().lower()
#手机号验证正则 #手机号验证正则
REGEX_MOBILE = "^1[356789]\d{9}$|^147\d{8}$|^176\d{8}$" REGEX_MOBILE = r"^1[356789]\d{9}$|^147\d{8}$|^176\d{8}$"
#身份证正则 #身份证正则
IDCARD_MOBILE ="^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$" IDCARD_MOBILE =r"^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$"
def starts_with_digit(s): def starts_with_digit(s):
""" """
@ -416,3 +420,127 @@ def verifyLyStateEncript(data,expire=300):
return jsonData,True return jsonData,True
except: except:
return data,False return data,False
#读取文件内容
def ReadFile(filename,mode='r'):
"""
filename:文件包含路径
返回若文件不存在则返回None
"""
try:
with open(filename, mode) as file:
content = file.read()
# 处理文件内容
except:
try:
with open(filename, mode, encoding="utf-8", errors='ignore') as file:
content = file.read()
except:
try:
with open(filename, mode, encoding="GBK", errors='ignore') as file:
content = file.read()
except:
return None
return content
#写入文件内容,不存在则创建
def WriteFile(file_path,content,mode="w",write=True,encoding="utf-8"):
"""
@name 写入文件内容不存在则创建
@author lybbn<2024-01-13>
file_path:文件包含路径
content:写入的内容
mode: w 覆盖写入默认a 追加写入
write:是否写入
"""
if write:
# 获取文件所在的目录路径
directory = os.path.dirname(file_path)
# 检查目录是否存在,如果不存在则创建
if not os.path.exists(directory):
os.makedirs(directory)
if 'b' in mode:encoding = None
# 写入内容到文件
with open(file_path, mode,encoding=encoding) as f:
if isinstance(content, int):
content = str(content)
f.write(content)
def DeleteFile(path,empty_tips=True):
"""
@name 删除文件
@author lybbn<2024-02-22>
"""
if empty_tips:
if not os.path.exists(path) and not os.path.islink(path):
raise ValueError("要删除的文件不存在")
os.remove(path)
if os.path.exists(path):
os.remove(path)
def DeleteDir(path):
"""
@name 删除目录
@author lybbn<2024-02-22>
"""
if not os.path.exists(path):
return
if os.path.islink(path):
os.remove(path)
else:
shutil.rmtree(path)
def RunCommand(cmdstr,cwd=None,shell=True,bufsize=4096,returncode=False,timeout=None,env=None):
"""
@name 执行命令(输出结果)
@author lybbn<2024-02-18>
@param cmdstr 命令 [必传]
@param cwd 执行命令的工作目录
@param returncode 是否需要返回returncode 0 成功 1 失败
@timeout 命令超时时间 单位s秒
"""
if platform.system() == 'Windows':
commands_list = cmdstr.split("\n")
commands = '&'.join(commands_list)
else:
commands_list = cmdstr.split("\n")
commands = '; '.join(commands_list)
try:
process = subprocess.Popen(commands,cwd=cwd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=shell,bufsize=bufsize,env=env)
# 获取输出结果
stdout, stderr = process.communicate(timeout=timeout)
except subprocess.TimeoutExpired:
process.kill() # 如果超时,则杀死子进程
stdout, stderr = process.communicate()
# 检测输出编码
stdout_encoding = chardet.detect(stdout)['encoding'] or 'utf-8'
stderr_encoding = chardet.detect(stderr)['encoding'] or 'utf-8'
try:
out = stdout.decode(stdout_encoding)
err = stderr.decode(stderr_encoding)
except:
try:
out = stdout.decode('utf-8')
err = stderr.decode('utf-8')
except:
try:
out = stdout.decode('gb2312')
err = stderr.decode('gb2312')
except:
try:
out = stdout.decode('utf-16')
err = stderr.decode('utf-16')
except:
try:
out = stdout.decode('latin-1')
err = stderr.decode('latin-1')
except:
out = stdout.decode('gb2312', 'ignore')
err = stderr.decode('gb2312', 'ignore')
if returncode:
return out, err,process.returncode
return out, err

View File

@ -31,6 +31,7 @@ from rest_framework_simplejwt.authentication import AUTH_HEADER_TYPE_BYTES
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework_simplejwt.tokens import UntypedToken from rest_framework_simplejwt.tokens import UntypedToken
from utils.ip_util import IPQQwry from utils.ip_util import IPQQwry
from config import IS_DEMO
IS_ALLOW_FRONTEND = ALLOW_FRONTEND IS_ALLOW_FRONTEND = ALLOW_FRONTEND
@ -248,6 +249,8 @@ def has_permission(user, path):
method = methodList.index(method) method = methodList.index(method)
if not hasattr(user, "role"): if not hasattr(user, "role"):
return False return False
if method == 5 and api == "/ws/msg/":
return True
userApiList = user.role.values('permission__api', 'permission__method') # 获取当前用户的角色拥有的所有接口 userApiList = user.role.values('permission__api', 'permission__method') # 获取当前用户的角色拥有的所有接口
for item in userApiList: for item in userApiList:
valid = ValidationApi(api, item.get('permission__api')) valid = ValidationApi(api, item.get('permission__api'))

View File

@ -35,9 +35,15 @@ class CustomPermission(BasePermission):
# 演示模式接口白名单(演示模式不做控制) # 演示模式接口白名单(演示模式不做控制)
# 演示模式判断 # 演示模式判断
api = request.path
method = request.method # 当前请求方法
demo_api_white_list = ['/api/lyformbuilder/lyformbuilder/previewcodejson/','/api/mall/goodsspu/export/'] demo_api_white_list = ['/api/lyformbuilder/lyformbuilder/previewcodejson/','/api/mall/goodsspu/export/']
if IS_DEMO and not request.path in demo_api_white_list: if IS_DEMO and not api in demo_api_white_list:
if not request.method in ['GET', 'OPTIONS']: if not method in ['GET', 'OPTIONS']:
if method == "POST" and api in ["/api/system/fileManage/"]:
action = request.data.get("action","")
if action in ["list_dir"]:
return True
raise ValueError('演示模式,不允许操作!') raise ValueError('演示模式,不允许操作!')
# 对ViewSet下的def方法进行权限判断 # 对ViewSet下的def方法进行权限判断
# 当权限为空时,则可以访问 # 当权限为空时,则可以访问
@ -53,8 +59,8 @@ class CustomPermission(BasePermission):
if request.user.is_superuser: if request.user.is_superuser:
return True return True
else: else:
api = request.path # 当前请求接口 # api = request.path # 当前请求接口
method = request.method # 当前请求方法 # method = request.method # 当前请求方法
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'] methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
method = methodList.index(method) method = methodList.index(method)
if not hasattr(request.user, "role"): if not hasattr(request.user, "role"):

View File

@ -11,7 +11,6 @@ from user_agents import parse
from mysystem.models import LoginLog from mysystem.models import LoginLog
def get_request_user(request): def get_request_user(request):
""" """
获取请求user 获取请求user

View File

View File

@ -0,0 +1,816 @@
#!/bin/python
#coding: utf-8
# +-------------------------------------------------------------------
# | system: 如意面板
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# | Date: 2024-01-03
# +-------------------------------------------------------------------
# ------------------------------
# 文件/目录操作
# ------------------------------
import re
import os
import shutil
import zipfile
import tarfile
import datetime
import mimetypes
import requests
from natsort import natsorted, ns
from utils.server.system import system
from utils.security.no_delete_list import check_no_delete,check_in_black_list
from utils.common import ast_convert,WriteFile,RunCommand,current_os
from itertools import chain
def get_file_name_from_url(url):
"""
@name 使用 os.path.basename() 函数获取 URL 中的文件名
@author lybbn<2024-02-22>
"""
file_name = os.path.basename(url)
return file_name
def get_file_extension(file_path):
"""
@name 获取文件后缀扩展
@author lybbn<2024-02-22>
"""
_, extension = os.path.splitext(file_path)
return extension
def detect_file_type(file_path):
"""
@name 检测文件类型
@author lybbn<2024-02-22>
"""
file_type, _ = mimetypes.guess_type(file_path)
return file_type
def auto_detect_file_language(file_path):
"""
@name 智能检测文件所属语言
@author lybbn<2024-03-08>
"""
ext = get_file_extension(file_path)
if ext in ['.readme','.md']:
return "markdown"
elif ext in ['.sh']:
return "shell"
elif ext in ['.lua']:
return "lua"
elif ext in ['.rb']:
return "ruby"
elif ext in ['.js','.ts']:
return "javascript"
elif ext in ['.html','htm']:
return "html"
elif ext in ['.css','.scss','.sass','.less']:
return "css"
elif ext in ['.json']:
return "json"
elif ext in ['.py']:
return "python"
elif ext in ['.yaml','.yml']:
return "yaml"
elif ext in ['.conf','.ini']:
if 'nginx' in file_path:
return "nginx"
return "properties"
elif ext in ['.vue']:
return "vue"
elif ext in ['.php']:
return "php"
elif ext in ['.java']:
return "java"
elif ext in ['.go']:
return "go"
elif ext in ['.sql']:
return "sql"
elif ext in ['.xml']:
return "xml"
else:
return "log"
def list_dirs(dst_path):
"""
列出指定目录下文件\目录名
返回指定目录下文件+目录名的列表
"""
if not os.path.exists(dst_path):
return []
data = []
for f in os.listdir(dst_path):
data.append(f)
return data
def get_size(file_path):
"""
@name 获取文件大小
@author lybbn<2024-02-22>
"""
return os.path.getsize(file_path)
def is_link(file_path):
"""
@name 是否软链接
@author lybbn<2024-02-22>
"""
return os.path.islink(file_path)
def get_directory_size(dst_path):
"""
@name 计算指定目录大小
@author lybbn<2024-02-22>
"""
if current_os == "windows":
total_size = 0
for path, dirs, files in os.walk(dst_path):
for file in files:
if not os.path.exists(file): continue
if os.path.islink(file): continue
file_path = os.path.join(path, file)
total_size += os.path.getsize(file_path)
return total_size
else:
result,err,returncode = RunCommand(f"du -sh {dst_path}",returncode=True)
if returncode == 0:
size = result.split()[0]
return re.sub(r'(\d+)([a-zA-Z])', r'\1 \2', size)+"B"
else:
return "0 B"
def get_path_files_nums(path):
"""
@name 获取指定目录文件数量
@author lybbn<2024-02-22>
"""
if os.path.isfile(path):
return 1
if not os.path.exists(path):
return 0
i = 0
for name in os.listdir(path):
i += 1
return i
def get_filename_ext(filename):
"""
@name 获取文件扩展名
@author lybbn<2024-02-22>
"""
tmpList = filename.split('.')
return tmpList[-1]
def windows_path_replace(path,is_windows = True):
"""
@name 把path中的\\ sep替换成 /
@author lybbn<2024-02-22>
"""
if is_windows:
path = path.replace("\\", "/")
return path
def list_files_in_directory(dst_path,sort="name",is_reverse=False,is_windows=False,search=None,containSub=False,isDir=False):
"""
@name 列出指定目录下文件\目录名列表包含文件\目录属性大小路径权限所属者
目录size大小默认不计算为0
owner所属者为空可能为文件/目录无权限查看或被占用等
@author lybbn<2024-02-22>
@param sort 排序
@param is_reverse True 降序(desc) False 升序(asc)
@param search 搜索名称
@param containSub 搜索内容是否包含所有子目录
@param isDir 是否只列出目录
"""
def get_file_info(entry):
"""获取文件信息的内部函数"""
file_info = entry.stat()
modified_time = datetime.datetime.fromtimestamp(file_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
gid = file_info.st_gid
group_name = "" if is_windows else system.GetGroupidName(entry.path, gid)
return {
"name": entry.name,
"type": "file" if entry.is_file() else "dir",
"path": windows_path_replace(entry.path, is_windows=is_windows),
"size": file_info.st_size if entry.is_file() else None,
"permissions": oct(file_info.st_mode)[-3:],
"owner_uid": file_info.st_uid,
"owner": system.GetUidName(entry.path, file_info.st_uid),
"gid": gid,
"group": group_name,
"modified": formatted_time
}
def process_entry(entry):
"""处理单个目录项"""
if search and search.lower() not in entry.name.lower():
return None
if isDir and entry.is_file():
return None
return get_file_info(entry)
# 处理Windows磁盘根目录情况
if is_windows and not dst_path:
disk_paths = system().GetDiskInfo()
datainfo = {
'data':[],
'file_nums':0,
'dir_nums':0,
'total_nums':0
}
for d in disk_paths:
if search and d['path'].lower().find(search) == -1:
continue
datainfo['data'].append({
"name": d['path'].lower(),
"type": "pan",
"path": windows_path_replace(d['path'].lower(), is_windows=is_windows),
"size": d['size'][0],
"permissions": "",
"owner_uid": None,
"owner": "",
"modified": ""
})
datainfo['total_nums'] = len(disk_paths)
return datainfo
# 检查路径有效性
if not os.path.exists(dst_path):
return {
'data': [],
'file_nums': 0,
'dir_nums': 0,
'total_nums': 0
}
if not os.path.isdir(dst_path):
raise ValueError("错误:非目录")
# 处理不包含子目录的情况
if not containSub:
dirData = []
fileData = []
for entry in os.scandir(dst_path):
item = process_entry(entry)
if item:
if item["type"] == "dir":
dirData.append(item)
else:
fileData.append(item)
# 对目录和文件分别排序
if sort == "name":
dirData = natsorted(dirData, key=lambda x: x["name"], alg=ns.PATH, reverse=is_reverse)
fileData = natsorted(fileData, key=lambda x: x["name"], alg=ns.PATH, reverse=is_reverse)
elif sort == "modified":
dirData = sorted(dirData, key=lambda x: x["modified"], reverse=is_reverse)
fileData = sorted(fileData, key=lambda x: x["modified"], reverse=is_reverse)
elif sort == "size":
dirData = sorted(dirData, key=lambda x: x["size"] if x["size"] is not None else 0, reverse=is_reverse)
fileData = sorted(fileData, key=lambda x: x["size"] if x["size"] is not None else 0, reverse=is_reverse)
data = dirData + fileData
else:
# 处理包含子目录的情况
data = []
count_limit = 0
max_limit = 3000
for root, dirs, files in os.walk(dst_path):
if count_limit >= max_limit:
break
for entry in chain((os.path.join(root, f) for f in files),
(os.path.join(root, d) for d in dirs)):
if count_limit >= max_limit:
break
info = process_entry(os.DirEntry(entry))
if info:
data.append(info)
count_limit += 1
# 对结果进行排序
if sort == "name":
data = natsorted(data, key=lambda x: x["name"], alg=ns.PATH, reverse=is_reverse)
elif sort == "modified":
data = sorted(data, key=lambda x: x["modified"], reverse=is_reverse)
elif sort == "size":
data = sorted(data, key=lambda x: x["size"] if x["size"] is not None else 0, reverse=is_reverse)
# 统计文件数量
file_nums = sum(1 for item in data if item["type"] == "file")
dir_nums = sum(1 for item in data if item["type"] == "dir")
return {
'data': data,
'file_nums': file_nums,
'dir_nums': dir_nums,
'total_nums': file_nums + dir_nums
}
def list_files_in_directory_old(dst_path,sort="name",is_reverse=False,is_windows=False,search=None,containSub=False,isDir=False):
"""
@name 列出指定目录下文件\目录名列表包含文件\目录属性大小路径权限所属者
目录size大小默认不计算为0
owner所属者为空可能为文件/目录无权限查看或被占用等
@author lybbn<2024-02-22>
@param sort 排序
@param is_reverse True 降序(desc) False 升序(asc)
@param search 搜索名称
@param containSub 搜索内容是否包含所有子目录
@param isDir 是否只列出目录
"""
is_dir_first = True#是否把目录放在前面
if is_windows:
if not dst_path:
disk_paths = system().GetDiskInfo()
datainfo = {
'data':[],
'file_nums':0,
'dir_nums':0,
'total_nums':0
}
for d in disk_paths:
if search:
if d['path'].lower().find(search) == -1:
continue
datainfo['data'].append({"name":d['path'].lower(),"type":"pan","path":windows_path_replace(d['path'].lower(),is_windows=is_windows),"size":d['size'][0],"permissions":"","owner_uid":None,"owner":"","modified":""})
datainfo['total_nums'] = len(disk_paths)
return datainfo
if not os.path.exists(dst_path):
return {
'data':[],
'file_nums':0,
'dir_nums':0,
'total_nums':0
}
if not os.path.isdir(dst_path):
raise ValueError("错误:非目录")
data = []
dirData = []
fileData = []
file_nums = 0
dir_nums = 0
if not containSub:
for entry in os.scandir(dst_path):
if entry.is_file():
if isDir:
continue
if search:
if entry.name.lower().find(search) == -1:
continue
file_nums = file_nums + 1
file_info = entry.stat()
modified_time = datetime.datetime.fromtimestamp(file_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
gid=file_info.st_gid
group_name=""
if not is_windows:
group_name = system.GetGroupidName(entry.path,gid)
tempData = {"name":entry.name,"type":"file","path":windows_path_replace(entry.path,is_windows=is_windows),"size":file_info.st_size,"permissions":oct(file_info.st_mode)[-3:],"owner_uid":file_info.st_uid,"owner":system.GetUidName(entry.path,file_info.st_uid),"gid":gid,"group":group_name,"modified":formatted_time}
data.append(tempData)
if is_dir_first:
fileData.append(tempData)
elif entry.is_dir():
if search:
if entry.name.lower().find(search) == -1:
continue
dir_nums = dir_nums + 1
dir_info = entry.stat()
modified_time = datetime.datetime.fromtimestamp(dir_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
gid=dir_info.st_gid
group_name=""
if not is_windows:
group_name = system.GetGroupidName(entry.path,gid)
tempData = {"name":entry.name,"type":"dir","path":windows_path_replace(entry.path,is_windows=is_windows),"size":None,"permissions":oct(dir_info.st_mode)[-3:],"owner_uid":dir_info.st_uid,"owner":system.GetUidName(entry.path,dir_info.st_uid),"gid":gid,"group":group_name,"modified":formatted_time}
data.append(tempData)
if is_dir_first:
dirData.append(tempData)
else:
count_limit = 0
max_limit = 3000
for root, dirs, files in os.walk(dst_path):
if count_limit >= max_limit:
break
# 在当前目录下搜索文件
for file in files:
if count_limit >= max_limit:
break
if search:
if file.lower().find(search) == -1:
continue
file_nums = file_nums + 1
file_path = os.path.join(root, file)
file_info = os.stat(file_path)
modified_time = datetime.datetime.fromtimestamp(file_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
tempData = {"name":file,"type":"file","path":windows_path_replace(file_path,is_windows=is_windows),"size":file_info.st_size,"permissions":oct(file_info.st_mode)[-3:],"owner_uid":file_info.st_uid,"owner":system.GetUidName(file_path,file_info.st_uid),"modified":formatted_time}
data.append(tempData)
if is_dir_first:
fileData.append(tempData)
count_limit += 1
# 在当前目录下搜索目录
for dir in dirs:
if count_limit >= max_limit:
break
if search:
if dir.lower().find(search) == -1:
continue
dir_nums = dir_nums + 1
dir_path = os.path.join(root, dir)
dir_info = os.stat(dir_path)
modified_time = datetime.datetime.fromtimestamp(dir_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
tempData = {"name":dir,"type":"dir","path":windows_path_replace(dir_path,is_windows=is_windows),"size":None,"permissions":oct(dir_info.st_mode)[-3:],"owner_uid":dir_info.st_uid,"owner":system.GetUidName(dir_path,dir_info.st_uid),"modified":formatted_time}
data.append(tempData)
if is_dir_first:
dirData.append(tempData)
count_limit += 1
if is_dir_first:
data = []
temp_dir_date1 = dirData[:4000]
temp_dir_date2 = natsorted(temp_dir_date1,key=lambda x: x.get("name", ""), alg=ns.PATH, reverse=is_reverse)
temp_dir_data = temp_dir_date2 + dirData[4000:]
temp_file_date1 = fileData[:4000]
temp_file_date2 = natsorted(temp_file_date1,key=lambda x: x.get("name", ""), alg=ns.PATH, reverse=is_reverse)
temp_file_data = temp_file_date2 + fileData[4000:]
data.extend(temp_dir_data)
data.extend(temp_file_data)
# if sort == "name":
# # 根据 sort 参数对结果进行排序,海量文件可能导致排序缓慢因此限制排序前4000个
# temp_date1 = data[:4000]
# temp_date2 = natsorted(temp_date1,key=lambda x: x.get("name", ""), alg=ns.PATH, reverse=is_reverse)
# data = temp_date2 + data[4000:]
if sort == "modified":
# 根据 sort 参数对结果进行排序,海量文件可能导致排序缓慢因此限制排序前4000个
temp_date1 = data[:4000]
temp_date2 = sorted(temp_date1, key=lambda x: x["modified"], reverse=is_reverse)
data = temp_date2 + data[4000:]
elif sort == "size":
# 根据 sort 参数对结果进行排序,海量文件可能导致排序缓慢因此限制排序前4000个
temp_date1 = data[:4000]
temp_date2 = sorted(temp_date1, key=lambda x: x["size"] if x["size"] is not None else 0, reverse=is_reverse)
data = temp_date2 + data[4000:]
data_info = {
'data':data,
'file_nums':file_nums,
'dir_nums':dir_nums,
'total_nums':file_nums+dir_nums
}
return data_info
def get_filedir_attribute(path,is_windows=False):
"""
@name 获取文件/目录属性
@author lybbn<2024-02-22>
"""
if not path:
return None
if os.path.isfile(path):
name = os.path.basename(path)
file_info = os.stat(path)
modified_time = datetime.datetime.fromtimestamp(file_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
access_time = datetime.datetime.fromtimestamp(file_info.st_atime)
formatted_at = access_time.strftime("%Y-%m-%d %H:%M:%S")
return {"name":name,"type":"file","is_link":is_link(path),"path":windows_path_replace(path,is_windows=is_windows),"size":file_info.st_size,"permissions":oct(file_info.st_mode)[-3:],"owner_uid":file_info.st_uid,"owner":system.GetUidName(path,file_info.st_uid),"gid":file_info.st_gid,"group":system.GetGroupidName(path,file_info.st_gid),"modified":formatted_time,"access_at":formatted_at}
elif os.path.isdir(path):
name = os.path.basename(path)
dir_info = os.stat(path)
modified_time = datetime.datetime.fromtimestamp(dir_info.st_mtime)
formatted_time = modified_time.strftime("%Y-%m-%d %H:%M:%S")
access_time = datetime.datetime.fromtimestamp(dir_info.st_atime)
formatted_at = access_time.strftime("%Y-%m-%d %H:%M:%S")
return {"name":name,"type":"dir","is_link":is_link(path),"path":windows_path_replace(path,is_windows=is_windows),"size":get_directory_size(path),"permissions":oct(dir_info.st_mode)[-3:],"owner_uid":dir_info.st_uid,"owner":system.GetUidName(path,dir_info.st_uid),"gid":dir_info.st_gid,"group":system.GetGroupidName(path,dir_info.st_gid),"modified":formatted_time,"access_at":formatted_at}
return None
def create_file(path,is_windows=False):
"""
@name 创建文件
@author lybbn<2024-02-22>
"""
#去除干扰
filename = os.path.basename(path).strip()
filepath = os.path.dirname(path).strip()
if not filename:
raise ValueError("请填写文件名")
if not is_windows:
path = os.path.join(filepath, filename)
else:
filepath = filepath.replace("//", "/").replace("/", "\\")
path = os.path.join(filepath, filename)
if path[-1] == '.':
raise ValueError("文件名不能以'.'点结尾")
if len(filename)>100 or len(filename) < 1:
raise ValueError("长度在1到100个字符之间")
black_list = ['\\','/', '&', '*', '|', ';', '"', "'", '<', '>']
for black in black_list:
if black in filename:
raise ValueError("文件名不能包含指定特殊字符")
if os.path.exists(path):
return ValueError("该文件已存在")
if not os.path.exists(filepath):
os.makedirs(filepath)
# 创建空文件
open(path, 'w+').close()
def create_dir(path,is_windows=False):
"""
@name 创建目录
@author lybbn<2024-02-22>
"""
path = path.replace("//", "/")
if path[-1] == '.':
raise ValueError("目录名不能以'.'点结尾")
dirname = os.path.basename(path)
if len(dirname)>100 or len(dirname) < 1:
raise ValueError("长度在1到100个字符之间")
black_list = ['\\','/', '&', '*', '|', ';', '"', "'", '<', '>']
for black in black_list:
if black in dirname:
raise ValueError("文件名不能包含指定特殊字符")
if os.path.exists(path):
return ValueError("该目录已存在")
os.makedirs(path)
def delete_dir(path,is_windows=False):
"""
@name 删除目录
@author lybbn<2024-02-22>
"""
if not os.path.exists(path) and not os.path.islink(path):
raise ValueError("目录不存在")
#检查哪些目录不能被删除
check_no_delete(path,is_windows)
if os.path.islink(path):
os.remove(path)
else:
shutil.rmtree(path)
def delete_file(path,is_windows=False):
"""
@name 删除文件
@author lybbn<2024-02-22>
"""
if not os.path.exists(path) and not os.path.islink(path):
raise ValueError("文件不存在")
#检查哪些目录不能被删除
check_no_delete(path,is_windows)
os.remove(path)
def rename_file(sPath,dPath,is_windows=False):
"""
@name 重命名文件或目录
@author lybbn<2024-02-22>
@params sPath 源路径
@params dPath 新路径
"""
if sPath == dPath:
return ValueError("源目标名称相同,已忽略")
dPath = dPath.replace("//", "/")
sPath = sPath.replace('//', '/')
if dPath[-1] == '.':
raise ValueError("不能以'.'点结尾")
if not os.path.exists(sPath):
raise ValueError("源文件/目录不存在")
if os.path.exists(dPath):
raise ValueError("目标存在相同名称")
if dPath[-1] == '/':
dPath = dPath[:-1]
#安全检查
if check_in_black_list(dPath,is_windows):
raise ValueError("与系统内置冲突,请更换名称")
os.rename(sPath, dPath)
def copy_file(sPath,dPath,is_windows=False):
"""
@name 复制文件
@author lybbn<2024-02-22>
@params sPath 源路径
@params dPath 新路径
"""
# if sPath == dPath:
# raise ValueError("源目标相同,已忽略")
dPath = dPath.replace("//", "/")
sPath = sPath.replace('//', '/')
if dPath[-1] == '.':
raise ValueError("不能以'.'点结尾")
if not os.path.exists(sPath):
raise ValueError("源文件不存在")
# if os.path.exists(dPath):
# raise ValueError("目标存在相同名称")
shutil.copyfile(sPath, dPath)
def copy_dir(sPath,dPath,is_windows=False,cover=False):
"""
@name 复制目录
@author lybbn<2024-02-22>
@params sPath 源路径
@params dPath 新路径
"""
if sPath == dPath:
raise ValueError("源和目标相同,已忽略")
dPath = dPath.replace("//", "/")
sPath = sPath.replace('//', '/')
if dPath[-1] == '.':
raise ValueError("不能以'.'点结尾")
if not os.path.exists(sPath):
raise ValueError("源目录不存在")
if cover and os.path.exists(dPath):
shutil.rmtree(dPath)
# if os.path.exists(dPath):
# raise ValueError("目标存在相同名称")
# if not os.path.exists(dPath):
# os.makedirs(dPath)
shutil.copytree(sPath, dPath)
def move_file(sPath,dPath,is_windows=False,cover=False):
"""
@name 移动文件/目录
@author lybbn<2024-02-22>
@params sPath 源路径
@params dPath 新路径
"""
if sPath == dPath:
raise ValueError("源和目标相同,已忽略")
dPath = dPath.replace("//", "/")
sPath = sPath.replace('//', '/')
if dPath[-1] == '.':
raise ValueError("不能以'.'点结尾")
if dPath[-1] == '/':
dPath = dPath[:-1]
if not os.path.exists(sPath):
raise ValueError("源目录不存在")
#安全检查
if check_in_black_list(dPath,is_windows):
raise ValueError("与系统内置冲突,请更换名称")
is_dir = os.path.isdir(sPath)
if cover and os.path.exists(dPath):
if is_dir:
shutil.rmtree(dPath)
else:
os.remove(dPath)
shutil.move(sPath, dPath)
def batch_operate(param,is_windows=False):
"""
@name 批量操作移动复制压缩权限删除
@author lybbn<2024-02-22>
@params param 请求参数
"""
type = param.get('type',None)
if type in ['copy','move']:
confirm = param.get('confirm',False)
skip_list = ast_convert(param.get('skipList',[]))#需要跳过覆盖的文件列表
dPath = param.get('path',"")
sPath = ast_convert(param.get('spath',[]))
if not dPath or not sPath:
return False,"参数错误",4000,None
dPath = dPath.replace('//', '/')
# if dPath[-1] == '/':
# dPath = dPath[:-1]
conflict_list = []
if not confirm:#初次先检查有冲突则返回确认
for d in sPath:
if d[-1] == '.':
raise ValueError("%s不能以'.'点结尾"%d)
if d[-1] == '/':
d = d[:-1]
dfile = dPath + '/' + os.path.basename(d)
if os.path.exists(dfile):
conflict_list.append({
'path':d,
'name':os.path.basename(d)
})
if conflict_list:
return False,"文件冲突",4050,conflict_list
if skip_list:
for s in skip_list:
if s['path'] in sPath:
sPath.remove(s)
if type == 'copy':
for sf in sPath:
dfile = dPath + '/' + os.path.basename(sf)
if os.path.commonpath([dfile, sf]) == sf:
return False,'{}复制到{}有包含关系,请更换目标目录!'.format(sf, dfile),4000,None
for sf in sPath:
dfile = dPath + '/' + os.path.basename(sf)
if os.path.isdir(sf):
shutil.copytree(sf, dfile)
else:
shutil.copyfile(sf, dfile)
return True,"批量复制成功",2000,None
else:
for sf in sPath:
dfile = dPath + '/' + os.path.basename(sf)
move_file(sPath=sf,dPath=dfile,is_windows=is_windows,cover=True)
return True,"批量剪切成功",2000,None
elif type == 'zip':
dPath = param.get('path',"")
sPath = ast_convert(param.get('spath',[]))
zip_type = param.get('zip_type',"")
if not dPath or not sPath:
return False,"参数错误",4000,None
if not zip_type in ["tar","zip"]:
return False,"不支持的压缩格式",4000,None
dPath = dPath.replace('//', '/')
# if dPath[-1] == '/':
# dPath = dPath[:-1]
func_zip(zip_filename=dPath,items=sPath,zip_type=zip_type)
return True,"压缩成功",2000,None
elif type == 'unzip':
dPath = param.get('path',"")
sPath = param.get('spath',"")
zip_type = param.get('zip_type',"")
if not dPath or not sPath:
return False,"参数错误",4000,None
dPath = dPath.replace('//', '/')
func_unzip(zip_filename=sPath,extract_path=dPath)
return True,"解压成功",2000,None
elif type == 'pms':
pass
elif type == 'del':
sPath = ast_convert(param.get('spath',[]))
for sf in sPath:
if os.path.isdir(sf):
delete_dir(path=sf,is_windows=is_windows)
else:
delete_file(path=sf,is_windows=is_windows)
return True,"批量删除成功",2000,None
else:
return False,"类型错误",4000,None
def func_zip(zip_filename,items,zip_type):
"""
@name 压缩
@author lybbn<2024-03-07>
@param zip_filename 压缩后的文件名含路径
@param items 需要压缩的文件或目录列表['/var/log/ruyi.log']
@param zip_type 压缩类型(tar 格式.tar.gzzip 格式 .zip)
"""
if not items:
raise ValueError("需要压缩的文件或目录不能为空")
if zip_type == "zip":
zip_directories_and_files(zip_filename,items)
elif zip_type == "tar":
create_tar_gz(zip_filename,items)
else:
raise ValueError("不支持的压缩格式")
def func_unzip(zip_filename,extract_path):
"""
@name 解压
@author lybbn<2024-03-07>
@param zip_filename 压缩文件名含路径
@param extract_path 需要解压的目标目录
"""
if current_os == "windows":
#解除占用
from utils.server.windows import kill_cmd_if_working_dir
kill_cmd_if_working_dir(extract_path)
_, ext = os.path.splitext(zip_filename)
if ext in ['.tar.gz','.tgz','.tar.bz2','.tbz']:
with tarfile.open(zip_filename, 'r') as tar:
tar.extractall(extract_path)
elif ext == '.zip':
with zipfile.ZipFile(zip_filename, 'r') as zipf:
zipf.extractall(extract_path)
else:
raise ValueError("不支持的文件格式")
def zip_directories_and_files(zip_filename, items):
with zipfile.ZipFile(zip_filename, 'w') as zipf:
for item in items:
if os.path.isfile(item):
zipf.write(item, os.path.basename(item))
elif os.path.isdir(item):
for root, _, files in os.walk(item):
for file in files:
file_path = os.path.join(root, file)
zipf.write(file_path, os.path.relpath(file_path, os.path.dirname(item)))
def create_tar_gz(tar_filename, items):
with tarfile.open(tar_filename, "w:gz") as tar:
for item in items:
if os.path.isfile(item):
tar.add(item, arcname=os.path.basename(item))
elif os.path.isdir(item):
tar.add(item, arcname=os.path.basename(item))

View File

@ -0,0 +1,97 @@
#!/bin/python
#coding: utf-8
# +-------------------------------------------------------------------
# | system: 如意面板
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# | Date: 2024-01-03
# +-------------------------------------------------------------------
# ------------------------------
# 操作系统/项目 不能删除目录
# ------------------------------
#linux 系统目录
Linux_System_List = [
{'path':'/dev','desc':'系统设备目录'},
{'path':'/mnt','desc':'系统挂载目录'},
{'path':'/media','desc':'系统多媒体目录'},
{'path':'/tmp','desc':'系统临时目录'},
{'path':'/sys','desc':'系统目录'},
{'path':'/proc','desc':'系统进程目录'},
{'path': '/etc', 'desc': '系统配置目录'},
{'path': '/boot', 'desc': '系统引导目录'},
{'path': '/root', 'desc': '根用户家目录'},
{'path': '/home', 'desc': '用户家目录'},
{'path': '/var', 'desc': '系统目录'},
{'path': '/', 'desc': '系统根目录'},
{'path': '/*', 'desc': '系统根目录'},
{'path': '/bin', 'desc': '系统命令目录'},
{'path': '/usr/bin', 'desc': '用户命令目录'},
{'path': '/sbin', 'desc': '系统管理员命令目录'},
{'path': '/usr/sbin', 'desc': '系统管理员命令目录'},
{'path': '/lib', 'desc': '系统动态库目录'},
{'path': '/lib32', 'desc': '系统动态库目录'},
{'path': '/lib64', 'desc': '系统动态库目录'},
{'path': '/usr/lib', 'desc': '用户库目录'},
{'path': '/usr/lib64', 'desc': '用户库目录'},
{'path': '/usr/local/lib', 'desc': '用户库目录'},
{'path': '/usr/local/lib64', 'desc': '用户库目录'},
{'path': '/usr/local/libexec', 'desc': '用户库目录'},
{'path': '/usr/local/sbin', 'desc': '系统脚本目录'},
{'path': '/usr/local/bin', 'desc': '系统脚本目录'},
{'path': '/var/log', 'desc': '系统日志目录'},
]
#Windows 系统目录
Windows_System_List = [
{'path': 'c:/', 'desc': 'C盘禁止删除'},
{'path': 'c:/Windows', 'desc': 'Windows 操作系统核心文件目录'},
{'path': 'c:/Program Files', 'desc': '应用程序安装目录64 位系统)'},
{'path': 'c:/Program Files (x86)', 'desc': '应用程序安装目录32 位系统)'},
{'path': 'c:/Users', 'desc': '用户个人文件目录'},
{'path': 'c:/ProgramData', 'desc': '应用程序共享数据目录'},
{'path': 'c:/Windows/System32', 'desc': 'Windows 系统关键系统文件目录'},
{'path': 'c:/Users/Public', 'desc': '公共用户文件目录'},
]
def check_in_black_list(path,is_windows=False):
if is_windows:
for wd in Windows_System_List:
if path == wd['path']:
return True
else:
for lx in Linux_System_List:
if path == lx['path']:
return True
return False
def check_no_delete(path,is_windows=False):
"""
@name 检查哪些目录不能被删除
@author lybbn<2024-02-22>
"""
path = path.replace('//', '/')
if path[-1:] == '/' or path[-1:] == '\\':
path = path[:-1]
if is_windows:
drive_name = path.split(':')[0]
if not drive_name:
raise ValueError("路径错误")
drive_name = drive_name.lower()
path_without_drive =path.split(':')[1] if drive_name else None
if not path_without_drive:
raise ValueError("不能直接删除磁盘")
path = drive_name+":"+path_without_drive
for wd in Windows_System_List:
if path == wd['path']:
raise ValueError("%s】不可删除!"%wd['desc'])
if not is_windows:
for lx in Linux_System_List:
if path == lx['path']:
raise ValueError("%s】不可删除!"%lx['desc'])

View File

@ -0,0 +1,54 @@
#!/bin/python
#coding: utf-8
# +-------------------------------------------------------------------
# | system: 如意面板
# +-------------------------------------------------------------------
# | Author: lybbn
# +-------------------------------------------------------------------
# | QQ: 1042594286
# +-------------------------------------------------------------------
# | Date: 2024-02-03
# +-------------------------------------------------------------------
# ------------------------------
# 字符安全过滤
# ------------------------------
import re
import urllib.parse
def filter_xss1(content):
"""
@name xss过滤只替换xss相关关键字符
@author lybbn<2024-02-08>
"""
dic_str = {
'<':'',
'>':'',
'"':'',
"'":''
}
for i in dic_str.keys():
content = content.replace(i,dic_str[i])
return content
def filter_xss2(content):
"""
@name xss过滤替换script中的尖括号为html转义
@author lybbn<2024-02-08>
"""
return content.replace('<', '&lt;').replace('>', '&gt;')
def is_validate_db_passwd(passwd):
"""
@name 是否有效数据库密码
@author lybbn<2024-08-24>
"""
if not passwd:
return False,"密码不能为空"
pattern = r"[\'\"`]|%27|%22|%60"
encoded_password = urllib.parse.quote(passwd)
match = re.search(pattern, encoded_password)
if match:
return False,"密码不能包含特殊字符"
return True,"ok"

View File

@ -11,6 +11,8 @@
# ------------------------------ # ------------------------------
# linux系统命令工具类封装 # linux系统命令工具类封装
# ------------------------------ # ------------------------------
import pwd
import grp
import os, sys, re, time, json import os, sys, re, time, json
import psutil import psutil
from django.core.cache import cache from django.core.cache import cache
@ -391,3 +393,35 @@ def RestartServer():
os.system("sync && init 6 &") os.system("sync && init 6 &")
except: except:
pass pass
def GetUidName(file_path,uid=0):
"""
通过系统uid获取对应名称
"""
try:
return pwd.getpwuid(uid).pw_name
except Exception as e:
return ""
def GetGroupidName(file_path,gid=0):
"""
通过系统goup id所属组id获取对应名称
"""
try:
return grp.getgrgid(gid).gr_name
except Exception as e:
return ""
def ForceRemoveDir(directory):
"""
强制删除目录
"""
sys_dir = ['/root','/','/proc','/*','/root/*']
if directory in sys_dir:
raise ValueError("受保护的目录,无法删除!!!")
if os.path.exists(directory):
try:
# 执行 rm -rf 命令
subprocess.run(['rm', '-rf', directory], check=True)
except subprocess.CalledProcessError as e:
raise ValueError(f"强制删除目录错误: {e}")

View File

@ -83,3 +83,13 @@ class system:
def GetFileLastNumsLines(cls,path,num=1000): def GetFileLastNumsLines(cls,path,num=1000):
data = myos.GetFileLastNumsLines(path=path,num=num) data = myos.GetFileLastNumsLines(path=path,num=num)
return data return data
@classmethod
def GetUidName(cls,file_path,uid=0):
data = myos.GetUidName(file_path,uid)
return data
@classmethod
def GetGroupidName(cls,file_path,gid=0):
data = myos.GetGroupidName(file_path,gid=gid)
return data

View File

@ -20,8 +20,7 @@ import winreg
from config import EXEC_LOG_PATH, TEMP_EXEC_PATH from config import EXEC_LOG_PATH, TEMP_EXEC_PATH
from django.core.cache import cache from django.core.cache import cache
from pathlib import Path from pathlib import Path
import win32security
BASE_DIR = Path(__file__).resolve().parent
def ReadReg(path, key): def ReadReg(path, key):
@ -71,13 +70,13 @@ def to_size(size):
if not size: return '0.00 b' if not size: return '0.00 b'
size = float(size) size = float(size)
d = ('b', 'KB', 'MB', 'GB', 'TB'); d = ('b', 'KB', 'MB', 'GB', 'TB')
s = d[0]; s = d[0]
for b in d: for b in d:
if size < 1024: return ("%.2f" % size) + ' ' + b; if size < 1024: return ("%.2f" % size) + ' ' + b
size = size / 1024; size = size / 1024
s = b; s = b
return ("%.2f" % size) + ' ' + b; return ("%.2f" % size) + ' ' + b
def is_64bitos(): def is_64bitos():
@ -310,11 +309,11 @@ def GetBootTime():
if sys_time: return sys_time if sys_time: return sys_time
import math import math
tStr = time.time() - psutil.boot_time() tStr = time.time() - psutil.boot_time()
min = tStr / 60; min = tStr / 60
hours = min / 60; hours = min / 60
days = math.floor(hours / 24); days = math.floor(hours / 24)
hours = math.floor(hours - (days * 24)); hours = math.floor(hours - (days * 24))
min = math.floor(min - (days * 60 * 24) - (hours * 60)); min = math.floor(min - (days * 60 * 24) - (hours * 60))
sys_time = "{}".format(int(days)) sys_time = "{}".format(int(days))
cache.set(key, sys_time, 1800) cache.set(key, sys_time, 1800)
return sys_time return sys_time
@ -352,10 +351,10 @@ def GetCpuInfo(interval=1):
arrs = ret.strip().split('\n\n') arrs = ret.strip().split('\n\n')
for x in arrs: for x in arrs:
val = x.strip() val = x.strip()
if not val: continue; if not val: continue
try: try:
val = int(val) val = int(val)
cpuW += 1; cpuW += 1
except: except:
pass pass
@ -365,7 +364,7 @@ def GetCpuInfo(interval=1):
if not cpu_name: if not cpu_name:
try: try:
cpu_name = '{} * {}'.format( cpu_name = '{} * {}'.format(
ReadReg(r'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 'ProcessorNameString').strip(), cpuW); ReadReg(r'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 'ProcessorNameString').strip(), cpuW)
except: except:
cpu_name = '' cpu_name = ''
cache.set('lybbn_cpu_cpu_name', cpu_name, 86400) cache.set('lybbn_cpu_cpu_name', cpu_name, 86400)
@ -388,7 +387,7 @@ def GetDiskInfo():
diskInfo = cache.get(key) diskInfo = cache.get(key)
if diskInfo: return diskInfo if diskInfo: return diskInfo
try: try:
diskIo = psutil.disk_partitions(); diskIo = psutil.disk_partitions()
except: except:
import string import string
diskIo = [] diskIo = []
@ -454,3 +453,27 @@ def RestartServer():
os.system("shutdown /r /f /t 0") os.system("shutdown /r /f /t 0")
except: except:
pass pass
def GetUidName(file_path,uid=0):
"""
通过系统uid获取对应名称
"""
#如果运行GetUidName在file_path所在的分区可能导致文件占用导致获取失败
try:
security_descriptor = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION)
owner_sid = security_descriptor.GetSecurityDescriptorOwner()
owner_name, domain_name, type = win32security.LookupAccountSid(None, owner_sid)
return owner_name
except Exception as e:
return ""
def GetGroupidName(file_path,gid=0):
"""
通过系统goup id所属组id获取对应名称
"""
try:
group_sid = win32security.GetFileSecurity(file_path, win32security.GROUP_SECURITY_INFORMATION).GetSecurityDescriptorGroup()
group_name, domain, type = win32security.LookupAccountSid(None, group_sid)
return group_name
except Exception as e:
return ""

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"email": "1042594286@qq.com", "email": "1042594286@qq.com",
"url": "https://gitee.com/lybbn" "url": "https://gitee.com/lybbn"
}, },
"version": "1.3.6", "version": "1.3.7",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "npm run dev", "start": "npm run dev",
@ -16,10 +16,19 @@
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.5.1", "@codemirror/autocomplete": "^6.5.1",
"@codemirror/lang-css": "^6.1.1", "@codemirror/lang-css": "^6.1.1",
"@codemirror/lang-go": "^6.0.1",
"@codemirror/lang-java": "^6.0.1",
"@codemirror/lang-javascript": "^6.1.6", "@codemirror/lang-javascript": "^6.1.6",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.2",
"@codemirror/lang-php": "^6.0.1",
"@codemirror/lang-python": "^6.1.2", "@codemirror/lang-python": "^6.1.2",
"@codemirror/lang-sql": "^6.8.0",
"@codemirror/lang-vue": "^0.1.1", "@codemirror/lang-vue": "^0.1.1",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/language": "^6.11.0",
"@codemirror/legacy-modes": "^6.5.0",
"@codemirror/theme-one-dark": "^6.1.1", "@codemirror/theme-one-dark": "^6.1.1",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
@ -32,8 +41,8 @@
"core-js": "^3.32.2", "core-js": "^3.32.2",
"cropper": "^4.1.0", "cropper": "^4.1.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"echarts": "^5.4.3", "echarts": "5.6.0",
"element-plus": "^2.7.8", "element-plus": "2.9.6",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
@ -45,25 +54,25 @@
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"tinymce": "5.10.2", "tinymce": "5.10.2",
"vue": "^3.3.4", "vue": "3.5.13",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vue-clipboard3": "^2.0.0", "vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1", "vue-codemirror": "^6.1.1",
"vue-i18n": "^9.2.2", "vue-i18n": "^10.0.4",
"vue-router": "^4.2.5", "vue-router": "^4.4.5",
"xe-utils": "^3.5.13" "xe-utils": "^3.5.31"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.20", "@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.22.15", "@babel/eslint-parser": "^7.25.9",
"@vue/babel-plugin-jsx": "^1.1.5", "@vue/babel-plugin-jsx": "^1.2.5",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-service": "~5.0.8", "@vue/cli-service": "~5.0.8",
"@vue/compiler-sfc": "^3.3.4", "@vue/compiler-sfc": "^3.5.12",
"compression-webpack-plugin": "^10.0.0", "compression-webpack-plugin": "^11.1.0",
"sass": "^1.68.0", "sass": "^1.80.6",
"sass-loader": "^13.3.2", "sass-loader": "^16.0.3",
"svg-sprite-loader": "^6.0.11" "svg-sprite-loader": "^6.0.11"
}, },
"eslintConfig": { "eslintConfig": {

View File

@ -4,18 +4,56 @@
</el-config-provider> </el-config-provider>
</template> </template>
<script setup> <script setup>
import {ref, onMounted,watch,computed } from 'vue' import {ref, onMounted,watch,computed,onBeforeUnmount } from 'vue'
import {useSiteThemeStore} from "@/store/siteTheme"; import {useSiteThemeStore} from "@/store/siteTheme";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const i18n = useI18n(); const i18n = useI18n();
import config from '@/config' import config from '@/config'
import WebSocket from '@/utils/websocket';
import { useMutitabsStore } from "@/store/mutitabs";
import { useRoute } from 'vue-router';
import { ElNotification } from 'element-plus'
const route = useRoute()
const siteThemeStore = useSiteThemeStore() const siteThemeStore = useSiteThemeStore()
const colorPrimary = siteThemeStore.colorPrimary const colorPrimary = siteThemeStore.colorPrimary
const menuHeaderColor = siteThemeStore.menuHeaderColor const menuHeaderColor = siteThemeStore.menuHeaderColor
onMounted(()=>{ const wsReceive = (message) => {
const data = JSON.parse(message.data);
const { unread } = data;
useMutitabsStore().setUnread(unread);
if (data.msg_type === 'SYS') {
ElNotification({
title: '系统消息',
message: data.content,
type: 'success',
position: 'bottom-right',
duration: 5000,
});
}
};
async function initWebSocket() {
WebSocket.initWebSocket(wsReceive)
}
watch( () => route.path, () => {
if (!WebSocket.websocket) {
try {
initWebSocket()
} catch (e) {
console.log('websocket错误');
}
}
},
{
deep: true,
}
);
onMounted(()=>{
siteThemeStore.setColorPrimary(colorPrimary) siteThemeStore.setColorPrimary(colorPrimary)
if (siteThemeStore.siteTheme === 'dark') { if (siteThemeStore.siteTheme === 'dark') {
document.documentElement.classList.add('dark') document.documentElement.classList.add('dark')
@ -35,7 +73,10 @@
"color:#999;font-size: 12px", "color:#999;font-size: 12px",
"color:#333" "color:#333"
) )
onBeforeUnmount(() => {
//
WebSocket.closeWebSocket();
});
</script> </script>
<style lang="scss"> <style lang="scss">
#app { #app {

View File

@ -1,6 +1,4 @@
import axios from 'axios'; import {reqExpost,ajaxGet,ajaxPost,ajaxDelete,ajaxPut,ajaxPatch,uploadImg,ajaxGetDetailByID,ajaxDownloadExcel,uploadFilev2,getDownloadFile,downloadFile} from './request';
import {reqExpost,ajaxGet,ajaxPost,ajaxDelete,ajaxPut,ajaxPatch,uploadImg,ajaxGetDetailByID,ajaxDownloadExcel,uploadFilev2} from './request';
import {url} from './url';
// 获取登录页的信息 // 获取登录页的信息
export const login = params => ajaxPost({url: `token/`,params}) export const login = params => ajaxPost({url: `token/`,params})
@ -257,7 +255,12 @@ export const messagesMessagenoticeEdit = params => ajaxPut({url: `messages/messa
//消息公告-删除 //消息公告-删除
export const messagesMessagenoticeDelete = params => ajaxDelete({url: `messages/messagenotice/`,params}) export const messagesMessagenoticeDelete = params => ajaxDelete({url: `messages/messagenotice/`,params})
//我的消息 列表
export const getOwnMessage = params => ajaxGet({url: `messages/messagenotice/ownmsg/`,params})
//我的消息 删除
export const delOwnMessage = params => ajaxPost({url: `messages/messagenotice/delownmsg/`,params})
//我的消息 设置已读
export const readOwnMessage = params => ajaxPost({url: `messages/messagenotice/readownmsg/`,params})
/** /**
*省市区选择 *省市区选择
@ -487,6 +490,18 @@ export const systemFiles= params => ajaxGet({url: `system/files/`,params})
// 文件管理 新增 // 文件管理 新增
export const systemFilesAdd= (params,config) => uploadFilev2({url: `system/files/`,params,config}) export const systemFilesAdd= (params,config) => uploadFilev2({url: `system/files/`,params,config})
/**
*文件管理
* */
// 文件管理 - 文件管理
export const sysFileManage = params => ajaxPost({url: `system/fileManage/`,params})
// 文件管理 - 文件管理-下载
export const sysdownloadFile = params => getDownloadFile({url: `download/`,params})
export const sysFileDownload = params => downloadFile({url: `system/fileManage/download/`,params})
// 文件管理 - 文件管理-生成token
export const sysFileGetToken = params => ajaxPost({url: `system/fileManage/getToken/`,params})
/** /**
*流程管理 *流程管理
* */ * */

View File

@ -5,11 +5,20 @@ import router from "@/router";
import {setStorage,getStorage,getToken} from '@/utils/util' import {setStorage,getStorage,getToken} from '@/utils/util'
import sysConfig from "@/config" import sysConfig from "@/config"
import {useMutitabsStore} from "@/store/mutitabs"; import {useMutitabsStore} from "@/store/mutitabs";
import { cancelRequestState } from "@/store/cancelRequest";
var request = axios.create({ var request = axios.create({
timeout: sysConfig.TIMEOUT, timeout: sysConfig.TIMEOUT,
}); });
request.interceptors.request.use(config => {
const cancelRequest = cancelRequestState()
config.cancelToken = new axios.CancelToken(cancel => {
cancelRequest.addCancelToken(cancel)
})
return config
})
// http response 拦截器 // http response 拦截器
request.interceptors.response.use( request.interceptors.response.use(
response => { response => {
@ -189,6 +198,26 @@ export function ajaxDownloadExcel (opt) {
return ajax(opt,"excel") return ajax(opt,"excel")
} }
export function getDownloadFile (param) {
const urlParams = new URLSearchParams(param.params);
let urls = url + param.url;
urls += '?' + urlParams.toString()
window.location.href = urls
}
export function downloadFile (param) {
let token= getToken()
return axios({
method: "post",
url: url+param.url,
headers: {
Authorization: 'JWT ' + token,
},
data:JSON.parse(JSON.stringify(param.params)),
responseType: 'blob'
}).then(res => res);
}
//websocket获取jwt请求token //websocket获取jwt请求token
export function getJWTAuthorization() { export function getJWTAuthorization() {
var token= getToken() var token= getToken()
@ -378,3 +407,29 @@ export async function uploadFilev2(param){
let config = param.params.config || {}//额外配置 let config = param.params.config || {}//额外配置
return await http.post(newurl, data, config); return await http.post(newurl, data, config);
} }
export function uploadFile (param,onProgress) {
const token= getToken()
return new Promise((resolve, reject) => {
axios({
method: 'post',
url: url+param.url ,
headers: {
'Content-Type': 'multipart/form-data',
Authorization: sysConfig.TOKEN_PREFIX + token,
},
data:param.formData,
onUploadProgress: (progressEvent) => {
if (onProgress) {
onProgress(progressEvent);
}
},
})
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -3,7 +3,7 @@
*/ */
<template> <template>
<div :class="isBackgroud?'lyPagination-page-bk':'lyPagination-page'"> <div :class="isBackgroud?'lyPagination-page-bk':'lyPagination-page'">
<el-pagination class="page-box" @size-change="handleSizeChange" @current-change="handleCurrentChange" background :size="small?'small':'default'" :current-page="childMsg.page" :page-sizes="pageSizes" :page-size="childMsg.limit" :layout="layout" :total="childMsg.total"></el-pagination> <el-pagination class="page-box" :class="'page-box-'+position" @size-change="handleSizeChange" @current-change="handleCurrentChange" background :size="small?'small':'default'" :current-page="childMsg.page" :page-sizes="pageSizes" :page-size="childMsg.limit" :layout="layout" :total="childMsg.total"></el-pagination>
</div> </div>
</template> </template>
<script setup> <script setup>
@ -18,15 +18,21 @@
return siteThemeStore.pagingLayout return siteThemeStore.pagingLayout
}) })
const isBackgroud = computed(()=>{
return pagingLayout.value == 'backgroud'?true:false
})
const props = defineProps({ const props = defineProps({
childMsg: { type: Object, default: () => {} }, childMsg: { type: Object, default: () => {} },
pageSizes: { type: Array, default: [10,20,30,40,50,100] }, pageSizes: { type: Array, default: [10,20,30,40,50,100] },
layout: { type: String, default: "total, sizes, prev, pager, next, jumper" }, layout: { type: String, default: "total, sizes, prev, pager, next, jumper" },
small: {type:Boolean, default:false} small: {type:Boolean, default:false},
position:{type:String, default:"center"},
border: {type:Boolean, default:true},
})
const isBackgroud = computed(()=>{
if(props.border){
return pagingLayout.value == 'backgroud'?true:false
}else{
return true
}
}) })
let pageparm = ref({ let pageparm = ref({
@ -70,4 +76,13 @@
} }
} }
} }
.page-box-center {
margin: 10px auto;
}
.page-box-left {
margin: 10px 0 auto;
}
.page-box-right {
margin: 10px 10px 10px auto;
}
</style> </style>

View File

@ -0,0 +1,430 @@
<!--
/**
* 代码编辑器
* version: 1.1
* author: lybbn
* program 如意面板
* email: 1042594286@qq.com
* date: 2024-02-08
* EditDate: 2024-03-08
*/
-->
<template>
<codemirror
ref="lyCodemirrorRef"
class="lyCodemirror"
:class="!showLineNums?'lyCodemirrorDynamic':''"
v-model="code"
:placeholder="placeholder"
:style="{ height: _height,fontSize:fontSize}"
:autofocus="true"
:indent-with-tab="true"
:tab-size="4"
:extensions="extensions"
@ready="handleReady"
/>
</template>
<script setup>
import {ref,reactive, onMounted,computed,watch, shallowRef,nextTick,onBeforeUnmount} from 'vue'
import { Codemirror } from 'vue-codemirror'
import { EditorState,Compartment } from "@codemirror/state";
import { basicSetup,minimalSetup } from "codemirror";
import { defaultKeymap,standardKeymap, insertTab } from "@codemirror/commands";
import { EditorView,keymap, drawSelection,highlightSpecialChars,highlightWhitespace,highlightActiveLine,dropCursor,highlightActiveLineGutter,rectangularSelection,crosshairCursor } from "@codemirror/view";
import { syntaxHighlighting,StreamLanguage } from "@codemirror/language";
import { autocompletion } from "@codemirror/autocomplete";
// import { historyKeymap } from "@codemirror/history";
import { ruby } from "@codemirror/legacy-modes/mode/ruby"; // Ruby
import { shell } from "@codemirror/legacy-modes/mode/shell"; // Bash
import { nginx } from "@codemirror/legacy-modes/mode/nginx";
import { lua } from "@codemirror/legacy-modes/mode/lua";
import { properties } from "@codemirror/legacy-modes/mode/properties";
import { oneDark } from "@codemirror/theme-one-dark";
import { javascript } from "@codemirror/lang-javascript";
import { json } from "@codemirror/lang-json";
import { css } from "@codemirror/lang-css";
import { python } from "@codemirror/lang-python";
import { vue } from "@codemirror/lang-vue";
import { yaml } from "@codemirror/lang-yaml";
import { php } from "@codemirror/lang-php";
import { java } from "@codemirror/lang-java";
import { go } from "@codemirror/lang-go";
import { html } from "@codemirror/lang-html";
import { sql } from "@codemirror/lang-sql";
import { markdown } from "@codemirror/lang-markdown";
import { xml } from "@codemirror/lang-xml";
const emit = defineEmits(["update:modelValue","change","scrollBottomChange"])
const props = defineProps({
modelValue: {
type: String,
default: ""
},
mode: {
type: String,
default: "javascript",//mode=log
},
height: {
type: [String,Number],
default: "auto",
},
placeholder: {
type: String,
default:""
},
theme: {
type: String,
default: "dark"
},
readOnly: {
type: Boolean,
default: false
},
bottom:{
type: Boolean,
default: false
},
//
lineWrapping:{
type: Boolean,
default: false
},
//
showLineNums:{
type: Boolean,
default: true
},
fontSize:{
type: String,
default: "14px"
}
})
let lyCodemirrorRef = ref(null)
let _height = computed(() => {
return Number(props.height)?Number(props.height)+'px':props.height
})
//codemodelValue
const code = computed({
get() {
return props.modelValue
},
set(value) {
emit('change', value)
emit('update:modelValue', value)
}
})
//
function moveToLine(view,line) {
if(view == null || view.state == null){
return false
}
if (!/^\d+$/.test(line) || +line <= 0 || +line > view.state.doc.lines){
return false
}
let pos = view.state.doc.line(+line).from
view.dispatch({selection: {anchor: pos}, userEvent: "select",scrollIntoView:true})
return true
}
let view = ref(null)
const handleReady = (payload) => {
view.value = payload.view
// const state = view.value.state
// const ranges = state.selection.ranges
// const selected = ranges.reduce((r, range) => r + range.to - range.from, 0)
// const cursor = ranges[0].anchor
// const length = state.doc.length
// const lines = state.doc.lines
view.value.contentDOM.addEventListener('mousedown', () => {
const selectionChangeHandler = () => {
if(props.mode == 'log'){
return
}
const hasSelection = !view.value.state.selection.main.empty;
const cmHighlightSpaceElements = view.value.dom.querySelectorAll('.cm-highlightSpace');
cmHighlightSpaceElements.forEach((element) => {
if (hasSelection) {
element.classList.add('show-ryspace');
} else {
element.classList.remove('show-ryspace');
}
});
const cmHighlightTabElements = view.value.dom.querySelectorAll('.cm-highlightTab');
cmHighlightTabElements.forEach((element) => {
if (hasSelection) {
element.classList.add('show-rytab');
} else {
element.classList.remove('show-rytab');
}
});
};
const mouseupHandler = () => {
view.value.contentDOM.removeEventListener('selectionchange', selectionChangeHandler);
view.value.contentDOM.removeEventListener('mouseup', mouseupHandler);
//
selectionChangeHandler();
};
view.value.contentDOM.addEventListener('selectionchange', selectionChangeHandler);
view.value.contentDOM.addEventListener('mouseup', mouseupHandler);
});
}
//
const getDocLines = ()=>{
if(!!view.value){
return view.value.state.doc.lines
}
return 0
}
let allLines = ref(getDocLines())
let languageConf = new Compartment, tabSize = new Compartment
let lang = computed(() => {
const langMap = {
javascript: javascript,
json: json,
css: css,
python: python,
vue: vue,
php: php,
java: java,
go: go,
yaml: yaml,
xml: xml,
markdown: markdown,
html: html,
sql: sql,
shell: () => StreamLanguage.define(shell),
ruby: () => StreamLanguage.define(ruby),
nginx: () => StreamLanguage.define(nginx),
lua: () => StreamLanguage.define(lua),
properties: () => StreamLanguage.define(properties),
}
return langMap[props.mode]?.() || null
})
//
function myCompletions(context) {
let word = context.matchBefore(/\w*/)
if (word.from == word.to && !context.explicit) return null;
return {
from: word.from,
options: [
// { label: "getWidgetRef", type: "variable" },
],
};
}
let isReadOnly = computed(() => {
return EditorState.readOnly.of(props.readOnly)
})
let extensions = shallowRef([
// basicSetup,
// keymap.of([...standardKeymap, {key: "Tab", run: insertTab}]),
// languageConf.of(lang.value),
oneDark,
// autocompletion({ override: [myCompletions] }),
isReadOnly.value,
// EditorState.readOnly.of(props.readOnly),
// EditorView.editable.of(!props.readOnly),
// placeholder(props.placeholder),
// EditorView.lineWrapping,
drawSelection(),
highlightSpecialChars(),
highlightWhitespace({
space: '·',
tab: '→'
}),
highlightActiveLine(), //
// dropCursor(),
highlightActiveLineGutter(),
rectangularSelection(),
crosshairCursor()
])
function reloadConfig(){
const newConfig = [
oneDark,
isReadOnly.value,
drawSelection(),
highlightSpecialChars(),
highlightWhitespace({
space: '·',
tab: '→'
}),
highlightActiveLine(), //
// dropCursor(),
highlightActiveLineGutter(),
rectangularSelection(),
crosshairCursor()
]
if(!!lang.value && lang.value !== null){
newConfig.push(lang.value)
}
if(props.lineWrapping){
newConfig.push(EditorView.lineWrapping)
}
extensions.value = newConfig
}
function debounce(func, wait = 300) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
watch(props,
debounce((newProps, oldProps) => { //
reloadConfig()
}, 300),
{
deep: true,
flush: 'post' // flush
}
);
// watch(()=>props.mode,(nval)=>{
// if(!!lang.value && lang.value !== null){
// if (!extensions.value.some(ext => ext === lang.value)) {
// extensions.value.push(lang.value);
// //
// const temp = [...extensions.value];
// extensions.value = [];
// extensions.value = temp;
// }
// }
// },{deep:true})
let scrollDOM = ref(null)
let isAtBottom = ref(false)
onMounted(()=>{
if(props.bottom && !!view.value){
nextTick(()=>{
moveToLine(view.value,getDocLines())
// view.value.focus()
})
}
if(!!lang.value && lang.value !== null){
if (!extensions.value.some(ext => ext === lang.value)) {
extensions.value.push(lang.value);
//
const temp = [...extensions.value];
extensions.value = [];
extensions.value = temp;
}
}
if(props.lineWrapping){
extensions.value.push(EditorView.lineWrapping)
}
if(!!view.value){
scrollDOM.value = view.value.scrollDOM
//
scrollDOM.value.addEventListener('scroll', handleDomScroll);
}
})
function handleDomScroll(e) {
// 使requestAnimationFrame
requestAnimationFrame(() => {
const { scrollHeight, scrollTop, clientHeight } = scrollDOM.value
const isBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 1
if(isAtBottom.value !== isBottom) {
isAtBottom.value = isBottom
emit("scrollBottomChange", isBottom)
}
})
}
function isScrollAtBottom(){
return isAtBottom.value
}
function scrollToBottom(){
if(!!view.value){
moveToLine(view.value,getDocLines())
// view.value.focus()
}
}
watch(()=>code.value,newValue=>{
if(props.bottom && !!view.value){
nextTick(()=>{
moveToLine(view.value,getDocLines())
// view.value.focus()
})
}
})
onBeforeUnmount(() => {
//
if (scrollDOM.value) {
scrollDOM.value.removeEventListener('scroll', handleDomScroll)
}
//
view.value = null
scrollDOM.value = null
languageConf = null
tabSize = null
});
defineExpose({
scrollToBottom,
reloadConfig,
isScrollAtBottom
})
</script>
<style>
.cm-content {
/* font-family: 'Consolas', 'Monaco', 'monospace'; */
font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace;
/* font: 14px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; */
}
.ͼo {
color: #edede1;
background-color: #272821;
}
.ͼo .cm-gutters {
background-color: #2F3128;
color: #8F908A;
border: none;
}
/* .ͼ1 .cm-highlightSpace{
background-image: radial-gradient(circle at 50% 55%, #666 10%, transparent 8%);
background-size: 12px 20px;
} */
.ͼ1 .cm-highlightSpace {
/* 默认不显示空格符号 */
background-image: none;
}
.ͼ1 .cm-highlightTab {
/* 默认不显示Tab符号 */
background-image: none;
}
/* 当编辑器聚焦时,选中代码显示空格符号 */
.ͼ1 .cm-highlightSpace.show-ryspace {
background-image: radial-gradient(circle at 50% 55%, #666 10%, transparent 8%);
background-size: 12px 20px;
}
.ͼ1 .cm-highlightTab.show-rytab {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='20'%3E%3Cpath stroke='%23888' stroke-width='1' fill='none' d='M1 10H196L190 5M190 15L196 10M197 4L197 16'/%3E%3C/svg%3E");
}
</style>
<style scoped>
.lyCodemirrorDynamic:deep(.cm-gutters) {
display: none;
}
</style>

View File

@ -0,0 +1,198 @@
<!--
* @Descripttion: 弹窗扩展组件
* @version: 1.0
* @program: 如意面板
* @Author: lybbn
* @Email1042594286@qq.com
* @Date: 2024.01.11
* @EditDate: 2024.01.11
-->
<template>
<div class="ly-dialog">
<el-dialog
v-model="visible"
:close-on-click-modal="closeOnClickModal"
:title="title"
:width="width"
:top="top"
:fullscreen="screeFull"
:center="center"
:before-close="beforeClose"
:append-to-body="appendToBody"
:destroy-on-close="true"
:draggable="draggable"
:show-close="false"
@closed="closed"
ref="lyDialogRef"
>
<template #header="{ close, titleId, titleClass }">
<div>
<slot name="header">
<div :id="titleId" :class="titleClass" style="white-space: nowrap;overflow: hidden;text-overflow: ellipsis;width: 90%;">{{ title }}</div>
</slot>
<div class="ly-dialog__headerbtn">
<button aria-label="fullscreen" type="button" @click="handleFullScreenClick" v-if="showFullScreen">
<el-icon v-if="screeFull" class="el-dialog__close"><Minus /></el-icon>
<el-icon v-else class="el-dialog__close"><full-screen /></el-icon>
</button>
<button aria-label="close" type="button" @click="close" v-if="showClose">
<el-icon class="el-dialog__close"><close /></el-icon>
</button>
</div>
</div>
</template>
<div v-loading="loading" style="height: 100%;">
<slot></slot>
</div>
<template v-if="$slots.footer" #footer>
<slot name="footer"></slot>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref,watch,onMounted } from 'vue';
import 'element-plus/es/components/dialog/style/css'
const emits = defineEmits(['closed','onChangeFullScreen'])
let lyDialogRef = ref(null)
let visible = ref(false)
let screeFull = ref(false)
const props = defineProps({
title: {
type: String,
default: ''
},
modelValue: {
type: Boolean,
default: true
},
width: {
type: String,
default: '50%'
},
center: {
type: Boolean,
default: false
},
top: {
type: String,
default: '10vh'
},
draggable: {
type: Boolean,
default: true
},
appendToBody: {
type: Boolean,
default: false
},
closeOnClickModal: {
type: Boolean,
default: false
},
fullscreen: {
type: Boolean,
default: false
},
showFullScreen: {
type: Boolean,
default: true
},
showClose: {
type: Boolean,
default: true
},
loading: {
type: Boolean,
default: false
},
beforeClose:Function//
});
function openDialog() {
visible.value = true
}
function closeDialog() {
visible.value = false
}
function closed() {
emits('closed')
}
function handleFullScreenClick(){
screeFull.value = !screeFull.value
emits('onChangeFullScreen',screeFull.value)
}
function getRef(){
return lyDialogRef.value
}
onMounted(()=>{
screeFull.value = props.fullscreen
visible.value = props.modelValue
emits('onChangeFullScreen',screeFull.value)
})
watch(()=>props.modelValue,(nval)=>{
visible.value = nval; // modelValuevisible
},{deep:true})
watch(()=>props.fullscreen,(nval)=>{
screeFull.value = nval
},{deep:true})
defineExpose({
getRef
})
</script>
<style scoped>
.ly-dialog__headerbtn {
position: absolute;
top: var(--el-dialog-padding-primary);
right: var(--el-dialog-padding-primary);
}
.ly-dialog__headerbtn button {
padding: 0;
background: transparent;
border: none;
outline: none;
cursor: pointer;
font-size: var(--el-message-close-size,16px);
margin-left: 15px;
color: var(--el-color-info);
}
.ly-dialog__headerbtn button:hover .el-dialog__close {
color: var(--el-color-primary);
}
.ly-dialog:deep(.el-dialog) .el-dialog__body {
padding: 20px;
}
.ly-dialog:deep(.el-dialog).is-fullscreen {
display: flex;
flex-direction: column;
top:0px !important;
left:0px !important;
padding: 0;
}
.ly-dialog:deep(.el-dialog).is-fullscreen .el-dialog__header {
border-bottom:var(--el-border);
margin-right:0 !important;
padding: 16px;
}
.ly-dialog:deep(.el-dialog).is-fullscreen .el-dialog__body {
flex:1;
overflow: auto;
}
.ly-dialog:deep(.el-dialog).is-fullscreen .el-dialog__footer {
padding-bottom: 10px;
border-top:var(--el-border);
padding: 16px;
}
</style>

View File

@ -0,0 +1,279 @@
<!--
* @Descripttion: 在线文件编辑器
* @version: 1.0
* @program: 如意面板
* @Author: lybbn
* @Email1042594286@qq.com
* @Date: 2024.03.03
* @EditDate: 2024.03.03
-->
<template>
<LyDialog ref="codeMirrorDialogRef" :loading="loadingPage" v-model="dialogVisible" :title="loadingTitle" :width="width" :top="top" :before-close="handleClose" :fullscreen="fullscreen" @onChangeFullScreen="handleChangeFullScreen">
<div class="handlecaoz" ref="handlecaozRef">
<el-button :disabled="loadingPage" size="small" type="info" color="#2F3128" dark icon="Refresh" @click="getData">刷新</el-button>
<el-button :disabled="loadingPage||codeMirrorReadOnly" size="small" type="info" color="#2F3128" dark @click="handleSave(false)">
<template #icon>
<el-icon v-if="saveIcon=='InfoFilled'" style="color: var(--el-color-warning);"><InfoFilled /></el-icon>
<el-icon v-else><FolderChecked /></el-icon>
</template>
保存
</el-button>
</div>
<lyCodeEditor @keydown="handleKeydown" v-model="content" @change="handleContentChange" fontSize="15px" :placeholder="!!content?'':''" :showLineNums="true" :lineWrapping="true" :mode="codeMirrorMode" :height="codeMirrorHeight" :read-only="codeMirrorReadOnly" ref="lyCodemirror"></lyCodeEditor>
<div class="handlestausbar" ref="handlestausbarRef">
文件位置{{ fileState.path }}
</div>
</LyDialog>
</template>
<script setup>
import { nextTick, ref,onMounted,onUnmounted } from 'vue';
import LyDialog from "@/components/dialog/dialogv2.vue";
import lyCodeEditor from '@/components/codeEditor/index.vue'
import { ElMessage,ElMessageBox } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['closed'])
const props = defineProps({
apiObj: { type: Function, default: null },
successCode: { type: Number, default: 2000 },//
fullscreen: { type: Boolean, default: false },
width: { type: String, default: '60%' },//dialog
top: { type: String, default: '10vh' },
})
let codeMirrorDialogRef = ref(null)
let codeMirrorHeight = ref("55vh")
let codeMirrorMode = ref("log")
let codeMirrorReadOnly = ref(false)
let lyCodemirror = ref(null)
let handlecaozRef = ref(null)
let handlestausbarRef = ref(null)
let content = ref("")
let oldContent = ref("")
let saveIcon = ref("FolderChecked")
let dialogVisible = ref(false)
let loadingTitle = ref("")
let loadingPage = ref(false)
let fileState = ref({
name:"",
path:"",
st_mtime:"",
})
let isFullScress = ref(false)
function handleClose() {
if(oldContent.value != content.value){
ElMessageBox.confirm('检测到文件变动,是否保存更改?', '提示', {
confirmButtonText: '保存',
cancelButtonText: '不保存',
type: 'warning'
}).then(() => {
handleSave(true)
})
.catch(() => {
emits('closed')
})
}else{
emits('closed')
}
}
function handleOpen(params,flag){
loadingTitle.value = flag
dialogVisible.value = true
const tempdata = deepClone(params)
fileState.value.name = tempdata.name
fileState.value.path = tempdata.path
nextTick(()=>{
getData()
})
}
function handleContentChange(value){
if(value == oldContent.value){
saveIcon.value = "FolderChecked"
}else{
saveIcon.value = "InfoFilled"
}
}
function handleChangeFullScreen(isfull){
isFullScress.value = isfull
nextTick(()=>{
if(codeMirrorDialogRef.value){
const dialogRef = codeMirrorDialogRef.value.getRef()
if(dialogRef.dialogContentRef){
const dialogHeaderRef = dialogRef.dialogContentRef.$refs.headerRef
const dialogHeaderHeight = dialogHeaderRef.offsetHeight
const contentHeight = document.body.offsetHeight
if(isfull){
codeMirrorHeight.value = contentHeight - dialogHeaderHeight - handlecaozRef.value.offsetHeight - handlestausbarRef.value.offsetHeight
}else{
codeMirrorHeight.value = "55vh"
}
}
}
})
}
function formatJsonBody(value){
try {
return JSON.stringify(JSON.parse(value),null,4)
} catch (err) {
return value
}
}
function getData(){
loadingPage.value = true;
var reqData = {path:fileState.value.path,action:"read_file_body"}
if (!!props.apiObj && props.apiObj != {}) {
props.apiObj(reqData).then(res => {
loadingPage.value = false;
if (res.code == props.successCode) {
codeMirrorMode.value = res.data.language
if(codeMirrorMode.value == "json"){
content.value = formatJsonBody(res.data.content)
}else{
content.value = res.data.content
}
oldContent.value = content.value
fileState.st_mtime = res.data.st_mtime
} else {
ElMessage.warning(res.msg)
codeMirrorReadOnly.value = true
}
})
}else{
loadingPage.value = false;
}
}
function handleSave(closeDialog=false){
loadingPage.value = true;
var reqData = {path:fileState.value.path,content:content.value,action:"save_file_body",st_mtime:fileState.value.st_mtime,force:false}
if (!!props.apiObj && props.apiObj != {}) {
props.apiObj(reqData).then(res => {
loadingPage.value = false;
if (res.code == props.successCode) {
oldContent.value = content.value
ElMessage.success("保存成功")
fileState.st_mtime = res.data.st_mtime
saveIcon.value = "FolderChecked"
if(closeDialog){
emits('closed')
}
}else if(res.code == 4050){
ElMessageBox.confirm(`在线文件可能发生变动,与当前版本不符,是否强制保存`, '提醒', {
closeOnClickModal: false,
cancelButtonText:"不保存",
confirmButtonText:"强制保存",
type:"warning"
}).then(ret => {
loadingPage.value = true;
let param = {path:fileState.value.path,content:content.value,action:"save_file_body",st_mtime:fileState.value.st_mtime,force:true}
props.apiObj(param).then(res2 => {
loadingPage.value = false;
if(res2.code == props.successCode){
oldContent.value = content.value
ElMessage.success("保存成功")
fileState.st_mtime = res2.data.st_mtime
saveIcon.value = "FolderChecked"
if(closeDialog){
emits('closed')
}
}else{
ElMessage.warning(res2.msg)
}
})
}).catch(() => {
})
}
else {
ElMessage.warning(res.msg)
}
})
}else{
loadingPage.value = false;
}
}
//
function listenResize() {
nextTick(() => {
if(codeMirrorDialogRef.value){
const dialogRef = codeMirrorDialogRef.value.getRef()
if(dialogRef.dialogContentRef){
const dialogHeaderRef = dialogRef.dialogContentRef.$refs.headerRef
const dialogHeaderHeight = dialogHeaderRef.offsetHeight
const contentHeight = document.body.offsetHeight
if(isFullScress.value){
codeMirrorHeight.value = contentHeight - dialogHeaderHeight - handlecaozRef.value.offsetHeight - handlestausbarRef.value.offsetHeight
}else{
codeMirrorHeight.value = "55vh"
}
}
}
})
}
function handleKeydown(event){
if (event.ctrlKey && event.key.toLowerCase() === 's') {
event.preventDefault();
if(oldContent.value == content.value){
ElMessage.warning("检测到文件未变动,无需保存")
}else{
handleSave(false)
}
}
}
onMounted(()=>{
//
window.addEventListener('resize', listenResize);
listenResize()
})
onUnmounted(()=>{
//
window.removeEventListener("resize", listenResize);
})
defineExpose({
handleOpen
})
</script>
<style scoped>
:deep(.el-dialog) .el-dialog__body {
padding: 0 !important;
}
:deep(.el-dialog){
padding: 0 !important;
}
:deep(.el-dialog) .el-dialog__header{
padding: 15px !important;
}
.handlecaoz{
background: #2F3128;
}
.handlecaoz:deep(.el-button){
border-radius: 0;
border-right-color:#272821;
}
.el-button + .el-button {
margin-left: 0px;
}
.handlestausbar{
background: #2F3128;
overflow-x: auto;
height:25px;
line-height: 25px;
}
</style>

View File

@ -0,0 +1,405 @@
<!--
* @Descripttion: 目录选择组件
* @version: 1.0
* @program: 如意面板
* @Author: lybbn
* @Email1042594286@qq.com
* @Date: 2024.03.02
* @EditDate: 2024.03.02
-->
<template>
<div class="lyfileselect">
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="680px" :before-close="handleClose" :showFullScreen="false" :draggable="false">
<div ref="tableSelect">
<el-row class="handle-header" >
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="handle-path-filter">
<div>
<el-button icon="Refresh" @click="handleSearch" size="small"/>
</div>
<div class="handle-path-filter-right">
<span class="handle-path-filter-right-inner">
<span>
<el-link @click.stop="jumpRootClickPath">
<el-icon :size="20"><HomeFilled /></el-icon>
</el-link>
</span>
<span v-for="path in paths" :key="path.url" class="flex-center">
<span class="right-arrow" v-if="!!path.url">></span>
<el-link @click.stop="jumpClickPath(path.url)">
{{ path.name }}
</el-link>
</span>
</span>
</div>
</el-col>
</el-row>
</div>
<el-table ref="ruyFileTtableRef" :header-cell-class-name="cellselectionclass" v-loading="loadingPage" :data="tableData" height="40vh" style="width: 100%;border-top: 1px solid var(--el-border-color-lighter);" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30"></el-table-column>
<el-table-column prop="name" label="名称" min-width="200" show-overflow-tooltip>
<template #default="scope">
<div class="flex-center">
<img class="ruyi-fileicons" v-if="scope.row.type == 'dir'" src="@/assets/img/fileicons/folder.png" />
<img class="ruyi-fileicons" v-else-if="scope.row.type == 'file' && getFileExt(scope.row.path) === 'py'" src="@/assets/img/fileicons/python.png" />
<img class="ruyi-fileicons" v-else-if="scope.row.type == 'file' && getFileExt(scope.row.path) === 'php'" src="@/assets/img/fileicons/php.png" />
<img class="ruyi-fileicons" v-else-if="scope.row.type == 'file' && isImage(scope.row.path)" src="@/assets/img/fileicons/image.png" />
<img class="ruyi-fileicons" v-else-if="scope.row.type == 'file' && isVideo(scope.row.path)" src="@/assets/img/fileicons/video.png" />
<el-icon :size="20" v-else-if="scope.row.type == 'pan'"><MessageBox /></el-icon>
<img class="ruyi-fileicons" v-else src="@/assets/img/fileicons/file.png" />
<!-- <el-icon :size="20" v-if="scope.row.type == 'dir'"><Folder /></el-icon>
<el-icon :size="20" v-else-if="scope.row.type == 'file'"><Document /></el-icon>
<el-icon :size="20" v-else-if="scope.row.type == 'pan'"><MessageBox /></el-icon> -->
<el-link type="primary" @click.stop="nameJumpClick(scope.row)" style="margin-left: 5px;">{{ scope.row.name }}</el-link>
</div>
</template>
</el-table-column>
<el-table-column label="权限" width="70" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.permissions">{{ scope.row.permissions}}</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="用户" width="130" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.owner">{{scope.row.owner}}</span>
<span v-else>失败</span>
</template>
</el-table-column>
<el-table-column prop="modified" label="修改时间" width="170" />
</el-table>
<Pagination :small="true" v-bind:child-msg="pageparm" @callFather="callFather" :border="false" position="center"></Pagination>
<template #footer>
<div style="float: left;font-size: 14px;">请勾选复选框</div>
<el-button @click="handleClose" :loading="loadingSave">关闭</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave" :disabled="((onlyFileSelect && !selectIsFile))">选择</el-button>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { ref,computed } from 'vue';
import {sysFileGetToken,sysFileDownload,sysdownloadFile,sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import Pagination from "@/components/Pagination.vue";
import { ElMessage } from 'element-plus'
import { deepClone,downloadFileContent,formatUnitSize,canEditOnline } from "@/utils/util"
import { usePathSearchStatus } from '@/views/systemManage/files/hooks/pathSearchStatus';
import {useSiteThemeStore} from "@/store/siteTheme";
const emits = defineEmits(['change','closed'])
const props = defineProps({
//
multiple:{
type: Boolean,
default: false
},
//
onlyFileSelect:{
type: Boolean,
default: false
},
})
const siteThemeStore = useSiteThemeStore()
const ismobile = computed(() => {
return siteThemeStore.ismobile
})
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingPage = ref(false)
let loadingTitle = ref('')
let isWindows = ref(false)
let tableData = ref([])
let file_nums = ref(0)
let dir_nums = ref(0)
let paths = ref([])
const { pathSearchFilterStatus, searchPath, searchPathInputRef, searchPathInputBlur } = usePathSearchStatus(paths);
let formInline = ref({
path:"default",
sort:"name",
order:"asc",
search:"",
isDir:false,//
containSub:false,
page:1,
limit:100
})
let pageparm = ref({
page: 1,
limit: 100,
total: 0
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
const tempdata = deepClone(item)
formInline.value.path = tempdata.path
formInline.value.isDir = tempdata.isDir === undefined?false:tempdata.isDir
getData("list_dir")
}
}
let ruyFileTtableRef = ref(null)
let mutipleSelect = ref([])
function handleSelectionChange(selection){
mutipleSelect.value = [];
if (selection.length > 1) {
//
selection.shift();
//
ruyFileTtableRef.value.clearSelection();
//
ruyFileTtableRef.value.toggleRowSelection(selection[0]);
}
mutipleSelect.value = selection
}
let selectIsFile = computed(() => {
let isFile = true
mutipleSelect.value.forEach(ele=>{
if(ele.type != "file"){
isFile = false
}
})
if(mutipleSelect.value.length<1){
return false
}
return isFile
})
function callFather(parm) {
formInline.value.page = parm.page
formInline.value.limit = parm.limit
getData("list_dir")
}
function getData(action="list_dir"){
loadingPage.value = true;
let tempParams = {
...formInline.value
}
tempParams.action = action
sysFileManage(tempParams).then(res => {
loadingPage.value = false;
if (res.code == 2000) {
tableData.value = res.data.data.data
paths.value = res.data.data.paths
file_nums.value = res.data.data.file_nums
dir_nums.value = res.data.data.dir_nums
pageparm.value.page = res.data.page
pageparm.value.limit = res.data.limit
pageparm.value.total = res.data.total
isWindows.value = res.data.data.is_windows
formInline.value.path = res.data.data.path
} else {
ElMessage.warning(res.msg)
}
})
}
function jumpClickPath(path){
formInline.value.sort = "name"
formInline.value.order = "asc"
formInline.value.path = path
getData("list_dir")
}
function jumpRootClickPath(){
formInline.value.sort = "name"
formInline.value.order = "asc"
formInline.value.path = ""
getData("list_dir")
}
function nameJumpClick(row){
if(row.type == 'dir' || row.type == 'pan'){
jumpClickPath(row.path)
}else if(row.type == 'file'){
//
if(isImage(row.name)){
sysFileDownload({filename:row.path}).then(res=>{
if(res.headers['content-type'] == 'application/json'){
const reader = new FileReader();
reader.readAsText(res.data) // ,
reader.onload = () => {
const jsonData = JSON.parse(reader.result);
ElMessage.warning(jsonData.msg)
};
}else{
const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
imageViewerUrlList.value = [downloadUrl]
showImageViewer.value = true
}
})
}
//
else if(isVideo(row.name)){
isVideoDialogShow.value = true
nextTick(()=>{
moduleLyVideoFlag.value.handleOpen(row.path,'视频播放')
})
}
//
else{
if(canEditOnline(row.name)){
}else{
ElMessage.warning("该文件不支持在线编辑")
return
}
}
}
}
function getFileExt(filename){
const fileExtension = filename.split('.').pop(); //
const ext = fileExtension == filename?"":fileExtension
return ext.toLowerCase()
}
function isImage(filename){
let ext = getFileExt(filename)
if(ext === 'png' || ext === 'jpg' || ext === 'jpeg' || ext === 'gif' || ext === 'webp'){
return true
}
return false
}
function isVideo(filename){
let ext = getFileExt(filename)
if(ext === 'mp4' || ext === 'flv' || ext === 'm4a' || ext === 'avi'){
return true
}
return false
}
function isMediaFile(row){
if(row.type == 'file'){
if(isImage(row.path)){
return true
}else if(isVideo(row.path)){
return true
}
}
return false
}
function handleBack(){
let tempPaths = deepClone(paths.value)
tempPaths.unshift({name:"",url:""})
let path = ""
let tempLength = tempPaths.length - 2>=0 ?tempPaths.length - 2 :0
path = tempPaths[tempLength].url;
jumpClickPath(path);
}
function handleSearch(){
formInline.value.page = 1
formInline.value.limit = 100
getData("list_dir")
}
function cellselectionclass({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
return "chooseAllbtn"
}
}
function submitData(){
if(mutipleSelect.value.length>0){
//
emits('change',mutipleSelect.value[0].path)
handleClose()
}else{
if(isWindows && formInline.value.path === ""){
ElMessage.warning("请选择具体目录或磁盘")
return
}
emits('change',formInline.value.path)
handleClose()
}
}
defineExpose({
handleOpen
})
</script>
<style scoped>
.lyfileselect:deep(.el-dialog__body){
padding: 0 !important;
}
.handle-header{
margin-bottom: 15px;
}
.handle-path-filter{
display: flex;
margin-bottom: 10px;
align-items: baseline;
}
.handle-path-filter-left{
padding-right: 1rem;
}
.handle-path-filter-right{
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 1rem;
padding-right: 1rem;
background-color: rgb(var(--el-color-white) / 1);
display: flex;
flex-grow: 1;
align-items: center;
overflow-x: auto;
}
.handle-path-filter-right-inner{
display: flex;
align-items: center;
white-space: nowrap;
}
.handle-path-filter-right-2{
flex-grow: 1;
}
.el-input:deep(.el-input-group__append){
color: var(--el-color-white);
background-color: var(--el-color-primary) !important;
box-shadow:unset;
}
.el-input:deep(.el-input-group__append):hover{
background-color: var(--el-color-primary-light-1) !important;
}
.search-sites-input{
display: flex;
align-items: center;
}
.handel-button-groups{
display: flex;
align-items: center;
}
.flex-center{
display: flex;
align-items: center;
}
.right-arrow{
margin-left: 6px;
margin-right: 6px;
font-size: 16px;
}
.ruyi-fileicons{
width: 21px;
height: 21px;
}
:deep(.chooseAllbtn) .cell {
visibility: hidden;
}
</style>

View File

@ -11,7 +11,7 @@
*/ */
--> -->
<template> <template>
<el-icon v-if="isEleIcon" :style="style"> <el-icon v-if="isEleIcon" :style="style" class="lyadminfixlag">
<component <component
v-if="iconName" v-if="iconName"
:is="iconName" :is="iconName"
@ -73,6 +73,11 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.lyadminfixlag {
/*transform: translateZ(0);
will-change: transform;*/
font-size: 1.3em !important;
}
.svg-icon-lyicon{ .svg-icon-lyicon{
height: 1em; height: 1em;
width: 1em; width: 1em;

View File

@ -9,12 +9,9 @@
@tab-remove="removeTab" @tab-remove="removeTab"
@tab-click="tabClick($event)" @tab-click="tabClick($event)"
@contextmenu.prevent.native="openContextMenu($event)"> @contextmenu.prevent.native="openContextMenu($event)">
<el-tab-pane <template v-for="(item,index) in editableTabs" :key="index">
:key="item.name" <el-tab-pane :label="item.title" :name="item.name" v-if="index<100"></el-tab-pane>
v-for="item in editableTabs" </template>
:label="item.title"
:name="item.name">
</el-tab-pane>
</el-tabs> </el-tabs>
<transition name="el-zoom-in-top"> <transition name="el-zoom-in-top">
<ul v-show="contextMenuVisible" :style="{left:left+'px',top:top+'px'}" class="contextmenu" id="lycontextmenu"> <ul v-show="contextMenuVisible" :style="{left:left+'px',top:top+'px'}" class="contextmenu" id="lycontextmenu">
@ -246,6 +243,7 @@
relogin()// relogin()//
} }
mutitabsstore.tabsPage = JSON.parse(lytabsPage); mutitabsstore.tabsPage = JSON.parse(lytabsPage);
// mutitabsstore.tabsPage = Object.freeze(JSON.parse(lytabsPage))
const currentRouteName = route.name const currentRouteName = route.name
const currentRouteQuery = route.query const currentRouteQuery = route.query
if(currentRouteName == 'login' || currentRouteName == 'root'){ if(currentRouteName == 'login' || currentRouteName == 'root'){

View File

@ -213,7 +213,6 @@
window.removeEventListener("resize", handleResize); window.removeEventListener("resize", handleResize);
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.divleft{ .divleft{

View File

@ -1,7 +1,7 @@
//API DOMAIN //API DOMAIN
const API_DOMAIN = process.env.NODE_ENV === 'development' ? "127.0.0.1:8000" : "47.112.174.207:7070" const API_DOMAIN = process.env.NODE_ENV === 'development' ? "127.0.0.1:8000" : "django-vue-lyadmin-pro.lybbn.cn"
// 接口地址 // 接口地址
const API_BASEURL = process.env.NODE_ENV === 'development' ? "http://"+ API_DOMAIN +"/api/" : "http://"+ API_DOMAIN +"/api/" const API_BASEURL = process.env.NODE_ENV === 'development' ? "http://"+ API_DOMAIN +"/api/" : "https://"+ API_DOMAIN +"/api/"
//版本号 //版本号
const APP_VER = require('../../package.json').version const APP_VER = require('../../package.json').version
//是否开启代理 //是否开启代理
@ -40,6 +40,9 @@ module.exports = {
//是否开启多标签 //是否开启多标签
ISMULTITABS: true, ISMULTITABS: true,
//Token前缀注意最后有个空格如不需要需设置空字符串
TOKEN_PREFIX: "JWT ",
//语言 简体中文 zh-cn、 英文 en此功能只是示例 //语言 简体中文 zh-cn、 英文 en此功能只是示例
LANG: 'zh-cn', LANG: 'zh-cn',

View File

@ -8,6 +8,7 @@ import '../assets/css/nprogress.scss'//自定义样式
NProgress.inc(0.4) NProgress.inc(0.4)
NProgress.configure({ easing: 'ease', speed: 500, showSpinner: true }) NProgress.configure({ easing: 'ease', speed: 500, showSpinner: true })
import {setStorage,getStorage} from '@/utils/util' import {setStorage,getStorage} from '@/utils/util'
import { cancelRequestState } from "@/store/cancelRequest";
const routes = [ const routes = [
{ {
@ -348,6 +349,8 @@ router.beforeEach((to, from, next) => {
const whiteList = ['buttonConfig', 'menuManage', 'lyterminal', 'buttonManage','lyFilePreview'] const whiteList = ['buttonConfig', 'menuManage', 'lyterminal', 'buttonManage','lyFilePreview']
// 进度条 // 进度条
NProgress.start() NProgress.start()
const cancelReques = cancelRequestState();
cancelReques.clearAllCancelToken()
let userId = store.userId ? store.userId : '' let userId = store.userId ? store.userId : ''
if (to.meta.requireAuth) { // 判断该路由是否需要登录权限 if (to.meta.requireAuth) { // 判断该路由是否需要登录权限
if (userId) { // 通过vuex state获取当前的token是否存在 if (userId) { // 通过vuex state获取当前的token是否存在

View File

@ -0,0 +1,31 @@
import { defineStore } from 'pinia'
export const cancelRequestState = defineStore('cancelRequest', {
state:() => {
return {
cancelTokenList: []
}
},
getters:{
},
actions: {
addCancelToken(val) {
if (!this.cancelTokenList) {
this.cancelTokenList = []
}
if (val) {
this.cancelTokenList.push(val)
}
},
// 取消所有请求
clearAllCancelToken() {
this.cancelTokenList.forEach(cancel => {
if (cancel) {
cancel()
}
})
this.cancelTokenList = []
}
},
})

View File

@ -22,6 +22,13 @@ export const useMutitabsStore = defineStore('mutitabs', {
//控制是否支持多选项卡 //控制是否支持多选项卡
isMultiTabs:config.ISMULTITABS, isMultiTabs:config.ISMULTITABS,
isFullscreen:false,//是否全屏 isFullscreen:false,//是否全屏
isWebSocketOpen: false,
// 未读消息
unread: 0,
currentOs:getStorage('currentOs')||"windows",
fileInfo:{
currentDir:"",//文件管理当前所在文件夹path路径
},
} }
}, },
getters:{ getters:{
@ -69,6 +76,10 @@ export const useMutitabsStore = defineStore('mutitabs', {
this.refresh = val; this.refresh = val;
setStorage('refresh',val) setStorage('refresh',val)
}, },
setCurrentOS(val) {
this.currentOs = val;
setStorage('currentOs',val)
},
refreshUserinfo(val){ refreshUserinfo(val){
this.roleNames = val.role_names this.roleNames = val.role_names
this.userName = val.name this.userName = val.name
@ -295,5 +306,11 @@ export const useMutitabsStore = defineStore('mutitabs', {
} }
}) })
}, },
setWebSocketState(socketState) {
this.isWebSocketOpen = socketState
},
setUnread (number) {
this.unread = number
}
}, },
}) })

View File

@ -0,0 +1,42 @@
import { ElMessage } from 'element-plus';
let messageDom = null;
const messageTypeList = ['success', 'error', 'warning', 'info'];
const Message = (options) => {
if (messageDom) messageDom.close();
messageDom = ElMessage(options);
};
messageTypeList.forEach((type) => {
Message[type] = (options) => {
if (typeof options === 'string') options = { message: options };
options.type = type;
return Message(options);
};
});
export const MsgOk = (message) => {
Message.success({
message: message,
type: 'success',
showClose: true,
duration: 3000,
});
};
export const MsgWarn = (message) => {
Message.warning({
message: message,
type: 'warning',
showClose: true,
duration: 3000,
});
};
export const MsgError = (message) => {
Message.error({
message: message,
type: 'error',
showClose: true,
duration: 3000,
});
};

View File

@ -42,6 +42,7 @@ Print.prototype = {
} }
str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}</style>"; str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}</style>";
str += "<style>html,body{background-color:#fff;}</style>"; str += "<style>html,body{background-color:#fff;}</style>";
str += "<style> @media print {.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#e4e7ed;}.el-radio__input.is-disabled.is-checked .el-radio__inner::after{background-color:#999999} .el-radio{color:#999999}.el-radio__input.is-checked .el-radio__inner, .el-checkbox__input.is-checked .el-checkbox__inner { background-color: #333333; } }</style>";
// str += "<style>body{zoom: 90%;}</style>";//设置默认打印缩放比 // str += "<style>body{zoom: 90%;}</style>";//设置默认打印缩放比
str += "<style>html,body,div{height: auto!important;}</style>";//打印多页 str += "<style>html,body,div{height: auto!important;}</style>";//打印多页
// str += "<style>@page {size: auto;margin: 0 6mm 10mm 6mm;}</style>";//去除页眉,其中size是纸张尺寸如A4 size:A4; portrait 或 landscape 为纸张方向, 如A4横向 size:A4 landscape; // str += "<style>@page {size: auto;margin: 0 6mm 10mm 6mm;}</style>";//去除页眉,其中size是纸张尺寸如A4 size:A4; portrait 或 landscape 为纸张方向, 如A4横向 size:A4 landscape;

View File

@ -473,6 +473,126 @@ function isEmpty(nstr){
} }
} }
const getFileExt = function (fileName) {
// 从文件名中获取扩展名
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex === -1) {
// 如果没有找到点,返回空字符串或其他默认值
return '';
} else {
// 使用 substring 方法获取最后一个点之后的部分作为扩展名
return fileName.substring(lastDotIndex + 1).toLowerCase();
}
}
const getFileTypeDesc = function (fileName) {
const ext = getFileExt(fileName);
switch (ext) {
case 'py':
return [ext,'Python源文件'];
case 'php':
return [ext,'Php源文件'];
case 'java':
return [ext,'Java源文件'];
case 'go':
return [ext,'Go源文件'];
case 'js':
return [ext,'JavaScript源文件'];
case 'ts':
return [ext,'TypeScript源文件'];
case 'vue':
return [ext,'Vue源文件'];
case 'json':
return [ext,'Json源文件'];
case 'css':
return [ext,'CSS样式表'];
case 'html':
case 'htm':
return [ext,'HTML文件'];
case 'pdf':
return [ext,'PDF文件'];
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return [ext,'Image图片'];
case 'mp4':
case 'flv':
case 'm4a':
case 'avi':
return [ext,'Video视频文件'];
case 'sql':
return [ext,'SQL脚本文件'];
case 'txt':
return [ext,'文本格式'];
default:
return [ext,'未知文件'];
}
}
const canEditOnline = function (fileName) {
let ext = getFileExt(fileName);
let black_list = ['msi','psd','dll','sys','gz', 'zip', 'rar','7z', 'bz2', 'exe', 'db','sqlite','sqlite3','.mdb', 'pdf', 'doc', 'xls', 'docx', 'xlsx', 'ppt','pptx','mp4','flv','avi', 'png', 'gif', 'jpg', 'jpeg', 'bmp', 'icon', 'ico', 'pyc','class', 'so', 'pyd']
if (black_list.includes(ext)) {
return false
}
return true
}
// 生成指定长度随机字符串
const generateRandomString = function (length) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}
return result;
}
// 获取路径中的文件名,如果为目录则获取目录名
const getFileNameFromPath = function(path) {
const isWindowsPath = /^[A-Za-z]:\//.test(path);
if(isWindowsPath){
if(path.length>3){
path = path.replace(/\/$/, '');
}else{
return ""
}
}else{
if(path.length>1){
path = path.replace(/\/$/, '');
}
if(path === "/"){
return ""
}
}
// 使用 split('/') 将路径按照斜杠分割成数组,并取最后一个元素作为文件名
const parts = path.split('/');
return parts[parts.length - 1];
}
const downloadFileContent = function (content, fileName) {
const downloadUrl = window.URL.createObjectURL(new Blob([content]));
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
a.download = fileName;
const event = new MouseEvent('click');
a.dispatchEvent(event);
}
const addCopySuffix = function (name) {
const parts = name.split('.');
if (parts.length > 1) {
// 如果有扩展名,则在扩展名之前加上'-副本'
return parts.slice(0, -1).join('.') + '-副本.' + parts[parts.length - 1];
} else {
// 如果没有扩展名,则在最后加上'-副本'
return name + '-副本';
}
}
export{ export{
timestampToTime, timestampToTime,
dateFormats, dateFormats,
@ -506,5 +626,11 @@ export{
removeStorage, removeStorage,
getToken, getToken,
getDefaultWorkflowConfig, getDefaultWorkflowConfig,
isEmpty isEmpty,
getFileNameFromPath,
canEditOnline,
getFileTypeDesc,
generateRandomString,
downloadFileContent,
addCopySuffix
} }

View File

@ -0,0 +1,111 @@
import { ref, onMounted, onUnmounted } from 'vue';
import { ElNotification as message } from 'element-plus';
import { getToken } from "@/utils/util";
import { domain } from '@/api/url';
import { useMutitabsStore } from "@/store/mutitabs";
function getJWTAuthorization() {
const token = getToken();
const jwt = 'JWTlybbn' + token;
return jwt;
}
const lyWebSocket = {
websocket:null,
socketOpen:false,
hearbeatTimer:null,
hearbeatInterval:10 * 1000,
isReconnect :true,
reconnectCount:3,
reconnectCurrent:1,
reconnectTimer:null,
reconnectInterval:5 * 1000,
initWebSocket(revMessage){
if (!('WebSocket' in window)) {
message.warning('该浏览器不支持WebSocket');
return null;
}
const token = getToken();
if (!token) {
return null;
}
const wsUrl = (window.location.protocol === 'http:' ? 'ws://' : 'wss://') + `${domain}/ws/msg/`;
lyWebSocket.websocket = new WebSocket(wsUrl, ['JWTLYADMIN', getJWTAuthorization()]);
lyWebSocket.websocket.onmessage = (e) => {
if (revMessage) {
revMessage(e);
}
};
lyWebSocket.websocket.onclose = (e) => {
lyWebSocket.socketOpen = false;
useMutitabsStore().setWebSocketState(lyWebSocket.socketOpen);
if (lyWebSocket.isReconnect) {
lyWebSocket.reconnectTimer = setTimeout(() => {
if (lyWebSocket.reconnectCurrent > lyWebSocket.reconnectCount) {
clearTimeout(lyWebSocket.reconnectTimer);
lyWebSocket.isReconnect = false;
lyWebSocket.socketOpen = false;
useMutitabsStore().setWebSocketState(lyWebSocket.socketOpen);
return;
}
lyWebSocket.reconnectCurrent++;
lyWebSocket.reconnectWebSocket(revMessage); // 传递 revMessage 参数
}, lyWebSocket.reconnectInterval);
}
};
lyWebSocket.websocket.onopen = () => {
lyWebSocket.socketOpen = true;
useMutitabsStore().setWebSocketState(lyWebSocket.socketOpen);
lyWebSocket.isReconnect = true;
lyWebSocket.startHeartbeat();
};
lyWebSocket.websocket.onerror = (e) => {
};
},
startHeartbeat(){
if (lyWebSocket.hearbeatTimer) {
clearInterval(lyWebSocket.hearbeatTimer);
}
lyWebSocket.hearbeatTimer = setInterval(() => {
const data = {
time: new Date().getTime()
};
lyWebSocket.sendWebSocketMessage(data);
}, lyWebSocket.hearbeatInterval);
},
sendWebSocketMessage(data, callback = null){
if (lyWebSocket.websocket && lyWebSocket.websocket.readyState === lyWebSocket.websocket.OPEN) {
lyWebSocket.websocket.send(JSON.stringify(data));
callback && callback();
} else {
clearInterval(lyWebSocket.hearbeatTimer);
lyWebSocket.socketOpen = false;
useMutitabsStore().setWebSocketState(lyWebSocket.socketOpen);
}
},
closeWebSocket(){
lyWebSocket.isReconnect = false;
lyWebSocket.websocket && lyWebSocket.websocket.close();
lyWebSocket.websocket = null;
lyWebSocket.socketOpen = false;
useMutitabsStore().setWebSocketState(lyWebSocket.socketOpen);
},
reconnectWebSocket(){
if (lyWebSocket.websocket && !lyWebSocket.isReconnect) {
lyWebSocket.closeWebSocket();
}
lyWebSocket.initWebSocket(null);
}
};
export default lyWebSocket

View File

@ -195,6 +195,7 @@
mutitabsstore.refreshUserinfo(res.data) mutitabsstore.refreshUserinfo(res.data)
mutitabsstore.setUserId(res.data.userId) mutitabsstore.setUserId(res.data.userId)
mutitabsstore.setRefresh(res.data.refresh) mutitabsstore.setRefresh(res.data.refresh)
mutitabsstore.setCurrentOS(res.data.current_os)
getMenu() getMenu()
} else { } else {
getCaptchas() getCaptchas()

View File

@ -2,7 +2,7 @@
<div class="login_bg"> <div class="login_bg">
<div class="login_adv" style="background-image: url(static/img/auth_banner.jpg);"> <div class="login_adv" style="background-image: url(static/img/auth_banner.jpg);">
<div class="login_adv__title"> <div class="login_adv__title">
<h2>django-vue-lyadmin pro-V2test</h2> <h2>django-vue-lyadmin pro</h2>
<p>{{ $t('login.describe') }}</p> <p>{{ $t('login.describe') }}</p>
</div> </div>
<div class="login_adv__mask"></div> <div class="login_adv__mask"></div>

View File

@ -0,0 +1,139 @@
<template>
<div>
<ly-dialog v-model="dialogVisible" :title="loadingTitle" width="50%" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="right" label-width="auto" :disabled="loadingTitle=='详情'">
<el-form-item label="公告标题:">
{{formData.messageid.msg_title}}
</el-form-item>
<!-- <el-form-item label="跳转路径:" prop="to_path">
<el-input type="text" v-model.trim="formData.to_path"></el-input>
</el-form-item> -->
<el-form-item label="目标类型:">
<el-radio-group v-model="formData.messageid.target_type">
<el-radio :value="1" border>平台公告</el-radio>
<el-radio :value="2" border>按用户</el-radio>
</el-radio-group>
</el-form-item>
<!-- <el-form-item label="发送对象:" prop="target_user" v-if="formData.target_type == 2" class="is-required">
<ly-table-select v-model="formData.target_user" :apiObj="getUserList" :table-width="800" multiple clearable collapse-tags collapse-tags-tooltip :props="tableSelectProps" @change="selectChange">
<template #header="{form, submit}">
<el-form :inline="true" :model="form">
<el-form-item>
<el-input type="text" v-model="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submit">查询</el-button>
</el-form-item>
</el-form>
</template>
<el-table-column prop="id" label="ID" width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="username" label="用户名" width="100"></el-table-column>
<el-table-column prop="nickname" label="昵称" width="100"></el-table-column>
<el-table-column prop="mobile" label="手机号" width="150"></el-table-column>
<el-table-column prop="create_datetime" label="注册时间"></el-table-column>
</ly-table-select>
</el-form-item> -->
<el-form-item label="公告内容:">
<div v-html="formData.messageid.msg_content"></div>
<!-- <TEditor v-model="formData.messageid.msg_content"></TEditor> -->
</el-form-item>
<!-- <el-form-item label="是否发布:" prop="status">-->
<!-- <el-switch-->
<!-- v-model="formData.status"-->
<!-- active-color="#13ce66"-->
<!-- inactive-color="#ff4949">-->
<!-- </el-switch>-->
<!-- </el-form-item>-->
</el-form>
<!-- <template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">确定</el-button>
</template> -->
</ly-dialog>
</div>
</template>
<script>
import TEditor from '@/components/TEditor'
import LyDialog from "@/components/dialog/dialog";
import {deepClone} from "@/utils/util";
import LyTableSelect from "@/components/lyTableSelect";
import {readOwnMessage} from '@/api/api'
export default {
components: {LyDialog, TEditor,LyTableSelect},
emits: ['refreshData'],
name: "addModuleNotice",
data() {
return {
dialogVisible:false,
loadingSave:false,
loadingTitle:'',
formData:{
messageid:{
msg_title:'',
to_path:'',
msg_content:'',
target_type:1,
target_user:[],
}
},
rules:{
},
//
tableSelectProps: {
label: 'username',
value: 'id',
}
}
},
mounted() {
window.addEventListener("focusin", this.onFocusIn,true);
},
unmounted() {
window.removeEventListener("focusin", this.onFocusIn);
},
methods:{
onFocusIn(e){
e.stopImmediatePropagation()//
},
handleClose() {
this.dialogVisible=false
this.loadingSave=false
this.formData = {
messageid:{
msg_title:'',
to_path:'',
msg_content:'',
target_type:1,
target_user:[],
}
}
this.$emit('refreshData')
},
addModuleFn(item,flag) {
this.loadingTitle=flag
this.dialogVisible=true
if(item){
this.formData=deepClone(item)
this.submitData()
}
},
submitData() {
readOwnMessage({id:this.formData.id}).then(res => {
if(res.code ==2000) {
}
})
},
}
}
</script>
<style>
.set-specs .el-form-item__content{
background: #e6e6e6 !important;
}
</style>

View File

@ -0,0 +1,207 @@
<template>
<div :class="{'ly-is-full':isFull}">
<div class="tableSelect" ref="tableSelect">
<el-form :inline="true" :model="formInline" label-position="left">
<el-form-item label="标题:">
<el-input size="default" v-model.trim="formInline.search" maxlength="60" clearable placeholder="消息标题" @change="search" style="width:200px"></el-input>
</el-form-item>
<el-form-item label="" v-show="hasPermission(this.$route.name,'Search')"><el-button @click="search" type="primary" icon="Search">查询</el-button></el-form-item>
<el-form-item label=""><el-button @click="handleEdit('','reset')" icon="Refresh">重置</el-button></el-form-item>
</el-form>
</div>
<el-table :height="'calc('+(tableHeight)+'px)'" border :data="tableData" ref="tableref" v-loading="loadingPage" style="width: 100%">
<el-table-column type="index" width="60" align="center" label="序号">
<template #default="scope">
<span v-text="getIndex(scope.$index)"></span>
</template>
</el-table-column>
<el-table-column min-width="90" prop="messageid.msg_title" label="公告标题"></el-table-column>
<!-- <el-table-column min-width="120" prop="to_path" label="跳转路径"></el-table-column> -->
<!-- <el-table-column min-width="120" prop="image" label="封面图">-->
<!-- <template #default="scope">-->
<!-- <el-image :src=scope.row.image :preview-src-list="[scope.row.image]" style="width: 60px;height: 60px"></el-image>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column min-width="180" prop="msg_content" show-overflow-tooltip label="内容">
<template #default="scope">
<div v-html="customEllipsis(scope.row.msg_content)" class="ellipsis"></div>
</template>
</el-table-column> -->
<!-- <el-table-column min-width="80" prop="sort" label="排序"></el-table-column>-->
<el-table-column min-width="100" label="目标类型">
<template #default="scope">
<el-tag v-if="scope.row.messageid.target_type == 1">平台公告</el-tag>
<el-tag v-else-if="scope.row.messageid.target_type == 2" type="warning">按用户</el-tag>
</template>
</el-table-column>
<el-table-column min-width="90" label="是否已读">
<template #default="scope">
<el-tag v-if="scope.row.is_read">已读</el-tag>
<el-tag v-else type="danger">未读</el-tag>
</template>
</el-table-column>
<el-table-column min-width="150" prop="create_datetime" label="创建时间"></el-table-column>
<el-table-column label="操作" fixed="right" width="180">
<template #header>
<div style="display: flex;justify-content: space-between;align-items: center;">
<div>操作</div>
<div @click="setFull">
<el-tooltip content="全屏" placement="bottom">
<el-icon ><full-screen /></el-icon>
</el-tooltip>
</div>
</div>
</template>
<template #default="scope">
<span class="table-operate-btn" @click="handleEdit(scope.row,'detail')" v-show="hasPermission(this.$route.name,'Retrieve')">详情</span>
<span class="table-operate-btn" @click="handleEdit(scope.row,'delete')" v-show="hasPermission(this.$route.name,'Delete')">删除</span>
</template>
</el-table-column>
</el-table>
<Pagination v-bind:child-msg="pageparm" @callFather="callFather"></Pagination>
<add-module ref="addModuleFlag" @refreshData="getData"></add-module>
</div>
</template>
<script>
import addModule from "./components/addModuleNoticeDetail";
import Pagination from "@/components/Pagination";
import {dateFormats,getTableHeight} from "@/utils/util";
import {getOwnMessage,delOwnMessage} from '@/api/api'
export default {
components:{
Pagination,
addModule
},
name:'messagNotice',
data() {
return {
isFull:false,
tableHeight:500,
loadingPage:false,
formInline:{
page: 1,
limit: 10,
},
pageparm: {
page: 1,
limit: 10,
total: 0
},
statusList:[
{id:1,name:'是'},
{id:0,name:'否'}
],
tableData:[]
}
},
methods:{
//
getIndex($index) {
// ( - 1) * + + 1
return (this.pageparm.page-1)*this.pageparm.limit + $index +1
},
setFull(){
this.isFull=!this.isFull
window.dispatchEvent(new Event('resize'))
},
//10
customEllipsis(value) {
value = value.replace(/<.*?>/ig,"") //v-html
if(!value) return ""
if (value.length > 10) {
return value.slice(0, 10) + "..."
}
return value
},
addModule() {
this.$refs.addModuleFlag.addModuleFn(null,'新增')
},
changeStatus(row) {
// console.log(row,'row----')
},
handleEdit(row,flag) {
let vm = this
if(flag=='detail') {
vm.$refs.addModuleFlag.addModuleFn(row,'详情')
}
else if(flag=='delete') {
vm.$confirm('您确定要删除选中的内容?',{
closeOnClickModal:false
}).then(res=>{
delOwnMessage({id:row.id}).then(res=>{
if(res.code == 2000) {
vm.$message.success(res.msg)
vm.search()
} else {
vm.$message.warning(res.msg)
}
})
}).catch(()=>{
})
}
else if(flag=="reset"){
this.formInline = {
page:1,
limit: 10
}
this.pageparm={
page: 1,
limit: 10,
total: 0
}
this.getData()
}
},
callFather(parm) {
this.formInline.page = parm.page
this.formInline.limit = parm.limit
this.getData()
},
search() {
this.formInline.page = 1
this.formInline.limit = 10
this.getData()
},
//
async getData(){
this.loadingPage = true
getOwnMessage(this.formInline).then(res => {
this.loadingPage = false
if(res.code ==2000) {
this.tableData = res.data.data
this.pageparm.page = res.data.page;
this.pageparm.limit = res.data.limit;
this.pageparm.total = res.data.total;
}
})
},
//
listenResize() {
this.$nextTick(() => {
this.getTheTableHeight()
})
},
getTheTableHeight(){
let tabSelectHeight = this.$refs.tableSelect?this.$refs.tableSelect.offsetHeight:0
tabSelectHeight = this.isFull?tabSelectHeight - 110:tabSelectHeight
this.tableHeight = getTableHeight(tabSelectHeight)
}
},
created() {
this.getData()
},
mounted() {
//
window.addEventListener('resize', this.listenResize);
this.$nextTick(() => {
this.getTheTableHeight()
})
},
unmounted() {
//
window.removeEventListener("resize", this.listenResize);
},
}
</script>

View File

@ -0,0 +1,81 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="560px" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="right" label-width="auto">
<el-form-item label="目录名:" prop="dirname">
<el-input v-model="formData.dirname" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">保存</el-button>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {sysFileGetToken,sysFileDownload,sysdownloadFile,sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('创建目录')
let formData = ref({
dirname:'',
})
let rules = ref({
dirname: [
{required: true, message: '请输入目录名',trigger: 'blur'}
],
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.path = tempdata.path
}
}
let rulesForm = ref(null)
function submitData() {
rulesForm.value.validate(obj=>{
if(obj) {
loadingSave.value=true
let param = {
action:'create_dir',
dirname:formData.value.dirname,
path:formData.value.path
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
handleClose()
emits('refreshData')
} else {
ElMessage.warning(res.msg)
}
})
}
})
}
defineExpose({
handleOpen
})
</script>

View File

@ -0,0 +1,81 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="560px" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="right" label-width="auto">
<el-form-item label="文件名:" prop="filename">
<el-input v-model="formData.filename" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">保存</el-button>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {sysFileGetToken,sysFileDownload,sysdownloadFile,sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('创建文件')
let formData = ref({
filename:'',
})
let rules = ref({
filename: [
{required: true, message: '请输入文件名',trigger: 'blur'}
],
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.path = tempdata.path
}
}
let rulesForm = ref(null)
function submitData() {
rulesForm.value.validate(obj=>{
if(obj) {
loadingSave.value=true
let param = {
action:'create_file',
filename:formData.value.filename,
path:formData.value.path
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
handleClose()
emits('refreshData')
} else {
ElMessage.warning(res.msg)
}
})
}
})
}
defineExpose({
handleOpen
})
</script>

View File

@ -0,0 +1,431 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="680px" :before-close="handleClose" class="lyinnertabs">
<el-tabs v-model="activeName" type="card" @tab-click="handleTabClick">
<el-tab-pane label="常规" name="attr">
<el-form :inline="false" :model="formData" ref="rulesForm" class="journal-detail" label-position="left" label-width="auto" v-loading="loadingPage">
<el-form-item label="名称:" prop="name" @click="copyText(formData.name)">
{{formData.name}}
</el-form-item>
<el-form-item label="类型:" prop="typedesc">
{{formData.typedesc}}
</el-form-item>
<el-form-item label="路径:" prop="path" @click="copyText(formData.path)">
{{formData.path}}
</el-form-item>
<el-form-item label="大小:" prop="size">
<span v-if="userState.currentOs=='windows'">{{ calc_size(formData.size) + ""+formData.size+"字节"}}</span>
<span v-else-if="userState.currentOs!='windows'&& formData.type=='dir'">{{formData.size}}</span>
<span v-else>{{ calc_size(formData.size)}}</span>
</el-form-item>
<el-form-item label="权限:" prop="permissions">
{{ formData.permissions }}
</el-form-item>
<el-form-item label="用户/UID" prop="owner">
<span>{{ formData.owner }}</span><span>&nbsp;({{ formData.owner_uid }})</span>
</el-form-item>
<el-form-item label="组/GID" prop="group" v-if="userState.currentOs!='windows'">
<span>{{ formData.group }}</span><span>&nbsp;({{ formData.gid }})</span>
</el-form-item>
<el-form-item label="修改时间:" prop="modified">
{{formData.modified}}
</el-form-item>
<el-form-item label="访问时间:" prop="access_at">
{{formData.access_at}}
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="权限" name="qx" v-if="userState.currentOs!='windows'">
<el-row :gutter="20">
<el-col :span="8">
<el-card shadow="hover" header="所有者">
<div style="display: flex;flex-direction: column;margin-left: 20px;">
<el-checkbox v-for="dm1 in PermUL" v-model="dm1.val" :key="dm1.name" :label="dm1.name" @change="handleULChange($event,dm1.name)"></el-checkbox>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" header="所属组">
<div style="display: flex;flex-direction: column;margin-left: 20px;">
<el-checkbox v-for="dm2 in PermGL" v-model="dm2.val" :key="dm2.name" :label="dm2.name" :value="dm2.val" @change="handleGLChange($event,dm2.name)"></el-checkbox>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="hover" header="公共">
<div style="display: flex;flex-direction: column;margin-left: 20px;">
<el-checkbox v-for="dm3 in PermOL" v-model="dm3.val" :key="dm3.name" :label="dm3.name" :value="dm3.val" @change="handleOLChange($event,dm3.name)"></el-checkbox>
</div>
</el-card>
</el-col>
</el-row>
<div style="margin-top: 20px;display: flex;align-items: center;">
<div style="margin-left: 10px;">
权限<el-input type="number" style="width: 90px;" placeholder="请输入权限值" v-model="newPerms" @input="handlePermInputChange"></el-input>
</div>
<div style="margin-left: 15px;">
所有者<el-select v-model="belongUser" placeholder="请选择所有者" style="width: 150px;">
<el-option
v-for="item in userList"
:key="item.value"
:label="item.name"
:value="item.name"
/>
</el-select>
</div>
<div style="margin-left: 15px;">
所属组<el-select v-model="belongGroup" placeholder="请选择所属用户" style="width: 150px;">
<el-option
v-for="item in userList"
:key="item.value"
:label="item.name"
:value="item.name"
/>
</el-select>
</div>
</div>
<div style="margin-top: 20px;display: flex;align-items: center;">
<el-checkbox v-model="isSubDir" label="应用到子目录" style="margin-left: 10px;"></el-checkbox>
<el-button type="primary" style="margin-left: 10px;" @click=submitClick>保存</el-button>
</div>
</el-tab-pane>
</el-tabs>
</LyDialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {sysFileGetToken,sysFileDownload,sysdownloadFile,sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage } from 'element-plus'
import { deepClone,getFileTypeDesc,formatUnitSize } from "@/utils/util"
import useClipboard from "vue-clipboard3";
import {useMutitabsStore} from "@/store/mutitabs";
const emits = defineEmits(['refreshData','closed'])
const userState = useMutitabsStore()
let activeName = ref("attr")
let dialogVisible = ref(false)
let loadingPage = ref(false)
let loadingTitle = ref('创建文件')
let formData = ref({
path:"",
permissions:777,
})
let PermUL = ref(
[{name:"读取",value:4,val:false},{name:"写入",value:2,val:false},{name:"执行",value:1,val:false}]
)
let PermGL = ref(
[{name:"读取",value:4,val:false},{name:"写入",value:2,val:false},{name:"执行",value:1,val:false}]
)
let PermOL = ref(
[{name:"读取",value:4,val:false},{name:"写入",value:2,val:false},{name:"执行",value:1,val:false}]
)
let newPerms = ref("777")
let isSubDir = ref(true)
let userList = ref([
{name:"root",value:0},
{name:"www",value:1},
{name:"redis",value:2},
{name:"mysql",value:3},
])
let belongUser = ref("")
let belongGroup = ref("")
let isCloseRefresh = ref(false)
function handleClose() {
if(isCloseRefresh.value){
emits('refreshData')
}
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
formData.path = item.path
if(item){
getData(item.path)
}
}
function calc_size(size){
return formatUnitSize(size)
}
const { toClipboard } = useClipboard()
function copyText(content) {
toClipboard(content).then(()=>{
ElMessage.success("复制成功")
}).catch(()=>{
ElMessage.warning("复制失败")
})
}
function handleULChange(e,name){
let nums = 0
PermUL.value.forEach(item=>{
if(item.val){
nums = nums + item.value
}
})
newPerms.value = `${nums}${newPerms.value[1]}${newPerms.value[2]}`
}
function handleGLChange(e,name){
let nums = 0
PermGL.value.forEach(item=>{
if(item.val){
nums = nums + item.value
}
})
newPerms.value = `${newPerms.value[0]}${nums}${newPerms.value[2]}`
}
function handleOLChange(e,name){
let nums = 0
PermOL.value.forEach(item=>{
if(item.val){
nums = nums + item.value
}
})
newPerms.value = `${newPerms.value[0]}${newPerms.value[1]}${nums}`
}
function handlePermInputChange(e){
calcPermCheckBox(e)
}
function handleTabClick(e){
if(e.props.name=="attr"){
getData(formData.path)
}else{
newPerms.value = formData.value.permissions
belongUser.value = formData.value.owner
belongGroup.value = formData.value.group
calcPermCheckBox(formData.value.permissions)
}
}
function calcPermCheckBox(permissions){
PermUL.value.forEach(item=>{
if(permissions[0]=="7"){
item.val = true
}else if(permissions[0]=="6"){
item.val = true
if(item.value == 1){
item.val = false
}
}else if(permissions[0]=="5"){
item.val = true
if(item.value == 2){
item.val = false
}
}else if(permissions[0]=="4"){
item.val = true
if(item.value == 1 || item.value == 2){
item.val = false
}
}else if(permissions[0]=="3"){
item.val = true
if(item.value == 4){
item.val = false
}
}else if(permissions[0]=="2"){
item.val = true
if(item.value == 4 || item.value == 1){
item.val = false
}
}else if(permissions[0]=="1"){
item.val = true
if(item.value == 4 || item.value == 2){
item.val = false
}
}else{
item.val = false
}
})
PermGL.value.forEach(item=>{
if(permissions[1]=="7"){
item.val = true
}else if(permissions[1]=="6"){
item.val = true
if(item.value == 1){
item.val = false
}
}else if(permissions[1]=="5"){
item.val = true
if(item.value == 2){
item.val = false
}
}else if(permissions[1]=="4"){
item.val = true
if(item.value == 1 || item.value == 2){
item.val = false
}
}else if(permissions[1]=="3"){
item.val = true
if(item.value == 4){
item.val = false
}
}else if(permissions[1]=="2"){
item.val = true
if(item.value == 4 || item.value == 1){
item.val = false
}
}else if(permissions[1]=="1"){
item.val = true
if(item.value == 4 || item.value == 2){
item.val = false
}
}else{
item.val = false
}
})
PermOL.value.forEach(item=>{
if(permissions[2]=="7"){
item.val = true
}else if(permissions[2]=="6"){
item.val = true
if(item.value == 1){
item.val = false
}
}else if(permissions[2]=="5"){
item.val = true
if(item.value == 2){
item.val = false
}
}else if(permissions[2]=="4"){
item.val = true
if(item.value == 1 || item.value == 2){
item.val = false
}
}else if(permissions[2]=="3"){
item.val = true
if(item.value == 4){
item.val = false
}
}else if(permissions[2]=="2"){
item.val = true
if(item.value == 4 || item.value == 1){
item.val = false
}
}else if(permissions[2]=="1"){
item.val = true
if(item.value == 4 || item.value == 2){
item.val = false
}
}else{
item.val = false
}
})
}
function getData(path){
let param = {
action:"get_filedir_attribute",
path:path
}
loadingPage.value = true
sysFileManage(param).then(res => {
loadingPage.value = false;
if (res.code == 2000) {
formData.value = res.data
if(formData.value.type == 'dir'){
formData.value.typedesc = "文件夹"
}else{
formData.value.typedesc = getFileTypeDesc(formData.value.name)[1]
}
if(formData.value.is_link){
formData.value.typedesc = "链接文件"
}
} else {
ElMessage.warning(res.msg)
}
})
}
function submitClick(){
if(newPerms.value.length !=3){
ElMessage.warning("权限配置错误正确格式777")
return
}
if(parseInt(newPerms.value)>777){
ElMessage.warning("权限配置错误最大为777")
return
}
if(parseInt(newPerms.value)<0){
ElMessage.warning("权限配置错误最小为000")
return
}
let param = {
action:"set_file_access",
path:formData.value.path,
user:belongUser.value,
group:belongGroup.value,
access:newPerms.value,
issub:isSubDir.value,
}
userState.loadingInfo.isLoading = true
userState.loadingInfo.content = "正在设置..."
sysFileManage(param).then(res => {
userState.loadingInfo.isLoading = false
userState.loadingInfo.content = ""
if (res.code == 2000) {
ElMessage.success(res.msg)
isCloseRefresh.value = true
} else {
ElMessage.warning(res.msg)
}
})
}
defineExpose({
handleOpen
})
</script>
<style scoped>
.journal-detail:deep(.el-form-item) .el-form-item__content{
background: var(--el-color-info-light-9);
padding-left: 10px;
word-break: break-all;
}
.lyinnertabs:deep(.el-tabs__header){
border-bottom: 1px solid var(--el-border-color-light);
height: 32px;
width: fit-content;
}
.lyinnertabs:deep(.el-tabs__item.is-active){
border-bottom-color: var(--el-bg-color);
border-left: 1px solid var(--el-color-primary) !important;
border-right: 1px solid var(--el-color-primary);
border-top: 1px solid var(--el-color-primary);
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.4);
}
.lyinnertabs:deep(.el-tabs__item){
border-bottom: none;
border-left: 1px solid var(--el-border-color-light);
border-right: 1px solid var(--el-border-color-light);
border-top: 1px solid var(--el-border-color-light);
margin-right: 2px;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
height: 32px;
line-height: 32px;
padding: 0 16px;
}
.lyinnertabs:deep(.el-tabs__item:first-child){
border-left: 1px solid var(--el-border-color-light);
}
.lyinnertabs:deep(.el-tabs__nav){
border: none;
border-bottom: none;
border-radius: 4px 4px 0 0;
box-sizing: border-box;
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="650px" :before-close="handleClose">
<el-alert title="以下目标文件名称与源文件冲突,确定后勾选的文件将跳过,未勾选文件将会被覆盖!!!" type="warning" />
<el-table ref="lytable" :data="formData.conflict_list" table-layout="auto" max-height="500px" :scrollbar-always-on="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30"></el-table-column>
<el-table-column prop="name" min-width="200" label="文件名" show-overflow-tooltip></el-table-column>
<el-table-column prop="path" min-width="220" label="路径" show-overflow-tooltip></el-table-column>
</el-table>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">确定</el-button>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { ref,onMounted,nextTick } from 'vue';
import {sysFileGetToken,sysFileDownload,sysdownloadFile,sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('')
let formData = ref({
action:"batch_operate",
path:"",
spath:[],
type:"",
confirm:false,
skip_list:[],
conflict_list:[],
})
let lytable = ref(null)
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.conflict_list = tempdata.conflict_list
formData.value.spath = tempdata.spath
formData.value.path = tempdata.path
formData.value.confirm = true
formData.value.type = tempdata.type
formData.value.action = tempdata.action
formData.value.skip_list = tempdata.conflict_list
nextTick(()=>{
lytable.value.toggleAllSelection()
})
}
}
function handleSelectionChange(e){
formData.value.skip_list = e
}
function submitData() {
let param = {
action:"batch_operate",
path:formData.value.path,
spath:formData.value.spath,
type:formData.value.type,
confirm:true,
skip_list:formData.value.skip_list,
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
handleClose()
emits('refreshData')
} else {
ElMessage.warning(res.msg)
}
})
}
defineExpose({
handleOpen
})
</script>

View File

@ -0,0 +1,127 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="560px" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="right" label-width="auto">
<el-form-item label="类型:" prop="cover">
<el-radio-group v-model="formData.cover" @change="handleCoverChange">
<el-radio :value="true" :label="true">覆盖</el-radio>
<el-radio :value="false" :label="false">重命名</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="名称:" prop="name" v-if="formData.cover">
<el-input v-model="formData.name" clearable :disabled="true" ></el-input>
</el-form-item>
<el-form-item label="名称:" prop="newName" v-else>
<el-input v-model="formData.newName" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">保存</el-button>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage } from 'element-plus'
import { deepClone,addCopySuffix } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('')
let formData = ref({
sFilePath:"",
dPath:"",
name:"",
newName:"",
type:"",
cover:false,
action:"copy",
})
let rules = ref({
name: [
{required: true, message: '请输入名称',trigger: 'blur'}
],
newName: [
{required: true, message: '请输入名称',trigger: 'blur'}
],
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.sFilePath = tempdata.sFilePath
formData.value.dPath = tempdata.dPath
formData.value.name = tempdata.name
formData.value.type = tempdata.type
formData.value.action = tempdata.action
handleCoverChange()
}
}
function handleCoverChange(e){
if(formData.value.cover){
formData.value.newName = formData.value.name
}else{
formData.value.newName = addCopySuffix(formData.value.name)
}
}
let rulesForm = ref(null)
function submitData() {
rulesForm.value.validate(obj=>{
if(obj) {
loadingSave.value=true
let action = 'copy_file'
if(formData.value.type == 'dir'){
action = 'copy_dir'
}
if(formData.value.action == 'move'){
action = 'move_file'
}
let cover = false
let name = formData.value.newName
if(formData.value.cover){
cover = true
name = formData.value.name
}
let param = {
action:action,
path:formData.value.dPath,
spath:formData.value.sFilePath,
name:name,
cover:cover
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
handleClose()
emits('refreshData')
} else {
ElMessage.warning(res.msg)
}
})
}
})
}
defineExpose({
handleOpen
})
</script>

View File

@ -0,0 +1,85 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="560px" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="right" label-width="auto">
<el-form-item label="名称:" prop="dname">
<el-input v-model="formData.dname" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">保存</el-button>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import {sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('创建文件')
let formData = ref({
sname:'',
dname:''
})
let rules = ref({
dname: [
{required: true, message: '请输入名称',trigger: 'blur'}
],
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.dname = tempdata.name
formData.value.sname = tempdata.name
formData.value.path = tempdata.path
}
}
let rulesForm = ref(null)
function submitData() {
rulesForm.value.validate(obj=>{
if(obj) {
loadingSave.value=true
let param = {
action:'rename_file',
sname:formData.value.sname,
dname:formData.value.dname,
path:formData.value.path
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
handleClose()
emits('refreshData')
} else {
ElMessage.warning(res.msg)
}
})
}
})
}
defineExpose({
handleOpen
})
</script>

View File

@ -0,0 +1,111 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="680px" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="top" label-width="auto">
<el-form-item label="解压文件:" prop="filename">
<el-input v-model="formData.filename" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="解压到:" prop="path">
<el-input v-model="formData.path">
<template #prepend>
<el-button @click="chooseDir" icon="Folder"></el-button>
</template>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave">解压</el-button>
</template>
</LyDialog>
<lyFileList ref="chooseDirShowFlag" @change="handleChooseChange" v-if="isChooseDirShow" @closed="isChooseDirShow=false"></lyFileList>
</div>
</template>
<script setup>
import { ref,nextTick } from 'vue';
import {sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import lyFileList from "@/components/file/fileList.vue"
import { ElMessage } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('解压文件')
let formData = ref({
filename:'',
path:''
})
let isChooseDirShow = ref(false)
let chooseDirShowFlag = ref(null)
let rules = ref({
path: [
{required: true, message: '请选择解压到目录',trigger: 'blur'}
],
filename: [
{required: true, message: '解压文件错误',trigger: 'blur'}
],
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.filename = tempdata.filename
formData.value.path = tempdata.path
}
}
function chooseDir(){
isChooseDirShow.value = true
nextTick(()=>{
chooseDirShowFlag.value.handleOpen({path:formData.value.path,isDir:true},"目录选择")
})
}
function handleChooseChange(path){
let newpath = deepClone(path)
formData.value.path = newpath
}
let rulesForm = ref(null)
function submitData() {
rulesForm.value.validate(obj=>{
if(obj) {
loadingSave.value=true
let param = {
action:'batch_operate',
type:"unzip",
spath:formData.value.filename,
path:formData.value.path
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
handleClose()
emits('refreshData')
} else {
ElMessage.warning(res.msg)
}
})
}
})
}
defineExpose({
handleOpen
})
</script>

View File

@ -0,0 +1,434 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="690px" :before-close="handleClose">
<el-alert style="margin-bottom: 10px;" :title="'上传成功'+uploadedNums+'个,耗时'+timeDifference+'秒,平均速度'+avgSpeed" type="success" v-if="uploadOk" @click="handleReset"/>
<div class="handle-button" v-if="!uploadOk">
<div>
<el-button type="primary" @click="uploadClick('file')">上传文件</el-button>
<el-button type="primary" @click="uploadClick('dir')">上传目录</el-button>
</div>
<div>
<el-button @click="clearFiles">清空列表</el-button>
</div>
</div>
<div>
<div class="el-upload-dragger" @dragover="handleDragover" @drop="handleDrop" @dragleave="handleDragleave">
<div class="handle-drag">
<div>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
请拖拽需要上传的文件/目录到此处
</div>
</div>
</div>
</div>
</div>
<el-upload ref="lyUploadRef" :show-file-list="false" multiple v-model:file-list="uploadFileList" action="#" :auto-upload="false" :on-change="handleUploadOnChange" :on-exceed="handleExceed" :on-success="handleSuccess">
<template #tip>
<el-text>{{ uploadHelperText }}</el-text>
<!-- <el-progress :duration="0" v-if="loadingSave" text-inside :stroke-width="20" :percentage="uploadPrecent" /> -->
</template>
</el-upload>
<el-table ref="tableContainer" :data="uploadFileList" table-layout="auto" max-height="300px" :scrollbar-always-on="true">
<el-table-column prop="name" min-width="200" label="文件名" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.raw.webkitRelativePath != ''">{{ scope.row.raw.webkitRelativePath }}</span>
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column prop="size" min-width="100" label="大小" show-overflow-tooltip>
<template #default="scope">
{{formatUnitSize(scope.row.size)}}
</template>
</el-table-column>
<el-table-column prop="status" min-width="90" label="状态" show-overflow-tooltip>
<template #default="scope">
<el-progress :duration="0" v-if="scope.row.status== 'ready'" text-inside :stroke-width="15" :percentage="scope.row.percentage" />
<!-- <span v-if="scope.row.status == 'ready'">等待上传</span> -->
<span v-else-if="scope.row.status == 'success'" style="color: green;">上传成功</span>
<span v-else style="color: red;">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column width="45" fixed="right">
<template #default="scope">
<span v-if="scope.row.status != 'success' && !loadingSave">
<el-button type="primary" link @click="removeFile(index)" icon="Close"></el-button>
</span>
</template>
</el-table-column>
</el-table>
<template #footer>
<div class="uploadfooterbt">
<div>{{uploadFileList.length}}</div>
<div>
<el-button @click="handleClose" :loading="loadingSave">关闭</el-button>
<el-button type="primary" @click="submitData" :loading="loadingSave" :disabled="uploadFileList.length < 1" v-if="!uploadOk">确认上传</el-button>
</div>
</div>
</template>
</LyDialog>
</div>
</template>
<script setup>
import { nextTick, ref,onMounted } from 'vue';
import {uploadFile} from '@/api/request';
import LyDialog from "@/components/dialog/dialog.vue"
import { ElMessage, formItemContextKey } from 'element-plus'
import { deepClone,formatUnitSize } from "@/utils/util"
const emits = defineEmits(['refreshData','closed'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('')
let path = ref("")
let uploadFileList = ref([])
let uploadHelperText = ref("")
let uploadPrecent = ref(0)
let lyUploadRef = ref(null)
let uploadType = ref("file")
let uploadElement = ref(null)
let tableContainer = ref(null)
let uploadOk = ref(false)
let timeDifference = ref(0)
let avgSpeed = ref("")
function handleClose() {
emits('refreshData')
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
let tempdata = deepClone(item)
path.value = tempdata.path
nextTick(()=>{
uploadElement.value = document.querySelector('.el-upload__input');
})
}
function handleReset(){
uploadOk.value = false
uploadFileList.value = []
timeDifference.value = 0
avgSpeed.value = ""
clearFiles()
}
function uploadClick(type){
uploadType.value = type
if(type == "file"){
uploadElement.value.webkitdirectory = false
}else{
uploadElement.value.webkitdirectory = true
}
lyUploadRef.value.$el.querySelector('input').click();
}
function clearFiles(){
lyUploadRef.value.clearFiles();
};
function removeFile(index){
uploadFileList.value.splice(index, 1);
};
function handleDragover(event){
event.preventDefault();
}
function handleDrop(event){
event.preventDefault();
const items = event.dataTransfer.items;
if (items) {
for (let i = 0; i < items.length; i++) {
const entry = items[i].webkitGetAsEntry();
if (entry) {
traverseFileTree(entry);
}
}
}
}
function handleDragleave(event){
event.preventDefault();
}
function traverseFileTree(item, path = ''){
path = path || ''
if (item.isFile) {
item.file((file) => {
uploadFileList.value.push(convertFileToUploadFile(file, path));
});
} else if (item.isDirectory) {
const dirReader = item.createReader();
dirReader.readEntries((entries) => {
for (let i = 0; i < entries.length; i++) {
traverseFileTree(entries[i], path + item.name + '/');
}
});
}
};
function convertFileToUploadFile(file, path){
const uid = Date.now();
const uploadRawFile = new File([file], file.name, {
type: file.type,
lastModified: file.lastModified,
});
uploadRawFile.uid = uid;
let fileName = file.name;
if (path != '') {
fileName = path + file.name;
}
return {
name: fileName,
size: file.size,
status: 'ready',
uid: uid,
raw: uploadRawFile,
};
};
function handleUploadOnChange(_uploadFile,uploadFiles){
//
const indexCF = uploadFiles.findIndex(f=>f.name===_uploadFile.name)
const lastIndexCF = uploadFiles.findLastIndex(f=>f.name===_uploadFile.name)
if(indexCF!=lastIndexCF){
if(_uploadFile.raw.webkitRelativePath == uploadFiles[indexCF].raw.webkitRelativePath){
ElMessage.error(_uploadFile.name +" 文件已存在")
uploadFiles.pop()
}
}
if (_uploadFile.size == 64 || _uploadFile.size == 0) {
uploadFileList.value = uploadFiles;
const reader = new FileReader();
reader.readAsDataURL(_uploadFile.raw);
reader.onload = async () => {};
reader.onerror = () => {
uploadFileList.value = uploadFileList.value.filter((file) => file.uid !== _uploadFile.uid);
ElMessage.error({
message: `${_uploadFile.name}】文件类型错误或为空文件夹`,
type: 'error',
showClose: true,
duration: 3000,
});
};
} else {
uploadFileList.value = uploadFiles;
}
scrollBottom()
}
function handleExceed(files){
lyUploadRef.value.clearFiles();
for (let i = 0; i < files.length; i++) {
const file = files[i];
lyUploadRef.value.handleStart(file);
}
}
function handleSuccess(res, file){
file.status = 'success';
}
let uploadedNums = ref(0)
async function scrollTop(topDistance){
nextTick(()=>{
topDistance = topDistance || 0
if(topDistance == 0){
tableContainer.value.setScrollTop(0)
}
else{
tableContainer.value.setScrollTop(topDistance)
}
})
}
async function scrollToRow(index){
// const row = uploadFileList.value[index]
//
// tableContainer.value.setCurrentRow(row)
//
nextTick(() => {
tableContainer.value.setScrollTop(38 * index)
})
}
async function scrollBottom(){
nextTick(()=>{
// let tableBodyHeight = tableContainer.value.scrollBarRef.wrapRef.offsetHeight
// tableContainer.value.scrollBarRef.setScrollTop(tableBodyHeight)
nextTick(() => {
tableContainer.value.setScrollTop(38 * uploadFileList.value.length)
})
})
}
function getPathNoFilename(path){
return path ? path.split('/').slice(0, -1).join('/') : path;
}
function averageUploadSpeed(timediff){
let totalsize = 0
for (let i = 0; i < uploadFileList.value.length; i++) {
totalsize = totalsize + uploadFileList.value[i].size
}
const avgSpeed = totalsize / timediff
return formatUnitSize(avgSpeed)
}
async function submitData() {
// await scrollTop(0)
await scrollToRow(1)
const startTime = new Date()
loadingSave.value=true
uploadedNums.value = 0
const files = uploadFileList.value.slice();
for (let i = 0; i < files.length; i++) {
const file = files[i];
const fileSize = file.size;
uploadHelperText.value = `${file.name}】开始上传`
if (fileSize <= 1024 * 1024 * 5) {
const formData = new FormData();
formData.append('lyfile', file.raw);
if (file.raw.webkitRelativePath != '') {
formData.append('path', path.value + '/' + getPathNoFilename(file.raw.webkitRelativePath));
} else {
formData.append('path', path.value + '/' + getPathNoFilename(file.name));
}
// uploadPrecent.value = 0;
await uploadFile({url:"/api/sys/fileManage/upload/",formData:formData}, (progress) => {
// uploadPrecent.value = Math.round((progress.loaded / progress.total) * 100);
uploadFileList.value[i].percentage = Math.round((progress.loaded / progress.total) * 100);
})
.then((res) => {
//
if(res.code === 2000){
uploadedNums.value ++
uploadFileList.value[i].status = 'success';
}else{
uploadedNums.value ++
uploadFileList.value[i].status = res.msg;
}
})
.catch((error) => {
//
ElMessage({
message: error,
grouping: true,
type: 'warning',
})
uploadedNums.value ++
uploadFileList.value[i].status = error;
});
}else{
// const chunkSize = 1024 * 1024 * 5;
const chunkSize = 1024 * 1024 * 5;
const chunkCount = Math.ceil(fileSize / chunkSize);
let uploadedChunkCount = 0;
uploadPrecent.value = 0;
for (let c = 0; c < chunkCount; c++) {
const start = c * chunkSize;
const end = Math.min(start + chunkSize, fileSize);
const chunk = file.raw.slice(start, end);
const formData = new FormData();
formData.append('lyfilechunk', file.name);
if (file.raw.webkitRelativePath != '') {
formData.append('path', path.value + '/' + getPathNoFilename(file.raw.webkitRelativePath));
} else {
formData.append('path', path.value + '/' + getPathNoFilename(file.name));
}
formData.append('chunkSize', chunkSize);
formData.append('totalSize', fileSize);
formData.append('chunk', chunk);
formData.append('chunkIndex', c.toString());
formData.append('chunkCount', chunkCount.toString());
let chunkok = true
await uploadFile({url:"/api/sys/fileManage/upload/",formData:formData}, (progress) => {
// uploadPrecent.value = Math.round(
// ((uploadedChunkCount + progress.loaded / progress.total) * 100) / chunkCount,
// );
uploadFileList.value[i].percentage = Math.round(
((uploadedChunkCount + progress.loaded / progress.total) * 100) / chunkCount,
);
})
.then((res) => {
//
if(res.code === 2000){
chunkok = true
}else{
uploadFileList.value[i].status = res.msg;
chunkok = false
}
})
.catch((error) => {
//
ElMessage({
message: error,
grouping: true,
type: 'warning',
})
uploadFileList.value[i].status = error;
chunkok = false
});
if(!chunkok){
break
}
uploadedChunkCount++;
if (uploadedChunkCount == chunkCount) {
uploadedNums.value++;
uploadFileList.value[i].status = 'success';
break;
}
}
}
if (i == files.length - 1) {
loadingSave.value = false;
uploadHelperText.value = '';
uploadOk.value = true
if (uploadedNums.value == files.length) {
const endTime = new Date();
const difftime = (endTime - startTime) / 1000 ; //
timeDifference.value = difftime.toFixed(2)
avgSpeed.value = averageUploadSpeed(timeDifference.value)
}
}
// starttop= starttop+40
// await scrollTop(starttop)
await scrollToRow(i+1)
}
loadingSave.value=false
}
defineExpose({
handleOpen
})
</script>
<style scoped>
.handle-button{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.handle-drag{
display: flex;
align-items: center;
justify-content: center;
height: 10em;
}
.uploadfooterbt{
display: flex;
align-items: center;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,141 @@
<template>
<div>
<LyDialog v-model="dialogVisible" :title="loadingTitle" width="560px" :before-close="handleClose">
<el-form :inline="false" :model="formData" :rules="rules" ref="rulesForm" label-position="top" label-width="auto">
<el-form-item label="类型:" prop="zip_type">
<el-radio-group v-model="formData.zip_type" @change="handleZipChange">
<el-radio value="zip">zip</el-radio>
<el-radio value="tar">tar.gz</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="名称:(注意同名文件会被覆盖)" prop="name">
<el-input v-model="formData.name">
<template #append>{{ formData.ext }}</template>
</el-input>
</el-form-item>
<el-form-item label="压缩保存路径:" prop="path">
<el-input v-model="formData.path">
<template #prepend>
<el-button @click="chooseDir" icon="Folder"></el-button>
</template>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :loading="loadingSave">取消</el-button>
<el-button type="primary" @click="submitData(false)" :loading="loadingSave">压缩</el-button>
<el-button type="primary" @click="submitData(true)" :loading="loadingSave">压缩并下载</el-button>
</template>
</LyDialog>
<lyFileList ref="chooseDirShowFlag" @change="handleChooseChange" v-if="isChooseDirShow" @closed="isChooseDirShow=false"></lyFileList>
</div>
</template>
<script setup>
import { ref,nextTick } from 'vue';
import {sysFileManage} from "@/api/api"
import LyDialog from "@/components/dialog/dialog.vue"
import lyFileList from "@/components/file/fileList.vue"
import { ElMessage } from 'element-plus'
import { deepClone } from "@/utils/util"
const emits = defineEmits(['refreshData','closed','download'])
let dialogVisible = ref(false)
let loadingSave = ref(false)
let loadingTitle = ref('创建文件')
let formData = ref({
zip_type:"zip",
name:"",
ext:".zip",
path:"",
spath:[],
})
let isChooseDirShow = ref(false)
let chooseDirShowFlag = ref(null)
let rules = ref({
zip_type: [
{required: true, message: '请选择压缩格式',trigger: 'blur'}
],
name: [
{required: true, message: '请输入压缩后名称',trigger: 'blur'}
],
path: [
{required: true, message: '请输入压缩后保存路径',trigger: 'blur'}
],
})
function handleClose() {
emits('closed')
}
function handleOpen(item,flag) {
loadingTitle.value=flag
dialogVisible.value=true
if(item){
let tempdata = deepClone(item)
formData.value.path = tempdata.path
formData.value.spath = tempdata.selectPath
formData.value.name = tempdata.name
}
}
function chooseDir(){
isChooseDirShow.value = true
nextTick(()=>{
chooseDirShowFlag.value.handleOpen({path:formData.value.path,isDir:true},"目录选择")
})
}
function handleZipChange(e){
if(formData.value.zip_type == 'zip'){
formData.value.ext = '.zip'
}else if(formData.value.zip_type == 'tar'){
formData.value.ext = '.tar.gz'
}else{
formData.value.ext = '.zip'
}
}
function handleChooseChange(path){
let newpath = deepClone(path)
formData.value.path = newpath
}
let rulesForm = ref(null)
function submitData(download=false) {
rulesForm.value.validate(obj=>{
if(obj) {
loadingSave.value=true
let filename = formData.value.path+"/"+formData.value.name+formData.value.ext
let param = {
path:filename,
spath:formData.value.spath,
action:'batch_operate',
type:"zip",
zip_type:formData.value.zip_type
}
sysFileManage(param).then(res=>{
loadingSave.value=false
if(res.code ==2000) {
ElMessage.success(res.msg)
if(download){
emits('download',{name:formData.value.name+formData.value.ext,path:filename})
}
emits('refreshData')
handleClose()
} else {
ElMessage.warning(res.msg)
}
})
}
})
}
defineExpose({
handleOpen
})
</script>

Some files were not shown because too many files have changed in this diff Show More