38 Commits

Author SHA1 Message Date
meilin.huang
e4d949a64b fix: machine file bug 2024-12-26 12:17:58 +08:00
zongyangleo
3f6fb5afef !129 fix: db 相关bug
* fix: db 相关bug
2024-12-26 04:11:28 +00:00
meilin.huang
68f553f4b0 refactor: remove router、ioc is adjusted to inject by type 2024-12-16 23:29:18 +08:00
meilin.huang
7f2a49ba3c fix: 机器文件内容写入导致内容清空、feat: ioc支持根据类型注入 2024-12-13 12:15:24 +08:00
meilin.huang
e56788af3e refactor: dbm 2024-12-08 13:04:23 +08:00
meilin.huang
ebc89e056f fix: fixed some minor issues 2024-11-28 20:11:16 +08:00
zongyangleo
d07cd74a8c !127 fix: 达梦特殊字段类型覆盖解析
* fix: 达梦特殊字段类型覆盖解析
2024-11-28 10:44:49 +00:00
meilin.huang
6cc15ebeda feat: tag refactor & other 2024-11-26 17:32:44 +08:00
zongyangleo
2b712cd548 !126 feat: 解析达梦特殊字段
* feat: 解析达梦特殊字段
2024-11-26 04:04:09 +00:00
meilin.huang
cda2963e1c fix: i18n & other optimizations 2024-11-23 17:23:18 +08:00
meilin.huang
bffa9c2676 fix: i18n 2024-11-21 20:12:16 +08:00
meilin.huang
9366a76b84 release: v1.9.1 2024-11-21 19:02:15 +08:00
meilin.huang
e03ceecd9a fix: redis set command causes ttl loss 2024-11-21 13:20:45 +08:00
meilin.huang
99a746085b feat: i18n 2024-11-20 22:43:53 +08:00
meilin.huang
74ae031853 refactor: dbm重构、调整metadata与dialect接口 2024-11-01 17:27:22 +08:00
meilin.huang
af14be9801 refactor: 前端文件夹名称调整 2024-10-31 17:24:56 +08:00
meilin.huang
df7413a9ea fix: base.app事务方法调整 2024-10-30 19:59:45 +08:00
meilin.huang
e967e02095 fix: 数据库实例删除,事务问题 2024-10-30 12:17:55 +08:00
meilin.huang
c1d09f447d fix: pgsql查询列信息解析问题修复等 2024-10-28 12:13:41 +08:00
meilin.huang
5e5afd49dc fix: sql缺少字段补充、dockefile调整等 2024-10-24 11:56:22 +08:00
meilin.huang
2118acf244 release: v1.9.0 2024-10-23 17:30:05 +08:00
meilin.huang
44a1bd626e feat: 数据库迁移至文件支持文件保存天数等 2024-10-22 20:39:44 +08:00
meilin.huang
ea3c70a8a8 feat: 新增统一文件模块,统一文件操作 2024-10-21 22:27:42 +08:00
zongyangleo
6343173cf8 !124 一些更新和bug
* fix: 代码合并
* feat:支持数据库版本兼容,目前兼容了oracle11g部分特性
* fix: 修改数据同步bug,数据sql里指定修改字段别,导致未正确记录修改字段值
* feat: 数据库迁移支持定时迁移和迁移到sql文件
2024-10-20 03:52:23 +00:00
meilin.huang
6837a9c867 fix: sql切割转义等问题处理 2024-10-18 17:15:58 +08:00
meilin.huang
a726927a28 feat: sql脚本执行调整 2024-10-18 12:32:53 +08:00
meilin.huang
e135e4ce64 feat: sql解析器替换、工单统一由‘我的流程’发起、流程定义支持自定义条件触发审批、资源隐藏编号、model支持物理删除等 2024-10-16 17:24:50 +08:00
zongyangleo
43edef412c !123 一些bug修复
* fix: 数据同步、数据迁移体验优化
* fix: des加密传输sql
* fix: 修复达梦字段注释显示问题
* fix: mysql timestamp 字段类型导出ddl错误修复
2024-08-22 00:43:39 +00:00
meilin.huang
2deb3109c2 feat: dbms表数据新增表单视图 2024-07-19 17:06:11 +08:00
meilin.huang
a80221a950 fix: 数据库实例删除等问题修复 2024-07-05 13:14:31 +08:00
meilin.huang
10630847df fix: 工单流程信息展示问题修复 2024-06-24 17:17:57 +08:00
meilin.huang
f43851698e fix: 资源关联多标签删除、数据库实例删除等问题修复与数据库等名称过滤优化 2024-06-07 12:31:40 +08:00
Coder慌
73884bb693 !122 fix: mysql导出修复
Merge pull request !122 from zongyangleo/dev_1.8.6_fix
2024-06-05 04:17:03 +00:00
zongyangleo
1b5bb1de8b fix: mysql导出修复 2024-06-01 13:35:31 +08:00
meilin.huang
4814793546 fix: 修复数据库表数据横向滚动后切换tab导致表头错位&数据取消居中显示 2024-05-31 12:12:40 +08:00
meilin.huang
d85bbff270 release v1.8.6 2024-05-23 17:18:22 +08:00
meilin.huang
bb1522f4dc refactor: 数据库管理迁移至数据库实例-库管理、机器管理-文件支持用户和组信息查看 2024-05-21 12:34:26 +08:00
zongyangleo
a7632fbf58 !121 fix: rdp ssh
* fix: rdp ssh
2024-05-21 04:06:13 +00:00
828 changed files with 416903 additions and 13403 deletions

View File

