577 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
meilin.huang
c4cb4234fd fix: 问题修复 2024-05-16 17:26:32 +08:00
meilin.huang
89e12678eb refactor: 引入dayjs、新增refreshToken无感刷新、团队新增有效期、数据库等问题修复 2024-05-13 19:55:43 +08:00
meilin.huang
137ebb8e9e fix: 数据库表新增数据表单全必填问题修复等 2024-05-11 12:09:55 +08:00
meilin.huang
05625bd8c1 feat: 1.8.3 2024-05-10 19:59:49 +08:00
meilin.huang
4afeac5fdd refactor: 代码优化与数据库表列显示配置优化 2024-05-09 21:29:34 +08:00
meilin.huang
1d0e91f1af refactor: 机器计划任务与流程定义关联至标签 2024-05-08 21:04:25 +08:00
Coder慌
cf5111a325 !118 修复空数组分隔异常
Merge pull request !118 from 蒋小小/N/A
2024-05-07 11:59:12 +00:00
meilin.huang
78957a8ebd refactor: base.repo与app重构优化 2024-05-05 14:53:30 +08:00
meilin.huang
4ed892a656 feat: 文档更新与sqlite文件更新 2024-04-29 17:09:41 +08:00
meilin.huang
3486b07003 fix: 修复机器列表查询与放开vnc 2024-04-29 12:50:49 +08:00
meilin.huang
a5cd7caf19 refactor: base.repo与base.app精简优化 2024-04-29 12:29:56 +08:00
meilin.huang
f2c7ef78c0 refactor: 精简base.repo与base.app等 2024-04-28 23:45:57 +08:00
meilin.huang
653953ee76 feat: 机器新增命令过滤配置、首页功能完善(操作记录与快捷操作) 2024-04-27 01:35:21 +08:00
meilin.huang
a831614d5a fix: sql脚本问题修复等 2024-04-23 11:35:45 +08:00
meilin.huang
ebe73e2f19 feat: 标签支持拖拽移动与机器支持执行命令查看 2024-04-21 19:35:58 +08:00
蒋小小
29fd5a25d2 修复空数组分隔异常
Signed-off-by: 蒋小小 <bwcx_jzy@163.com>
2024-04-20 17:33:19 +00:00
zongyangleo
44805ce580 !116 fix: 新版本问题修复
* fix: 新版本问题修复
2024-04-19 11:27:29 +00:00
meilin.huang
2a6d620830 fix: 问题修复 2024-04-18 20:50:14 +08:00
meilin.huang
01d3e1ad28 refactor: 数据库实例与凭证关联至标签&其他问题修复重构等 2024-04-17 21:28:28 +08:00
meilin.huang
f4162c38db fix: 问题修复与redis密码迁移至凭证 2024-04-13 17:01:12 +08:00
zongyangleo
1a4626c24d !115 fix: 新版本问题修复
* fix: 新版本问题修复
2024-04-12 14:39:08 +00:00
meilin.huang
d6eb9683d1 fix: 新版本问题修复 2024-04-12 20:30:28 +08:00
meilin.huang
e2b524dadb feat: release1.8.0 2024-04-12 17:07:28 +08:00
zongyangleo
8998a21626 !114 feat:rdp优化,mssql迁移优化,term支持trzsz
* fix: 合并代码
* refactor: rdp优化,mssql迁移优化,term支持trzsz
2024-04-12 07:53:42 +00:00
meilin.huang
abc015aec0 refactor: 数据库授权凭证迁移 2024-04-12 13:24:20 +08:00
meilin.huang
4ef8d27b1e refactor: 授权凭证优化 2024-04-10 23:17:20 +08:00
meilin.huang
40b6e603fc reafctor: 团队管理与授权凭证优化 2024-04-10 13:04:31 +08:00
meilin.huang
21498584b1 refactor: 初步提交全局授权凭证-资源多账号改造 2024-04-09 12:55:51 +08:00
meilin.huang
408bac09a1 refactor: 标签资源重构 2024-04-06 18:19:17 +08:00
zongyangleo
582d879a77 !112 feat: 机器管理支持ssh+rdp连接win服务器
* feat: rdp 文件管理
* feat: 机器管理支持ssh+rdp连接win服务器
2024-04-06 04:03:38 +00:00
meilin.huang
38ff5152e0 refactor: dbms优化 2024-03-29 21:40:26 +08:00
meilin.huang
d1d372e1bf feat: 数据迁移新增实时日志&数据库游标遍历查询问题修复 2024-03-28 22:20:39 +08:00
Coder慌
5e4793433b !111 refactor:获取表索引,默认过滤主键索引
Merge pull request !111 from zongyangleo/dev_0327_fix
2024-03-27 13:06:45 +00:00
zongyangleo
54ad19f97e refactor:获取表索引,默认过滤主键索引 2024-03-27 08:26:12 +08:00
meilin.huang
fc166650b3 refactor: dbm重构等 2024-03-26 21:46:03 +08:00
zongyangleo
2acc295259 !110 feat: 支持各源数据库导出sql,数据库迁移部分bug修复
* feat: 各源数据库导出
* fix: 数据库迁移 bug修复
2024-03-26 09:05:28 +00:00
meilin.huang
4b3ed1310d refactor: dbms 2024-03-21 20:28:24 +08:00
meilin.huang
b2cfd1517c refactor: dbms与标签管理优化 2024-03-21 17:15:52 +08:00
zongyangleo
b13d27ccd6 !109 refactor:ddl生成方式重构,数据类型和长度重构,所有数据库迁移调试
* feat:同步sqlite全量sql
* refactor:ddl生成方式重构,数据类型和长度重构,所有数据库迁移调试
2024-03-21 03:35:18 +00:00
meilin.huang
68e0088016 refactor: dbms优化 2024-03-18 12:25:40 +08:00
zongyangleo
bd1e83989d !108 feat:支持不同源数据库迁移
* feat:支持不同源数据库迁移
2024-03-15 09:01:51 +00:00
meilin.huang
263dfa6be7 refactor: dbm包重构 2024-03-15 13:31:53 +08:00
meilin.huang
eb55f93864 refactor: dbm包重构 2024-03-11 20:04:20 +08:00
meilin.huang
8589105e44 feat: oracle支持服务名、数据库执行超时时间配置等 2024-03-07 17:26:11 +08:00
meilin.huang
986b187f0a feat: v1.7.4 2024-03-04 20:33:04 +08:00
zongyangleo
008d34c453 !107 feat:支持修改表名、注释,oracle bug修复
* fix:修复oracle查询数据参数超过1000错误 ORA-01795
* feat:支持右键重命名表
* feat:支持修改表名、表注释
2024-03-04 11:32:04 +00:00
meilin.huang
49d3f988c9 feat: redis支持工单流程审批 2024-03-02 19:08:19 +08:00
zongyangleo
76475e807e !106 feat:数据同步支持唯一键冲突策略
* refactor:sql同步
* fix: 表格右键导出菜单换行符修复
* feat:数据同步支持唯一键冲突策略
2024-03-01 04:03:03 +00:00
meilin.huang
f93231da61 feat: dbms新增支持工单流程审批 2024-02-29 22:12:50 +08:00
meilin.huang
bf75483a3c refactor: 简化api层相关调用 2024-02-25 12:46:18 +08:00
meilin.huang
b56b0187cf refactor: api层尽可能屏蔽gin框架相关代码 2024-02-24 16:30:29 +08:00
meilin.huang
7e7f02b502 refactor: 机器终端操作优化 2024-02-23 22:53:17 +08:00
meilin.huang
878985f7c5 refactor: 依赖版本升级等 2024-02-22 21:03:13 +08:00
meilin.huang
2133d9b737 fix: 终端操作col和row初始化问题修复 2024-02-18 18:42:25 +08:00
meilin.huang
d711a36749 feat: v1.7.3 2024-02-08 09:53:48 +08:00
meilin.huang
9dbf104ef1 refactor: 机器操作界面调整 2024-02-07 21:14:29 +08:00
zongyangleo
20eb06fb28 !101 feat: 新增机器操作菜单
* feat: 新增机器操作菜单
2024-02-07 06:37:59 +00:00
meilin.huang
9c20bdef39 Merge branch 'dev' of https://gitee.com/objs/mayfly-go into dev 2024-02-06 15:33:31 +08:00
zongyangleo
3fdd98a390 !99 feat: DBMS新增kingbaseES、vastbase,还有一些优化
* refactor: 重构机器列表展示
* fix:修复编辑表问题
* refactor: 优化下拉实例显示
* feat: DBMS新增kingbaseES(已测试postgres、oracle兼容模式) 、vastbase
2024-02-06 07:32:03 +00:00
meilin.huang
d4f456c0cf Merge branch 'dev' of https://gitee.com/objs/mayfly-go into dev 2024-02-06 15:17:39 +08:00
kanzihuang
f2b6e15cf4 !100 定时清理数据库备份数据
* feat: 优化数据库 BINLOG 同步机制
* feat: 删除数据库实例前需删除关联的数据库备份与恢复任务
* refactor: 重构数据库备份与恢复模块
* feat: 定时清理数据库备份历史和本地 Binlog 文件
* feat: 压缩数据库备份文件
2024-02-06 07:16:56 +00:00
meilin.huang
6be0ea6aed fix: dbms数据行编辑 2024-02-01 12:05:41 +08:00
meilin.huang
eee08be2cc feat: 数据库支持编辑行数据 2024-01-31 20:41:41 +08:00
meilin.huang
252fc553f2 feat: v1.7.2 2024-01-31 12:53:27 +08:00
meilin.huang
ac2ceed3f9 refactor: code review 2024-01-30 21:56:49 +08:00
kanzihuang
3f828cc5b0 !96 删除数据库备份和恢复历史
* feat: 删除数据库备份历史
* refactor dbScheduler
* feat: 从数据库备份历史中恢复数据库
* feat: 删除数据库恢复历史记录
* refactor dbScheuler
2024-01-30 13:12:43 +00:00
zongyangleo
fc1b9ef35d !97 一些优化
* refactor: 重构表格分页组件,适配大数据量分页
* fix:定时任务修复
* feat: gaussdb单独提出来
2024-01-30 13:09:26 +00:00
meilin.huang
d0b71a1c40 refactor: dialect使用方式调整 2024-01-29 16:02:28 +08:00
meilin.huang
a743a6a05a Merge branch 'master' into dev 2024-01-29 12:21:22 +08:00
zongyangleo
0e6b9713ce !93 feat: DBMS支持mssql和一些功能优化
* feat: 表格+表格元数据缓存
* feat:跳板机支持多段跳
* fix: 所有数据库区分字段主键和自增
* feat: DBMS支持mssql
* refactor: 去除无用的getter方法
2024-01-29 04:20:23 +00:00
meilin.huang
b9afbc764d refactor: 去除无用的getter方法 2024-01-29 11:34:48 +08:00
meilin.huang
923e183a67 refactor: code review 2024-01-26 17:17:26 +08:00
meilin.huang
7e9a381641 refactor: 数据库meta使用注册方式,方便可插拔 2024-01-24 17:01:17 +08:00
zongyangleo
bed95254d0 !91 fix: oracle数据同步 bug
* fix: oracle数据同步 bug
2024-01-24 08:29:16 +00:00
meilin.huang
e4d13f3377 refactor: 引入日志切割库、indexApi拆分等 2024-01-23 19:30:28 +08:00
Coder慌
d530365ef9 !90 fix: 依赖注入支持私有变量
Merge pull request !90 from kanzihuang/feat-db-bak
2024-01-23 09:02:37 +00:00
wanli
070d4ea104 fix: 依赖注入支持私有变量 2024-01-23 16:29:41 +08:00
zongyangleo
3fc86f0fae !88 feat: dbms表支持右键菜单:删除表、编辑表、新建表、复制表
* feat: 支持复制表
* feat: dbms表支持右键菜单:删除表、编辑表、新建表
2024-01-23 04:08:02 +00:00
kanzihuang
3b77ab2727 !89 feat: 给数据库备份和恢复配置操作权限
* feat: 给数据库备份和恢复配置操作权限
* refactor: 数据库备份与恢复采用最新依赖注入机制
2024-01-23 04:06:08 +00:00
meilin.huang
76cb991282 fix: 数据同步更新时间展示等问题 2024-01-23 09:27:05 +08:00
meilin.huang
9efd20f1b9 refactor: ioc与系统初始化处理方式调整 2024-01-22 11:35:28 +08:00
kanzihuang
de5b9e46d3 !87 fix: 修复数据库备份与恢复问题
* feat: 修复数据库备份与恢复问题
* feat: 启用 BINLOG 支持全量备份和增量备份,未启用 BINLOG 仅支持全量备份
* feat: 数据库恢复后自动备份,避免数据丢失
2024-01-22 03:12:16 +00:00
meilin.huang
f27d3d200f feat: 新增简易版ioc 2024-01-21 22:52:20 +08:00
meilin.huang
f4a64b96a9 feat: v1.7.1新增支持sqlite&oracle分页限制等问题修复 2024-01-19 21:33:37 +08:00
zongyangleo
9a59749763 !86 dbms支持sqlite和一些bug修复
* fix: 达梦数据库连接修复,以支持带特殊字符的密码和schema
* fix: oracle bug修复
* feat: dbms支持sqlite
* fix: dbms 修改字段名bug
2024-01-19 08:59:35 +00:00
kanzihuang
b017b902f8 !85 fix: 修复 BINLOG同步任务加载问题
* Merge branch 'dev' of gitee.com:dromara/mayfly-go into feat-db-bak
* fix: 修复 BINLOG 同步任务加载问题
2024-01-19 00:40:44 +00:00
meilin.huang
7c53353c60 fix: sqlite数据问题时间类型问题修复等 2024-01-18 17:18:17 +08:00
meilin.huang
63f0615445 feat: v1.7.0 2024-01-17 17:02:15 +08:00
kanzihuang
94da6df33e !84 fix: 修复数据库备份与恢复问题
* refactor dbScheduler
* fix: 按团队名称检索团队
* feat: 创建数据库资源时支持全选数据库
* refactor dbScheduler
* fix: 修复数据库备份与恢复问题
2024-01-17 08:37:22 +00:00
meilin.huang
cc3981d99c fix: 数据库编辑导致标签关联删除修复、cron组件调整 2024-01-17 12:13:18 +08:00
meilin.huang
8332d9b354 feat: 新增cron组件、cron支持6位秒级别 2024-01-16 20:04:04 +08:00
meilin.huang
493925064c feat: 机器定时删除终端操作记录 2024-01-15 20:51:41 +08:00
kanzihuang
c0232c4c75 fix: 修复数据库备份与恢复问题 (#85)
* fix: 修复数据库备份与恢复问题

* fix: 修复数据库备份与恢复问题
2024-01-15 20:11:28 +08:00
zongyangleo
b873855b44 !82 feat: dbms支持oracle数据库
* fix:oracle bug修复
* feat: dbms支持oracle数据库
2024-01-15 11:55:59 +00:00
meilin.huang
9c524292f0 refactor: 数组比较方法优化等 2024-01-13 13:38:53 +08:00
meilin.huang
bfd346e65a fix: 机器文件下载问题修复&dbm重构 2024-01-12 13:15:30 +08:00
meilin.huang
bc811cbd49 refactor: 数据同步编辑页优化等 2024-01-11 12:35:44 +08:00
kanzihuang
bbec3eca0d feat: 实现数据库备份和恢复并发调度 (#84) 2024-01-11 11:35:51 +08:00
meilin.huang
3857d674ba refactor: 数据同步编辑页调整、echarts组件重构 2024-01-10 23:41:55 +08:00
meilin.huang
25b0ae4d2f fix: model.FillBaseInfo遗漏调整完善等 2024-01-09 21:37:56 +08:00
meilin.huang
b7aa281611 refactor: code review 2024-01-09 17:31:21 +08:00
meilin.huang
3c89a285f5 feat: 数据库表格数据表头支持展示备注 2024-01-09 12:19:20 +08:00
Coder慌
d3d26c85c3 !81 fix: 数据同步相关bug修复
Merge pull request !81 from zongyangleo/dev_0109
2024-01-09 04:12:53 +00:00
刘宗洋
a764c4f974 fix: 数据同步相关bug修复 2024-01-09 10:35:13 +08:00
meilin.huang
af454f7d5d refactor: 数据同步优化, base.App、base.Repo新增Save方法 2024-01-07 21:46:25 +08:00
meilin.huang
eea759e10e refactor: code review 2024-01-06 22:36:50 +08:00
meilin.huang
e158422091 refactor: code review、数据库备份恢复支持ssh隧道操作 2024-01-05 22:16:38 +08:00
meilin.huang
5ada63d4a1 Merge branch 'dev' of https://gitee.com/objs/mayfly-go into dev 2024-01-05 20:51:50 +08:00
Coder慌
5ef35001cc !80 fix: 字段映射大小写等问题
Merge pull request !80 from zongyangleo/dev_0105_table_sync_fix
2024-01-05 12:51:32 +00:00
kanzihuang
0be50f0995 feat: 新增数据库类型 mariadb, 分别为 mysql 和 mariadb 设置不同的数据库备份与恢复程序路径 (#81) 2024-01-05 17:23:29 +08:00
刘宗洋
61e1b0ca70 fix: 字段映射大小写等问题 2024-01-05 14:43:51 +08:00
zongyangleo
85910bf440 !79 feat: 支持自定义数据定时同步
* fix: 达梦数据权限问题
* feat: 支持自定义数据定时同步
2024-01-05 05:31:32 +00:00
meilin.huang
7a7a7020b4 feat: 新增系统样式配置,支持改logo图标与标题 2024-01-05 12:09:12 +08:00
kanzihuang
ae3d2659aa 重构数据库备份与恢复模块 (#80)
* fix: 保存 LastResult 时截断字符串过长部分,以避免数据库报错

* refactor: 新增 entity.DbTaskBase 和 persistence.dbTaskBase, 用于实现数据库备份和恢复任务处理相关部分

* fix: aeskey变更后,解密密码出现数组越界访问错误

* fix: 时间属性为零值时,保存到 mysql 数据库报错

* refactor db.infrastructure.service.scheduler

* feat: 实现立即备份功能

* refactor db.infrastructure.service.db_instance

* refactor: 从数据库中获取数据库备份目录、mysql文件路径等配置信息

* fix: 数据库备份和恢复问题

* fix: 修改 .gitignore 文件,忽略数据库备份目录和数据库程序目录
2024-01-05 08:55:34 +08:00
meilin.huang
76fd6675b5 refactor: code review 2023-12-29 16:48:15 +08:00
may-fly
664118a709 Merge pull request #78 from kanzihuang/feat-db-bak
feat: 实现数据库备份与恢复
2023-12-29 08:57:57 +08:00
kanzihuang
e344722794 feat: 实现数据库备份与恢复 2023-12-29 08:30:10 +08:00
meilin.huang
1a7d425f60 refactor: 动态路由重构 2023-12-28 17:21:33 +08:00
meilin.huang
a0582192bf refactor: code review 2023-12-27 19:55:36 +08:00
meilin.huang
4ac9f02d6a refactor: code review 2023-12-26 22:31:51 +08:00
meilin.huang
7e0febef8f refactor: 页面小优化 2023-12-25 17:43:42 +08:00
meilin.huang
94ed4b77d6 refactor: 单元格编辑优化 2023-12-22 17:04:06 +08:00
meilin.huang
2b419bca11 refactor: 数据库表支持editor编辑调整 2023-12-22 12:29:46 +08:00
meilin.huang
54a0f0b3c7 refactor: 数据库虚拟table卡顿优化 2023-12-22 00:47:01 +08:00
Coder慌
86bccc3b3d !78 feat: 表格支持编辑json、xml数据
Merge pull request !78 from zongyangleo/dev_1221
2023-12-21 10:55:30 +00:00
刘宗洋
0e601b5033 feat: 表格支持编辑json、xml数据 2023-12-21 15:03:11 +08:00
meilin.huang
85d745fcee feat: 数据库表单元格编辑封装 2023-12-21 13:07:02 +08:00
meilin.huang
550631c03b refactor: 达梦ssh连接调整 2023-12-20 23:01:51 +08:00
Coder慌
f29a1560aa !77 fix: 达梦支持ssh
Merge pull request !77 from zongyangleo/dev_1220
2023-12-20 13:25:45 +00:00
刘宗洋
8c4c41cf0b fix: 达梦支持ssh 2023-12-20 20:37:29 +08:00
meilin.huang
f5c90277b1 feat: 新增数据库版本查看 2023-12-20 17:29:16 +08:00
meilin.huang
2ae0cd7ab4 refactor: 数据库表操作界面优化 2023-12-19 19:23:33 +08:00
meilin.huang
1f6c14ee2f refactor: 系统模块角色分配相关优化 2023-12-18 22:39:32 +08:00
meilin.huang
574d27f6da refactor: PageTable优化 2023-12-17 14:38:53 +08:00
meilin.huang
7d62841783 refactor: rsa存储方式调整等 2023-12-17 01:43:38 +08:00
Coder慌
970d74bd70 !75 refactor: 表格日期组件大小调整
Merge pull request !75 from zongyangleo/dev_1216
2023-12-16 16:46:00 +00:00
刘宗洋
0c797f8da9 refactor: 表格日期组件大小调整 2023-12-16 23:10:38 +08:00
meilin.huang
68f8603c75 refactor: PageTable优化 2023-12-16 17:41:15 +08:00
meilin.huang
06bce33c48 refactor: 样式调整 2023-12-15 17:33:22 +08:00
Coder慌
f8837f28c3 !74 fix: 数据库查询结果日期格式化
Merge pull request !74 from zongyangleo/dev_1215_fix
2023-12-15 07:27:47 +00:00
刘宗洋
61aed08dde fix: 数据库查询结果日期格式化 2023-12-15 15:21:54 +08:00
meilin.huang
a5a813f95f refactor: sql执行列返回字段类型 2023-12-14 21:27:11 +08:00
meilin.huang
18cf2e54c4 refactor: 样式调整 2023-12-14 13:05:21 +08:00
Coder慌
5c72b1de57 !73 fix: 达梦ddl相关
Merge pull request !73 from zongyangleo/dev_1214
2023-12-14 01:30:10 +00:00
刘宗洋
6e44e90d67 fix: 达梦数据库ddl 2023-12-14 08:24:14 +08:00
Coder慌
0e699ba20e !71 fix: 达梦数据库操作bug
Merge pull request !71 from zongyangleo/dev_1213_1
2023-12-13 11:39:42 +00:00
刘宗洋
cf24c2671f fix: 达梦数据库操作bug 2023-12-13 18:39:44 +08:00
meilin.huang
d3b99ec88d Merge branch 'dev' of https://gitee.com/objs/mayfly-go into dev 2023-12-13 17:36:50 +08:00
meilin.huang
0b5ab090a4 refactor: 报错代码调整 2023-12-13 17:32:17 +08:00
Coder慌
454698286c !70 fix: 达梦数据库大小写敏感问题
Merge pull request !70 from zongyangleo/dev_1213
2023-12-13 09:31:14 +00:00
刘宗洋
14e0aadbba fix: 达梦数据库操作bug 2023-12-13 17:26:53 +08:00
meilin.huang
73986a834c fix: 删除机器、数据库时关联的标签未删除 2023-12-13 14:01:13 +08:00
meilin.huang
8faf1831d9 refactor: PageTable组件重构 2023-12-12 23:31:53 +08:00
Coder慌
d86ef0a9ab !69 fix: 库名提示,兼容mysql\pgsql\dm
Merge pull request !69 from zongyangleo/dev_1212
2023-12-12 14:30:51 +00:00
刘宗洋
c2bb0c589e fix: 库名提示,兼容mysql\pgsql\dm 2023-12-12 22:06:49 +08:00
Coder慌
9994f20a2c !66 refactor: sql代码提示重构
Merge pull request !66 from zongyangleo/dev_1212
2023-12-12 09:04:48 +00:00
刘宗洋
b5014c307f refactor: sql代码提示重构 2023-12-12 16:42:45 +08:00
meilin.huang
75bd4ca3df refactor: 前端样式调整 2023-12-11 17:08:52 +08:00
meilin.huang
d00bd2ed72 fix: PageTable调整后一些页面问题修复 2023-12-11 11:00:20 +08:00
meilin.huang
e444500835 refactor: PageTable组件重构、使用useFetch封装接口请求 2023-12-11 01:00:09 +08:00
meilin.huang
6709135a0b refactor: sql取消执行逻辑调整、前端使用vueuse重构部分代码 2023-12-09 16:17:26 +08:00
meilin.huang
59a7ff9ac7 feat: 常用操作界面支持Splitpane等 2023-12-07 23:57:11 +08:00
Coder慌
172c729535 !65 feat: 达梦数据库支持编辑表结构、索引
Merge pull request !65 from zongyangleo/dev_1207_dm
2023-12-07 10:45:23 +00:00
刘宗洋
ac5198db1c feat: 达梦数据库支持编辑表结构、索引 2023-12-07 17:32:32 +08:00
刘宗洋
5c5c2c2037 Merge remote-tracking branch 'upstream/dev' into dev_1207_dm 2023-12-07 14:32:07 +08:00
meilin.huang
1db990b554 refactor: 新增达梦图标、调整前端DbDialect接口 2023-12-07 11:48:17 +08:00
刘宗洋
70c887a16a fix: 支持达梦数据库查询索引 2023-12-07 10:03:50 +08:00
Coder慌
2430c4f6aa !64 feat: 支持达梦数据库查询
Merge pull request !64 from zongyangleo/dev_1207_dm
2023-12-07 01:28:44 +00:00
刘宗洋
84fd14c129 feat: 支持达梦数据库查询 2023-12-07 09:21:40 +08:00
meilin.huang
a376a82240 feat: 数据库sql执行支持取消执行操作 2023-12-07 01:07:34 +08:00
meilin.huang
e1e03dc09a fix: 字段补充 2023-12-06 16:02:07 +08:00
meilin.huang
790d644c34 refactor: 终端记录调整 2023-12-06 13:17:50 +08:00
meilin.huang
9de8dae954 feat: 前后端传递sql编码处理 2023-12-06 09:23:23 +08:00
meilin.huang
57361d8241 feat: 支持关联多标签、计划任务立即执行、标签相关操作优化 2023-12-05 23:03:51 +08:00
meilin.huang
b347bd7ef5 feat: 数据库超时时间设置 2023-11-30 15:02:48 +08:00
meilin.huang
070c8ac0da fix: 排序导致条件丢失 2023-11-29 20:13:29 +08:00
meilin.huang
e221c2f42e feat: 新增系统全局分页size配置,可根据屏幕大小自行设置 2023-11-29 17:34:54 +08:00
Coder慌
c7bab3a71b !62 fix:gauss驱动支持ssh
Merge pull request !62 from zongyangleo/dev_1128
2023-11-29 08:40:25 +00:00
刘宗洋
82c17a51a2 fix:libpq驱动支持gaussdb sha256加密登录 2023-11-28 22:49:42 +08:00
meilin.huang
e4447e6bc2 refactor: code review 2023-11-27 17:40:47 +08:00
Coder慌
b9570d9a5f !61 fix: 1、pgsql驱动换成高斯db驱动 2、sql格式化支持传数据库类型
Merge pull request !61 from zongyangleo/dev_1127
2023-11-27 08:58:32 +00:00
刘宗洋
01e8a2c14d fix:
1、pgsql驱动换成高斯db驱动
2、sql格式化支持传数据库类型
2023-11-27 16:55:00 +08:00
meilin.huang
64bd51c3b0 refactor: code review 2023-11-26 21:21:35 +08:00
Coder慌
54ab34df3f !60 feat: 支持pgsql编辑表、索引
Merge pull request !60 from zongyangleo/dev_1126
2023-11-26 02:32:27 +00:00
刘宗洋
206490ba3e feat: 支持pgsql编辑表、索引 2023-11-26 01:47:49 +08:00
meilin.huang
16612d2c9c refactor: 多tab结果集调整 2023-11-24 17:03:08 +08:00
meilin.huang
6b65605360 feat: sql查询支持多tab结果集 2023-11-24 12:12:47 +08:00
meilin.huang
bb37ed3b95 refactor: 数据库操作界面小优化 2023-11-22 12:19:07 +08:00
meilin.huang
d102cc8c08 feat: 数据库表操作支持复制单元格数据菜单 2023-11-20 12:21:27 +08:00
meilin.huang
a6df74d63d refactor: 虚拟表格优化 2023-11-18 21:15:33 +08:00
meilin.huang
f79760943e refactor: 虚拟表格与contextmenu菜单优化 2023-11-18 15:22:25 +08:00
meilin.huang
a40ec21a05 refactor: 数据库表使用虚拟表替换,提升数据量较大时的渲染速度 2023-11-17 13:31:28 +08:00
meilin.huang
43230267b6 refactor: 界面小调整 2023-11-15 12:28:49 +08:00
meilin.huang
0ae99cdaf9 refactor: contextmenu组件优化、标签&资源替换为contextmenu操作 2023-11-14 17:36:51 +08:00
meilin.huang
f234c72514 refactor: DB-数据操作优化 2023-11-13 17:41:03 +08:00
meilin.huang
76527d95bd refactor: 机器相关配置迁移至系统配置、pgsql数据操作完善、新增context-path 2023-11-12 20:14:44 +08:00
meilin.huang
27c53385f2 refactor: 列表操作按钮调整 2023-11-09 12:11:11 +08:00
Coder慌
a1b25e9766 !59 fix: sql代码提示修复:支持跨schema提示
Merge pull request !59 from zongyangleo/dev_1109
2023-11-09 02:13:31 +00:00
刘宗洋
abad0ed481 fix: sql代码提示修复:支持跨schema提示 2023-11-09 09:48:58 +08:00
meilin.huang
eddda41291 feat: 机器列表新增运行状态 & refactor: 登录账号信息存储与context 2023-11-07 21:05:21 +08:00
meilin.huang
d9adf0fd25 fix: 表问题修复 2023-11-03 22:29:01 +08:00
meilin.huang
0ce82b41ba refactor: 标签树展示调整 2023-11-03 17:09:20 +08:00
meilin.huang
37026f3269 feat: 数据库表操作显示表size&其他小优化 2023-11-02 12:46:21 +08:00
meilin.huang
3155380f16 refactor: tooltip延迟显示等 2023-10-31 12:36:04 +08:00
meilin.huang
f2b0f294d8 refactor: machine包调整 2023-10-30 17:34:56 +08:00
meilin.huang
12f63ef3dd refactor: db/redis/mongo连接代码包独立 2023-10-27 17:41:45 +08:00
meilin.huang
a1303b52eb refactor: 新增base.Repo与base.App,重构repo与app层代码 2023-10-26 17:15:49 +08:00
meilin.huang
10f6b03fb5 refactor: code review 2023-10-20 21:31:46 +08:00
may-fly
45d2449221 Merge pull request #74 from kanzihuang/fix-showcreatetable
fix: show create table for postgres
2023-10-20 21:15:54 +08:00
wanli
9e5f146e05 fix: show create table for postgres 2023-10-20 17:37:09 +08:00
meilin.huang
2b91bbe185 refactor: websocket支持单用户多连接 2023-10-19 19:00:23 +08:00
may-fly
747ea6404d Merge pull request #73 from kanzihuang/feat-notify
feature: 每个客户端独立处理后端发送的系统消息
2023-10-18 08:36:21 -05:00
wanli
ccfc6bd1df feature: 每个客户端独立处理后端发送的系统消息 2023-10-18 20:31:27 +08:00
kanzihuang
361eafedae feature: 执行 SQL 脚本时显示执行进度 2023-10-18 15:41:57 +08:00
meilin.huang
a64b894b08 refactor: sqlite依赖替换 2023-10-17 12:32:59 +08:00
may-fly
0ad805c170 Merge pull request #72 from kanzihuang/refactor-dbtype
refactor: 实现 DbType 类型,集中处理部分差异化的数据库操作
2023-10-15 19:41:19 -05:00
kanzihuang
ba82b5b516 refactor: 实现 DbType 类型,集中处理部分差异化的数据库操作 2023-10-15 11:39:42 +08:00
may-fly
f04b82c933 Merge pull request #71 from kanzihuang/fix-exec-postgres-sql-pullrequest
fix: 执行或导入 SQL 脚本支持 PostgreSQL
2023-10-14 09:57:12 -05:00
kanzihuang
23b137ab9b fix: 执行或导入 SQL 脚本支持 PostgreSQL 2023-10-14 21:10:53 +08:00
meilin.huang
a4d3a4627a refactor: 系统水印优化等 2023-10-14 16:00:16 +08:00
meilin.huang
77ae6e3bab refactor: 系统水印重构 2023-10-14 00:38:51 +08:00
meilin.huang
e0f1f40ba0 fix: 缓存使用redis无法set问题修复&admin账号默认有所有菜单 2023-10-12 21:50:55 +08:00
meilin.huang
d300f604f1 review 2023-10-12 12:14:56 +08:00
may-fly
2c2c0ff40b Merge pull request #69 from kanzihuang/feat-progress-notify-pullrequest
feat: 显示 SQL 文件执行进度
2023-10-10 20:52:24 -05:00
kanzihuang
b4ddbbd38f fix: 使用最新版 vitess sqlparser 解析 SQL 语句
解决 xwb1989/sqlparser 不支持 current_timestamp() 的问题
2023-10-10 23:56:01 +08:00
wanli
7544288451 feat: 前端显示 SQL 文件执行进度 2023-10-10 23:28:25 +08:00
meilin.huang
41443dccc0 feat: 支持sqlite存储数据 2023-10-10 23:21:29 +08:00
meilin.huang
22e218fc5f refactor: 系统消息调整 2023-10-10 17:39:46 +08:00
meilin.huang
4da0d1abaa refactor: form表单label统一去除':' 2023-10-09 17:29:52 +08:00
meilin.huang
6563b53436 refactor: 包依赖升级等 2023-10-08 12:14:19 +08:00
meilin.huang
fac71a4794 fix: 前端代理默认端口调整&水印开关不生效 2023-09-27 17:19:58 +08:00
meilin.huang
92dff6fdc3 refactor: review 2023-09-26 17:38:52 +08:00
meilin.huang
a1eca3d691 refactor: 登录页调整 2023-09-23 22:52:05 +08:00
meilin.huang
6681dc1057 refactor: 数据库sql提示优化&机器终端支持全屏 2023-09-20 20:42:23 +08:00
meilin.huang
829a68feaa fix: 数据库多库切换关键字提示错误修复&sql编辑器组件统一 2023-09-19 23:00:32 +08:00
meilin.huang
72677e270d feat: 前端用户信息迁移至localstorage 2023-09-16 17:07:48 +08:00
meilin.huang
dd4ac390de refactor: 界面小优化 2023-09-14 17:21:01 +08:00
meilin.huang
0bd7d38c23 fix: 切换暗模式时编辑器主题同步调整 2023-09-13 23:57:28 +08:00
meilin.huang
ead3b0d0d8 feat: 界面新增暗模式 2023-09-13 19:54:43 +08:00
meilin.huang
4b973b22a4 refactor: 系统websocket消息重构 2023-09-12 20:54:07 +08:00
meilin.huang
e4e68d02bc feat: 机器文件优化&sql新增引号包裹 2023-09-11 22:59:13 +08:00
meilin.huang
ef8822d671 refactor: monaco编辑器按需加载 2023-09-11 17:34:24 +08:00
meilin.huang
8e75e1f6ef refactor: 部分日志请求入参调整为json 2023-09-09 23:34:26 +08:00
meilin.huang
08c381fa60 feat: 机器文件支持文件夹上传&数据库列表组件拆分 2023-09-08 22:24:45 +08:00
may-fly
d7a10d4032 Merge pull request #60 from kanzihuang/feature-export-databases
fix: 前端 DBMS->数据操作->新建查询,页面不显示查询结果
2023-09-08 13:53:17 +08:00
wanli
c324a030f9 fix: 前端 DBMS->数据操作->新建查询,页面不显示查询结果 2023-09-08 12:21:24 +08:00
may-fly
b618b8f93b Merge pull request #58 from kanzihuang/feature-export-databases
feat: 优化数据库批量导出功能
2023-09-07 19:55:53 +08:00
wanli
4d2e110e1e refactor: 优化数据库导出速度 2023-09-07 19:34:35 +08:00
may-fly
ecd79a2e15 Merge pull request #57 from kanzihuang/feature-export-databases
feat: 批量导出数据库时可按名称筛选数据库
2023-09-07 17:20:42 +08:00
wanli
f4f297d3f7 refactor: 优化数据库导出速度 2023-09-07 16:51:49 +08:00
kanzihuang
b5549c0fae fix: 数据库导出失败时将错误提示输出到 SQL 文件 2023-09-07 16:41:47 +08:00
kanzihuang
929bfb3200 fix: 数据库导出失败时向前端发消息 2023-09-07 16:41:47 +08:00
kanzihuang
7d3593a944 feat: 批量导出数据库时可按名称筛选数据库 2023-09-07 16:41:37 +08:00
meilin.huang
9e0db2bc99 feat: 机器文件新增批量删除、copy、mv、rename等操作 2023-09-07 16:33:53 +08:00
meilin.huang
25b0d276b3 refactor: 机器文件操作界面重构 2023-09-06 18:06:52 +08:00
may-fly
0cb7a7cf83 Merge pull request #56 from kanzihuang/feature-export-databases
feat: 批量导出数据库(仅支持 MySQL 数据库)
2023-09-05 15:28:07 +08:00
wanli
52f72400ba refactor: 获取数据库连接调整 2023-09-05 15:22:16 +08:00
wanli
0eaff33168 fix: 由于批量导出数据库不支持 PostgreSQL,非 MySQL 数据库限制使用该功能。 2023-09-05 14:50:45 +08:00
wanli
086dbf278b feature: 导出数据库时采用gzip压缩 2023-09-05 14:50:45 +08:00
wanli
57a5e237ae fix: 执行脚SQL脚本时解析SQL失败
SQL脚本中包含use mayfly-go,应为use `mayfly-go`。
2023-09-05 14:50:45 +08:00
wanli
eee6cf7b14 fix: 导出数据库时获取表结构失败
原因是表名为 group, 应在表名前后添加 `` 符号
2023-09-05 14:50:45 +08:00
wanli
b9c6ac8d6d feature: 批量导出数据库 2023-09-05 14:50:45 +08:00
meilin.huang
618d782af3 refactor: 获取数据库连接调整 2023-09-05 14:41:12 +08:00
meilin.huang
d0ac7de4cb review: 数据库实例管理调整 2023-09-05 12:49:12 +08:00
may-fly
baf8053613 Merge pull request #55 from kanzihuang/feature-instance
feature: 从数据库管理模块中拆分数据库实例管理功能
2023-09-05 08:44:58 +08:00
wanli
b973d63331 refactor: 数据库实例表 t_instance 改为 t_db_instance
同时,为避免混淆,将 application.DbInstance 改为 application.DbConnection
2023-09-05 08:13:32 +08:00
kanzihuang
85b64d7e8d feature: 拆分数据库实例的SQL脚本 2023-09-04 23:13:05 +08:00
kanzihuang
86ad183c41 feature: 将数据库实例管理集成到数据库管理模块中 2023-09-04 23:12:50 +08:00
kanzihuang
f7b685cfad feature: 实现数据库实例管理 2023-09-04 23:09:28 +08:00
meilin.huang
649116a0b8 refactor: 日志堆栈描述调整 2023-09-03 13:04:29 +08:00
meilin.huang
899a3a8243 refactor: slog替换logrus、日志操作统一、支持json、text格式等 2023-09-02 17:24:18 +08:00
meilin.huang
d51cd4b289 refactor: 终端重构、系统参数配置调整 2023-08-31 21:49:20 +08:00
meilin.huang
537b179e78 fix: 数据导出精度修复、redis数据操作优化 2023-08-29 21:31:08 +08:00
meilin.huang
1e5b1868ab refactor: redis操作界面重构 2023-08-28 17:25:59 +08:00
may-fly
245406673c Merge pull request #54 from kanzihuang/fix-ldap-login
fix: LDAP login
2023-08-26 12:37:29 +08:00
kanzihuang
51fa197af6 fix: LDAP login 2023-08-26 12:06:29 +08:00
meilin.huang
649b2bb165 refactor: ldap登录配置迁移至系统配置 2023-08-25 23:26:14 +08:00
meilin.huang
3634c902d0 feat: 系统配置新增权限控制 2023-08-25 19:41:52 +08:00
may-fly
756e580469 Merge pull request #53 from kanzihuang/feature-ldap
feat: 实现 LDAP 登录
2023-08-25 12:10:16 +08:00
kanzihuang
4e1350d1cc feat: 实现 LDAP 登录 2023-08-25 03:48:42 +00:00
meilin.huang
2e969d46fb feat: mongo优化 2023-08-25 10:20:32 +08:00
Coder慌
a5bcbe151d !58 feat:sql编写体验优化
Merge pull request !58 from zongyangleo/dev_0823
2023-08-23 05:42:22 +00:00
刘宗洋
c4abba361a feat:sql编写体验优化
1.添加自定义关键字
2.自定义函数注释和模板
3.点击按钮添加limit
2023-08-23 12:41:58 +08:00
meilin.huang
24b46b1133 refactor: 代码小优化 2023-08-16 17:37:33 +08:00
meilin.huang
3ae7e0de75 refactor: validator调整 2023-08-04 12:22:21 +08:00
meilin.huang
c2ee4f9955 refactor: 后端validator校验错误转译 2023-07-31 17:34:32 +08:00
may-fly
2479412334 Merge pull request #46 from CodFrm/dev
ci: 优化镜像构建和docker compose
2023-07-28 16:12:50 +08:00
王一之
6da8d7fd67 ci: 优化镜像构建和docker compose 2023-07-28 13:56:32 +08:00
meilin.huang
0f596a712d feat: 数据库表查询支持页数选择 2023-07-27 16:47:05 +08:00
meilin.huang
8f37b71d7f refactor: oauth2登录优化 2023-07-26 10:24:32 +08:00
meilin.huang
5083b2bdfe refactor: oauth2登录调整 2023-07-24 22:36:07 +08:00
meilin.huang
155ae65b4a refactor: oauth2登录重构 2023-07-22 20:51:46 +08:00
may-fly
ffacfc3ae8 Merge pull request #44 from CodFrm/develop/auth
OAuth2 登录和数据库升级问题优化
2023-07-22 11:53:11 +08:00
王一之
b1ab66ecf9 feat: 优化数据库迁移与添加老表迁移 2023-07-22 00:48:41 +08:00
王一之
f5bb0cad3e fix: 修复记录ip与归属地信息问题 2023-07-21 22:46:55 +08:00
王一之
a0de5afcb0 chore: 处理合并问题 2023-07-21 22:29:41 +08:00
王一之
358d33d60e fix: 修复OAuth2绑定按钮逻辑错误 2023-07-21 22:04:38 +08:00
王一之
062d28b6e6 feat: OAuth2 登录 2023-07-21 22:04:38 +08:00
王一之
513f8ea012 wip: oauth2登录和oauth2 otp登录验证 2023-07-21 22:04:37 +08:00
王一之
179b58e557 wip: 自定义oauth2登录配置 2023-07-21 22:01:53 +08:00
meilin.huang
b7450f8869 fix: 前端界面小问题修复 2023-07-21 21:01:25 +08:00
meilin.huang
7f9e972828 feat: 代码优化、机器计划任务完善 2023-07-21 17:07:04 +08:00
meilin.huang
7b51705f4e feat: 新增机器计划任务、数据物理删除调整为逻辑删除、支持记录登录ip归属地等 2023-07-20 22:41:13 +08:00
Coder慌
6bd9e5333d !55 https://gitee.com/objs/mayfly-go/issues/I7LFVH问题解决
Merge pull request !55 from amell/databases_404_fix
2023-07-16 04:04:22 +00:00
Coder慌
112d735ac0 !56 https://gitee.com/objs/mayfly-go/issues/I7LFXS的BUG修复
Merge pull request !56 from amell/dev
2023-07-16 04:04:00 +00:00
amell
52553ed53f [fix] https://gitee.com/objs/mayfly-go/issues/I7LFXS的缺陷修复 2023-07-16 11:13:52 +08:00
amell
70d84e32d1 [fix] https://gitee.com/objs/mayfly-go/issues/I7LFVH的缺陷修复 2023-07-16 09:44:55 +08:00
meilin.huang
3269dfa5d6 refactor: 后端路由定义方式&请求参数绑定重构 2023-07-08 20:05:55 +08:00
meilin.huang
183a6e4905 refactor: 团队成员分配使用pagetable组件重构 2023-07-07 14:43:51 +08:00
meilin.huang
5463ae9d7e refactor: 前端统一使用prettier格式化&枚举值统一管理 2023-07-06 20:59:22 +08:00
Coder慌
f25bdb07ce !54 fix: 修复字段粘连无法提示的问题
Merge pull request !54 from zongyangleo/dev_0706
2023-07-06 06:54:28 +00:00
刘宗洋
aa5c08d564 fix: 修复字段粘连无法提示的问题 2023-07-06 11:08:00 +08:00
meilin.huang
dc9a2985f3 refactor: 分页组件统一替换&其他优化 2023-07-05 22:06:32 +08:00
meilin.huang
f4ac6d8360 refactor: 机器文件操作优化 2023-07-05 00:26:00 +08:00
meilin.huang
3266039aaf fix: 双击修改表sql问题修复 2023-07-04 15:07:03 +08:00
meilin.huang
e3f4c298b0 feat: 系统日志支持按描述搜索 2023-07-04 14:13:47 +08:00
meilin.huang
fa58f6d2de fix: 无法添加成员 2023-07-03 21:58:37 +08:00
meilin.huang
ae5a1fd7de refactor: code review 2023-07-03 21:42:04 +08:00
meilin.huang
c240079df4 refactor: 前端代码优化 2023-07-02 17:06:00 +08:00
meilin.huang
aca4e6751e refactor: 前端风格统一 2023-07-01 21:24:07 +08:00
meilin.huang
ce32fc7f2c refactor: 代码重构、分页数据组件支持多选 2023-07-01 14:34:42 +08:00
meilin.huang
d423572e01 fix: 新增资源选择标签问题修复、项目文档转移 2023-06-29 20:20:58 +08:00
meilin.huang
d9807b1bf0 feat: 数据库表数据支持字段设置、表格宽度自适应调整 2023-06-29 11:49:14 +08:00
meilin.huang
1bc53b4c80 refactor: 表格宽度自适应宽度计算方式调整 2023-06-29 00:40:42 +08:00
meilin.huang
6bc2603a4d fix: 查询label宽度自动适配 2023-06-28 21:50:04 +08:00
meilin.huang
e2c929aae1 feat: 统一分页表格组件、修复系统配置无法配置单个属性 2023-06-28 21:35:03 +08:00
Coder慌
0d155d592b !53 feat:修改表结构:主键默认自增,自动生成索引名
Merge pull request !53 from zongyangleo/dev_0625
2023-06-26 01:26:51 +00:00
Coder慌
ae510ff1ff !52 update server/mayfly-go.sql.
Merge pull request !52 from noday/N/A
2023-06-26 01:24:48 +00:00
noday
5b0654ad2c update server/mayfly-go.sql.
sql values字段不匹配
2023-06-26 01:18:48 +00:00
刘宗洋
466f97ecbe feat:修改表结构:主键默认自增,自动生成索引名 2023-06-25 21:12:28 +08:00
meilin.huang
27a14c22d7 fix: sql遗漏调整 2023-06-25 19:10:37 +08:00
meilin.huang
4709edcd1c fix: 遗漏sql补充 2023-06-21 15:18:43 +08:00
meilin.huang
414de9f2eb fix: 数据库提示问题修复 2023-06-20 17:08:13 +08:00
Coder慌
a53e7e7dab !50 SQL字段补全的BUG修复
Merge pull request !50 from amell/sql_autocomplete
2023-06-17 11:42:39 +00:00
amell
7fa6628dc5 [bugfix] issue修复: https://gitee.com/objs/mayfly-go/issues/I7E9LF 2023-06-17 19:29:19 +08:00
Coder慌
62c25afea8 !49 对于update和delete的SQL操作,建议增加where条件检测,缺失where条件时不执行相应的SQL
Merge pull request !49 from amell/sql_where
2023-06-17 09:25:02 +00:00
amell
481b622e3b [fix] https://gitee.com/objs/mayfly-go/issues/I7E8ZF的修复 2023-06-17 16:57:06 +08:00
meilin.huang
64f8f9a200 fix: meta_sql文件中windows换行符不同问题 2023-06-17 16:04:21 +08:00
meilin.huang
0eca951465 Merge branch 'dev' 2023-06-17 15:15:56 +08:00
meilin.huang
ef4e34c584 feat: 新增双因素校验(OTP) 2023-06-17 15:15:03 +08:00
Coder慌
d91acbc7ee !45 修复同时执行多条SQL的bug
Merge pull request !45 from amell/mutil_sql
2023-06-17 04:47:48 +00:00
amell
b397d1022e [fix] issue: I7E6QQ的缺陷修复 2023-06-17 08:59:37 +08:00
meilin.huang
b42a98aff5 fix: sql脚本问题修复 2023-06-16 08:59:22 +08:00
meilin.huang
adc65439e4 feat: redis支持flushdb、菜单资源支持拖拽排序、禁用等 2023-06-15 19:18:29 +08:00
Coder慌
445cf3716b !44 feat: sql字段提示优化
Merge pull request !44 from zongyangleo/dev_20230613
2023-06-13 09:03:53 +00:00
刘宗洋
f4b59b8503 feat:单表查询支持无别名提示字段 2023-06-13 17:01:39 +08:00
刘宗洋
4f08975df2 fix:修复数据库schema提示 2023-06-13 15:57:08 +08:00
刘宗洋
570db453d7 feat:sql字段提示优化 2023-06-13 14:54:52 +08:00
meilin.huang
b93984bf6f refactor: json tag完善 2023-06-11 19:59:35 +08:00
meilin.huang
e483db1b97 fix: 哨兵节点密码调整 2023-06-06 20:51:54 +08:00
meilin.huang
17d96acceb refactor: interface{} -> any
feat: 新增外链菜单
2023-06-01 12:31:32 +08:00
meilin.huang
9900b236ef refactor: 组件升级、代码优化 2023-05-24 12:32:17 +08:00
Coder慌
4fa52412c1 !42 fix:修改表sql,如果是修改的主键,sql bug修复
Merge pull request !42 from zongyangleo/dev_20230417
2023-04-17 09:40:11 +00:00
刘宗洋
0076869deb fix:修改表:如果修改的字段是主键 2023-04-17 16:21:06 +08:00
meilin.huang
af55193591 feat: redis支持zset、redis数据操作界面优化 2023-04-16 00:50:36 +08:00
meilin.huang
1d858118d5 feat: 菜单权限获取方式调整、前端代码优化、数据库新增数据方式调整 2023-04-13 20:11:22 +08:00
meilin.huang
8e64ba67fa fix: 问题修复 2023-04-07 22:45:21 +08:00
meilin.huang
58fb11b78f refactor: 界面优化 2023-04-05 22:41:53 +08:00
Coder慌
f6e9076a40 !41 feat: 表格添加刷新按钮;修改默认字段生成规则
Merge pull request !41 from zongyangleo/dev_20230328
2023-04-04 07:50:24 +00:00
刘宗洋
2fefa43aea feat: 表格添加刷新按钮 2023-03-28 15:22:05 +08:00
刘宗洋
f43b0467ba fix: 修改默认字段生成规则 2023-03-28 12:21:24 +08:00
meilin.huang
14da4d77e0 fix: DBMS-数据查询无数据时分页信息未清除 2023-03-25 22:22:55 +08:00
meilin.huang
cefad86b85 fix: mysql多个联合索引显示问题等 2023-03-19 14:49:12 +08:00
meilin.huang
738ff86344 fix: redis集群修复 2023-03-17 09:46:41 +08:00
meilin.huang
110abc4ac7 feat: 代码优化 2023-03-16 16:40:57 +08:00
meilin.huang
5f1aaa40d8 feat: pinia替换vuex,代码优化 2023-03-15 11:41:03 +08:00
meilin.huang
0695ad9a85 feat: 新增机器授权凭证管理与其他优化 2023-03-06 16:59:57 +08:00
meilin.huang
7c086bbec8 fix: 问题修复 2023-02-22 15:54:53 +08:00
meilin.huang
c75fe7135a refactor: 图标优化等 2023-02-20 18:41:45 +08:00
meilin.huang
edf29976dd feat: 资源操作相关标签树调整为el-tree 2023-02-18 23:02:14 +08:00
meilin.huang
7b5f963ec4 fix: 构建脚本调整 2023-02-15 21:32:51 +08:00
meilin.huang
f22badb861 feat: 优化 2023-02-15 21:28:01 +08:00
meilin.huang
8c501c90cd feat: 标签路径展示优化,新增提示 2023-02-14 18:24:39 +08:00
Coder慌
11eebdfcf0 !39 feat: 表和schema超出20条后启用模糊高亮过滤
Merge pull request !39 from zongyangleo/dev_20230213_1
2023-02-14 06:36:41 +00:00
刘宗洋
46e331783f fix: sql提示bug修复 2023-02-14 14:03:18 +08:00
刘宗洋
7e33e64659 Merge remote-tracking branch 'upstream/dev' into dev_20230213_1 2023-02-14 12:24:48 +08:00
刘宗洋
7e4152ad6d fix: sql提示bug修复 2023-02-14 12:23:38 +08:00
meilin.huang
d189ee7c22 feat: 数据库表格重构 2023-02-14 11:44:48 +08:00
刘宗洋
641c2abb24 feat: 表名+schema模糊高亮过滤 2023-02-14 10:33:57 +08:00
meilin.huang
3ab4ac891b 表和schema超出20条后启用过滤 2023-02-13 21:36:18 +08:00
meilin.huang
70b586e45a refactor: sqlexec组件重构优化、新增数据库相关系统参数配置、相关问题修复 2023-02-13 21:11:16 +08:00
Coder慌
77aa724003 !37 fix: ref修复
Merge pull request !37 from zongyangleo/dev_20230206
2023-02-07 09:30:37 +00:00
刘宗洋
db7b50d96a fix: ref修复 2023-02-07 17:22:52 +08:00
meilin.huang
00fee24a85 feat: 代码小调整 2023-02-07 16:54:44 +08:00
Coder慌
fa0cb73ec9 !36 feat: redis新增实例树、各数据操作按钮跳转
Merge pull request !36 from zongyangleo/dev_20230206
2023-02-07 03:33:23 +00:00
刘宗洋
6ee815b71d debugger 2023-02-07 11:06:54 +08:00
刘宗洋
311a048af5 Merge remote-tracking branch 'upstream/dev' into dev_20230206
# Conflicts:
#	mayfly_go_web/src/views/ops/db/SqlExec.vue
#	mayfly_go_web/src/views/ops/db/component/InstanceTree.vue
#	mayfly_go_web/src/views/ops/mongo/MongoInstanceTree.vue
2023-02-07 10:59:46 +08:00
刘宗洋
3da9ecfaa3 feat: 数据操作按钮跳转 2023-02-07 10:32:42 +08:00
刘宗洋
4c2c6f613e feat: redis新增实例树 2023-02-07 08:39:14 +08:00
meilin.huang
ba63736871 feat: 小功能优化 2023-02-06 17:14:16 +08:00
Coder慌
ed3dbafc35 !34 feat:mongo新增实例树
Merge pull request !34 from zongyangleo/dev_20230206
2023-02-06 08:54:31 +00:00
刘宗洋
fdeffbd495 Merge branch 'dev' of https://gitee.com/objs/mayfly-go into dev_20230206
yes.
2023-02-06 16:46:52 +08:00
刘宗洋
9870812e6b feat:mongo新增实例树 2023-02-06 16:46:40 +08:00
Coder慌
46f35e14c4 !33 feat: 数据管理添加实例树,细节修改
Merge pull request !33 from zongyangleo/dev_20230206
2023-02-06 06:53:38 +00:00
刘宗洋
e89cf96ff4 feat:实例树优化:加宽菜单宽度、表加载中loading状态、新建查询等按钮自成一行、加宽实例详情宽度、实例树高度默认撑满 2023-02-06 14:50:57 +08:00
Coder慌
5cd19bf38d !32 feat: 数据管理添加实例树
Merge pull request !32 from zongyangleo/dev_20230206
2023-02-06 03:13:25 +00:00
刘宗洋
a6f69f2b62 修改团队bug 2023-02-06 11:08:18 +08:00
刘宗洋
e34b9adada feat: 数据管理添加实例树 2023-02-06 10:35:30 +08:00
meilin.huang
9f43f731b5 fix: 问题修复 2023-01-19 19:45:12 +08:00
meilin.huang
594ca43505 refactor: 包名变更ctx -> req 2023-01-14 16:29:52 +08:00
meilin.huang
2a91cdb67a fix: pgsql主键字段获取修复 2023-01-09 20:05:35 +08:00
Coder慌
11148e720b !30 feat: 执行sql,无数据时也提示一下
Merge pull request !30 from zongyangleo/dev_20221228
2023-01-01 06:43:54 +00:00
刘宗洋
c5835d6d8c feat: 执行sql,无数据时也提示一下 2022-12-28 10:23:38 +08:00
meilin.huang
4a26bb3ba5 feat: 支持redis缓存权限等信息 2022-12-26 20:53:07 +08:00
meilin.huang
4fec38724d fix: pgsql隧道连接问题修复 2022-12-22 18:41:34 +08:00
meilin.huang
85349df8a1 code review 2022-12-17 22:24:21 +08:00
meilin.huang
ffe250f8a9 feat: 构建文档调整&移除静态文件 2022-12-16 23:14:00 +08:00
Coder慌
eeb310a1d2 !28 redis监控信息面板重构
Merge pull request !28 from zongyangleo/dev_20221216
2022-12-16 03:40:02 +00:00
刘宗洋
a42c606d20 redis监控信息面板重构 2022-12-16 11:35:16 +08:00
Coder慌
4e64d46cd2 !27 redis监控信息面板重构
Merge pull request !27 from zongyangleo/dev_20221216
2022-12-16 03:17:50 +00:00
刘宗洋
9886ee6828 redis监控信息面板重构 2022-12-16 11:13:13 +08:00
Coder慌
0e1bde09c3 !26 fix: 修复双击编辑事件报错
Merge pull request !26 from zongyangleo/dev_20221213
2022-12-13 06:15:42 +00:00
刘宗洋
dda600709b fix: 修复双击编辑事件报错 2022-12-13 11:08:42 +08:00
meilin.huang
15f38491b2 feat: 新增docker部署 2022-12-09 22:02:37 +08:00
may-fly
4b140732f7 Merge pull request #23 from 1ch0/master
Feat: add Dockerfile
2022-12-08 19:14:41 +08:00
meilin.huang
4cb9ff3f14 Merge branch 'dev' 2022-12-05 21:50:23 +08:00
meilin.huang
e4f3e2c4c1 feat: redis scan优化 2022-12-05 21:45:35 +08:00
Coder慌
a80adb7dd8 !25 优化redis在scan查询时当前游标没有数据的场景
Merge pull request !25 from yechuankai/master
2022-12-05 09:56:09 +00:00
叶传开
195127a9d4 优化redis在scan查询时当前游标没有数据的场景 2022-12-05 13:21:28 +08:00
1ch0
f2119b2c52 Feat: add docker-compose 2022-12-05 11:27:05 +08:00
1ch0
f15c45793b Feat: add Dockerfile 2022-12-03 08:25:22 +08:00
meilin.huang
24f543e667 refactor: redis值格式化方式调整 2022-11-25 18:59:23 +08:00
meilin.huang
772995705f fix: 获取表主键逻辑完善 2022-11-23 20:48:37 +08:00
meilin.huang
3475c39fe6 fix: 小问题修复 2022-11-22 17:15:08 +08:00
Coder慌
e6e393379f !24 feat: 查询结果支持批量修改
Merge pull request !24 from zongyangleo/dev_20221121
2022-11-22 08:28:55 +00:00
刘宗洋
03cc91c3e5 fix: 表字段批量修改,查询tab:重复执行sql就取消修改,或者点击取消按钮
各表tab:独立计算修改字段,点击取消或者刷新取消修改
2022-11-22 15:54:11 +08:00
Coder慌
f82f7bec6a !23 feat: 查询结果支持批量修改
Merge pull request !23 from zongyangleo/dev_20221121
2022-11-22 05:52:45 +00:00
刘宗洋
4afd5bbd5e feat: 查询结果支持批量修改 2022-11-22 12:35:54 +08:00
meilin.huang
86aac2bf08 refactor: 列表字段精简 2022-11-21 19:49:50 +08:00
Coder慌
70c8b25a67 !22 bug修复
Merge pull request !22 from zongyangleo/dev_sqlexec_add
2022-11-19 09:24:00 +00:00
刘宗洋
231af72444 bug 2022-11-19 16:46:10 +08:00
meilin.huang
480e930385 refactor: 操作日志信息完善等 2022-11-18 17:52:30 +08:00
meilin.huang
debc34f0fb fix: 主键字段获取调整&其他小优化 2022-11-14 18:07:27 +08:00
meilin.huang
99ce3bd099 feat: 去除jsoneditor,统一使用monaco 2022-11-10 18:55:10 +08:00
Coder慌
99431cf9a2 !21 bug修复
Merge pull request !21 from zongyangleo/dev_20221109
2022-11-09 15:17:59 +00:00
刘宗洋
83711c69f9 fix: monaco bug, label加上description 2022-11-09 15:41:54 +08:00
meilin.huang
9e67032280 feat: 小调整 2022-11-07 21:57:51 +08:00
meilin.huang
fa37937410 feat: monaco主题移至全局配置 2022-11-05 22:18:59 +08:00
meilin.huang
1be2cad78e feat: monaco编辑器调整 2022-11-05 21:08:01 +08:00
meilin.huang
2b1e687ed4 feat: 初步移除codemirror 2022-11-05 15:13:40 +08:00
Coder慌
881009321b !20 feat: 新增monaco编辑器
Merge pull request !20 from zongyangleo/dev_upstream
2022-11-04 06:10:35 +00:00
刘宗洋
aed99b63b8 monaco bug 2022-11-04 11:32:06 +08:00
刘宗洋
dfa34ba371 Merge branch 'dev_monaco_20221104' into dev_upstream
# Conflicts:
#	mayfly_go_web/src/views/ops/db/DbList.vue
#	mayfly_go_web/src/views/ops/db/SqlExec.vue
#	mayfly_go_web/src/views/ops/db/component/SqlExecDialog.vue
2022-11-04 11:17:19 +08:00
meilin.huang
20beb30dd8 feat: 支持执行多条sql 2022-11-04 11:12:44 +08:00
刘宗洋
4475972af3 fix: sql执行器 获取sql bug 2022-11-04 11:12:44 +08:00
刘宗洋
a843c65783 index.html添加百秒级时间戳,防止被浏览器缓存 2022-11-04 11:12:44 +08:00
刘宗洋
c2de1d3fa2 refactor:
1.修改表后,刷新表数据
2.修改索引sql拼接bug
3.暂时只有mysql支持编辑表
2022-11-04 11:12:44 +08:00
meilin.huang
c8d091da06 feat: 小功能优化&前端基于setup语法糖重构 2022-11-04 11:12:42 +08:00
刘宗洋
553208ba57 fix: 子组件事件通知修复 2022-11-04 11:12:17 +08:00
刘宗洋
072028699a feat: monaco 2022-11-04 11:12:16 +08:00
Coder慌
9cdcf145a5 !18 bug修复
Merge pull request !18 from zongyangleo/dev_upstream_fix_20221102
2022-11-03 07:23:07 +00:00
meilin.huang
4df1c19e81 fix: 修复setup导致open失效问题 2022-11-03 14:05:04 +08:00
刘宗洋
ac26a214bc fix: 子组件事件通知修复 2022-11-02 22:45:39 +08:00
meilin.huang
ad616496d1 feat: 支持执行多条sql 2022-11-02 19:27:40 +08:00
刘宗洋
9870582779 refactor: sql解析失败逻辑重构 2022-11-01 21:25:00 +08:00
meilin.huang
20cc696b33 refactor: 标签列表选择调整 2022-11-01 21:25:00 +08:00
刘宗洋
d7263f2b3c fix: sql执行器 获取sql bug 2022-11-01 21:24:58 +08:00
meilin.huang
74e5ee41fb fix: sql字符串拼接改为占位符形式,防sql注入 2022-11-01 21:24:37 +08:00
刘宗洋
f936331dff refactor:
1.修改表后,刷新表数据
2.修改索引sql拼接bug
3.暂时只有mysql支持编辑表
2022-11-01 21:24:34 +08:00
meilin.huang
ba311c3504 fix: sql accout表字段修复 2022-11-01 21:23:51 +08:00
meilin.huang
03291594b1 feat: 小功能优化&前端基于setup语法糖重构 2022-11-01 21:23:49 +08:00
刘宗洋
a6d9a4b5ae refactor: 1.修改表后,刷新表数据;2.为了方便sqlmode,sql逻辑改为代码逻辑 3.修改索引sql拼接bug 2022-11-01 21:22:25 +08:00
刘宗洋
875de022c1 monaco 2022-11-01 21:22:23 +08:00
Coder慌
2c863a2774 !17 refactor: sql解析失败逻辑重构
Merge pull request !17 from zongyangleo/dev_reafactor_20221031
2022-11-01 07:48:59 +00:00
刘宗洋
f2f086a82c refactor: sql解析失败逻辑重构 2022-11-01 12:45:21 +08:00
meilin.huang
936ca61f94 refactor: 标签列表选择调整 2022-10-31 23:03:26 +08:00
Coder慌
87ae2f81fa !16 bug修复
Merge pull request !16 from zongyangleo/dev_reafactor_20221031
2022-10-31 14:30:16 +00:00
刘宗洋
ecf67db2b1 fix: sql执行器 获取sql bug 2022-10-31 22:27:21 +08:00
meilin.huang
2e5589e112 fix: sql字符串拼接改为占位符形式,防sql注入 2022-10-31 18:39:52 +08:00
Coder慌
2598a60898 !15 代码重构
Merge pull request !15 from zongyangleo/dev_reafactor_20221031
2022-10-31 07:53:32 +00:00
刘宗洋
0de226bbf3 refactor:
1.修改表后,刷新表数据
2.修改索引sql拼接bug
3.暂时只有mysql支持编辑表
2022-10-31 15:43:44 +08:00
meilin.huang
422f0d8491 fix: sql accout表字段修复 2022-10-30 22:56:57 +08:00
meilin.huang
b028708b94 feat: 小功能优化&前端基于setup语法糖重构 2022-10-29 20:08:15 +08:00
meilin.huang
812c0d0f6a feat: 微调 2022-10-27 19:33:21 +08:00
Coder慌
46df5293dd !13 支持编辑表
Merge pull request !13 from zongyangleo/dev_edit_table
2022-10-27 09:58:27 +00:00
刘宗洋
ab42b3e90b feat: mysql 支持编辑表结构、索引 2022-10-27 14:27:32 +08:00
Coder慌
1378259cc7 !12 新增了一些功能
Merge pull request !12 from zongyangleo/dev_link
2022-10-27 06:19:41 +00:00
刘宗洋
c8f0b0a83f 合并代码 2022-10-27 14:09:32 +08:00
meilin.huang
acec760ec1 fix: 字段补充 2022-10-27 14:06:50 +08:00
刘宗洋
2fe70d49f6 Merge remote-tracking branch 'upstream/dev' into dev_link_merge
# Conflicts:
#	mayfly_go_web/src/views/ops/db/DbList.vue
#	mayfly_go_web/src/views/ops/db/SqlExec.vue
#	mayfly_go_web/src/views/ops/mongo/MongoDataOp.vue
#	mayfly_go_web/src/views/ops/mongo/MongoList.vue
#	mayfly_go_web/src/views/ops/redis/DataOperation.vue
#	mayfly_go_web/src/views/ops/redis/RedisList.vue
2022-10-27 10:53:29 +08:00
刘宗洋
9013fff804 feat: DBMS 数据库管理、redis管理、mongo管理,新增【数据操作】快捷跳转 2022-10-27 10:35:11 +08:00
meilin.huang
e925a808c4 feat: 使用标签替代项目 2022-10-26 20:49:29 +08:00
meilin.huang
6c197edddd refactor: db代码review 2022-10-16 14:22:19 +08:00
meilin.huang
c35e91b7b6 refactor: db元信息获取调整 2022-10-16 11:14:13 +08:00
meilin.huang
575947795a refactor: db元信息获取调整 2022-10-16 11:01:45 +08:00
meilin.huang
51f116c7d2 feat: 小问题修复 2022-10-15 17:38:34 +08:00
meilin.huang
c28254855c feat: 菜单sql调整&其他小优化 2022-10-11 08:25:20 +08:00
meilin.huang
e8f3671ffb feat: redis支持设置多库操作 2022-09-29 13:14:50 +08:00
meilin.huang
ac62767a18 fix: 数据库查询-前端long类型精度丢失&bit类型无法展示 2022-09-28 21:40:59 +08:00
meilin.huang
2db4c20dd3 feat: linux文件排序 2022-09-26 18:08:12 +08:00
meilin.huang
cfb7fd5b29 feat: 数据库查询结果导出&其他小问题修复 2022-09-23 14:27:50 +08:00
meilin.huang
22c401f9d8 feat: redis支持list查看&其他小优化 2022-09-22 11:56:21 +08:00
meilin.huang
be00b90c1d refactor: 代码结构调整 2022-09-09 18:26:08 +08:00
meilin.huang
fb3f89c594 feat: 版本升级 2022-09-07 15:20:34 +08:00
meilin.huang
e7a66378ea feat: 终端勾选隧道保存报错问题修复&其他优化 2022-09-07 11:18:47 +08:00
meilin.huang
2f88b48973 feat: 新增终端回放记录&其他小优化 2022-08-29 21:43:24 +08:00
meilin.huang
7761fe0288 feat: 新增系统全局配置&修改账号密码 2022-08-26 20:15:36 +08:00
may-fly
09e6bdcf7e Merge pull request #9 from 1ch0/master
fix: store mongodb password incorrectly
2022-08-26 10:20:12 +08:00
1ch0
61a4d87f59 perf: hide mongodb passwords when printing logs 2022-08-26 10:01:08 +08:00
1ch0
c219ec33b0 fix: store mongodb password incorrectly 2022-08-26 09:58:01 +08:00
may-fly
fd86f36218 Merge pull request #8 from 1ch0/master
Perf: hide mongodb passwords when printing logs
2022-08-25 18:13:38 +08:00
Echo Cheng
efac41f392 Perf: hide mongodb passwords when printing logs 2022-08-25 17:58:07 +08:00
meilin.huang
52df61ae0d refactor: 构建发行版脚本优化 2022-08-24 21:36:16 +08:00
meilin.huang
cf2bc6785c feat: 使用embed将静态资源打包进二进制文件&其他小功能优化 2022-08-24 20:55:42 +08:00
meilin.huang
98a4c92576 feat: redis支持sentinel 2022-08-23 18:50:07 +08:00
meilin.huang
b1ee9b65ff fix: 小问题优化 2022-08-21 21:00:28 +08:00
meilin.huang
99cc4c5e5e fix: script type调整 2022-08-19 22:00:37 +08:00
meilin.huang
226bb8f089 fix: 终端断连提示 2022-08-19 21:42:26 +08:00
meilin.huang
37ed5134e8 feat: 机器脚本入参支持选择框 2022-08-15 20:14:02 +08:00
meilin.huang
0f54d4a472 refactor: code rewiew&功能小优化 2022-08-13 19:31:16 +08:00
Coder慌
64805360d6 update README.md. 2022-08-11 05:52:35 +00:00
Coder慌
7f69fe2ad9 update README.md. 2022-08-11 02:58:06 +00:00
meilin.huang
f913510d3c refactor: code review 2022-08-10 19:46:17 +08:00
meilin.huang
f2d9e7786d refactor: redis hash类型使用hscan获取数据 2022-08-05 21:41:21 +08:00
meilin.huang
e1afb1ed54 fix: sql脚本默认账号密码调整&终端默认配色调整 2022-08-04 20:47:13 +08:00
meilin.huang
12f8cf0111 feat: 资源密码加密处理&登录密码加密加强等 2022-08-02 21:44:01 +08:00
meilin.huang
daa2ef5203 feat: 数据库支持选中数据生成insert语句 2022-07-27 15:36:56 +08:00
meilin.huang
1e3e183930 feat: 优化机器脚本添加参数的前端交互 2022-07-26 18:32:45 +08:00
meilin.huang
366563a0fe fix: sql文件字段名调整 2022-07-24 18:54:23 +08:00
meilin.huang
577802e5ad fix: 定时任务问题修复 2022-07-24 15:37:13 +08:00
meilin.huang
76d6fc3ba5 feat: linux支持ssh隧道访问&其他优化 2022-07-23 16:41:04 +08:00
meilin.huang
f0540559bb feat: 数据库、redis、mongo支持ssh隧道等 2022-07-20 23:25:52 +08:00
Coder慌
802e379f60 !8 feat: 新增mysql ssh代理连接方式
Merge pull request !8 from das/N/A
2022-07-20 03:13:29 +00:00
大圣之家
8c9253da80 feat: 新增mysql ssh代理连接方式 2022-07-20 01:37:25 +00:00
meilin.huang
5271bd21e8 feat: 登录强制校验弱密码&关键信息加密传输 2022-07-18 20:36:31 +08:00
meilin.huang
db554ebdc9 feat: 新增系统操作日志&其他优化 2022-07-14 11:39:12 +08:00
meilin.huang
1c18a01bf6 feat: 新增pgsql数据操作&redis集群操作 2022-07-10 12:14:06 +08:00
meilin.huang
729a3d7028 feat: 新增linux文件夹创建&删除&其他优化 2022-07-04 20:21:24 +08:00
meilin.huang
b88923a128 feat: 数据库表数据支持分页查看 2022-07-02 18:59:46 +08:00
meilin.huang
fe8cd93c78 feat: 新增数据库导出功能&其他小优化 2022-06-30 16:42:25 +08:00
meilin.huang
64b49dae2e fix: 功能优化&小问题修复 2022-06-25 19:52:11 +08:00
meilin.huang
edbbbca5f9 fix: sql脚本导入参数修复 2022-06-21 17:54:07 +08:00
meilin.huang
5ad0d90038 feat: sql执行记录新增查看回滚sql 2022-06-17 17:58:35 +08:00
meilin.huang
9b9173dea7 feat: 新增数据库sql执行记录&执行前原值等信息 2022-06-16 15:55:18 +08:00
meilin.huang
f58331c1c1 feat: mongo新增json编辑器、其他优化 2022-06-08 10:21:02 +08:00
meilin.huang
b2dc9dff0b refactor: 后端包结构重构、去除无用的文件 2022-06-02 17:41:11 +08:00
meilin.huang
51d06ab206 fix: 数据库连接设置超时时间&界面优化 2022-05-27 15:45:12 +08:00
Coder慌
799e0ac9fc readme完善 2022-05-24 07:35:58 +00:00
meilin.huang
56e7a8843b feat: 新增mongo管理与数据操作 2022-05-17 20:23:08 +08:00
1311 changed files with 506560 additions and 28665 deletions

8
.gitignore vendored
View File

@@ -16,3 +16,11 @@
*/node_modules/
**/vendor/
.idea
.vscode
out
server/docs/docker-compose
server/config.yml
server/ip2region.xdb
mayfly-go.log

18
.vscode/launch.json vendored
View File

@@ -1,18 +0,0 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "mayfly-go",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}/main.go",
"env": {},
"args": []
},
]
}

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
ARG BASEIMAGES=m.daocloud.io/docker.io/alpine:3.20.2
FROM ${BASEIMAGES} AS builder
ARG TARGETARCH
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 wget -cO mayfly-go.zip ${MAYFLY_GO_URL} && \
unzip mayfly-go.zip && \
mv ${MAYFLY_GO_DIR_NAME}/* /opt
FROM ${BASEIMAGES}
ARG TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY --from=builder /opt/mayfly-go /usr/local/bin/mayfly-go
WORKDIR /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"]

3
Makefile Normal file
View File

@@ -0,0 +1,3 @@
docker:
docker build . -t mayfly-go

View File

@@ -1,36 +0,0 @@
# mayfly-go
#### Description
golang实现linux运维等
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

114
README.md
View File

@@ -1,33 +1,109 @@
# mayfly-go
# 🌈Dromara mayfly-go
#### 介绍
简单基于DDD(领域驱动设计)分层架构实现web版mysql,redis,linux统一操作管理平台
<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">
<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>
## 前言
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?jump_from=webapi">119699946</a>
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=IdJSHW0jTMhmWFHBUS9a83wxtrxDDhFj&jump_from=webapi">119699946</a>
### 系统相关资料
- 项目文档: https://objs.gitee.io/mayfly-go-docs
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
## 演示环境
### 系统功能
http://go.mayfly.run
账号/密码test/test123.
记录操作记录
![记录操作记录](https://images.gitee.com/uploads/images/2021/0508/204608_83ef7c33_1240250.png "屏幕截图.png")
## 系统核心功能截图
#### 首页
![首页](https://foruda.gitee.com/images/1714378104294194769/149fd257_1240250.png "屏幕截图")
#### 机器操作
##### 状态查看
![机器状态查看](https://foruda.gitee.com/images/1714378556642584686/93c46ec0_1240250.png "屏幕截图")
##### ssh 终端
![终端操作](https://foruda.gitee.com/images/1714378353790214943/2864ba66_1240250.png "屏幕截图")
##### 文件操作
![文件操作](https://foruda.gitee.com/images/1714378417206086701/74a188d8_1240250.png "屏幕截图")
![文件查看](https://foruda.gitee.com/images/1714378482611638688/7753faf6_1240250.png "屏幕截图")
#### 数据库操作
##### sql 编辑器
![sql编辑器](https://foruda.gitee.com/images/1714378747473077515/3c9387c0_1240250.png "屏幕截图")
##### 在线增删改查数据
![选表查数据](https://foruda.gitee.com/images/1714378625059063750/3951e5a8_1240250.png "屏幕截图")
#### Redis 操作
![redis操作](https://foruda.gitee.com/images/1714378855845451114/4c3f0097_1240250.png "屏幕截图")
#### Mongo 操作
![mongo操作](https://foruda.gitee.com/images/1714378916425714642/77fc0ed9_1240250.png "屏幕截图")
#### 工单流程审批
![流程审批](https://foruda.gitee.com/images/1714379057627690037/ad136862_1240250.png "屏幕截图")
#### 系统管理
账号管理
![账号管理](https://images.gitee.com/uploads/images/2021/0607/173919_a8d7dc18_1240250.png "屏幕截图.png")
角色管理
![角色管理](https://images.gitee.com/uploads/images/2021/0607/174028_3654fb28_1240250.png "屏幕截图.png")
##### 账号管理
资源管理
![资源管理](https://images.gitee.com/uploads/images/2021/0607/174436_e9e1535c_1240250.png "屏幕截图.png")
![账号管理](https://foruda.gitee.com/images/1714379179491881231/c6d802ae_1240250.png "屏幕截图")
**其他更多功能&操作指南可查看在线文档**: https://objs.gitee.io/mayfly-go-docs
##### 角色管理
![角色管理](https://foruda.gitee.com/images/1714379269408676381/6ac1e85c_1240250.png "屏幕截图")
##### 菜单资源管理
![菜单资源管理](https://foruda.gitee.com/images/1714379321338009940/a00d6a02_1240250.png "屏幕截图")
**其他更多功能&操作指南可查看上述项目文档**
## 💌 支持作者
如果觉得项目不错,或者已经在使用了,希望你可以去 <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

@@ -1,74 +0,0 @@
package biz
import (
"fmt"
"mayfly-go/base/global"
"mayfly-go/base/utils"
"reflect"
)
func ErrIsNil(err error, msg string, params ...interface{}) {
if err != nil {
global.Log.Error(msg + ": " + err.Error())
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func ErrIsNilAppendErr(err error, msg string) {
if err != nil {
panic(NewBizErr(fmt.Sprintf(msg, err.Error())))
}
}
func IsNil(err error) {
switch t := err.(type) {
case *BizError:
panic(t)
case error:
global.Log.Error("非业务异常: " + err.Error())
panic(NewBizErr(fmt.Sprintf("非业务异常: %s", err.Error())))
}
}
func IsTrue(exp bool, msg string, params ...interface{}) {
if !exp {
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func IsTrueBy(exp bool, err BizError) {
if !exp {
panic(err)
}
}
func NotEmpty(str string, msg string, params ...interface{}) {
if str == "" {
panic(NewBizErr(fmt.Sprintf(msg, params...)))
}
}
func NotNil(data interface{}, msg string) {
if reflect.ValueOf(data).IsNil() {
panic(NewBizErr(msg))
}
}
func NotBlank(data interface{}, msg string) {
if utils.IsBlank(reflect.ValueOf(data)) {
panic(NewBizErr(msg))
}
}
func IsEquals(data interface{}, data1 interface{}, msg string) {
if data != data1 {
panic(NewBizErr(msg))
}
}
func Nil(data interface{}, msg string) {
if !reflect.ValueOf(data).IsNil() {
panic(NewBizErr(msg))
}
}

View File

@@ -1,34 +0,0 @@
package biz
// 业务错误
type BizError struct {
code int16
err string
}
var (
Success *BizError = NewBizErrCode(200, "success")
BizErr *BizError = NewBizErrCode(400, "biz error")
ServerError *BizError = NewBizErrCode(500, "server error")
PermissionErr *BizError = NewBizErrCode(501, "token error")
)
// 错误消息
func (e *BizError) Error() string {
return e.err
}
// 错误码
func (e *BizError) Code() int16 {
return e.code
}
// 创建业务逻辑错误结构体,默认为业务逻辑错误
func NewBizErr(msg string) *BizError {
return &BizError{code: BizErr.code, err: msg}
}
// 创建业务逻辑错误结构体可设置指定错误code
func NewBizErrCode(code int16, msg string) *BizError {
return &BizError{code: code, err: msg}
}

View File

@@ -1,28 +0,0 @@
package captcha
import (
"mayfly-go/base/biz"
"github.com/mojocn/base64Captcha"
)
var store = base64Captcha.DefaultMemStore
var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit
// 生成验证码
func Generate() (string, string) {
c := base64Captcha.NewCaptcha(driver, store)
// 获取
id, b64s, err := c.Generate()
biz.ErrIsNilAppendErr(err, "获取验证码错误: %s")
return id, b64s
}
// 验证验证码
func Verify(id string, val string) bool {
if id == "" || val == "" {
return false
}
// 同时清理掉这个图片
return store.Verify(id, val, true)
}

View File

@@ -1,12 +0,0 @@
package config
import "fmt"
type App struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
}
func (a *App) GetAppInfo() string {
return fmt.Sprintf("[%s:%s]", a.Name, a.Version)
}

View File

@@ -1,62 +0,0 @@
package config
import (
"flag"
"fmt"
"mayfly-go/base/utils"
"mayfly-go/base/utils/assert"
"path/filepath"
)
// 配置文件映射对象
var Conf *Config
func init() {
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
flag.Parse()
// 获取启动参数中,配置文件的绝对路径
path, _ := filepath.Abs(*configFilePath)
startConfigParam = &CmdConfigParam{ConfigFilePath: path}
// 读取配置文件信息
yc := &Config{}
if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
panic(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))
}
// 校验配置文件内容信息
yc.Valid()
Conf = yc
}
// 启动配置参数
type CmdConfigParam struct {
ConfigFilePath string // -e 配置文件路径
}
// 启动可执行文件时的参数
var startConfigParam *CmdConfigParam
// yaml配置文件映射对象
type Config struct {
App *App `yaml:"app"`
Server *Server `yaml:"server"`
Jwt *Jwt `yaml:"jwt"`
Redis *Redis `yaml:"redis"`
Mysql *Mysql `yaml:"mysql"`
Log *Log `yaml:"log"`
}
// 配置文件内容校验
func (c *Config) Valid() {
assert.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空")
c.Jwt.Valid()
}
// 获取执行可执行文件时,指定的启动参数
func getStartConfig() *CmdConfigParam {
configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
flag.Parse()
// 获取配置文件绝对路径
path, _ := filepath.Abs(*configFilePath)
sc := &CmdConfigParam{ConfigFilePath: path}
return sc
}

View File

@@ -1,13 +0,0 @@
package config
import "mayfly-go/base/utils/assert"
type Jwt struct {
Key string `yaml:"key"`
ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
}
func (j *Jwt) Valid() {
assert.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空")
assert.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空")
}

View File

@@ -1,30 +0,0 @@
package config
import "path"
type Log struct {
Level string `yaml:"level"`
File *LogFile `yaml:"file"`
}
type LogFile struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
}
// 获取完整路径文件名
func (l *LogFile) GetFilename() string {
var filepath, filename string
if fp := l.Path; fp == "" {
filepath = "./"
} else {
filepath = fp
}
if fn := l.Name; fn == "" {
filename = "default.log"
} else {
filename = fn
}
return path.Join(filepath, filename)
}

View File

@@ -1,17 +0,0 @@
package config
type Mysql struct {
Host string `mapstructure:"path" json:"host" yaml:"host"`
Config string `mapstructure:"config" json:"config" yaml:"config"`
Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`
Username string `mapstructure:"username" json:"username" yaml:"username"`
Password string `mapstructure:"password" json:"password" yaml:"password"`
MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"`
MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"`
LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`
LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
}
func (m *Mysql) Dsn() string {
return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config
}

View File

@@ -1,32 +0,0 @@
package config
import "fmt"
type Server struct {
Port int `yaml:"port"`
Model string `yaml:"model"`
Cors bool `yaml:"cors"`
Tls *Tls `yaml:"tls"`
Static *[]*Static `yaml:"static"`
StaticFile *[]*StaticFile `yaml:"static-file"`
}
func (s *Server) GetPort() string {
return fmt.Sprintf(":%d", s.Port)
}
type Static struct {
RelativePath string `yaml:"relative-path"`
Root string `yaml:"root"`
}
type StaticFile struct {
RelativePath string `yaml:"relative-path"`
Filepath string `yaml:"filepath"`
}
type Tls struct {
Enable bool `yaml:"enable"` // 是否启用tls
KeyFile string `yaml:"key-file"` // 私钥文件路径
CertFile string `yaml:"cert-file"` // 证书文件路径
}

View File

@@ -1,84 +0,0 @@
package ctx
import (
"encoding/json"
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/logger"
"mayfly-go/base/utils"
"reflect"
"runtime/debug"
"github.com/sirupsen/logrus"
)
type LogInfo struct {
LogResp bool // 是否记录返回结果
Description string // 请求描述
}
func NewLogInfo(description string) *LogInfo {
return &LogInfo{Description: description, LogResp: false}
}
func (i *LogInfo) WithLogResp(logResp bool) *LogInfo {
i.LogResp = logResp
return i
}
func LogHandler(rc *ReqCtx) error {
li := rc.LogInfo
if li == nil {
return nil
}
lfs := logrus.Fields{}
if la := rc.LoginAccount; la != nil {
lfs["uid"] = la.Id
lfs["uname"] = la.Username
}
req := rc.GinCtx.Request
lfs[req.Method] = req.URL.Path
if err := rc.Err; err != nil {
logger.Log.WithFields(lfs).Error(getErrMsg(rc, err))
return nil
}
logger.Log.WithFields(lfs).Info(getLogMsg(rc))
return nil
}
func getLogMsg(rc *ReqCtx) string {
msg := rc.LogInfo.Description + fmt.Sprintf(" ->%dms", rc.timed)
if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
}
// 返回结果不为空,则记录返回结果
if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) {
respB, _ := json.Marshal(rc.ResData)
msg = msg + fmt.Sprintf("\n<-- %s", string(respB))
}
return msg
}
func getErrMsg(rc *ReqCtx, err interface{}) string {
msg := rc.LogInfo.Description
if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
rb, _ := json.Marshal(rc.ReqParam)
msg = msg + fmt.Sprintf("\n--> %s", string(rb))
}
var errMsg string
switch t := err.(type) {
case *biz.BizError:
errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
case error:
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack()))
case string:
errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack()))
}
return (msg + errMsg)
}

View File

@@ -1,113 +0,0 @@
package ctx
import (
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/cache"
"mayfly-go/base/config"
"time"
)
type Permission struct {
NeedToken bool // 是否需要token
Code string // 权限code
}
func NewPermission(code string) *Permission {
return &Permission{NeedToken: true, Code: code}
}
func (p *Permission) WithNeedToken(needToken bool) *Permission {
p.NeedToken = needToken
return p
}
type PermissionCodeRegistry interface {
// 保存用户权限code
SaveCodes(userId uint64, codes []string)
// 判断用户是否拥有该code的权限
HasCode(userId uint64, code string) bool
Remove(userId uint64)
}
type DefaultPermissionCodeRegistry struct {
cache *cache.TimedCache
}
func (r *DefaultPermissionCodeRegistry) SaveCodes(userId uint64, codes []string) {
if r.cache == nil {
r.cache = cache.NewTimedCache(time.Minute*time.Duration(config.Conf.Jwt.ExpireTime), 5*time.Second)
}
r.cache.Put(fmt.Sprintf("%v", userId), codes)
}
func (r *DefaultPermissionCodeRegistry) HasCode(userId uint64, code string) bool {
if r.cache == nil {
return false
}
codes, found := r.cache.Get(fmt.Sprintf("%v", userId))
if !found {
return false
}
for _, v := range codes.([]string) {
if v == code {
return true
}
}
return false
}
func (r *DefaultPermissionCodeRegistry) Remove(userId uint64) {
r.cache.Delete(fmt.Sprintf("%v", userId))
}
// 保存用户权限code
func SavePermissionCodes(userId uint64, codes []string) {
permissionCodeRegistry.SaveCodes(userId, codes)
}
// 删除用户权限code
func DeletePermissionCodes(userId uint64) {
permissionCodeRegistry.Remove(userId)
}
// 设置权限code注册器
func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) {
permissionCodeRegistry = pcr
}
var (
permissionCodeRegistry PermissionCodeRegistry = &DefaultPermissionCodeRegistry{}
// permissionError = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg)
)
func PermissionHandler(rc *ReqCtx) error {
permission := rc.RequiredPermission
// 如果需要的权限信息不为空并且不需要token则不返回错误继续后续逻辑
if permission != nil && !permission.NeedToken {
return nil
}
tokenStr := rc.GinCtx.Request.Header.Get("Authorization")
// header不存在则从查询参数token中获取
if tokenStr == "" {
tokenStr = rc.GinCtx.Query("token")
}
if tokenStr == "" {
return biz.PermissionErr
}
loginAccount, err := ParseToken(tokenStr)
if err != nil || loginAccount == nil {
return biz.PermissionErr
}
// 权限不为nil并且permission code不为空则校验是否有权限code
if permission != nil && permission.Code != "" {
if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) {
return biz.PermissionErr
}
}
rc.LoginAccount = loginAccount
return nil
}

View File

@@ -1,122 +0,0 @@
package ctx
import (
"io"
"mayfly-go/base/ginx"
"mayfly-go/base/model"
"mayfly-go/base/utils/assert"
"time"
"github.com/gin-gonic/gin"
)
// 处理函数
type HandlerFunc func(*ReqCtx)
type ReqCtx struct {
GinCtx *gin.Context // gin context
// NeedToken bool // 是否需要token
RequiredPermission *Permission // 需要的权限信息默认为nil需要校验token
LoginAccount *model.LoginAccount // 登录账号信息只有校验token后才会有值
LogInfo *LogInfo // 日志相关信息
ReqParam interface{} // 请求参数,主要用于记录日志
ResData interface{} // 响应结果
Err interface{} // 请求错误
timed int64 // 执行时间
noRes bool // 无需返回结果,即文件下载等
}
func (rc *ReqCtx) Handle(handler HandlerFunc) {
ginCtx := rc.GinCtx
defer func() {
if err := recover(); err != nil {
rc.Err = err
ginx.ErrorRes(ginCtx, err)
}
// 应用所有请求后置处理器
ApplyHandlerInterceptor(afterHandlers, rc)
}()
assert.IsTrue(ginCtx != nil, "ginContext == nil")
// 默认为不记录请求参数可在handler回调函数中覆盖赋值
rc.ReqParam = 0
// 默认响应结果为nil可在handler中赋值
rc.ResData = nil
// 调用请求前所有处理器
err := ApplyHandlerInterceptor(beforeHandlers, rc)
if err != nil {
panic(err)
}
begin := time.Now()
handler(rc)
rc.timed = time.Now().Sub(begin).Milliseconds()
if !rc.noRes {
ginx.SuccessRes(ginCtx, rc.ResData)
}
}
func (rc *ReqCtx) Download(reader io.Reader, filename string) {
rc.noRes = true
ginx.Download(rc.GinCtx, reader, filename)
}
// 新建请求上下文默认需要校验token
func NewReqCtx() *ReqCtx {
return &ReqCtx{}
}
func NewReqCtxWithGin(g *gin.Context) *ReqCtx {
return &ReqCtx{GinCtx: g}
}
// 调用该方法设置请求描述,则默认记录日志,并不记录响应结果
func (r *ReqCtx) WithLog(li *LogInfo) *ReqCtx {
r.LogInfo = li
return r
}
// 设置请求上下文需要的权限信息
func (r *ReqCtx) WithRequiredPermission(permission *Permission) *ReqCtx {
r.RequiredPermission = permission
return r
}
// 是否需要token
func (r *ReqCtx) WithNeedToken(needToken bool) *ReqCtx {
r.RequiredPermission = &Permission{NeedToken: false}
return r
}
// 处理器拦截器函数
type HandlerInterceptorFunc func(*ReqCtx) error
type HandlerInterceptors []HandlerInterceptorFunc
var (
beforeHandlers HandlerInterceptors
afterHandlers HandlerInterceptors
)
// 使用前置处理器函数
func UseBeforeHandlerInterceptor(b HandlerInterceptorFunc) {
beforeHandlers = append(beforeHandlers, b)
}
// 使用后置处理器函数
func UseAfterHandlerInterceptor(b HandlerInterceptorFunc) {
afterHandlers = append(afterHandlers, b)
}
// 应用指定处理器拦截器,如果有一个错误则直接返回错误
func ApplyHandlerInterceptor(his HandlerInterceptors, rc *ReqCtx) interface{} {
for _, handler := range his {
if err := handler(rc); err != nil {
return err
}
}
return nil
}

View File

@@ -1,49 +0,0 @@
package ctx
import (
"errors"
"mayfly-go/base/biz"
"mayfly-go/base/config"
"mayfly-go/base/model"
"time"
"github.com/dgrijalva/jwt-go"
)
var (
JwtKey = config.Conf.Jwt.Key
ExpTime = config.Conf.Jwt.ExpireTime
)
// 创建用户token
func CreateToken(userId uint64, username string) string {
// 带权限创建令牌
// 设置有效期过期需要重新登录获取token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": userId,
"username": username,
"exp": time.Now().Add(time.Minute * time.Duration(ExpTime)).Unix(),
})
// 使用自定义字符串加密 and get the complete encoded token as a string
tokenString, err := token.SignedString([]byte(JwtKey))
biz.ErrIsNil(err, "token创建失败")
return tokenString
}
// 解析token并返回登录者账号信息
func ParseToken(tokenStr string) (*model.LoginAccount, error) {
if tokenStr == "" {
return nil, errors.New("token error")
}
// Parse token
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return []byte(JwtKey), nil
})
if err != nil || token == nil {
return nil, err
}
i := token.Claims.(jwt.MapClaims)
return &model.LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
}

View File

@@ -1,78 +0,0 @@
package ginx
import (
"io"
"mayfly-go/base/biz"
"mayfly-go/base/global"
"mayfly-go/base/model"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// 绑定并校验请求结构体参数
func BindJsonAndValid(g *gin.Context, data interface{}) {
if err := g.ShouldBindJSON(data); err != nil {
panic(biz.NewBizErr(err.Error()))
}
}
// 绑定查询字符串到
func BindQuery(g *gin.Context, data interface{}) {
if err := g.ShouldBindQuery(data); err != nil {
panic(biz.NewBizErr(err.Error()))
}
}
// 获取分页参数
func GetPageParam(g *gin.Context) *model.PageParam {
return &model.PageParam{PageNum: QueryInt(g, "pageNum", 1), PageSize: QueryInt(g, "pageSize", 10)}
}
// 获取查询参数中指定参数值并转为int
func QueryInt(g *gin.Context, qm string, defaultInt int) int {
qv := g.Query(qm)
if qv == "" {
return defaultInt
}
qvi, err := strconv.Atoi(qv)
biz.ErrIsNil(err, "query param not int")
return qvi
}
// 获取路径参数
func PathParamInt(g *gin.Context, pm string) int {
value, _ := strconv.Atoi(g.Param(pm))
return value
}
// 文件下载
func Download(g *gin.Context, reader io.Reader, filename string) {
g.Header("Content-Type", "application/octet-stream")
g.Header("Content-Disposition", "attachment; filename="+filename)
io.Copy(g.Writer, reader)
}
// 返回统一成功结果
func SuccessRes(g *gin.Context, data interface{}) {
g.JSON(http.StatusOK, model.Success(data))
}
// 返回失败结果集
func ErrorRes(g *gin.Context, err interface{}) {
switch t := err.(type) {
case *biz.BizError:
g.JSON(http.StatusOK, model.Error(t))
case error:
g.JSON(http.StatusOK, model.ServerError())
global.Log.Error(t)
// panic(err)
case string:
g.JSON(http.StatusOK, model.ServerError())
global.Log.Error(t)
// panic(err)
default:
global.Log.Error(t)
}
}

View File

@@ -1,13 +0,0 @@
package global
import (
"github.com/go-redis/redis"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
var (
Log *logrus.Logger // 日志
Db *gorm.DB // gorm
RedisCli *redis.Client // redis
)

View File

@@ -1,66 +0,0 @@
package logger
import (
"fmt"
"mayfly-go/base/config"
"mayfly-go/base/global"
"os"
"strings"
"time"
"github.com/sirupsen/logrus"
)
var Log = logrus.New()
func init() {
Log.SetFormatter(new(LogFormatter))
Log.SetReportCaller(true)
logConf := config.Conf.Log
// 如果不存在日志配置信息则默认debug级别
if logConf == nil {
Log.SetLevel(logrus.DebugLevel)
return
}
// 根据配置文件设置日志级别
if level := logConf.Level; level != "" {
l, err := logrus.ParseLevel(level)
if err != nil {
panic(fmt.Sprintf("日志级别不存在: %s", level))
}
Log.SetLevel(l)
} else {
Log.SetLevel(logrus.DebugLevel)
}
if logFile := logConf.File; logFile != nil {
//写入文件
file, err := os.OpenFile(logFile.GetFilename(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666)
if err != nil {
panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
}
Log.Out = file
}
global.Log = Log
}
type LogFormatter struct{}
func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
timestamp := time.Now().Local().Format("2006-01-02 15:04:05.000")
level := entry.Level
logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String()))
// 如果存在调用信息,记录方法信息及行号
if caller := entry.Caller; caller != nil {
logMsg = logMsg + fmt.Sprintf(" [%s:%d]", caller.Function, caller.Line)
}
for k, v := range entry.Data {
logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v)
}
logMsg = logMsg + fmt.Sprintf(" : %s\n", entry.Message)
return []byte(logMsg), nil
}

View File

@@ -1,15 +0,0 @@
package model
type AppContext struct {
}
type LoginAccount struct {
Id uint64
Username string
}
type Permission struct {
CheckToken bool // 是否检查token
Code string // 权限码
Name string // 描述
}

View File

@@ -1,196 +0,0 @@
package model
import (
"fmt"
"mayfly-go/base/biz"
"mayfly-go/base/global"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
type Model struct {
Id uint64 `json:"id"`
CreateTime *time.Time `json:"createTime"`
CreatorId uint64 `json:"creatorId"`
Creator string `json:"creator"`
UpdateTime *time.Time `json:"updateTime"`
ModifierId uint64 `json:"modifierId"`
Modifier string `json:"modifier"`
}
// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息
func (m *Model) SetBaseInfo(account *LoginAccount) {
nowTime := time.Now()
isCreate := m.Id == 0
if isCreate {
m.CreateTime = &nowTime
}
m.UpdateTime = &nowTime
if account == nil {
return
}
id := account.Id
name := account.Username
if isCreate {
m.CreatorId = id
m.Creator = name
}
m.Modifier = name
m.ModifierId = id
}
func Tx(funcs ...func(db *gorm.DB) error) (err error) {
tx := global.Db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
err = fmt.Errorf("%v", err)
}
}()
for _, f := range funcs {
err = f(tx)
if err != nil {
tx.Rollback()
return
}
}
err = tx.Commit().Error
return
}
// 根据id获取实体对象。model需为指针类型需要将查询出来的值赋值给model
//
// 若error不为nil则为不存在该记录
func GetById(model interface{}, id uint64, cols ...string) error {
return global.Db.Select(cols).Where("id = ?", id).First(model).Error
}
// 根据id列表查询
func GetByIdIn(model interface{}, list interface{}, ids []uint64, orderBy ...string) {
var orderByStr string
if orderBy == nil {
orderByStr = "id desc"
} else {
orderByStr = strings.Join(orderBy, ",")
}
global.Db.Model(model).Where("id in (?)", ids).Order(orderByStr).Find(list)
}
// 根据id列表查询
func CountBy(model interface{}) int64 {
var count int64
global.Db.Model(model).Where(model).Count(&count)
return count
}
// 根据id更新model更新字段为model中不为空的值即int类型不为0ptr类型不为nil这类字段值
func UpdateById(model interface{}) error {
return global.Db.Model(model).Updates(model).Error
}
// 根据id删除model
func DeleteById(model interface{}, id uint64) error {
return global.Db.Delete(model, "id = ?", id).Error
}
// 根据条件删除
func DeleteByCondition(model interface{}) error {
return global.Db.Where(model).Delete(model).Error
}
// 插入model
func Insert(model interface{}) error {
return global.Db.Create(model).Error
}
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users *[]User可指定为非model结构体即只包含需要返回的字段结构体
func ListBy(model interface{}, list interface{}, cols ...string) {
global.Db.Model(model).Select(cols).Where(model).Find(list)
}
// 获取满足model中不为空的字段值条件的所有数据.
//
// @param list为数组类型 如 var users *[]User可指定为非model结构体
func ListByOrder(model interface{}, list interface{}, order ...string) {
var orderByStr string
if order == nil {
orderByStr = "id desc"
} else {
orderByStr = strings.Join(order, ",")
}
global.Db.Model(model).Where(model).Order(orderByStr).Find(list)
}
// 获取满足model中不为空的字段值条件的单个对象。model需为指针类型需要将查询出来的值赋值给model
//
// 若 error不为nil则为不存在该记录
func GetBy(model interface{}, cols ...string) error {
return global.Db.Select(cols).Where(model).First(model).Error
}
// 获取满足conditionModel中不为空的字段值条件的单个对象。model需为指针类型需要将查询出来的值赋值给model
// @param toModel 需要查询的字段
// 若 error不为nil则为不存在该记录
func GetByConditionTo(conditionModel interface{}, toModel interface{}) error {
return global.Db.Model(conditionModel).Where(conditionModel).First(toModel).Error
}
// 获取分页结果
func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interface{}, orderBy ...string) *PageResult {
var count int64
err := global.Db.Model(conditionModel).Where(conditionModel).Count(&count).Error
biz.ErrIsNilAppendErr(err, " 查询错误:%s")
if count == 0 {
return &PageResult{Total: 0, List: []string{}}
}
page := pageParam.PageNum
pageSize := pageParam.PageSize
var orderByStr string
if orderBy == nil {
orderByStr = "id desc"
} else {
orderByStr = strings.Join(orderBy, ",")
}
err = global.Db.Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error
biz.ErrIsNil(err, "查询失败")
return &PageResult{Total: count, List: toModels}
}
// 根据sql获取分页对象
func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...interface{}) *PageResult {
db := global.Db
selectIndex := strings.Index(sql, "SELECT ") + 7
fromIndex := strings.Index(sql, " FROM")
selectCol := sql[selectIndex:fromIndex]
countSql := strings.Replace(sql, selectCol, "COUNT(*) AS total ", 1)
// 查询count
var count int
err := db.Raw(countSql, args...).Scan(&count).Error
biz.ErrIsNilAppendErr(err, "查询失败: %s")
if count == 0 {
return &PageResult{Total: 0, List: []string{}}
}
// 分页查询
limitSql := sql + " LIMIT " + strconv.Itoa((param.PageNum-1)*param.PageSize) + ", " + strconv.Itoa(param.PageSize)
err = db.Raw(limitSql).Scan(toModel).Error
biz.ErrIsNil(err, "查询失败: %s")
return &PageResult{Total: int64(count), List: toModel}
}
func GetListBySql(sql string, params ...interface{}) []map[string]interface{} {
var maps []map[string]interface{}
global.Db.Raw(sql, params...).Scan(&maps)
return maps
}
func GetListBySql2Model(sql string, toEntity interface{}, params ...interface{}) error {
return global.Db.Raw(sql, params...).Find(toEntity).Error
}

View File

@@ -1,13 +0,0 @@
package model
// 分页参数
type PageParam struct {
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
}
// 分页结果
type PageResult struct {
Total int64 `json:"total"`
List interface{} `json:"list"`
}

View File

@@ -1,64 +0,0 @@
package rediscli
import (
"fmt"
"time"
"github.com/go-redis/redis"
)
var cli *redis.Client
func SetCli(client *redis.Client) {
cli = client
}
func GetCli() *redis.Client {
return cli
}
// get key value
func Get(key string) string {
val, err := cli.Get(key).Result()
switch {
case err == redis.Nil:
fmt.Println("key does not exist")
case err != nil:
fmt.Println("Get failed", err)
case val == "":
fmt.Println("value is empty")
}
return val
}
// set key value
func Set(key string, val string, expiration time.Duration) {
cli.Set(key, val, expiration)
}
func HSet(key string, field string, val interface{}) {
cli.HSet(key, field, val)
}
// hget
func HGet(key string, field string) string {
val, _ := cli.HGet(key, field).Result()
return val
}
// hget
func HExist(key string, field string) bool {
val, _ := cli.HExists(key, field).Result()
return val
}
// hgetall
func HGetAll(key string) map[string]string {
vals, _ := cli.HGetAll(key).Result()
return vals
}
// hdel
func HDel(key string, fields ...string) int {
return int(cli.HDel(key, fields...).Val())
}

View File

@@ -1,16 +0,0 @@
package starter
import (
"mayfly-go/base/global"
)
func PrintBanner() {
global.Log.Print(`
__ _
_ __ ___ __ _ _ _ / _| |_ _ __ _ ___
| '_ ' _ \ / _' | | | | |_| | | | |_____ / _' |/ _ \
| | | | | | (_| | |_| | _| | |_| |_____| (_| | (_) |
|_| |_| |_|\__,_|\__, |_| |_|\__, | \__, |\___/
|___/ |___/ |___/
`)
}

View File

@@ -1,45 +0,0 @@
package starter
import (
"mayfly-go/base/config"
"mayfly-go/base/global"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
func InitDb() {
global.Db = GormMysql()
}
func GormMysql() *gorm.DB {
m := config.Conf.Mysql
if m == nil || m.Dbname == "" {
global.Log.Panic("未找到数据库配置信息")
return nil
}
global.Log.Infof("连接mysql [%s]", m.Host)
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置
}
ormConfig := &gorm.Config{NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_",
SingularTable: true,
}, Logger: logger.Default.LogMode(logger.Silent)}
if db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig); err != nil {
global.Log.Panicf("连接mysql失败! [%s]", err.Error())
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
}

View File

@@ -1,34 +0,0 @@
package starter
import (
"fmt"
"mayfly-go/base/config"
"mayfly-go/base/global"
"github.com/go-redis/redis"
)
func InitRedis() {
global.RedisCli = ConnRedis()
}
func ConnRedis() *redis.Client {
// 设置redis客户端
redisConf := config.Conf.Redis
if redisConf == nil {
global.Log.Panic("未找到redis配置信息")
return nil
}
global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port)
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", redisConf.Host, redisConf.Port),
Password: redisConf.Password, // no password set
DB: redisConf.Db, // use default DB
})
// 测试连接
_, e := rdb.Ping().Result()
if e != nil {
global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d]", redisConf.Host, redisConf.Port))
}
return rdb
}

View File

@@ -1,34 +0,0 @@
package starter
import (
"mayfly-go/base/biz"
"mayfly-go/base/config"
"mayfly-go/base/ctx"
"mayfly-go/base/global"
"mayfly-go/server/initialize"
)
func RunWebServer() {
// 权限处理器
ctx.UseBeforeHandlerInterceptor(ctx.PermissionHandler)
// 日志处理器
ctx.UseAfterHandlerInterceptor(ctx.LogHandler)
// 注册路由
web := initialize.InitRouter()
server := config.Conf.Server
port := server.GetPort()
if app := config.Conf.App; app != nil {
global.Log.Infof("%s- Listening and serving HTTP on %s", app.GetAppInfo(), port)
} else {
global.Log.Infof("Listening and serving HTTP on %s", port)
}
var err error
if server.Tls != nil && server.Tls.Enable {
err = web.RunTLS(port, server.Tls.CertFile, server.Tls.KeyFile)
} else {
err = web.Run(port)
}
biz.ErrIsNilAppendErr(err, "服务启动失败: %s")
}

View File

@@ -1,33 +0,0 @@
package utils
// 数组比较
// 依次返回,新增值,删除值,以及不变值
func ArrayCompare(newArr []interface{}, oldArr []interface{}, compareFun func(interface{}, interface{}) bool) ([]interface{}, []interface{}, []interface{}) {
var unmodifierValue []interface{}
ni, oi := 0, 0
for {
if ni >= len(newArr) {
break
}
nv := newArr[ni]
for {
if oi >= len(oldArr) {
oi = 0
break
}
ov := oldArr[oi]
if compareFun(nv, ov) {
unmodifierValue = append(unmodifierValue, nv)
// 新数组移除该位置值
newArr = append(newArr[:ni], newArr[ni+1:]...)
oldArr = append(oldArr[:oi], oldArr[oi+1:]...)
ni = ni - 1
oi = oi - 1
}
oi = oi + 1
}
ni = ni + 1
}
return newArr, oldArr, unmodifierValue
}

View File

@@ -1,17 +0,0 @@
package utils
import (
"fmt"
"testing"
)
func TestArrayCompare(t *testing.T) {
newArr := []interface{}{1, 2, 3, 5}
oldArr := []interface{}{3, 6}
add, del, unmodifier := ArrayCompare(newArr, oldArr, func(i1, i2 interface{}) bool {
return i1.(int) == i2.(int)
})
fmt.Println(add...)
fmt.Println(del...)
fmt.Println(unmodifier...)
}

View File

@@ -1,13 +0,0 @@
package utils
import (
"crypto/md5"
"encoding/hex"
)
// md5
func Md5(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}

View File

@@ -1,14 +0,0 @@
package utils
import (
"encoding/json"
)
func Json2Map(jsonStr string) map[string]interface{} {
var res map[string]interface{}
if jsonStr == "" {
return res
}
_ = json.Unmarshal([]byte(jsonStr), &res)
return res
}

View File

@@ -1,44 +0,0 @@
package utils
import (
"reflect"
"strconv"
)
func GetString4Map(m map[string]interface{}, key string) string {
return m[key].(string)
}
func GetInt4Map(m map[string]interface{}, key string) int {
i := m[key]
iKind := reflect.TypeOf(i).Kind()
if iKind == reflect.Int {
return i.(int)
}
if iKind == reflect.String {
i, _ := strconv.Atoi(i.(string))
return i
}
return 0
}
// map构造器
type mapBuilder struct {
m map[string]interface{}
}
func MapBuilder(key string, value interface{}) *mapBuilder {
mb := new(mapBuilder)
mb.m = make(map[string]interface{}, 4)
mb.m[key] = value
return mb
}
func (mb *mapBuilder) Put(key string, value interface{}) *mapBuilder {
mb.m[key] = value
return mb
}
func (mb *mapBuilder) ToMap() map[string]interface{} {
return mb.m
}

View File

@@ -1,9 +0,0 @@
package utils
import "runtime"
// 获取调用堆栈信息
func GetStackTrace() string {
var buf [2 << 10]byte
return string(buf[:runtime.Stack(buf[:], false)])
}

View File

@@ -1,27 +0,0 @@
package ws
const SuccessMsgType = 1
const ErrorMsgType = 0
const InfoMsgType = 2
// websocket消息
type Msg struct {
Type int `json:"type"` // 消息类型
Title string `json:"title"` // 消息标题
Msg string `json:"msg"` // 消息内容
}
// 普通消息
func NewMsg(title, msg string) *Msg {
return &Msg{Type: InfoMsgType, Title: title, Msg: msg}
}
// 成功消息
func SuccessMsg(title, msg string) *Msg {
return &Msg{Type: SuccessMsgType, Title: title, Msg: msg}
}
// 错误消息
func ErrMsg(title, msg string) *Msg {
return &Msg{Type: ErrorMsgType, Title: title, Msg: msg}
}

View File

@@ -1,74 +0,0 @@
package ws
import (
"encoding/json"
"mayfly-go/base/global"
"net/http"
"time"
"github.com/gorilla/websocket"
)
var Upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024 * 1024 * 10,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
var conns = make(map[uint64]*websocket.Conn, 100)
func init() {
checkConn()
}
// 放置ws连接
func Put(userId uint64, conn *websocket.Conn) {
existConn := conns[userId]
if existConn != nil {
Delete(userId)
}
conn.SetCloseHandler(func(code int, text string) error {
Delete(userId)
return nil
})
conns[userId] = conn
}
func checkConn() {
heartbeat := time.Duration(60) * time.Second
tick := time.NewTicker(heartbeat)
go func() {
for range tick.C {
// 遍历所有连接ping失败的则删除掉
for uid, conn := range conns {
err := conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(heartbeat/2))
if err != nil {
Delete(uid)
return
}
}
}
}()
}
// 删除ws连接
func Delete(userid uint64) {
global.Log.Info("移除websocket连接uid = ", userid)
conn := conns[userid]
if conn != nil {
conn.Close()
delete(conns, userid)
}
}
// 对指定用户发送消息
func SendMsg(userId uint64, msg *Msg) {
conn := conns[userId]
if conn != nil {
bytes, _ := json.Marshal(msg)
conn.WriteMessage(websocket.TextMessage, bytes)
}
}

202
build_release.sh Executable file
View File

@@ -0,0 +1,202 @@
#bin/bash
#----------------------------------------------
# 前后端打包编译至指定目录,即快速制作发行版
#----------------------------------------------
project_path=`pwd`
# 构建后的二进制执行文件名
exec_file_name="mayfly-go"
# web项目目录
web_folder="${project_path}/frontend"
# server目录
server_folder="${project_path}/server"
function echo_red() {
echo -e "\033[1;31m$1\033[0m"
}
function echo_green() {
echo -e "\033[1;32m$1\033[0m"
}
function echo_yellow() {
echo -e "\033[1;33m$1\033[0m"
}
function buildWeb() {
cd ${web_folder}
copy2Server=$1
echo_yellow "-------------------Start bundling frontends-------------------"
yarn run build
if [ "${copy2Server}" == "2" ] ; then
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 ">>>>>>>>>>>>>>>>>>>End of packaging frontend<<<<<<<<<<<<<<<<<<<<\n"
}
function build() {
cd ${project_path}
# 打包产物的输出目录
toFolder=$1
os=$2
arch=$3
copyDocScript=$4
echo_yellow "-------------------Start a bundle build - ${os}-${arch}-------------------"
cd ${server_folder}
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 "The desired folder already exists. Clear the folder"
sudo rm -rf ${toFolder}
fi
echo_green "Create '${toFolder}' Directory"
mkdir ${toFolder}
echo_green "Move binary to '${toFolder}'"
mv ${server_folder}/${execFileName} ${toFolder}
# if [ "${copy2Server}" == "1" ] ; then
# echo_green "拷贝前端静态页面至'${toFolder}/static'"
# mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
# fi
if [ "${copyDocScript}" == "1" ] ; then
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} - Bundle build complete <<<<<<<<<<<<<<<<<<<<\n"
}
function buildLinuxAmd64() {
build "$1/mayfly-go-linux-amd64" "linux" "amd64" $2
}
function buildLinuxArm64() {
build "$1/mayfly-go-linux-arm64" "linux" "arm64" $2
}
function buildWindows() {
build "$1/mayfly-go-windows" "windows" "amd64" $2
}
function buildMac() {
build "$1/mayfly-go-mac" "darwin" "amd64" $2
}
function buildDocker() {
echo_yellow "-------------------Start building the docker image-------------------"
imageVersion=$1
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 "-------------------The docker buildx build image starts-------------------"
imageVersion=$1
imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
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 "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"
copyDocScript="1"
if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
# 构建结果的目的路径
read -p "Please enter the build product output directory [default current path]: " toPath
if [ ! -d ${toPath} ] ; then
echo_red "Build product output directory does not exist!"
exit;
fi
if [ "${toPath}" == "" ] ; then
toPath="."
fi
read -p "Whether to copy documents & Scripts [0-> No 1-> Yes][Default yes]: " copyDocScript
if [ "${copyDocScript}" == "" ] ; then
copyDocScript="1"
fi
# 进入目标路径,并赋值全路径
cd ${toPath}
toPath=`pwd`
# read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
runBuildWeb="2"
# 编译web前端
buildWeb ${runBuildWeb}
fi
if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
read -p "Please enter the docker image version (default latest) : " imageVersion
if [ "${imageVersion}" == "" ] ; then
imageVersion="latest"
fi
fi
case ${buildType} in
"1")
buildLinuxAmd64 ${toPath} ${copyDocScript}
;;
"2")
buildLinuxArm64 ${toPath} ${copyDocScript}
;;
"3")
buildWindows ${toPath} ${copyDocScript}
;;
"4")
buildMac ${toPath} ${copyDocScript}
;;
"5")
buildDocker ${imageVersion}
;;
"6")
buildxDocker ${imageVersion}
;;
*)
buildLinuxAmd64 ${toPath} ${copyDocScript}
buildLinuxArm64 ${toPath} ${copyDocScript}
buildWindows ${toPath} ${copyDocScript}
buildMac ${toPath} ${copyDocScript}
;;
esac
if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
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
rm -rf ${server_folder}/static/static/index.html
fi
}
runBuild

31
docker-compose.yaml Normal file
View File

@@ -0,0 +1,31 @@
version: "3.9"
services:
mysql:
image: "mysql:8"
container_name: mayfly-go-mysql
environment:
MYSQL_ROOT_PASSWORD: 111049
MYSQL_DATABASE: mayfly-go
TZ: Asia/Shanghai
volumes:
- ./server/docs/docker-compose/mysql/data/mydir:/mydir
- ./server/docs/docker-compose/mysql/data/datadir:/var/lib/mysql
restart: always
server:
image: ccr.ccs.tencentyun.com/mayfly/mayfly-go:v1.8.5
build:
context: .
dockerfile: Dockerfile
container_name: mayfly-go-server
ports:
- "8888:8888"
environment:
TZ: Asia/Shanghai
WAIT_HOSTS: mysql:3306
volumes:
- ./server/config.yml.example:/mayfly/config.yml
depends_on:
- mysql
restart: always

View File

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

11
frontend/.env.development Normal file
View File

@@ -0,0 +1,11 @@
# 本地环境
ENV = 'development'
VITE_OPEN = true
# 本地环境接口地址
VITE_API_URL = '/api'
# 路由模式
# Optional: hash | history
VITE_ROUTER_MODE = hash

9
frontend/.env.production Normal file
View File

@@ -0,0 +1,9 @@
# 线上环境
ENV = 'production'
# 线上环境接口地址
VITE_API_URL = '/api'
# 路由模式
# Optional: hash | history
VITE_ROUTER_MODE = hash

76
frontend/.eslintrc.cjs Normal file
View File

@@ -0,0 +1,76 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.vue'],
rules: {
'no-undef': 'off',
},
},
],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
// https://typescript-eslint.io/rules/no-unused-vars/
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/no-unused-vars': [2],
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/html-self-closing': 'off',
'vue/no-multiple-template-root': 'off',
'vue/require-default-prop': 'off',
'vue/no-v-model-argument': 'off',
'vue/no-arrow-functions-in-watch': 'off',
'vue/no-template-key': 'off',
'vue/no-v-html': 'off',
'vue/comment-directive': 'off',
'vue/no-parsing-error': 'off',
'vue/no-deprecated-v-on-native-modifier': 'off',
'vue/multi-word-component-names': 'off',
'no-useless-escape': 'off',
'no-sparse-arrays': 'off',
'no-prototype-builtins': 'off',
'no-constant-condition': 'off',
'no-use-before-define': 'off',
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off',
'generator-star-spacing': 'off',
'no-unreachable': 'off',
'no-multiple-template-root': 'off',
'no-unused-vars': 'error',
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
// 'no-console': 'error',
'no-redeclare': 'off',
},
};

View File

@@ -1,7 +1,8 @@
.DS_Store
node_modules
/dist
*.lock
pnpm-lock.yaml
# local env files
.env.local

39
frontend/.prettierrc.cjs Normal file
View File

@@ -0,0 +1,39 @@
module.exports = {
// 一行最多多少个字符
printWidth: 160,
// 指定每个缩进级别的空格数
tabWidth: 4,
// 使用制表符而不是空格缩进行
useTabs: false,
// 在语句末尾打印分号
semi: true,
// 使用单引号而不是双引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf',
};

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"
]
}

25
frontend/public/config.js Normal file
View File

@@ -0,0 +1,25 @@
window.globalConfig = {
// 默认为空以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
BaseApiUrl: '',
BaseWsUrl: '',
};
// index.html添加百秒级时间戳防止被浏览器缓存
// !(function () {
// let t = 't=' + new Date().getTime().toString().substring(0, 8);
// let search = location.search;
// let m = search && search.match(/t=\d*/g);
// console.log(location);
// if (m[0]) {
// if (m[0] !== t) {
// location.search = search.replace(m[0], t);
// }
// } else {
// if (search.indexOf('?') > -1) {
// location.search = search + '&' + t;
// } else {
// location.search = t;
// }
// }
// })();

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

137
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,137 @@
<template>
<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>
</el-config-provider>
</template>
<script setup lang="ts" name="app">
import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
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();
const themeConfigStores = useThemeConfig();
const { themeConfig } = storeToRefs(themeConfigStores);
// 定义变量内容
const { locale, t } = useI18n();
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 初始化系统主题
themeConfigStores.initThemeConfig();
});
});
// 监听 themeConfig isWartermark配置文件的变化
watch(
() => themeConfig.value.isWatermark,
(val) => {
if (val) {
setTimeout(() => {
setWatermarkContent();
refreshWatermarkTime();
resume();
}, 500);
} else {
pause();
}
}
);
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) {
pause();
}
refreshWatermarkTime();
}, 60000);
const setWatermarkContent = () => {
themeConfigStores.setWatermarkUser();
};
/**
* 刷新水印时间
*/
const refreshWatermarkTime = () => {
themeConfigStores.setWatermarkNowTime();
};
// 页面销毁时,关闭监听布局配置
onUnmounted(() => {
mittBus.off('openSetingsDrawer', () => {});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
nextTick(() => {
document.title = `${t((route.meta.title as string) || '')} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
});
}
);
</script>

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
}
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

126
frontend/src/common/Api.ts Normal file
View File

@@ -0,0 +1,126 @@
import request from './request';
import { useApiFetch } from '@/hooks/useRequest';
/**
* 可用于各模块定义各自api请求
*/
class Api {
/**
* 请求url
*/
url: string;
/**
* 请求方法
*/
method: string;
/**
* 请求前处理函数
* param1: param请求参数
*/
beforeHandler: Function;
constructor(url: string, method: string) {
this.url = url;
this.method = method;
}
/**
* 设置请求前处理回调函数
* @param func 请求前处理器
* @returns this
*/
withBeforeHandler(func: Function) {
this.beforeHandler = func;
return this;
}
/**
* 获取权限的完整url
*/
getUrl() {
return request.getApiUrl(this.url);
}
/**
* 响应式使用该api
* @param params 响应式params
* @param reqOptions 其他可选值
* @returns
*/
useApi<T>(params: any = null, reqOptions: RequestInit = {}) {
return useApiFetch<T>(this, params, reqOptions);
}
/**
* fetch 请求对应的该api
* @param {Object} param 请求该api的参数
*/
async request(param: any = null, options: any = {}): Promise<any> {
const { execute, data } = this.useApi(param, options);
await execute();
return data.value;
}
/**
* xhr 请求对应的该api
* @param {Object} param 请求该api的参数
*/
async xhrReq(param: any = null, options: any = {}): Promise<any> {
if (this.beforeHandler) {
await this.beforeHandler(param);
}
return request.xhrReq(this.method, this.url, param, options);
}
/** 静态方法 **/
/**
* 静态工厂返回Api对象并设置url与method属性
* @param url url
* @param method 请求方法(get,post,put,delete...)
*/
static create(url: string, method: string): Api {
return new Api(url, method);
}
/**
* 创建get api
* @param url url
*/
static newGet(url: string): Api {
return Api.create(url, 'get');
}
/**
* new post api
* @param url url
*/
static newPost(url: string): Api {
return Api.create(url, 'post');
}
/**
* new put api
* @param url url
*/
static newPut(url: string): Api {
return Api.create(url, 'put');
}
/**
* new delete api
* @param url url
*/
static newDelete(url: string): Api {
return Api.create(url, 'delete');
}
}
export default Api;
export class PageRes {
list: any[] = [];
total: number = 0;
}

102
frontend/src/common/Enum.ts Normal file
View File

@@ -0,0 +1,102 @@
export interface EnumValueTag {
color?: string;
type?: string;
}
/**
* 枚举值
*/
export class EnumValue {
/**
* 枚举值
*/
value: any;
/**
* 枚举描述
*/
label: string;
/**
* 展示的标签信息
*/
tag: EnumValueTag;
extra: any;
constructor(value: any, label: string) {
this.value = value;
this.label = label;
}
setTagType(type: string = 'primary'): EnumValue {
this.tag = { type };
return this;
}
tagTypeInfo(): EnumValue {
return this.setTagType('info');
}
tagTypeSuccess(): EnumValue {
return this.setTagType('success');
}
tagTypeDanger(): EnumValue {
return this.setTagType('danger');
}
tagTypeWarning(): EnumValue {
return this.setTagType('warning');
}
setTagColor(color: string): EnumValue {
this.tag = { color };
return this;
}
setExtra(extra: any): EnumValue {
this.extra = extra;
return this;
}
public static of(value: any, label: string): EnumValue {
return new EnumValue(value, label);
}
/**
* 根据枚举值获取指定枚举值对象
*
* @param enums 枚举对象
* @param value 需要匹配的枚举值
* @returns 枚举值对象
*/
static getEnumByValue(enums: any, value: any): EnumValue | null {
const enumValues = Object.values(enums) as any;
for (let enumValue of enumValues) {
if (enumValue.value == value) {
return enumValue;
}
}
return null;
}
/**
* 根据枚举值获取枚举描述
*
* @param enums 枚举对象
* @param value 枚举值
* @returns 枚举描述
*/
static getLabelByValue(enums: any, value: any) {
const enumValues = Object.values(enums) as any;
for (let enumValue of enumValues) {
if (enumValue['value'] == value) {
return enumValue['label'];
}
}
return '';
}
}
export default EnumValue;

View File

@@ -0,0 +1,43 @@
class SocketBuilder {
websocket: WebSocket;
constructor(url: string) {
if (typeof WebSocket === 'undefined') {
throw new Error('不支持websocket');
}
if (!url) {
throw new Error('websocket url不能为空');
}
this.websocket = new WebSocket(url);
}
static builder(url: string) {
return new SocketBuilder(url);
}
open(onopen: any) {
this.websocket.onopen = onopen;
return this;
}
error(onerror: any) {
this.websocket.onerror = onerror;
return this;
}
message(onmessage: any) {
this.websocket.onmessage = onmessage;
return this;
}
close(onclose: any) {
this.websocket.onclose = onclose;
return this;
}
build() {
return this.websocket;
}
}
export default SocketBuilder;

View File

@@ -0,0 +1,94 @@
import { i18n } from '@/i18n';
/**
* 不符合业务断言错误
*/
class AssertError extends Error {
constructor(message: string) {
super(message);
// 错误类名
this.name = 'AssertError';
}
}
/**
* 断言表达式为true
*
* @param condition 条件表达式
* @param msg 错误消息
*/
export function isTrue(condition: boolean, msg: string) {
if (!condition) {
throw new AssertError(msg);
}
}
/**
* 断言不能为空值即null,0,''等
*
* @param obj 对象1
* @param msg 错误消息
*/
export function notBlank(obj: any, msg: string) {
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) }));
}
/**
* 断言两对象相等
*
* @param obj1 对象1
* @param obj2 对象2
* @param msg 错误消息
*/
export function isEquals(obj1: any, obj2: any, msg: string) {
isTrue(obj1 === obj2, msg);
}
/**
* 断言对象不为null或undefiend
*
* @param obj 对象
* @param msg 错误提示
*/
export function notNull(obj: any, msg: string) {
if (obj == null || obj == undefined) {
throw new AssertError(msg);
}
}
/**
* 断言字符串不能为空
*
* @param str 字符串
* @param msg 错误提示
*/
export function notEmpty(str: string, msg: string) {
if (str == null || str == undefined || str == '') {
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

@@ -0,0 +1,21 @@
function getBaseApiUrl() {
let path = window.location.pathname;
if (path == '/') {
return window.location.host;
}
if (path.endsWith('/')) {
// 去除最后一个/
return window.location.host + path.replace(/\/$/, '');
}
return window.location.host + path;
}
const config = {
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
// 系统版本
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

@@ -0,0 +1,19 @@
import request from './request';
export default {
login: (param: any) => request.post('/auth/accounts/login', param),
refreshToken: (param: any) => request.get('/auth/accounts/refreshToken', param),
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),
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'),
getPermissions: () => request.get('/sys/accounts/permissions'),
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'),
};

260
frontend/src/common/request.ts Executable file
View File

@@ -0,0 +1,260 @@
import router from '../router';
import config from './config';
import { getClientId, getToken } from './utils/storage';
import { templateResolve } from './utils/string';
import { ElMessage } from 'element-plus';
import axios from 'axios';
import { useApiFetch } from '../hooks/useRequest';
import Api from './Api';
export default {
request,
xhrReq,
get,
post,
put,
del,
getApiUrl,
};
export interface Result {
/**
* 响应码
*/
code: number;
/**
* 响应消息
*/
msg: string;
/**
* 数据
*/
data?: any;
}
export enum ResultEnum {
SUCCESS = 200,
ERROR = 400,
PARAM_ERROR = 405,
SERVER_ERROR = 500,
NO_PERMISSION = 501,
ACCESS_TOKEN_INVALID = 502, // accessToken失效
}
export const baseUrl: string = config.baseApiUrl;
// const baseUrl: string = 'http://localhost:18888/api';
// const baseWsUrl: string = config.baseWsUrl;
/**
* 通知错误消息
* @param msg 错误消息
*/
function notifyErrorMsg(msg: string) {
// 危险通知
ElMessage.error(msg);
}
// create an axios instance
const axiosInst = axios.create({
baseURL: baseUrl, // url = base url + request url
timeout: 60000, // request timeout
});
// request interceptor
axiosInst.interceptors.request.use(
(config: any) => {
// do something before request is sent
const token = getToken();
if (token) {
// 设置token
config.headers['Authorization'] = token;
config.headers['ClientId'] = getClientId();
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// response interceptor
axiosInst.interceptors.response.use(
(response) => response,
(e: any) => {
const rejectPromise = Promise.reject(e);
if (axios.isCancel(e)) {
console.log('请求已取消');
return rejectPromise;
}
const statusCode = e.response?.status;
if (statusCode == 500) {
notifyErrorMsg('服务器未知异常');
return rejectPromise;
}
if (statusCode == 404) {
notifyErrorMsg('请求接口未找到');
return rejectPromise;
}
if (e.message) {
// 对响应错误做点什么
if (e.message.indexOf('timeout') != -1) {
notifyErrorMsg('网络请求超时');
return rejectPromise;
}
if (e.message == 'Network Error') {
notifyErrorMsg('网络连接错误');
return rejectPromise;
}
}
notifyErrorMsg('网络请求错误');
return rejectPromise;
}
);
/**
* xhr请求url
*
* @param method 请求方法
* @param url url
* @param params 参数
* @param options 可选
* @returns
*/
export function xhrReq(method: string, url: string, params: any = null, options: any = {}) {
if (!url) {
throw new Error('请求url不能为空');
}
// 简单判断该url是否是restful风格
if (url.indexOf('{') != -1) {
url = templateResolve(url, params);
}
const req: any = {
method,
url,
...options,
};
// post和put使用json格式传参
if (method === 'post' || method === 'put') {
req.data = params;
} else {
req.params = params;
}
return axiosInst
.request(req)
.then((response) => {
// 获取请求返回结果
const result: Result = response.data;
return parseResult(result);
})
.catch((e) => {
return Promise.reject(e);
});
}
/**
* fetch请求url
*
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
*
* @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
* @param {Object} uri uri
* @param {Object} params 参数
*/
async function request(method: string, url: string, params: any = null, options: any = {}): Promise<any> {
const { execute, data } = useApiFetch(Api.create(url, method), params, options);
await execute();
return data.value;
}
/**
* get请求uri
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
*
* @param {Object} url uri
* @param {Object} params 参数
*/
function get(url: string, params: any = null, options: any = {}): Promise<any> {
return request('get', url, params, options);
}
function post(url: string, params: any = null, options: any = {}): Promise<any> {
return request('post', url, params, options);
}
function put(url: string, params: any = null, options: any = {}): Promise<any> {
return request('put', url, params, options);
}
function del(url: string, params: any = null, options: any = {}): Promise<any> {
return request('delete', url, params, options);
}
function getApiUrl(url: string) {
// 只是返回api地址而不做请求用在上传组件之类的
return baseUrl + url + '?' + joinClientParams();
}
// 组装客户端参数,包括 token 和 clientId
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;
}
// 如果提示没有权限则移除token使其重新登录
if (result.code === ResultEnum.NO_PERMISSION) {
router.push({
path: '/401',
});
}
// 如果返回的code不为成功则会返回对应的错误msg则直接统一通知即可。忽略登录超时或没有权限的提示直接跳转至401页面
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
notifyErrorMsg(result.msg);
}
return Promise.reject(result);
}

View File

@@ -0,0 +1,36 @@
import openApi from './openApi';
import JSEncrypt from 'jsencrypt';
import { notBlank } from './assert';
var encryptor: any = null;
export async function getRsaPublicKey() {
let publicKey = sessionStorage.getItem('RsaPublicKey');
if (publicKey) {
return publicKey;
}
publicKey = (await openApi.getPublicKey()) as string;
sessionStorage.setItem('RsaPublicKey', publicKey);
return publicKey;
}
/**
* 公钥加密指定值
*
* @param value value
* @returns 加密后的值
*/
export async function RsaEncrypt(value: any) {
// 不存在则返回空值
if (!value) {
return '';
}
if (encryptor != null && sessionStorage.getItem('RsaPublicKey') != null) {
return encryptor.encrypt(value);
}
encryptor = new JSEncrypt();
const publicKey = (await getRsaPublicKey()) as string;
notBlank(publicKey, '获取公钥失败');
encryptor.setPublicKey(publicKey); //设置公钥
return encryptor.encrypt(value);
}

View File

@@ -0,0 +1,112 @@
import openApi from './openApi';
// 登录是否使用验证码配置key
const AccountLoginSecurityKey = 'AccountLoginSecurity';
const MachineConfigKey = 'MachineConfig';
const SysStyleConfigKey = 'SysStyleConfig';
/**
* 获取账号登录安全配置
*
* @returns
*/
export async function getAccountLoginSecurity(): Promise<any> {
const value = await getConfigValue(AccountLoginSecurityKey);
if (!value) {
return null;
}
const jsonValue = JSON.parse(value);
jsonValue.useCaptcha = convertBool(jsonValue.useCaptcha, true);
jsonValue.useOtp = convertBool(jsonValue.useOtp, true);
return jsonValue;
}
/**
* 获取全局系统样式配置logo、title等
*
* @returns
*/
export async function getSysStyleConfig(): Promise<any> {
const value = await getConfigValue(SysStyleConfigKey);
const defaultValue = {
useWatermark: true,
};
if (!value) {
return defaultValue;
}
const jsonValue = JSON.parse(value);
// 将字符串转为bool
jsonValue.useWatermark = convertBool(jsonValue.useWatermark, true);
return jsonValue;
}
/**
* 获取LDAP登录配置
*
* @returns
*/
export async function getLdapEnabled(): Promise<any> {
const value = await openApi.getLdapEnabled();
return convertBool(value, false);
}
/**
* 获取机器配置
*
* @returns
*/
export async function getMachineConfig(): Promise<any> {
const value = await getConfigValue(MachineConfigKey);
const defaultValue = {
// 默认1gb
uploadMaxFileSize: '1GB',
};
if (!value) {
return defaultValue;
}
try {
const jsonValue = JSON.parse(value);
return jsonValue;
} catch (e) {
return defaultValue;
}
}
/**
* 获取系统服务启动配置
*
* @returns 配置信息
*/
export async function getServerConf(): Promise<any> {
return openApi.getServerConf();
}
/**
* 获取系统配置值
*
* @param key 配置key
* @returns 配置值
*/
export async function getConfigValue(key: string): Promise<string> {
return (await openApi.getConfigValue({ key })) as string;
}
/**
* 获取bool类型系统配置值
*
* @param key 配置key
* @param defaultValue 默认值
* @returns 是否为ture1: true其他: false
*/
export async function getBoolConfigValue(key: string, defaultValue: boolean): Promise<boolean> {
const value = await getConfigValue(key);
return convertBool(value, defaultValue);
}
function convertBool(value: string, defaultValue: boolean) {
if (!value) {
return defaultValue;
}
return value == '1' || value == 'true';
}

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

@@ -0,0 +1,110 @@
import Config from './config';
import SocketBuilder from './SocketBuilder';
import { getToken } from '@/common/utils/storage';
import { joinClientParams } from './request';
import { ElNotification } from 'element-plus';
class SysSocket {
/**
* socket连接
*/
socket: any;
/**
* key -> 消息类别value -> 消息对应的处理器函数
*/
categoryHandlers: Map<string, any> = new Map();
/**
* 消息类型
*/
messageTypes: any = {
0: 'error',
1: 'success',
2: 'info',
};
/**
* 初始化全局系统消息websocket
*/
init() {
// 存在则不需要重新建立连接
if (this.socket) {
return;
}
const token = getToken();
if (!token) {
return null;
}
console.log('init system ws');
const sysMsgUrl = `${Config.baseWsUrl}/sysmsg?${joinClientParams()}`;
this.socket = SocketBuilder.builder(sysMsgUrl)
.message((event: { data: string }) => {
let message;
try {
message = JSON.parse(event.data);
} catch (e) {
console.error('解析ws消息失败', e);
return;
}
// 存在消息类别对应的处理器,则进行处理,否则进行默认通知处理
const handler = this.categoryHandlers.get(message.category);
if (handler) {
handler(message);
return;
}
// 默认通知处理
const type = this.getMsgType(message.type);
let msg = message.msg;
let duration = 0;
ElNotification({
duration: duration,
title: message.title,
message: msg,
type: type,
});
})
.open((event: any) => console.log(event))
.close(() => {
console.log('close sys socket');
this.socket = null;
})
.build();
}
destory() {
this.socket?.close();
this.socket = null;
this.categoryHandlers?.clear();
}
/**
* 注册消息处理函数
*
* @param category 消息类别
* @param handlerFunc 消息处理函数
*/
registerMsgHandler(category: any, handlerFunc: any) {
this.init();
if (this.categoryHandlers.has(category)) {
console.log(`${category}该类别消息处理器已存在...`);
return;
}
if (typeof handlerFunc != 'function') {
throw new Error('message handler需为函数');
}
this.categoryHandlers.set(category, handlerFunc);
}
getMsgType(msgType: any) {
return this.messageTypes[msgType];
}
}
// 全局系统消息websocket;
const sysSocket = new SysSocket();
export default sysSocket;

View File

@@ -0,0 +1,45 @@
export function exportCsv(filename: string, columns: string[], datas: []) {
// 二维数组
const cvsData = [columns];
for (let data of datas) {
// 数据值组成的一维数组
let dataValueArr: any = [];
for (let column of columns) {
let val: any = data[column];
if (val == null || val == undefined) {
val = '';
} else if (val && typeof val == 'string') {
// 替换换行符
val = val.replace(/[\r\n]/g, '\\n');
// csv格式如果有逗号整体用双引号括起来如果里面还有双引号就替换成两个双引号这样导出来的格式就不会有问题了
if (val.indexOf(',') != -1) {
// 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
if (val.indexOf('"') != -1) {
val = val.replace(/"/g, '""');
}
// 再将逗号转义
val = `"${val}"`;
}
}
dataValueArr.push(String(val));
}
cvsData.push(dataValueArr);
}
const csvString = cvsData.map((e) => e.join(',')).join('\n');
exportFile(`${filename}.csv`, csvString);
}
export function exportFile(filename: string, content: string) {
// 导出
let link = document.createElement('a');
let exportContent = '\uFEFF';
let blob = new Blob([exportContent + content], {
type: 'text/plain;charset=utf-8',
});
link.id = 'download-file';
link.setAttribute('href', URL.createObjectURL(blob));
link.setAttribute('download', `${filename}`);
document.body.appendChild(link);
link.click();
}

View File

@@ -0,0 +1,116 @@
import dayjs from 'dayjs';
/**
* 格式化日期
* @param date 日期 字符串 Date 时间戳等
* @param format 格式化格式 默认 YYYY-MM-DD HH:mm:ss
* @returns 格式化后内容
*/
export function formatDate(date: any, format: string = 'YYYY-MM-DD HH:mm:ss') {
if (!date) {
return '';
}
return dayjs(date).format(format);
}
/**
* 格式化字节单位
* @param size byte size
* @returns
*/
export function formatByteSize(size: number, fixed = 2) {
if (size === 0) {
return '0B';
}
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const base = 1024;
const exponent = Math.floor(Math.log(size) / Math.log(base));
return parseFloat((size / Math.pow(base, exponent)).toFixed(fixed)) + units[exponent];
}
/**
* 容量转为对应的字节大小,如 1KB转为 1024
* @param sizeString 1kb 1gb等
* @returns
*/
export function convertToBytes(sizeStr: string) {
sizeStr = sizeStr.trim();
const unit = sizeStr.slice(-2);
const valueStr = sizeStr.slice(0, -2);
const value = parseInt(valueStr, 10);
let bytes = 0;
switch (unit.toUpperCase()) {
case 'KB':
bytes = value * 1024;
break;
case 'MB':
bytes = value * 1024 * 1024;
break;
case 'GB':
bytes = value * 1024 * 1024 * 1024;
break;
default:
throw new Error('Invalid size unit');
}
return bytes;
}
/**
* 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
*
* @param time 时间数
* @param unit time对应的单位
* @returns
*/
export function formatTime(time: number, unit: string = 's') {
const units: any = {
y: 31536000,
M: 2592000,
d: 86400,
h: 3600,
m: 60,
s: 1,
};
if (!units[unit]) {
return 'Invalid unit';
}
let seconds = time * units[unit];
let result = '';
const timeUnits = Object.entries(units).map(([unit, duration]) => {
const value = Math.floor(seconds / duration);
seconds %= duration;
return { value, unit };
});
timeUnits.forEach(({ value, unit }) => {
if (value > 0) {
result += `${value}${unit} `;
}
});
return result;
}
/**
* formatAxis(new Date()) // 上午好
*/
export function formatAxis(param: any) {
let hour: number = new Date(param).getHours();
if (hour < 6) return '凌晨好';
else if (hour < 9) return '早上好';
else if (hour < 12) return '上午好';
else if (hour < 14) return '中午好';
else if (hour < 17) return '下午好';
else if (hour < 19) return '傍晚好';
else if (hour < 22) return '晚上好';
else return '夜里好';
}

View File

@@ -0,0 +1,46 @@
import { nextTick } from 'vue';
import '@/theme/loading.scss';
/**
* 页面全局 Loading
* @method start 创建 loading
* @method done 移除 loading
*/
export const NextLoading = {
// 创建 loading
start: () => {
const bodys: Element = document.body;
const div = <HTMLElement>document.createElement('div');
div.setAttribute('class', 'loading-next');
const htmls = `
<div class="loading-next-box">
<div class="loading-next-box-warp">
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
<div class="loading-next-box-item"></div>
</div>
</div>
`;
div.innerHTML = htmls;
bodys.insertBefore(div, bodys.childNodes[0]);
},
// 移除 loading
done: (time: number = 1000) => {
nextTick(() => {
setTimeout(() => {
const el = <HTMLElement>document.querySelector('.loading-next');
el?.parentNode?.removeChild(el);
}, time);
});
},
};
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,8 @@
// https://www.npmjs.com/package/mitt
import mitt, { Emitter } from 'mitt';
// 类型
const emitter: Emitter<any> = mitt<any>();
// 导出
export default emitter;

View File

@@ -0,0 +1,56 @@
/**
* 根据对象访问路径,获取对应的值
*
* @param obj 对象,如 {user: {name: 'xxx'}, orderNo: 1212211, products: [{id: 12}]}
* @param path 访问路径,如 orderNo 或者 user.name 或者product[0].id
* @returns 路径对应的值
*/
export function getValueByPath(obj: any, path: string) {
const keys = path.split('.');
let result = obj;
for (let key of keys) {
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;
}
if (key.includes('[') && key.includes(']')) {
// 处理包含数组索引的情况
const arrayKey = key.substring(0, key.indexOf('['));
const matchIndex = key.match(/\[(.*?)\]/);
if (!matchIndex) {
return undefined;
}
const index = parseInt(matchIndex[1]);
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];
}
}
return result;
}

View File

@@ -1,12 +1,9 @@
// 字体图标 url
const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/font_2298093_ysc3z187xhh.css',
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
];
const cssCdnUrlList: Array<string> = [];
// 第三方 js url
const jsCdnUrlList: Array<string> = [];
// 动态设置字体图标
// 动态批量设置字体图标
export function setCssCdn() {
if (cssCdnUrlList.length <= 0) return false;
cssCdnUrlList.map((v) => {
@@ -18,7 +15,7 @@ export function setCssCdn() {
});
}
// 批量设置第三方js
// 动态批量设置第三方js
export function setJsCdn() {
if (jsCdnUrlList.length <= 0) return false;
jsCdnUrlList.map((v) => {
@@ -28,11 +25,17 @@ export function setJsCdn() {
});
}
// 设置执行函数
/**
* js
* @method cssCdn
* @method jsCdn js
*/
const setIntroduction = {
// 设置css
cssCdn: () => {
setCssCdn();
},
// 设置js
jsCdn: () => {
setJsCdn();
},

View File

@@ -0,0 +1,133 @@
import { randomUuid } from './string';
const TokenKey = 'm-token';
const RefreshTokenKey = 'm-refresh-token';
const UserKey = 'm-user';
const TagViewsKey = 'm-tagViews';
const ClientIdKey = 'm-clientId';
// 获取请求token
export function getToken(): string {
return getLocal(TokenKey);
}
// 保存用户访问token
export function saveToken(token: string) {
setLocal(TokenKey, token);
}
export function getRefreshToken(): string {
return getLocal(RefreshTokenKey);
}
export function saveRefreshToken(refreshToken: string) {
return setLocal(RefreshTokenKey, refreshToken);
}
// 获取登录用户基础信息
export function getUser() {
return getLocal(UserKey);
}
// 保存用户信息
export function saveUser(userinfo: any) {
setLocal(UserKey, userinfo);
}
export function saveThemeConfig(themeConfig: any) {
setLocal('themeConfig', themeConfig);
}
export function getThemeConfig() {
return getLocal('themeConfig');
}
/**
* 清除当前登录用户相关信息
*/
export function clearUser() {
removeLocal(TokenKey);
removeLocal(UserKey);
removeLocal(RefreshTokenKey);
}
export function getTagViews() {
return getSession(TagViewsKey);
}
export function setTagViews(tagViews: Array<object>) {
setSession(TagViewsKey, tagViews);
}
export function removeTagViews() {
removeSession(TagViewsKey);
}
// 获取客户端UUID
export function getClientId(): string {
let uuid = getSession(ClientIdKey);
if (uuid == null) {
uuid = randomUuid();
setSession(ClientIdKey, uuid);
}
return uuid;
}
// 1. localStorage
// 设置永久缓存
export function setLocal(key: string, val: any) {
if (typeof val == 'object') {
val = JSON.stringify(val);
}
window.localStorage.setItem(key, val);
}
// 获取永久缓存
export function getLocal(key: string) {
let val: any = window.localStorage.getItem(key);
try {
return JSON.parse(val);
} catch (e) {
return val;
}
}
// 移除永久缓存
export function removeLocal(key: string) {
window.localStorage.removeItem(key);
}
// 移除全部永久缓存
export function clearLocal() {
window.localStorage.clear();
}
// 2. sessionStorage
// 设置临时缓存
export function setSession(key: string, val: any) {
if (typeof val == 'object') {
val = JSON.stringify(val);
}
window.sessionStorage.setItem(key, val);
}
// 获取临时缓存
export function getSession(key: string) {
let val: any = window.sessionStorage.getItem(key);
try {
return JSON.parse(val);
} catch (e) {
return val;
}
}
// 移除临时缓存
export function removeSession(key: string) {
window.sessionStorage.removeItem(key);
}
// 移除全部临时缓存
export function clearSession() {
clearUser();
window.sessionStorage.clear();
}

View File

@@ -0,0 +1,216 @@
import { v1 as uuidv1 } from 'uuid';
import Clipboard from 'clipboard';
import { ElMessage } from 'element-plus';
/**
* 模板字符串解析template = 'hahaha{name}_{id}' ,param = {name: 'hh', id: 1}
* 解析后为 hahahahh_1
* @param template 模板字符串
* @param param 参数占位符
* @returns
*/
export function templateResolve(template: string, param: any) {
return template.replace(/\{\w+\}/g, (word) => {
const key = word.substring(1, word.length - 1);
const value = param[key];
if (value != null || value != undefined) {
return value;
}
return '';
});
}
// 首字符头像
export function letterAvatar(name: string, size = 60, color = '') {
name = name || '';
size = size || 60;
var colours = [
'#1abc9c',
'#2ecc71',
'#3498db',
'#9b59b6',
'#34495e',
'#16a085',
'#27ae60',
'#2980b9',
'#8e44ad',
'#2c3e50',
'#f1c40f',
'#e67e22',
'#e74c3c',
'#00bcd4',
'#95a5a6',
'#f39c12',
'#d35400',
'#c0392b',
'#bdc3c7',
'#7f8c8d',
],
nameSplit = String(name).split(' '),
initials,
charIndex,
colourIndex,
canvas,
context,
dataURI;
if (nameSplit.length == 1) {
initials = nameSplit[0] ? nameSplit[0].charAt(0) : '?';
} else {
initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
}
if (window.devicePixelRatio) {
size = size * window.devicePixelRatio;
}
initials = initials.toLocaleUpperCase();
charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64;
colourIndex = charIndex % 20;
canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
context = canvas.getContext('2d') as any;
context.fillStyle = color ? color : colours[colourIndex - 1];
context.fillRect(0, 0, canvas.width, canvas.height);
context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'";
context.textAlign = 'center';
context.fillStyle = '#FFF';
context.fillText(initials, size / 2, size / 1.5);
dataURI = canvas.toDataURL();
canvas = null;
return dataURI;
}
/**
* 计算文本所占用的宽度px -> 该种方式较为准确
* 使用span标签包裹内容然后计算span的宽度 width px
* @param str
*/
export function getTextWidth(str: string) {
let width = 0;
let html = document.createElement('span');
html.innerText = str;
html.className = 'getTextWidth';
document?.querySelector('body')?.appendChild(html);
width = (document?.querySelector('.getTextWidth') as any).offsetWidth;
document?.querySelector('.getTextWidth')?.remove();
return width;
}
/**
*
* @returns uuid
*/
export function randomUuid() {
return uuidv1();
}
/**
* 拷贝文本至剪贴板
* @param txt 需要拷贝到剪贴板的文本
* @param selector click事件对应的元素selector默认为 #copyValue
* @returns
*/
export async function copyToClipboard(txt: string, selector: string = '#copyValue') {
// navigator clipboard 需要https等安全上下文
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard 向剪贴板写文本
try {
// 拷贝单元格数据
await navigator.clipboard.writeText(txt);
ElMessage.success('复制成功');
} catch (e: any) {
ElMessage.error('复制失败');
}
return;
}
let clipboard = new Clipboard(selector, {
text: function () {
return txt;
},
});
clipboard.on('success', () => {
ElMessage.success('复制成功');
// 释放内存
clipboard.destroy();
});
clipboard.on('error', () => {
// 不支持复制
ElMessage.error('该浏览器不支持自动复制');
// 释放内存
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

@@ -1,5 +1,20 @@
import { nextTick } from 'vue';
import * as svg from '@element-plus/icons-vue';
import iconfontJson from '@/assets/iconfont/iconfont.json';
import SvgIcon from '@/components/svgIcon/index.vue';
/**
* element plus svg
* @param app vue
* @description 使https://element-plus.gitee.io/zh-CN/component/icon.html
*/
export function registElSvgIcon(app: any) {
const icons = svg as any;
for (const i in icons) {
app.component(`${icons[i].name}`, icons[i]);
}
app.component('SvgIcon', SvgIcon);
}
// 获取阿里字体图标
const getAlicdnIconfont = () => {
@@ -9,7 +24,8 @@ const getAlicdnIconfont = () => {
let sheetsList = [];
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) {
console.log(styles[i]);
if (styles[i].href && styles[i].href.indexOf('iconfont') > -1) {
sheetsList.push(styles[i]);
}
}
@@ -28,19 +44,29 @@ const getAlicdnIconfont = () => {
});
};
// 获取本地阿里icons
const getLocalAliIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const prefix = iconfontJson.css_prefix_text;
resolve(iconfontJson.glyphs.map((x: any) => prefix + x.font_class));
});
});
};
// 初始化获取 css 样式,获取 element plus 自带图标
const elementPlusIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const icons = svg as any;
const sheetsIconList = [];
for (const i in icons) {
sheetsIconList.push(`${icons[i].name}`);
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
else reject('未获取到值,请刷新重试');
});
});
nextTick(() => {
const icons = svg as any;
const sheetsIconList = [];
for (const i in icons) {
sheetsIconList.push(`${icons[i].name}`);
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
else reject('未获取到值,请刷新重试');
});
});
};
// 初始化获取 css 样式,这里使用 fontawesome 的图标
@@ -76,9 +102,9 @@ const awesomeIconfont = () => {
// 定义导出方法集合
const initIconfont = {
// ali: () => {
// return getAlicdnIconfont();
// },
ali: () => {
return getLocalAliIconfont();
},
ele: () => {
return elementPlusIconfont();
},

View File

@@ -163,8 +163,7 @@ export function verifyPasswordStrength(val: string) {
// 中:字母+数字,字母+特殊字符,数字+特殊字符
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中';
// 强:字母+数字+特殊字符
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
v = '强';
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '强';
// 返回结果
return v;
}
@@ -172,11 +171,7 @@ export function verifyPasswordStrength(val: string) {
// IP地址
export function verifyIPAddress(val: string) {
// false: IP地址不正确
if (
!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
val
)
)
if (!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(val))
return false;
// true: IP地址正确
else return true;

View File

@@ -0,0 +1,13 @@
const mode = import.meta.env.VITE_ROUTER_MODE;
/**
* @description 获取不同路由模式所对应的 url
* @returns {String}
*/
export function getNowUrl() {
const url = {
hash: location.hash.substring(1),
history: location.pathname + location.search,
};
return url[mode];
}

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

@@ -0,0 +1,66 @@
<template>
<div v-show="isShow" :style="style">
<slot></slot>
</div>
</template>
<script setup lang="ts" name="GridItem">
import { computed, inject, Ref, ref, useAttrs, watch } from 'vue';
import { BreakPoint, Responsive } from '../interface/index';
type Props = {
offset?: number;
span?: number;
suffix?: boolean;
xs?: Responsive;
sm?: Responsive;
md?: Responsive;
lg?: Responsive;
xl?: Responsive;
};
const props = withDefaults(defineProps<Props>(), {
offset: 0,
span: 1,
suffix: false,
xs: undefined,
sm: undefined,
md: undefined,
lg: undefined,
xl: undefined,
});
const attrs = useAttrs() as { index: string };
const isShow = ref(true);
// 注入断点
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'));
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1));
watch(
() => [shouldHiddenIndex.value, breakPoint.value],
(n) => {
if (attrs.index) {
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
}
},
{ immediate: true }
);
const gap = inject('gap', 0);
const cols = inject('cols', ref(4));
const style = computed(() => {
let span = props[breakPoint.value]?.span ?? props.span;
let offset = props[breakPoint.value]?.offset ?? props.offset;
if (props.suffix) {
return {
gridColumnStart: cols.value - span - offset + 1,
gridColumnEnd: `span ${span + offset}`,
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
};
} else {
return {
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${span + offset > cols.value ? cols.value : span + offset}`,
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
};
}
});
</script>