@@ -1,43 +1,26 @@
# 构建前端资源
FROM node:18-bookworm-slim as fe-builder
ARG BASEIMAGES=m.daocloud.io/docker.io/alpine:3.20.2
WORKDIR /mayfly
FROM ${BASEIMAGES} AS builder
ARG TARGETARCH
COPY mayfly_go_web .
ARG MAYFLY_GO_VERSION
ARG MAYFLY_GO_DIR_NAME=mayfly-go-linux-${TARGETARCH}
ARG MAYFLY_GO_URL=https://gitee.com/dromara/mayfly-go/releases/download/${MAYFLY_GO_VERSION}/${MAYFLY_GO_DIR_NAME}.zip
RUN yarn config set registry 'https://registry.npmmirror.com' && \
yarn install && \
yarn build
RUN wget -cO mayfly-go.zip ${MAYFLY_GO_URL} && \
unzip mayfly-go.zip && \
mv ${MAYFLY_GO_DIR_NAME}/* /opt
# 构建后端资源
FROM golang:1.22 as be-builder
ENV GOPROXY https://goproxy.cn
WORKDIR /mayfly
FROM ${BASEIMAGES}
# Copy the go source for building server
COPY server .
RUN go mod tidy && go mod download
COPY --from=fe-builder /mayfly/dist /mayfly/static/static
# Build
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
go build -a -ldflags=-w \
-o mayfly-go main.go
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y ca-certificates expat libncurses5 && \
apt-get clean
ENV TZ=Asia/Shanghai
ARG TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /mayfly
COPY --from=builder /opt/mayfly-go /usr/local/bin/mayfly-go
COPY --from=be-builder /mayfly/mayfly-go /usr/local/bin/mayfly-go
WORKDIR /mayfly-go
CMD ["mayfly-go"]
EXPOSE 18888
CMD ["mayfly-go"]

39
Dockerfile.sourcebuild Normal file
View File

@@ -0,0 +1,39 @@
# 构建前端资源
FROM m.daocloud.io/docker.io/node:18-bookworm-slim AS fe-builder
WORKDIR /mayfly
COPY frontend .
RUN yarn config set registry 'https://registry.npmmirror.com' && \
yarn install && \
yarn build
# 构建后端资源
FROM m.daocloud.io/docker.io/golang:1.23 AS be-builder
ENV GOPROXY https://goproxy.cn
WORKDIR /mayfly
# Copy the go source for building server
COPY server .
RUN go mod tidy && go mod download
COPY --from=fe-builder /mayfly/dist /mayfly/static/static
# Build
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
go build -a -ldflags=-w \
-o mayfly-go main.go
FROM m.daocloud.io/docker.io/alpine:3.20.2
ARG TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /mayfly-go
COPY --from=be-builder /mayfly/mayfly-go /usr/local/bin/mayfly-go
CMD ["mayfly-go"]

View File

@@ -1,4 +1,10 @@
# 🌈mayfly-go
# 🌈Dromara mayfly-go
<p align="center">
<a href="./README_EN.md">English</a> |
<a href="https://www.yuque.com/may-fly/mayfly-go">项目文档</a> |
<a href="https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854">操作视频</a> |
</p>
<p align="center">
<a href="https://gitee.com/dromara/mayfly-go" target="_blank">
@@ -20,30 +26,25 @@
</a>
</p>
### 介绍
## 前言
web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)数据库mysql postgres oracle sqlserver 达梦 高斯 sqlite数据同步 数据迁移redis(单机 哨兵 集群)mongo 等集工单流程审批于一体的统一管理操作平台**
web 版 **linux(终端[终端回放、命令过滤] 文件 脚本 进程 计划任务)数据库mysql postgres oracle sqlserver 达梦 高斯 sqlite数据操作、数据同步数据迁移redis(单机 哨兵 集群)mongo 等集工单流程审批于一体的统一管理操作平台**
### 开发语言与主要框架
## 开发语言与主要框架
- 前端typescript、vue3、element-plus
- 后端golang、gin、gorm
### 交流及问题反馈加 QQ 群
## 交流及问题反馈加 QQ 群
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=IdJSHW0jTMhmWFHBUS9a83wxtrxDDhFj&jump_from=webapi">119699946</a>
### 系统相关资料
- 项目文档: https://www.yuque.com/may-fly/mayfly-go
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
### 演示环境
## 演示环境
http://go.mayfly.run
账号/密码test/test123.
### 系统核心功能截图
## 系统核心功能截图
#### 首页
@@ -101,8 +102,8 @@ http://go.mayfly.run
![菜单资源管理](https://foruda.gitee.com/images/1714379321338009940/a00d6a02_1240250.png "屏幕截图")
**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
**其他更多功能&操作指南可查看上述项目文档**
#### 💌 支持作者
## 💌 支持作者
如果觉得项目不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/dromara/mayfly-go">Github</a> 或者 <a target="_blank" href="https://gitee.com/dromara/mayfly-go">Gitee</a> 帮我点个 ⭐ Star这将是对我极大的鼓励与支持。

105
README_EN.md Normal file
View File

@@ -0,0 +1,105 @@
# 🌈Dromara mayfly-go
<p align="center">
<a href="./README.md">中文介绍</a> |
<a href="https://www.yuque.com/may-fly/mayfly-go">Documentation</a> |
<a href="https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854">Operate Video</a>
</p>
<p align="center">
<a href="https://gitee.com/dromara/mayfly-go" target="_blank">
<img src="https://gitee.com/dromara/mayfly-go/badge/star.svg?theme=white" alt="star"/>
<img src="https://gitee.com/dromara/mayfly-go/badge/fork.svg" alt="fork"/>
</a>
<a href="https://github.com/dromara/mayfly-go" target="_blank">
<img src="https://img.shields.io/github/stars/dromara/mayfly-go.svg?style=social" alt="github star"/>
<img src="https://img.shields.io/github/forks/dromara/mayfly-go.svg?style=social" alt="github fork"/>
</a>
<a href="https://hub.docker.com/r/mayflygo/mayfly-go/tags" target="_blank">
<img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
</a>
<a href="https://github.com/golang/go" target="_blank">
<img src="https://img.shields.io/badge/Golang-1.22%2B-yellow.svg" alt="golang"/>
</a>
<a href="https://cn.vuejs.org" target="_blank">
<img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
</a>
</p>
## Preface
Browser-based management platform. **linux(Terminal [terminal playback, command filtering], file, script, process, cronjob), database (mysql, postgres, oracle, sqlserver, Dameng, gauss, sqlite) data operation, data synchronization, data migration, redis(standlone, sentinel, cluster), mongo and other unified management and operation platforms that integrate work order process approval.**
## Development languages and major frameworks
- frontendtypescript、vue3、element-plus
- backendgolang、gin、gorm
## Demo
http://go.mayfly.run
account/passwordtest/test123.
## Screenshots of core features
#### Home page
![首页](https://foruda.gitee.com/images/1714378104294194769/149fd257_1240250.png "屏幕截图")
#### Machine Operation
##### Status
![机器状态查看](https://foruda.gitee.com/images/1714378556642584686/93c46ec0_1240250.png "屏幕截图")
##### SSH Terminal
![终端操作](https://foruda.gitee.com/images/1714378353790214943/2864ba66_1240250.png "屏幕截图")
##### File Operation
![文件操作](https://foruda.gitee.com/images/1714378417206086701/74a188d8_1240250.png "屏幕截图")
![文件查看](https://foruda.gitee.com/images/1714378482611638688/7753faf6_1240250.png "屏幕截图")
#### Database Operation
##### SQL Editor
![sql编辑器](https://foruda.gitee.com/images/1714378747473077515/3c9387c0_1240250.png "屏幕截图")
##### Add, delete, update and check data online
![选表查数据](https://foruda.gitee.com/images/1714378625059063750/3951e5a8_1240250.png "屏幕截图")
#### Redis Operation
![redis操作](https://foruda.gitee.com/images/1714378855845451114/4c3f0097_1240250.png "屏幕截图")
#### Mongo Operation
![mongo操作](https://foruda.gitee.com/images/1714378916425714642/77fc0ed9_1240250.png "屏幕截图")
#### Work order process approval
![流程审批](https://foruda.gitee.com/images/1714379057627690037/ad136862_1240250.png "屏幕截图")
#### System Management
##### Account
![账号管理](https://foruda.gitee.com/images/1714379179491881231/c6d802ae_1240250.png "屏幕截图")
##### Role
![角色管理](https://foruda.gitee.com/images/1714379269408676381/6ac1e85c_1240250.png "屏幕截图")
##### Menu & Permission
![菜单资源管理](https://foruda.gitee.com/images/1714379321338009940/a00d6a02_1240250.png "屏幕截图")
**Additional features & instructions can be found in the project documentation above.**
## 💌 Supporting Author
If you think the project is good, or you are already using it, I hope you can go to <a target="_blank" href="https://github.com/dromara/mayfly-go">Github</a> to help me click ⭐ Star, which will be a great encouragement and support for me.

View File

@@ -8,7 +8,7 @@ project_path=`pwd`
# 构建后的二进制执行文件名
exec_file_name="mayfly-go"
# web项目目录
web_folder="${project_path}/mayfly_go_web"
web_folder="${project_path}/frontend"
# server目录
server_folder="${project_path}/server"
@@ -28,13 +28,13 @@ function buildWeb() {
cd ${web_folder}
copy2Server=$1
echo_yellow "-------------------打包前端开始-------------------"
echo_yellow "-------------------Start bundling frontends-------------------"
yarn run build
if [ "${copy2Server}" == "2" ] ; then
echo_green '将打包后的静态文件拷贝至server/static/static'
echo_green 'Copy the packaged static files to server/static/static'
rm -rf ${server_folder}/static/static && mkdir -p ${server_folder}/static/static && cp -r ${web_folder}/dist/* ${server_folder}/static/static
fi
echo_yellow ">>>>>>>>>>>>>>>>>>>打包前端结束<<<<<<<<<<<<<<<<<<<<\n"
echo_yellow ">>>>>>>>>>>>>>>>>>>End of packaging frontend<<<<<<<<<<<<<<<<<<<<\n"
}
function build() {
@@ -46,26 +46,27 @@ function build() {
arch=$3
copyDocScript=$4
echo_yellow "-------------------${os}-${arch}打包构建开始-------------------"
echo_yellow "-------------------Start a bundle build - ${os}-${arch}-------------------"
cd ${server_folder}
echo_green "打包构建可执行文件..."
echo_green "Package build executables..."
execFileName=${exec_file_name}
# 如果是windows系统,可执行文件需要添加.exe结尾
if [ "${os}" == "windows" ];then
execFileName="${execFileName}.exe"
fi
go mod tidy
CGO_ENABLE=0 GOOS=${os} GOARCH=${arch} go build -ldflags=-w -o ${execFileName} main.go
if [ -d ${toFolder} ] ; then
echo_green "目标文件夹已存在,清空文件夹"
echo_green "The desired folder already exists. Clear the folder"
sudo rm -rf ${toFolder}
fi
echo_green "创建'${toFolder}'目录"
echo_green "Create '${toFolder}' Directory"
mkdir ${toFolder}
echo_green "移动二进制文件至'${toFolder}'"
echo_green "Move binary to '${toFolder}'"
mv ${server_folder}/${execFileName} ${toFolder}
# if [ "${copy2Server}" == "1" ] ; then
@@ -74,16 +75,17 @@ function build() {
# fi
if [ "${copyDocScript}" == "1" ] ; then
echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、mayfly-go.sqlite、readme.txt、startup.sh、shutdown.sh]"
echo_green "Copy resources such as scripts [config.yml.example、mayfly-go.sql、mayfly-go.sqlite、readme.txt、startup.sh、shutdown.sh]"
cp ${server_folder}/config.yml.example ${toFolder}
cp ${server_folder}/readme.txt ${toFolder}
cp ${server_folder}/readme_en.txt ${toFolder}
cp ${server_folder}/resources/script/startup.sh ${toFolder}
cp ${server_folder}/resources/script/shutdown.sh ${toFolder}
cp ${server_folder}/resources/script/sql/mayfly-go.sql ${toFolder}
cp ${server_folder}/resources/data/mayfly-go.sqlite ${toFolder}
fi
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
echo_yellow ">>>>>>>>>>>>>>>>>>> ${os}-${arch} - Bundle build complete <<<<<<<<<<<<<<<<<<<<\n"
}
function buildLinuxAmd64() {
@@ -103,25 +105,25 @@ function buildMac() {
}
function buildDocker() {
echo_yellow "-------------------构建docker镜像开始-------------------"
echo_yellow "-------------------Start building the docker image-------------------"
imageVersion=$1
imageName="mayflygo/mayfly-go:${imageVersion}"
docker build --platform linux/amd64 -t "${imageName}" .
echo_green "docker镜像构建完成->[${imageName}]"
echo_yellow "-------------------构建docker镜像结束-------------------"
imageName="mayfly/mayfly-go:${imageVersion}"
docker build --no-cache --platform linux/amd64 --build-arg MAYFLY_GO_VERSION="${imageVersion}" -t "${imageName}" .
echo_green "The docker image is built -> [${imageName}]"
echo_yellow "-------------------Finished building the docker image-------------------"
}
function buildxDocker() {
echo_yellow "-------------------docker buildx构建镜像开始-------------------"
echo_yellow "-------------------The docker buildx build image starts-------------------"
imageVersion=$1
imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
echo_green "docker多版本镜像构建完成->[${imageName}]"
echo_yellow "-------------------docker buildx构建镜像结束-------------------"
docker buildx build --no-cache --push --platform linux/amd64,linux/arm64 --build-arg MAYFLY_GO_VERSION="${imageVersion}" -t "${imageName}" .
echo_green "The docker multi-architecture version image is built -> [${imageName}]"
echo_yellow "-------------------The docker buildx image is finished-------------------"
}
function runBuild() {
read -p "请选择构建版本[0|其他->除docker镜像外其他 1->linux-amd64 2->linux-arm64 3->windows 4->mac 5->docker 6->docker buildx]: " buildType
read -p "Select build version [0 | Other->Other than docker image 1->linux-amd64 2->linux-arm64 3->windows 4->mac 5->docker 6->docker buildx]: " buildType
toPath="."
imageVersion="latest"
@@ -129,16 +131,16 @@ function runBuild() {
if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
# 构建结果的目的路径
read -p "请输入构建产物输出目录[默认当前路径]: " toPath
read -p "Please enter the build product output directory [default current path]: " toPath
if [ ! -d ${toPath} ] ; then
echo_red "构建产物输出目录不存在!"
echo_red "Build product output directory does not exist!"
exit;
fi
if [ "${toPath}" == "" ] ; then
toPath="."
fi
read -p "是否拷贝文档&脚本[0-> 1->是][默认是]: " copyDocScript
read -p "Whether to copy documents & Scripts [0-> No 1-> Yes][Default yes]: " copyDocScript
if [ "${copyDocScript}" == "" ] ; then
copyDocScript="1"
fi
@@ -154,7 +156,7 @@ function runBuild() {
fi
if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
read -p "请输入docker镜像版本号[默认latest]: " imageVersion
read -p "Please enter the docker image version (default latest) : " imageVersion
if [ "${imageVersion}" == "" ] ; then
imageVersion="latest"
@@ -189,7 +191,7 @@ function runBuild() {
esac
if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
echo_green "删除['${server_folder}/static/static']下静态资源文件."
echo_green "Delete static assets under ['${server_folder}/static/static']."
# 删除静态资源文件保留一个favicon.ico否则后端启动会报错
rm -rf ${server_folder}/static/static/assets
rm -rf ${server_folder}/static/static/config.js

View File

@@ -5,4 +5,6 @@ VITE_PORT = 8889
VITE_OPEN = false
# public path 配置线上环境路径(打包)
VITE_PUBLIC_PATH = ''
VITE_PUBLIC_PATH = ''
VITE_EDITOR=idea

21
frontend/index.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<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" />
<meta name="keywords" content="mayfly-go" />
<meta name="description" content="" />
<link type="favicon" rel="shortcut icon" href="favicon.ico" />
<title>mayfly-go</title>
</head>
<body>
<div id="app"></div>
<script type="application/javascript" src="./config.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

72
frontend/package.json Normal file
View File

@@ -0,0 +1,72 @@
{
"name": "mayfly-go-frontend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"build-preview": "npm run build && npm run preview",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@vueuse/core": "^12.0.0",
"asciinema-player": "^3.8.1",
"axios": "^1.6.2",
"clipboard": "^2.0.11",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"element-plus": "^2.9.1",
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"mitt": "^3.0.1",
"monaco-editor": "^0.52.2",
"monaco-sql-languages": "^0.12.2",
"monaco-themes": "^0.4.4",
"nprogress": "^0.2.0",
"pinia": "^2.3.0",
"qrcode.vue": "^3.6.0",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.6",
"splitpanes": "^3.1.5",
"sql-formatter": "^15.4.5",
"trzsz": "^1.1.5",
"uuid": "^9.0.1",
"vue": "^3.5.13",
"vue-i18n": "^10.0.5",
"vue-router": "^4.5.0",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
"xterm-addon-search": "^0.13.0",
"xterm-addon-web-links": "^0.9.0"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
"@types/lodash": "^4.14.178",
"@types/node": "^18.14.0",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/compiler-sfc": "^3.5.13",
"code-inspector-plugin": "^0.4.5",
"dotenv": "^16.3.1",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.31.0",
"prettier": "^3.2.5",
"sass": "^1.82.0",
"typescript": "^5.7.2",
"vite": "^6.0.3",
"vue-eslint-parser": "^9.4.3"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,24 +1,26 @@
<template>
<div class="h100">
<el-watermark
:zIndex="10000000"
:width="210"
v-if="themeConfig.isWatermark"
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
:content="themeConfig.watermarkText"
class="h100"
>
<router-view v-show="themeConfig.lockScreenTime !== 0" />
</el-watermark>
<router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<div class="h100">
<el-watermark
:zIndex="10000000"
:width="210"
v-if="themeConfig.isWatermark"
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
:content="themeConfig.watermarkText"
class="h100"
>
<router-view v-show="themeConfig.lockScreenTime !== 0" />
</el-watermark>
<router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
</div>
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
</div>
</el-config-provider>
</template>
<script setup lang="ts" name="app">
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
@@ -26,6 +28,10 @@ import LockScreen from '@/layout/lockScreen/index.vue';
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
import mittBus from '@/common/utils/mitt';
import { useIntervalFn } from '@vueuse/core';
import { useI18n } from 'vue-i18n';
import EnumValue from './common/Enum';
import { I18nEnum } from './common/commonEnum';
import { saveThemeConfig } from './common/utils/storage';
const setingsRef = ref();
const route = useRoute();
@@ -33,6 +39,9 @@ const route = useRoute();
const themeConfigStores = useThemeConfig();
const { themeConfig } = storeToRefs(themeConfigStores);
//
const { locale, t } = useI18n();
//
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
@@ -67,6 +76,31 @@ watch(
}
);
watch(
() => themeConfig.value.globalI18n,
(val) => {
locale.value = val;
}
);
watch(
themeConfig,
(val) => {
saveThemeConfig(val);
},
{ deep: true }
);
//
const getGlobalComponentSize = computed(() => {
return themeConfig.value.globalComponentSize;
});
// i18n
const getGlobalI18n = computed(() => {
return EnumValue.getEnumByValue(I18nEnum, locale.value)?.extra.el;
});
//
const { pause, resume } = useIntervalFn(() => {
if (!themeConfig.value.isWatermark) {
@@ -96,7 +130,7 @@ watch(
() => route.path,
() => {
nextTick(() => {
document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
document.title = `${t((route.meta.title as string) || '')} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
});
}
);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,121 @@
{
"id": "3953964",
"name": "mayfly-go",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "2967035",
"name": "符号-英文",
"font_class": "fuhao-yingwen",
"unicode": "e712",
"unicode_decimal": 59154
},
{
"icon_id": "26283783",
"name": "符号-中文",
"font_class": "fuhao-zhongwen",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "23957582",
"name": "MongoDB",
"font_class": "mongo",
"unicode": "e646",
"unicode_decimal": 58950
},
{
"icon_id": "4969649",
"name": "Redis",
"font_class": "op-redis",
"unicode": "e728",
"unicode_decimal": 59176
},
{
"icon_id": "22442993",
"name": "PostgreSQL",
"font_class": "op-postgres",
"unicode": "e8b7",
"unicode_decimal": 59575
},
{
"icon_id": "12295203",
"name": "达梦数据库",
"font_class": "db-dm",
"unicode": "e6f0",
"unicode_decimal": 59120
},
{
"icon_id": "10055634",
"name": "云数据库MongoDB",
"font_class": "op-mongo",
"unicode": "e7d7",
"unicode_decimal": 59351
},
{
"icon_id": "10055642",
"name": "云数据库 RDS MySQL",
"font_class": "op-mysql",
"unicode": "e7d8",
"unicode_decimal": 59352
},
{
"icon_id": "3876165",
"name": "redis",
"font_class": "redis",
"unicode": "e619",
"unicode_decimal": 58905
},
{
"icon_id": "25271976",
"name": "oracle",
"font_class": "oracle",
"unicode": "e507",
"unicode_decimal": 58631
},
{
"icon_id": "8105644",
"name": "mariadb",
"font_class": "mariadb",
"unicode": "e513",
"unicode_decimal": 58643
},
{
"icon_id": "13601813",
"name": "sqlite",
"font_class": "sqlite",
"unicode": "e546",
"unicode_decimal": 58694
},
{
"icon_id": "29340317",
"name": "temp-mssql",
"font_class": "MSSQLNATIVE",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "7699332",
"name": "gaussdb",
"font_class": "gauss",
"unicode": "e683",
"unicode_decimal": 59011
},
{
"icon_id": "34836637",
"name": "kingbase",
"font_class": "kingbase",
"unicode": "e882",
"unicode_decimal": 59522
},
{
"icon_id": "33047500",
"name": "vastbase",
"font_class": "vastbase",
"unicode": "e62b",
"unicode_decimal": 58923
}
]
}

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -69,7 +69,7 @@ class Api {
*/
async xhrReq(param: any = null, options: any = {}): Promise<any> {
if (this.beforeHandler) {
this.beforeHandler(param);
await this.beforeHandler(param);
}
return request.xhrReq(this.method, this.url, param, options);
}