View File

@@ -0,0 +1,159 @@
<template>
<div :style="style">
<slot></slot>
</div>
</template>
<script setup lang="ts" name="Grid">
import { ref, watch, useSlots, computed, provide, onBeforeMount, onMounted, onUnmounted, onDeactivated, onActivated, VNodeArrayChildren, VNode } from 'vue';
import type { BreakPoint } from './interface/index';
type Props = {
cols?: number | Record<BreakPoint, number>;
collapsed?: boolean;
collapsedRows?: number;
gap?: [number, number] | number;
};
const props = withDefaults(defineProps<Props>(), {
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
collapsed: false,
collapsedRows: 1,
gap: 0,
});
onBeforeMount(() => props.collapsed && findIndex());
onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
window.addEventListener('resize', resize);
});
onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
window.addEventListener('resize', resize);
});
onUnmounted(() => {
window.removeEventListener('resize', resize);
});
onDeactivated(() => {
window.removeEventListener('resize', resize);
});
// 监听屏幕变化
const resize = (e: UIEvent) => {
let width = (e.target as Window).innerWidth;
switch (!!width) {
case width < 768:
breakPoint.value = 'xs';
break;
case width >= 768 && width < 992:
breakPoint.value = 'sm';
break;
case width >= 992 && width < 1200:
breakPoint.value = 'md';
break;
case width >= 1200 && width < 1920:
breakPoint.value = 'lg';
break;
case width >= 1920:
breakPoint.value = 'xl';
break;
}
};
// 注入 gap 间距
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap);
// 注入响应式断点
let breakPoint = ref<BreakPoint>('xl');
provide('breakPoint', breakPoint);
// 注入要开始折叠的 index
const hiddenIndex = ref(-1);
provide('shouldHiddenIndex', hiddenIndex);
// 注入 cols
const gridCols = computed(() => {
if (typeof props.cols === 'object') return props.cols[breakPoint.value] ?? props.cols;
return props.cols;
});
provide('cols', gridCols);
// 寻找需要开始折叠的字段 index
const slots = useSlots().default!();
const findIndex = () => {
let fields: VNodeArrayChildren = [];
let suffix: VNode | null = null;
slots.forEach((slot: any) => {
// suffix
if (typeof slot.type === 'object' && slot.type.__name === 'GridItem' && slot.props?.suffix !== undefined) {
suffix = slot;
}
// slot children
if (typeof slot.type === 'symbol' && Array.isArray(slot.children)) {
fields.push(...slot.children);
}
});
// 计算 suffix 所占用的列
let suffixCols = 0;
if (suffix) {
suffixCols =
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
}
try {
let find = false;
fields.reduce((prev = 0, current, index) => {
prev +=
((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
hiddenIndex.value = index;
find = true;
throw 'find it';
}
return prev;
}, 0);
if (!find) hiddenIndex.value = -1;
} catch (e) {
// console.warn(e);
}
};
// 断点变化时执行 findIndex
watch(
() => breakPoint.value,
() => {
if (props.collapsed) findIndex();
}
);
// 监听 collapsed
watch(
() => props.collapsed,
(value) => {
if (value) return findIndex();
hiddenIndex.value = -1;
}
);
// 设置间距
const gridGap = computed(() => {
if (typeof props.gap === 'number') return `${props.gap}px`;
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
return 'unset';
});
// 设置 style
const style = computed(() => {
return {
display: 'grid',
gridGap: gridGap.value,
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`,
};
});
defineExpose({ breakPoint });
</script>

View File

@@ -0,0 +1,6 @@
export type BreakPoint = "xs" | "sm" | "md" | "lg" | "xl";
export type Responsive = {
span?: number;
offset?: number;
};

View File

@@ -0,0 +1,89 @@
<template>
<component
:is="item?.render ?? `el-${item.type}`"
v-bind="{ ...handleSearchProps, ...placeholder, clearable: true }"
v-on="{ ...handleEvents }"
v-model.trim="itemValue"
:data="item.type === 'tree-select' ? item.options : []"
:options="['cascader', 'select-v2'].includes(item.type!) ? item.options : []"
>
<template v-if="item.type === 'cascader'" #default="{ data }">
<span>{{ data[fieldNames.label] }}</span>
</template>
<template v-if="item.type === 'select'">
<component
:is="`el-option`"
v-for="(col, index) in item.options"
:key="index"
:label="$t(col[fieldNames.label])"
:value="col[fieldNames.value]"
></component>
</template>
<slot v-else></slot>
</component>
</template>
<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;
}
const props = defineProps<SearchFormItemProps>();
const itemValue = defineModel('modelValue');
// 判断 fieldNames 设置 label && value && children 的 key 值
const fieldNames = computed(() => {
return {
label: props.item?.fieldNames?.label ?? 'label',
value: props.item?.fieldNames?.value ?? 'value',
children: props.item.fieldNames?.children ?? 'children',
};
});
// 处理透传的 searchProps (type 为 tree-select、cascader 的时候需要给下默认 label && value && children)
const handleSearchProps = computed(() => {
const label = fieldNames.value.label;
const value = fieldNames.value.value;
const children = fieldNames.value.children;
const searchEl = props.item?.type;
let searchProps = props.item?.props ?? {};
if (searchEl === 'tree-select') {
searchProps = { ...searchProps, props: { ...searchProps.props, label, children }, nodeKey: value };
}
if (searchEl === 'cascader') {
searchProps = { ...searchProps, props: { ...searchProps.props, label, value, children } };
}
return searchProps;
});
// 处理透传的 事件
const handleEvents = computed(() => {
let itemEvents = props.item?.events ?? {};
return itemEvents;
});
// 处理默认 placeholder
const placeholder = computed(() => {
const search = props.item;
const label = t(search.label);
if (['datetimerange', 'daterange', 'monthrange'].includes(search?.props?.type) || search?.props?.isRange) {
return {
rangeSeparator: search?.props?.rangeSeparator ?? '至',
startPlaceholder: search?.props?.startPlaceholder ?? '开始时间',
endPlaceholder: search?.props?.endPlaceholder ?? '结束时间',
};
}
const placeholder =
search?.props?.placeholder ?? (search?.type?.includes('input') ? t('common.pleaseInput', { label }) : t('common.pleaseSelect', { label }));
return { placeholder: t(placeholder) };
});
</script>

View File

@@ -0,0 +1,318 @@
import Api from '@/common/Api';
import { VNode, ref, toValue } from 'vue';
export type FieldNamesProps = {
label: string;
value: string;
children?: string;
};
export type SearchItemType =
| 'input'
| 'input-number'
| 'select'
| 'select-v2'
| 'tree-select'
| 'cascader'
| 'date-picker'
| 'time-picker'
| 'time-select'
| 'switch'
| 'slider';
/**
* 表单组件可选项的api信息
*/
export class OptionsApi {
/**
* 请求获取options的api
*/
api: Api;
/**
* 请求参数
*/
params: any;
/**
* 是否立即执行否则在组件focus事件中获取
*/
immediate: boolean = false;
/**
* 是否只获取一次即若以获取则不继续调用该api
*/
once: boolean = true;
/**
* 转换函数主要用于将响应的api结果转换为满足组件options的结构
*/
convertFn: (apiResp: any) => any;
// remote: boolean = false;
/**
* 远程方法参数属性字段存在该值则说明使用remote-method进行远程搜索
*/
remoteMethodParamProp: string;
withConvertFn(fn: (apiResp: any) => any) {
this.convertFn = fn;
return this;
}
/**
* 立即获取该可选值
* @returns
*/
withImmediate() {
this.immediate = true;
return this;
}
/**
* 设为非一次性api即每次组件focus获取的时候都允许重新获取options
* @returns this
*/
withNoOnce() {
this.once = false;
return this;
}
/**
* 是否使用select的remote方式远程搜索调用
* @param remoteReqParamKey remote请求参数对应的prop需要将输入的value赋值给params[paramProp]进行远程搜索
*/
isRemote(paramProp: string) {
this.remoteMethodParamProp = paramProp;
return this;
}
/**
* 调用api获取组件可选项
* @returns 组件可选项信息
*/
async getOptions() {
let res = await this.api.request(toValue(this.params));
if (this.convertFn) {
res = this.convertFn(res);
}
return res;
}
static new(api: Api, params: any): OptionsApi {
const oa = new OptionsApi();
oa.api = api;
oa.params = params;
return oa;
}
}
/**
* 搜索项
*/
export class SearchItem {
/**
* 属性字段
*/
prop: string;
/**
* 当前项搜索框的 label
*/
label: string;
/**
* 表单项类型input、select、date等
*/
type: SearchItemType;
/**
* select等组件的可选值
*/
options: any;
/**
* 获取可选项的api信息
*/
optionsApi: OptionsApi;
/**
* 插槽名
*/
slot: string;
/**
* 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
*/
props?: any;
/**
* 搜索项事件,根据 element plus 官方文档来传递,该属性所有值会透传到组件
*/
events?: any;
/**
* 搜索提示
*/
tooltip?: string;
/**
* 搜索项所占用的列数,默认为 1 列
*/
span?: number;
/**
* 搜索字段左侧偏移列数
*/
offset?: number;
/**
* 指定 label && value && children 的 key 值用于select等类型组件
*/
fieldNames: FieldNamesProps;
/**
* 自定义搜索内容渲染tsx语法
*/
render?: (scope: any) => VNode;
constructor(prop: string, label: string) {
this.prop = prop;
this.label = label;
}
static new(prop: string, label: string): SearchItem {
return new SearchItem(prop, label);
}
static input(prop: string, label: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.type = 'input';
return tq;
}
static select(prop: string, label: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.type = 'select';
tq.withOneProps('filterable', true);
return tq;
}
static datePicker(prop: string, label: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.type = 'date-picker';
return tq;
}
static slot(prop: string, label: string, slotName: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.slot = slotName;
return tq;
}
/**
* 为组件设置一个props属性
* @param propsKey 属性key
* @param propsValue 属性value
* @returns
*/
withOneProps(propsKey: string, propsValue: any): SearchItem {
if (!this.props) {
this.props = {};
}
this.props[propsKey] = propsValue;
return this;
}
/**
* 为组件传递组件自身的props属性 (根据 element plus 官方文档来传递,该属性所有值会透传到组件)
* @returns this
*/
withProps(props: any = {}): SearchItem {
this.props = props;
return this;
}
/**
* 为组件传递组件自身事件函数
* @param event 事件名称
* @param fn 事件处理函数
* @returns
*/
bindEvent(event: string, eventFn: any): SearchItem {
if (!this.events) {
this.events = {};
}
this.events[event] = eventFn;
return this;
}
/**
* 设置枚举值用于选择等
* @param enumValues 枚举值对象
* @returns
*/
withEnum(enumValues: any): SearchItem {
this.options = Object.values(enumValues);
return this;
}
/**
* 设置获取组件options可选项值的api配置
* @param optionsApi 可选项api配置
* @returns this
*/
withOptionsApi(optionsApi: OptionsApi): SearchItem {
this.optionsApi = optionsApi;
// 使用api获取组件可选项需要将options转为响应式否则组件无法响应式获取组件可选项
this.options = ref(null);
// 存在远程搜索请求参数prop则为使用远程搜索可选项
if (optionsApi.remoteMethodParamProp) {
return this.withOneProps('remote', true).withOneProps('remote-method', async (value: any) => {
if (!value) {
this.options.value = [];
return;
}
// 将输入的内容赋值为真实api请求参数中指定的属性字段
optionsApi.params[optionsApi.remoteMethodParamProp] = value;
this.options.value = await this.optionsApi.getOptions();
});
}
// 立即执行则直接调用api获取并赋值options
if (this.optionsApi.immediate) {
this.optionsApi.getOptions().then((res) => {
this.options.value = res;
});
} else {
// 注册focus事件在触发focus时赋值options
this.bindEvent('focus', async () => {
if (!toValue(this.options) || !optionsApi.once) {
this.options.value = await this.optionsApi.getOptions();
}
});
}
return this;
}
withSpan(span: number): SearchItem {
this.span = span;
return this;
}
withOptions(options: any): SearchItem {
this.options = options;
return this;
}
/**
* 赋值placeholder
* @param val placeholder
* @returns
*/
withPlaceholder(val: string): SearchItem {
return this.withOneProps('placeholder', val);
}
}

View File

@@ -0,0 +1,138 @@
<template>
<div v-if="items.length" class="search-form">
<el-form ref="formRef" :model="searchParam" label-width="auto">
<Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol">
<GridItem v-for="(item, index) in items" :key="item.prop" v-bind="getResponsive(item)" :index="index">
<el-form-item>
<template #label>
<el-space :size="4">
<span>{{ `${$t(item?.label)}` }}</span>
<el-tooltip v-if="item.tooltip" :content="item?.tooltip" placement="top">
<SvgIcon name="QuestionFilled" />
</el-tooltip>
</el-space>
<span>:</span>
</template>
<SearchFormItem @keyup.enter="handleItemKeyupEnter(item)" v-if="!item.slot" :item="item" v-model="searchParam[item.prop]" />
<slot v-else :name="item.slot"></slot>
</el-form-item>
</GridItem>
<GridItem suffix>
<div class="operation">
<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">
<component :is="collapsed ? ArrowDown : ArrowUp"></component>
</el-icon>
</el-button>
</div>
</GridItem>
</Grid>
</el-form>
</div>
</template>
<script setup lang="ts" name="SearchForm">
import { computed, ref } from 'vue';
import { BreakPoint } from '@/components/Grid/interface/index';
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import SearchFormItem from './components/SearchFormItem.vue';
import Grid from '@/components/Grid/index.vue';
import GridItem from '@/components/Grid/components/GridItem.vue';
import SvgIcon from '@/components/svgIcon/index.vue';
import { SearchItem } from './index';
interface ProTableProps {
items: SearchItem[]; // 搜索配置项
searchCol: number | Record<BreakPoint, number>;
search: (params: any) => void; // 搜索方法
reset: (params: any) => void; // 重置方法
}
// 默认值
const props = withDefaults(defineProps<ProTableProps>(), {
items: () => [],
modelValue: () => ({}),
});
const searchParam: any = defineModel('modelValue');
// 获取响应式设置
const getResponsive = (item: SearchItem) => {
return {
span: item?.span,
offset: item.offset ?? 0,
// xs: item.search?.xs,
// sm: item.search?.sm,
// md: item.search?.md,
// lg: item.search?.lg,
// xl: item.search?.xl,
};
};
// 是否默认折叠搜索项
const collapsed = ref(true);
// 获取响应式断点
const gridRef = ref();
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
// 判断是否显示 展开/合并 按钮
const showCollapse = computed(() => {
let show = false;
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;
} else {
if (prev >= props.searchCol) show = true;
}
return prev;
}, 0);
return show;
});
const handleItemKeyupEnter = (item: SearchItem) => {
if (item.type == 'input') {
props.search(searchParam);
}
};
</script>
<style lang="scss">
.search-form {
padding: 18px 18px 0;
margin-bottom: 10px;
box-sizing: border-box;
overflow-x: hidden;
background-color: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
border-radius: 6px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.el-form {
.el-form-item__content > * {
width: 100%;
}
// 去除时间选择器上下 padding
.el-range-editor.el-input__wrapper {
padding: 0 10px;
}
.el-form-item {
margin-bottom: 18px !important;
}
}
.operation {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 18px;
}
}
</style>

View File

@@ -0,0 +1,28 @@
import { useUserInfo } from '@/store/userInfo';
/**
* 判断当前用户是否拥有指定权限
* @param code 权限code
* @returns
*/
export function hasPerm(code: string) {
if (!code) {
return true;
}
return useUserInfo().userInfo.permissions.some((v: any) => v === code);
}
/**
* 判断用户是否拥有权限对象里对应的code
* @returns {"xxx:save": true} key->permission code
* @param permCodes
*/
export function hasPerms(permCodes: any[]) {
const res = {} as { [key: string]: boolean };
for (let permCode of permCodes) {
if (hasPerm(permCode)) {
res[permCode] = true;
}
}
return res;
}

View File

@@ -6,7 +6,7 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import { useUserInfo } from '@/store/userInfo';
export default {
name: 'auth',
props: {
@@ -16,10 +16,9 @@ export default {
},
},
setup(props) {
const store = useStore();
// vuex
const getUserAuthBtnList = computed(() => {
return store.state.userInfos.userInfos.authBtnList.some((v: any) => v === props.value);
return useUserInfo().userInfo.authBtnList.some((v: any) => v === props.value);
});
return {
getUserAuthBtnList,

View File

@@ -6,7 +6,7 @@
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index.ts';
import { useUserInfo } from '@/store/userInfo';
import { judementSameArr } from '/@/utils/arrayOperation.ts';
export default {
name: 'authAll',
@@ -17,10 +17,9 @@ export default {
},
},
setup(props) {
const store = useStore();
// vuex
const getUserAuthBtnList = computed(() => {
return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
return judementSameArr(props.value, useUserInfo().userInfo.authBtnList);
});
return {
getUserAuthBtnList,

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