View File

@@ -1,3 +1,5 @@
import { i18n } from '@/i18n';
/**
*
*/
@@ -28,7 +30,22 @@ export function isTrue(condition: boolean, msg: string) {
* @param msg
*/
export function notBlank(obj: any, msg: string) {
isTrue(obj, msg);
if (obj == null || obj == undefined || obj == '') {
throw new AssertError(msg);
}
if (Array.isArray(obj) && obj.length == 0) {
throw new AssertError(msg);
}
}
/**
* null,0,''
*
* @param obj
* @param field i18n msgKey
*/
export function notBlankI18n(obj: any, field: string) {
notBlank(obj, i18n.global.t('common.fieldNotEmpty', { field: i18n.global.t(field) }));
}
/**
@@ -65,3 +82,13 @@ export function notEmpty(str: string, msg: string) {
throw new AssertError(msg);
}
}
/**
*
*
* @param str
* @param field i18n msgKey
*/
export function notEmptyI18n(str: string, field: string) {
notEmpty(str, i18n.global.t('common.fieldNotEmpty', { field: i18n.global.t(field) }));
}

View File

@@ -0,0 +1,40 @@
import EnumValue from './Enum';
// element plus 自带国际化
import zhcnLocale from 'element-plus/es/locale/lang/zh-cn';
import enLocale from 'element-plus/es/locale/lang/en';
// i18n
export const I18nEnum = {
ZhCn: EnumValue.of('zh-cn', '简体中文').setExtra({ icon: 'iconfont icon-fuhao-zhongwen', el: zhcnLocale }),
En: EnumValue.of('en', 'English').setExtra({ icon: 'iconfont icon-fuhao-yingwen', el: enLocale }),
};
// 资源类型
export const ResourceTypeEnum = {
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor', iconColor: 'var(--el-color-primary)' }).tagTypeSuccess(),
Db: EnumValue.of(2, '数据库实例').setExtra({ icon: 'Coin', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(),
};
// 标签关联的资源类型
export const TagResourceTypeEnum = {
PublicAuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
Machine: ResourceTypeEnum.Machine,
DbInstance: ResourceTypeEnum.Db,
Redis: ResourceTypeEnum.Redis,
Mongo: ResourceTypeEnum.Mongo,
AuthCert: EnumValue.of(5, '授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
Db: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
};
// 标签关联的资源类型路径
export const TagResourceTypePath = {
MachineAuthCert: `${TagResourceTypeEnum.Machine.value}/${TagResourceTypeEnum.AuthCert.value}`,
DbInstanceAuthCert: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}`,
Db: `${TagResourceTypeEnum.DbInstance.value}/${TagResourceTypeEnum.AuthCert.value}/${TagResourceTypeEnum.Db.value}`,
};

View File

@@ -15,7 +15,7 @@ const config = {
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本
version: 'v1.8.5',
version: 'v1.9.2',
};
export default config;

View File

@@ -0,0 +1,38 @@
import CryptoJS from 'crypto-js';
import { getToken } from '@/common/utils/storage';
/**
* AES 加密数据
* @param word
* @param key
*/
export function AesEncrypt(word: string, key?: string) {
if (!key) {
key = getToken().substring(0, 24);
}
const sKey = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(word, sKey, {
iv: sKey,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}
export function AesDecrypt(word: string, key?: string): string {
if (!key) {
key = getToken().substring(0, 24);
}
const sKey = CryptoJS.enc.Utf8.parse(key);
// key 和 iv 使用同一个值
const decrypted = CryptoJS.AES.decrypt(word, sKey, {
iv: sKey,
mode: CryptoJS.mode.CBC, // CBC算法
padding: CryptoJS.pad.Pkcs7, //使用pkcs7 进行padding 后端需要注意
});
return decrypted.toString(CryptoJS.enc.Base64);
}

View File

@@ -6,7 +6,8 @@ export default {
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
getPublicKey: () => request.get('/common/public-key'),
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
oauth2LoginConfig: () => request.get('/auth/oauth2-config'),
getServerConf: () => request.get('/sys/configs/server'),
oauth2LoginConfig: () => request.get('/auth/oauth2/config'),
changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
captcha: () => request.get('/sys/captcha'),
logout: () => request.post('/auth/accounts/logout'),
@@ -14,4 +15,5 @@ export default {
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
getLdapEnabled: () => request.get('/auth/ldap/enabled'),
ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
getFileDetail: (keys: string[]) => request.get(`/sys/files/detail/${keys.join(',')}`),
};

View File

@@ -0,0 +1,11 @@
import { i18n } from '@/i18n';
export const AccountUsernamePattern = {
pattern: /^[a-zA-Z0-9_]{5,16}$/g,
message: i18n.global.t('system.account.usernamePatternErrMsg'),
};
export const ResourceCodePattern = {
pattern: /^[a-zA-Z0-9_\-.:]{1,32}$/g,
message: i18n.global.t('system.menu.resourceCodePatternErrMsg'),
};

View File

@@ -209,6 +209,36 @@ export function joinClientParams(): string {
return `token=${getToken()}&clientId=${getClientId()}`;
}
/**
* url地址
* @param key key
* @returns url
*/
export function getFileUrl(key: string) {
return `${baseUrl}/sys/files/${key}`;
}
/**
* url
* @param key key
* @returns url
*/
export function getUploadFileUrl(key: string = '') {
return `${baseUrl}/sys/files/upload?token=${getToken()}&fileKey=${key}`;
}
/**
*
* @param key key
*/
export function downloadFile(key: string) {
const a = document.createElement('a');
a.setAttribute('href', `${getFileUrl(key)}`);
a.setAttribute('target', '_blank');
a.click();
a.remove();
}
function parseResult(result: Result) {
if (result.code === ResultEnum.SUCCESS) {
return result.data;

View File

@@ -73,6 +73,15 @@ export async function getMachineConfig(): Promise<any> {
}
}
/**
*
*
* @returns
*/
export async function getServerConf(): Promise<any> {
return openApi.getServerConf();
}
/**
*
*

View File

@@ -0,0 +1,47 @@
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
import syssocket from './syssocket';
import { h, reactive } from 'vue';
import { ElNotification } from 'element-plus';
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
export function initSysMsgs() {
registerDbSqlExecProgress();
}
const sqlExecNotifyMap: Map<string, any> = new Map();
function registerDbSqlExecProgress() {
syssocket.registerMsgHandler('execSqlFileProgress', function (message: any) {
const content = JSON.parse(message.msg);
const id = content.id;
let progress = sqlExecNotifyMap.get(id);
if (content.terminated) {
if (progress != undefined) {
progress.notification?.close();
sqlExecNotifyMap.delete(id);
progress = undefined;
}
return;
}
if (progress == undefined) {
progress = {
props: reactive(buildProgressProps()),
notification: undefined,
};
}
progress.props.progress.title = content.title;
progress.props.progress.executedStatements = content.executedStatements;
if (!sqlExecNotifyMap.has(id)) {
progress.notification = ElNotification({
duration: 0,
title: message.title,
message: h(ProgressNotify, progress.props),
type: syssocket.getMsgType(message.type),
showClose: false,
});
sqlExecNotifyMap.set(id, progress);
}
});
}

View File

@@ -1,9 +1,9 @@
import Config from './config';
import { ElNotification } from 'element-plus';
import SocketBuilder from './SocketBuilder';
import { getToken } from '@/common/utils/storage';
import { joinClientParams } from './request';
import { ElNotification } from 'element-plus';
class SysSocket {
/**
@@ -19,7 +19,7 @@ class SysSocket {
/**
*
*/
messageTypes = {
messageTypes: any = {
0: 'error',
1: 'success',
2: 'info',
@@ -56,11 +56,14 @@ class SysSocket {
return;
}
// 默认通知处理
const type = this.getMsgType(message.type);
let msg = message.msg;
let duration = 0;
ElNotification({
duration: 0,
duration: duration,
title: message.title,
message: message.msg,
message: msg,
type: type,
});
})

View File

@@ -69,7 +69,7 @@ export function convertToBytes(sizeStr: string) {
* @returns
*/
export function formatTime(time: number, unit: string = 's') {
const units = {
const units: any = {
y: 31536000,
M: 2592000,
d: 86400,

View File

@@ -9,7 +9,19 @@ export function getValueByPath(obj: any, path: string) {
const keys = path.split('.');
let result = obj;
for (let key of keys) {
if (!result || typeof result !== 'object') {
if (!result) {
return undefined;
}
// 如果是字符串则尝试使用json解析
if (typeof result == 'string') {
try {
result = JSON.parse(result);
} catch (e) {
console.error(e);
return undefined;
}
}
if (typeof result !== 'object') {
return undefined;
}
@@ -23,7 +35,18 @@ export function getValueByPath(obj: any, path: string) {
}
const index = parseInt(matchIndex[1]);
result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
let arrValue = result[arrayKey];
if (typeof arrValue == 'string') {
try {
arrValue = JSON.parse(arrValue);
} catch (e) {
result = undefined;
break;
}
}
result = Array.isArray(arrValue) ? arrValue[index] : undefined;
} else {
result = result[key];
}

View File

@@ -97,43 +97,6 @@ export function getTextWidth(str: string) {
return width;
}
/**
*
*/
export function getContentWidth(content: any): number {
if (!content) {
return 50;
}
// 以下分配的单位长度可根据实际需求进行调整
let flexWidth = 0;
for (const char of content) {
if (flexWidth > 500) {
break;
}
if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
// 小写字母、数字字符
flexWidth += 9.3;
continue;
}
if (char >= 'A' && char <= 'Z') {
flexWidth += 9;
continue;
}
if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符为字符分配16个单位宽度
flexWidth += 20;
} else {
// 其他种类字符
flexWidth += 8;
}
}
// if (flexWidth > 450) {
// // 设置最大宽度
// flexWidth = 450;
// }
return flexWidth;
}
/**
*
* @returns uuid
@@ -179,3 +142,75 @@ export async function copyToClipboard(txt: string, selector: string = '#copyValu
clipboard.destroy();
});
}
export function fuzzyMatchField(keyword: string, fields: any[], ...valueExtractFuncs: Function[]) {
keyword = keyword?.toLowerCase();
return fields.filter((field) => {
for (let valueExtractFunc of valueExtractFuncs) {
const value = valueExtractFunc(field)?.toLowerCase();
if (isPrefixSubsequence(keyword, value)) {
return true;
}
}
return false;
});
}
/**
* targetTemplate=username prefix=uname -> trueprefix=uname2 -> false
* @param prefix ,
* @param targetTemplate
* @returns
*/
export function isPrefixSubsequence(prefix: string, targetTemplate: string) {
let i = 0; // 指向prefix的索引
let j = 0; // 指向targetTemplate的索引
while (i < prefix.length && j < targetTemplate.length) {
if (prefix[i] === targetTemplate[j]) {
// 字符匹配,两个指针都向前移动
i++;
}
j++; // 目标字符串指针始终向前移动
}
// 如果prefix的所有字符都被找到返回true
return i === prefix.length;
}
/**
*
* @param length
*/
export function randomPassword(length = 10) {
const lowerCase = 'abcdefghijklmnopqrstuvwxyz';
const upperCase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numbers = '0123456789';
const specialChars = '!@#$%^&*()-_=+[]{}|;:,.<>?';
// 确保每个类别至少包含一个字符
let password = [getRandomChar(lowerCase), getRandomChar(upperCase), getRandomChar(numbers), getRandomChar(specialChars)];
// 剩余字符从所有字符集中随机选择
const allChars = lowerCase + upperCase + numbers + specialChars;
for (let i = 4; i < length; i++) {
password.push(getRandomChar(allChars));
}
// 打乱数组顺序以增加随机性
shuffleArray(password);
return password.join('');
}
function getRandomChar(charSet: string) {
const randomIndex = Math.floor(Math.random() * charSet.length);
return charSet[randomIndex];
}
function shuffleArray(array: string[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}

View File

@@ -4,6 +4,7 @@ export interface ViteEnv {
VITE_PORT: number;
VITE_OPEN: boolean;
VITE_PUBLIC_PATH: string;
VITE_EDITOR: string;
}
export function loadEnv(): ViteEnv {

View File

@@ -16,7 +16,7 @@
:is="`el-option`"
v-for="(col, index) in item.options"
:key="index"
:label="col[fieldNames.label]"
:label="$t(col[fieldNames.label])"
:value="col[fieldNames.value]"
></component>
</template>
@@ -28,6 +28,9 @@
<script setup lang="ts" name="SearchFormItem">
import { computed } from 'vue';
import { SearchItem } from '../index';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
interface SearchFormItemProps {
item: SearchItem;
@@ -70,7 +73,7 @@ const handleEvents = computed(() => {
// placeholder
const placeholder = computed(() => {
const search = props.item;
const label = search.label;
const label = t(search.label);
if (['datetimerange', 'daterange', 'monthrange'].includes(search?.props?.type) || search?.props?.isRange) {
return {
rangeSeparator: search?.props?.rangeSeparator ?? '至',
@@ -78,7 +81,9 @@ const placeholder = computed(() => {
endPlaceholder: search?.props?.endPlaceholder ?? '结束时间',
};
}
const placeholder = search?.props?.placeholder ?? (search?.type?.includes('input') ? `请输入${label}` : `请选择${label}`);
return { placeholder };
const placeholder =
search?.props?.placeholder ?? (search?.type?.includes('input') ? t('common.pleaseInput', { label }) : t('common.pleaseSelect', { label }));
return { placeholder: t(placeholder) };
});
</script>

View File

@@ -6,7 +6,7 @@
<el-form-item>
<template #label>
<el-space :size="4">
<span>{{ `${item?.label}` }}</span>
<span>{{ `${$t(item?.label)}` }}</span>
<el-tooltip v-if="item.tooltip" :content="item?.tooltip" placement="top">
<SvgIcon name="QuestionFilled" />
</el-tooltip>
@@ -21,8 +21,8 @@
</GridItem>
<GridItem suffix>
<div class="operation">
<el-button type="primary" :icon="Search" @click="search" plain> 搜索 </el-button>
<el-button :icon="Delete" @click="reset"> 重置 </el-button>
<el-button type="primary" :icon="Search" @click="search" plain> {{ $t('common.search') }} </el-button>
<el-button :icon="Delete" @click="reset"> {{ $t('common.reset') }} </el-button>
<el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
{{ collapsed ? '展开' : '合并' }}
<el-icon class="el-icon--right">
@@ -83,7 +83,7 @@ const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
// /
const showCollapse = computed(() => {
let show = false;
props.items.reduce((prev, current) => {
props.items.reduce((prev, current: any) => {
prev += (current![breakPoint.value]?.span ?? current?.span ?? 1) + (current![breakPoint.value]?.offset ?? current?.offset ?? 0);
if (typeof props.searchCol !== 'number') {
if (prev >= props.searchCol[breakPoint.value]) show = true;

View File

@@ -14,11 +14,11 @@ export function hasPerm(code: string) {
/**
* code
* @param perms { save: "xxx:save"}
* @returns {"xxx:save": true} key->permission code
* @param permCodes
*/
export function hasPerms(permCodes: any[]) {
const res = {};
const res = {} as { [key: string]: boolean };
for (let permCode of permCodes) {
if (hasPerm(permCode)) {
res[permCode] = true;

View File

@@ -22,7 +22,7 @@
@click="onCurrentContextmenuClick(v)"
>
<SvgIcon :name="v.icon" />
<span>{{ v.txt }}</span>
<span>{{ $t(v.txt) }}</span>
</li>
</template>
</ul>

View File

@@ -1,77 +1,81 @@
<template>
<div class="crontab">
<el-tabs v-model="state.activeName" @tab-change="changeTab(state.activeName)" type="border-card">
<el-tab-pane label="" name="second" v-if="shouldHide('second')">
<el-tab-pane :label="$t('components.crontab.second')" name="second" v-if="shouldHide('second')">
<CrontabSecond :cron="crontabValueObj" ref="secondRef" />
</el-tab-pane>
<el-tab-pane label="分钟" name="min" v-if="shouldHide('min')">
<el-tab-pane :label="$t('components.crontab.minute')" name="min" v-if="shouldHide('min')">
<CrontabMin :cron="crontabValueObj" ref="minRef" />
</el-tab-pane>
<el-tab-pane label="小时" name="hour" v-if="shouldHide('hour')">
<el-tab-pane :label="$t('components.crontab.hour')" name="hour" v-if="shouldHide('hour')">
<CrontabHour :cron="crontabValueObj" ref="hourRef" />
</el-tab-pane>
<el-tab-pane label="" name="day" v-if="shouldHide('day')">
<el-tab-pane :label="$t('components.crontab.day')" name="day" v-if="shouldHide('day')">
<CrontabDay :cron="crontabValueObj" ref="dayRef" />
</el-tab-pane>
<el-tab-pane label="" name="mouth" v-if="shouldHide('mouth')">
<el-tab-pane :label="$t('components.crontab.month')" name="mouth" v-if="shouldHide('mouth')">
<CrontabMouth :cron="crontabValueObj" ref="mouthRef" />
</el-tab-pane>
<el-tab-pane label="" name="week" v-if="shouldHide('week')">
<el-tab-pane :label="$t('components.crontab.week')" name="week" v-if="shouldHide('week')">
<CrontabWeek :cron="crontabValueObj" ref="weekRef" />
</el-tab-pane>
<el-tab-pane label="" name="year" v-if="shouldHide('year')">
<el-tab-pane :label="$t('components.crontab.year')" name="year" v-if="shouldHide('year')">
<CrontabYear :cron="crontabValueObj" ref="yearRef" />
</el-tab-pane>
</el-tabs>
<div class="popup-main">
<div class="popup-result">
<p class="title">时间表达式</p>
<p class="title">{{ $t('components.crontab.timeExpression') }}</p>
<table>
<thead>
<th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
<th>crontab完整表达式</th>
<tr>
<th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
<th>{{ $t('components.crontab.crontabCompleteExpression') }}</th>
</tr>
</thead>
<tbody>
<td>
<span>{{ crontabValueObj.second }}</span>
</td>
<td>
<span>{{ crontabValueObj.min }}</span>
</td>
<td>
<span>{{ crontabValueObj.hour }}</span>
</td>
<td>
<span>{{ crontabValueObj.day }}</span>
</td>
<td>
<span>{{ crontabValueObj.mouth }}</span>
</td>
<td>
<span>{{ crontabValueObj.week }}</span>
</td>
<td>
<span>{{ crontabValueObj.year }}</span>
</td>
<td>
<span>{{ contabValueString }}</span>
</td>
<tr>
<td>
<span>{{ crontabValueObj.second }}</span>
</td>
<td>
<span>{{ crontabValueObj.min }}</span>
</td>
<td>
<span>{{ crontabValueObj.hour }}</span>
</td>
<td>
<span>{{ crontabValueObj.day }}</span>
</td>
<td>
<span>{{ crontabValueObj.mouth }}</span>
</td>
<td>
<span>{{ crontabValueObj.week }}</span>
</td>
<td>
<span>{{ crontabValueObj.year }}</span>
</td>
<td>
<span>{{ crontabValueString }}</span>
</td>
</tr>
</tbody>
</table>
</div>
<CrontabResult :ex="contabValueString"></CrontabResult>
<CrontabResult :ex="crontabValueString"></CrontabResult>
<div class="pop_btn">
<el-button size="small" @click="hidePopup">取消</el-button>
<el-button size="small" type="warning" @click="clearCron">重置</el-button>
<el-button size="small" type="primary" @click="submitFill">确定</el-button>
<el-button size="small" @click="hidePopup">{{ $t('common.cancel') }}</el-button>
<el-button size="small" type="warning" @click="clearCron">{{ $t('common.reset') }}</el-button>
<el-button size="small" type="primary" @click="submitFill">{{ $t('common.confirm') }}</el-button>
</div>
</div>
</div>
@@ -202,7 +206,7 @@ function hidePopup() {
//
const submitFill = () => {
emit('fill', contabValueString.value);
emit('fill', crontabValueString.value);
hidePopup();
};
@@ -220,7 +224,7 @@ const clearCron = () => {
changeTab(state.activeName);
};
const contabValueString = computed(() => {
const crontabValueString = computed(() => {
let obj = state.crontabValueObj;
let str = obj.second + ' ' + obj.min + ' ' + obj.hour + ' ' + obj.day + ' ' + obj.mouth + ' ' + obj.week + (obj.year == '' ? '' : ' ' + obj.year);
return str;

View File

@@ -1,42 +1,45 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * / L M] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.day') }}{{ $t('components.crontab.dayCrontype1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2"> 不指定 </el-radio>
<el-radio v-model="radioValue" :label="2"> {{ $t('components.crontab.crontype2') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
周期从
<el-input-number v-model="cycle01" :min="0" :max="31" /> - <el-input-number v-model="cycle02" :min="0" :max="31" />
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="31" /> - <el-input-number v-model="cycle02" :min="0" :max="31" />
{{ $t('components.crontab.day') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="4">
<el-input-number v-model="average01" :min="0" :max="31" /> 号开始,每 <el-input-number v-model="average02" :min="0" :max="31" /> 日执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="31" /> {{ $t('components.crontab.crontypeStartDay') }}
{{ $t('components.crontab.crontypeEvery') }} <el-input-number v-model="average02" :min="0" :max="31" />
{{ $t('components.crontab.crontypeExecDay') }}
</el-radio>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-radio v-model="radioValue" :label="5">
每月
<el-input-number v-model="workday" :min="0" :max="31" /> 号最近的那个工作日
</el-radio>
</el-form-item>
</el-form-item> -->
<el-form-item>
<el-radio v-model="radioValue" :label="6"> 本月最后一天 </el-radio>
<el-radio v-model="radioValue" :label="6"> {{ $t('components.crontab.monthLastDay') }} </el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="7" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 7" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="7" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 7" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 31" :key="item" :value="`${item}`">{{ item }}</el-option>
</el-select>
</div>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 小时允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.hour') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" /> 小时
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.hour') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="0" :max="60" /> 小时开始,每 <el-input-number v-model="average02" :min="0" :max="60" /> 小时执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="60" /> {{ $t('components.crontab.crontypeStartHour') }}
{{ $t('components.crontab.crontypeEvery') }} <el-input-number v-model="average02" :min="0" :max="60" />
{{ $t('components.crontab.crontypeExecHour') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 60" :key="item" :value="`${item - 1}`">{{ item - 1 }}</el-option>
</el-select>
</div>

View File

@@ -1,10 +1,10 @@
<template>
<el-input v-model="cron" placeholder="可点击左边按钮配置">
<el-input v-model="cron" :placeholder="$t('components.crontab.crontabInputPlaceholder')">
<template #prepend>
<el-button @click="showCron = true" icon="Pointer"></el-button>
</template>
</el-input>
<el-dialog title="生成 cron" v-model="showCron" width="600px">
<el-dialog :title="$t('components.crontab.crontabTitle')" v-model="showCron" width="660px">
<Crontab :expression="cron" @hide="showCron = false" @fill="(ex: any) => (cron = ex)" />
</el-dialog>
</template>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 分钟允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.minute') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" /> 分钟
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.minute') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="0" :max="60" /> 分钟开始,每 <el-input-number v-model="average02" :min="0" :max="60" /> 分钟执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="60" /> {{ $t('components.crontab.crontypeStartMin') }}
{{ $t('components.crontab.crontypeEvery') }} <el-input-number v-model="average02" :min="0" :max="60" />
{{ $t('components.crontab.crontypeExecMin') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 60" :key="item" :value="`${item - 1}`">{{ item - 1 }}</el-option>
</el-select>
</div>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.month') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="1" :max="12" /> - <el-input-number v-model="cycle02" :min="1" :max="12" />
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="1" :max="12" /> - <el-input-number v-model="cycle02" :min="1" :max="12" />
{{ $t('components.crontab.month') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="1" :max="12" /> 月开始,每 <el-input-number v-model="average02" :min="1" :max="12" /> 月月执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="1" :max="12" /> {{ $t('components.crontab.crontypeStartMonth') }}
{{ $t('components.crontab.crontypeEvery') }}
<el-input-number v-model="average02" :min="1" :max="12" /> {{ $t('components.crontab.crontypeExecMonth') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 12" :key="item" :value="`${item}`">{{ item }}</el-option>
</el-select>
</div>

View File

@@ -1,11 +1,11 @@
<template>
<div class="popup-result">
<p class="title">最近5次运行时间</p>
<p class="title">{{ $t('components.crontab.last5runTimes') }}</p>
<ul class="popup-result-scroll">
<template v-if="isShow">
<li v-for="item in resultList" :key="item">{{ item }}</li>
</template>
<li v-else>计算结果中...</li>
<li v-else>{{ $t('components.crontab.calculationing') }}...</li>
</ul>
</div>
</template>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.second') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.second') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="0" :max="60" /> 秒开始,每 <el-input-number v-model="average02" :min="0" :max="60" /> 秒执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="60" /> {{ $t('components.crontab.crontypeStartSecond') }}
{{ $t('components.crontab.crontypeEvery') }}<el-input-number v-model="average02" :min="0" :max="60" />
{{ $t('components.crontab.crontypeExecSecond') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 60" :key="item" :value="`${item - 1}`">{{ item - 1 }}</el-option>
</el-select>
</div>

View File

@@ -1,22 +1,22 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * / L #] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.weekCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2"> 不指定 </el-radio>
<el-radio v-model="radioValue" :label="2"> {{ $t('components.crontab.crontype2') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
周期从星期
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="1" :max="7" /> -
<el-input-number v-model="cycle02" :min="1" :max="7" />
</el-radio>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-radio v-model="radioValue" :label="4">
<el-input-number v-model="average01" :min="1" :max="4" /> 周的星期
@@ -29,13 +29,13 @@
本月最后一个星期
<el-input-number v-model="weekday" :min="1" :max="7" />
</el-radio>
</el-form-item>
</el-form-item> -->
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="6" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 6" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-option v-for="(item, index) of weekList" :label="item" :key="index" :value="`${index + 1}`">{{ item }}</el-option>
<el-radio v-model="radioValue" :label="6" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 6" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="(item, index) of weekList" :label="item" :key="index" :value="`${index + 1}`">{{ $t(item) }}</el-option>
</el-select>
</div>
</el-form-item>
@@ -56,7 +56,15 @@ const state = reactive({
average01: 1,
average02: 1,
checkboxList: [] as any,
weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
weekList: [
'components.crontab.monday',
'components.crontab.tuesday',
'components.crontab.wednesday',
'components.crontab.thursday',
'components.crontab.friday',
'components.crontab.saturday',
'components.crontab.sunday',
],
});
const { radioValue, cycle01, cycle02, average01, average02, checkboxList, weekday, weekList } = toRefs(state);

View File

@@ -1,16 +1,16 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio :label="1" v-model="radioValue"> 不填允许的通配符[, - * /] </el-radio>
<el-radio :label="1" v-model="radioValue"> {{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="2" v-model="radioValue"> 每年 </el-radio>
<el-radio :label="2" v-model="radioValue"> {{ $t('components.crontab.yearly') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="3" v-model="radioValue">
周期从
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="fullYear" /> -
<el-input-number v-model="cycle02" :min="fullYear" />
</el-radio>
@@ -18,15 +18,17 @@
<el-form-item>
<el-radio :label="4" v-model="radioValue">
<el-input-number v-model="average01" :min="fullYear" /> 年开始,每 <el-input-number v-model="average02" :min="fullYear" /> 年执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="fullYear" /> {{ $t('components.crontab.crontypeStartYear') }}
{{ $t('components.crontab.crontypeEvery') }}
<el-input-number v-model="average02" :min="fullYear" /> {{ $t('components.crontab.crontypeExecYear') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="5" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 5" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="5" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 5" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 9" :key="item" :value="`${item - 1 + fullYear}`" :label="item - 1 + fullYear" />
</el-select>
</div>

View File

@@ -1,10 +1,18 @@
<template>
<div class="dynamic-form">
<el-form v-bind="$attrs" ref="formRef" :model="modelValue" label-width="auto">
<el-form-item v-for="item in props.formItems as any" :key="item.name" :prop="item.model" :label="item.name" :required="item.required ?? true">
<el-input v-if="!item.options" v-model="modelValue[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-form-item v-for="item in props.formItems as any" :key="item.name" :prop="item.model" :label="$t(item.name)" :required="item.required ?? true">
<el-input v-if="!item.options" v-model="modelValue[item.model]" :placeholder="$t(item.placeholder)" autocomplete="off" clearable></el-input>
<el-select v-else v-model="modelValue[item.model]" :placeholder="item.placeholder" filterable autocomplete="off" clearable style="width: 100%">
<el-select
v-else
v-model="modelValue[item.model]"
:placeholder="$t(item.placeholder)"
filterable
autocomplete="off"
clearable
style="width: 100%"
>
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
</el-select>
</el-form-item>

View File

@@ -6,8 +6,8 @@
<template #footer>
<span>
<slot name="btns">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="confirm"> </el-button>
<el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="confirm">{{ $t('common.confirm') }}</el-button>
</slot>
</span>
</template>

View File

@@ -1,41 +1,41 @@
<template>
<div class="dynamic-form-edit w100">
<el-table :data="formItems" stripe class="w100" empty-text="暂无表单项">
<el-table :data="formItems" stripe class="w100">
<el-table-column prop="name" label="model" min-width="100px">
<template #header>
<el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addItem()"> </el-button>
<span class="ml10">model</span>
<span class="ml10">model field</span>
</template>
<template #default="scope">
<el-input v-model="scope.row['model']" placeholder="字段model" clearable> </el-input>
<el-input v-model="scope.row['model']" :placeholder="$t('components.df.fieldModelPlaceholder')" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="name" label="label" min-width="100px">
<el-table-column prop="name" :label="$t('components.df.fieldLabel')" min-width="100px">
<template #default="scope">
<el-input v-model="scope.row['name']" placeholder="字段title" clearable> </el-input>
<el-input v-model="scope.row['name']" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="placeholder" label="字段说明" min-width="140px">
<el-table-column prop="placeholder" :label="$t('components.df.fieldPlaceholder')" min-width="140px">
<template #default="scope">
<el-input v-model="scope.row['placeholder']" placeholder="字段说明" clearable> </el-input>
<el-input v-model="scope.row['placeholder']" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="options" label="可选值" min-width="140px">
<el-table-column prop="options" :label="$t('components.df.optionalValues')" min-width="140px">
<template #default="scope">
<el-input v-model="scope.row['options']" placeholder="可选值 ,分割" clearable> </el-input>
<el-input v-model="scope.row['options']" :placeholder="$t('components.df.optionalValuesPlaceholder')" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="required" label="必填" min-width="40px">
<el-table-column prop="required" :label="$t('components.df.required')" min-width="65px">
<template #default="scope">
<el-checkbox v-model="scope.row['required']" />
</template>
</el-table-column>
<el-table-column label="操作" wdith="20px">
<el-table-column :label="$t('common.operation')" wdith="20px">
<template #default="scope">
<el-button type="danger" @click="deleteItem(scope.$index)" icon="delete" plain></el-button>
</template>

View File

@@ -0,0 +1,17 @@
<template>
<el-select v-bind="$attrs" v-model="modelValue">
<el-option v-for="item in props.enums" :key="item.value" :label="$t(item.label)" :value="item.value"> </el-option>
</el-select>
</template>
<script lang="ts" setup>
const props = defineProps({
enums: {
type: Object, // 需要为EnumValue类型
required: true,
},
});
const modelValue: any = defineModel('modelValue');
</script>
<style scoped lang="scss"></style>

View File

@@ -1,5 +1,5 @@
<template>
<el-tag v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
<el-tag :disable-transitions="true" v-bind="$attrs" :type="type" :color="color" effect="plain">{{ $t(enumLabel) }}</el-tag>
</template>
<script lang="ts" setup>

View File

@@ -0,0 +1,60 @@
<template>
<el-tooltip :content="formatByteSize(fileDetail?.size)" placement="left">
<el-link v-if="props.canDownload" target="_blank" rel="noopener noreferrer" icon="Download" type="primary" :href="getFileUrl(props.fileKey)"></el-link>
</el-tooltip>
{{ fileDetail?.filename }}
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import openApi from '@/common/openApi';
import { getFileUrl } from '@/common/request';
import { formatByteSize } from '@/common/utils/format';
const props = defineProps({
fileKey: {
type: String,
required: true,
},
files: {
type: [Array],
},
canDownload: {
type: Boolean,
default: true,
},
});
onMounted(async () => {
setFileInfo();
});
watch(
() => props.fileKey,
async (val) => {
if (val) {
setFileInfo();
}
}
);
const fileDetail: any = ref({});
const setFileInfo = async () => {
if (!props.fileKey) {
return;
}
if (props.files && props.files.length > 0) {
const file: any = props.files.find((file: any) => {
return file.fileKey === props.fileKey;
});
fileDetail.value = file;
return;
}
const files = await openApi.getFileDetail([props.fileKey]);
fileDetail.value = files?.[0];
};
</script>
<style lang="scss"></style>

View File

@@ -26,7 +26,7 @@
>
<template #default>
<div class="icon-selector-warp">
<div class="icon-selector-warp-title">{{ title }}</div>
<div class="ml10 mt10">{{ title }}</div>
<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
<el-tab-pane lazy label="ele" name="ele">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />

View File

@@ -196,6 +196,9 @@ watch(
(newValue: any) => {
if (!monacoEditorIns.hasTextFocus()) {
state.languageMode = props.language;
if (newValue == null) {
newValue = '';
}
monacoEditorIns?.setValue(newValue);
}
}
@@ -252,7 +255,9 @@ const changeLanguage = (value: any) => {
};
const setEditorValue = (value: any) => {
monacoEditorIns.getModel()?.setValue(value);
if (value) {
monacoEditorIns.getModel()?.setValue(value);
}
};
/**

View File

@@ -2,7 +2,8 @@
<div>
<transition name="el-zoom-in-top">
<!-- 查询表单 -->
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm" :search="search" :reset="reset" :search-col="searchCol">
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm" :search="search"
:reset="reset" :search-col="searchCol">
<!-- 遍历父组件传入的 solts 透传给子组件 -->
<template v-for="(_, key) in useSlots()" v-slot:[key]>
<slot :name="key"></slot>
@@ -27,54 +28,45 @@
<SvgIcon :size="16" name="CaretBottom" class="mr4 mt6 simple-search-form-btn" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="searchItem in searchItems"
:key="searchItem.prop"
@click="changeSimpleFormItem(searchItem)"
>
{{ searchItem.label }}
<el-dropdown-item v-for="searchItem in searchItems"
:key="searchItem.prop" @click="changeSimpleFormItem(searchItem)">
{{ $t(searchItem.label) }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="simple-search-form-label mt5">
<el-text truncated tag="b">{{ `${nowSearchItem?.label} : ` }}</el-text>
<el-text truncated tag="b">{{ `${$t(nowSearchItem?.label)} : ` }}</el-text>
</div>
<el-form-item style="width: 200px" :key="nowSearchItem.prop">
<SearchFormItem
@keyup.enter.native="searchFormItemKeyUpEnter"
v-if="!nowSearchItem.slot"
:item="nowSearchItem"
v-model="queryForm[nowSearchItem.prop]"
/>
<SearchFormItem @keyup.enter.native="searchFormItemKeyUpEnter"
v-if="!nowSearchItem.slot" :item="nowSearchItem"
v-model="queryForm[nowSearchItem.prop]" />
<slot @keyup.enter.native="searchFormItemKeyUpEnter" v-else :name="nowSearchItem.slot"></slot>
<slot @keyup.enter.native="searchFormItemKeyUpEnter" v-else
:name="nowSearchItem.slot">
</slot>
</el-form-item>
</div>
<div>
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search" circle @click="search" />
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search"
circle @click="search" />
<!-- <el-button v-if="showToolButton('refresh')" icon="Refresh" circle @click="execQuery()" /> -->
<el-button
v-if="showToolButton('search') && searchItems?.length > 1"
:icon="isShowSearch ? 'ArrowDown' : 'ArrowUp'"
circle
@click="isShowSearch = !isShowSearch"
/>
<el-button v-if="showToolButton('search') && searchItems?.length > 1"
:icon="isShowSearch ? 'ArrowDown' : 'ArrowUp'" circle
@click="isShowSearch = !isShowSearch" />
<el-popover
placement="bottom"
title="表格配置"
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
width="auto"
trigger="click"
>
<el-popover placement="bottom" title="表格配置"
popper-style="max-height: 550px; overflow: auto; max-width: 450px" width="auto"
trigger="click">
<div v-for="(item, index) in tableColumns" :key="index">
<el-checkbox v-model="item.show" :label="item.label" :true-value="true" :false-value="false" />
<el-checkbox v-model="item.show" :label="item.label" :true-value="true"
:false-value="false" />
</div>
<template #reference>
<el-button icon="Operation" circle :size="props.size"></el-button>
@@ -86,33 +78,16 @@
</div>
</div>
<el-table
ref="tableRef"
v-bind="$attrs"
:max-height="tableMaxHeight"
@selection-change="handleSelectionChange"
:data="tableData"
highlight-current-row
v-loading="loading"
:size="props.size as any"
:border="border"
>
<el-table ref="tableRef" v-bind="$attrs" :max-height="tableMaxHeight"
@selection-change="handleSelectionChange" :data="tableData" highlight-current-row
v-loading="loading" :size="props.size as any" :border="border">
<el-table-column v-if="props.showSelection" :selectable="selectable" type="selection" width="40" />
<template v-for="(item, index) in tableColumns">
<el-table-column
:key="index"
v-if="item.show"
:prop="item.prop"
:label="item.label"
:fixed="item.fixed"
:align="item.align"
:show-overflow-tooltip="item.showOverflowTooltip"
:min-width="item.minWidth"
:sortable="item.sortable || false"
:type="item.type"
:width="item.width"
>
<el-table-column :key="index" v-if="item.show" :prop="item.prop" :label="$t(item.label)"
:fixed="item.fixed" :align="item.align" :show-overflow-tooltip="item.showOverflowTooltip"
:min-width="item.minWidth" :sortable="item.sortable || false" :type="item.type"
:width="item.width">
<!-- 插槽预留功能 -->
<template #default="scope" v-if="item.slot">
<slot :name="item.slotName ? item.slotName : item.prop" :data="scope.row"></slot>
@@ -120,29 +95,21 @@
<!-- 枚举类型使用tab展示 -->
<template #default="scope" v-else-if="item.type == 'tag'">
<enum-tag :size="props.size" :enums="item.typeParam" :value="item.getValueByData(scope.row)"></enum-tag>
<enum-tag :size="props.size" :enums="item.typeParam"
:value="item.getValueByData(scope.row)"></enum-tag>
</template>
<template #default="scope" v-else>
<!-- 配置了美化文本按钮以及文本内容大于指定长度则显示美化按钮 -->
<el-popover
v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
effect="light"
trigger="click"
placement="top"
width="600px"
>
<el-popover v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
effect="light" trigger="click" placement="top" width="600px">
<template #default>
<el-input :autosize="{ minRows: 3, maxRows: 15 }" disabled v-model="formatVal" type="textarea" />
<el-input :autosize="{ minRows: 3, maxRows: 15 }" disabled v-model="formatVal"
type="textarea" />
</template>
<template #reference>
<el-link
@click="formatText(item.getValueByData(scope.row))"
:underline="false"
type="success"
icon="MagicStick"
class="mr5"
></el-link>
<el-link @click="formatText(item.getValueByData(scope.row))" :underline="false"
type="success" icon="MagicStick" class="mr5"></el-link>
</template>
</el-popover>
@@ -154,17 +121,10 @@
</div>
<el-row v-if="props.pageable" class="mt20" type="flex" justify="end">
<el-pagination
:small="props.size == 'small'"
@current-change="handlePageNumChange"
@size-change="handlePageSizeChange"
style="text-align: right"
layout="prev, pager, next, total, sizes"
:total="total"
v-model:current-page="queryForm.pageNum"
v-model:page-size="queryForm.pageSize"
:page-sizes="pageSizes"
/>
<el-pagination :small="props.size == 'small'" @current-change="pageNumChange"
@size-change="pageSizeChange" style="text-align: right" layout="prev, pager, next, total, sizes"
:total="total" v-model:current-page="queryForm.pageNum" v-model:page-size="queryForm.pageSize"
:page-sizes="pageSizes" />
</el-row>
</div>
</div>
@@ -185,7 +145,8 @@ import SvgIcon from '@/components/svgIcon/index.vue';
import { usePageTable } from '@/hooks/usePageTable';
import { ElTable } from 'element-plus';
const emit = defineEmits(['update:selectionData', 'pageChange']);
const emit = defineEmits(['update:selectionData', 'pageSizeChange', 'pageNumChange']);
export interface PageTableProps {
size?: string;
@@ -257,6 +218,15 @@ const changeSimpleFormItem = (searchItem: SearchItem) => {
nowSearchItem.value = searchItem;
};
const pageSizeChange = (val: number) => {
emit('pageSizeChange', val);
handlePageSizeChange(val);
};
const pageNumChange = (val: number) => {
emit('pageNumChange', val);
handlePageNumChange(val);
};
let { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
props.pageable,
props.pageApi,
@@ -353,6 +323,7 @@ defineExpose({
tableRef: tableRef,
search: getTableData,
getData,
total,
});
</script>
<style scoped lang="scss">
@@ -367,6 +338,7 @@ defineExpose({
// header
.table-header {
width: 100%;
.header-button-lf {
float: left;
}
@@ -435,7 +407,7 @@ defineExpose({
// }
// el-table header
.el-table__header .el-table__cell > .cell {
.el-table__header .el-table__cell>.cell {
// white-space: nowrap;
white-space: wrap;
}

View File

@@ -2,6 +2,7 @@ import EnumValue from '@/common/Enum';
import { formatDate } from '@/common/utils/format';
import { getValueByPath } from '@/common/utils/object';
import { getTextWidth } from '@/common/utils/string';
import { i18n } from '@/i18n';
export class TableColumn {
/**
@@ -183,7 +184,7 @@ export class TableColumn {
*/
isEnum(enums: any): TableColumn {
this.setFormatFunc((data: any, prop: string) => {
return EnumValue.getLabelByValue(enums, getValueByPath(data, prop));
return i18n.global.t(EnumValue.getLabelByValue(enums, getValueByPath(data, prop)));
});
return this;
}
@@ -243,7 +244,7 @@ export class TableColumn {
// 需要加上表格的内间距等,视情况加
const contentWidth: number = getTextWidth(maxWidthText) + 30;
// 获取label的宽度取较大的宽度
const columnWidth: number = getTextWidth(label) + 60;
const columnWidth: number = getTextWidth(i18n.global.t(label)) + 60;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
// 设置上限与累加需要额外增加的宽度
this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;

View File

@@ -1,5 +1,5 @@
<template>
<el-descriptions border size="small" :title="`${progress.title}`">
<el-descriptions border size="small" :title="`${props.progress.title}`">
<el-descriptions-item label="时间">{{ state.elapsedTime }}</el-descriptions-item>
<el-descriptions-item label="已处理">{{ progress.executedStatements }}</el-descriptions-item>
</el-descriptions>

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