diff --git a/README.md b/README.md index bd68f71..3a755e1 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ 15. 代码生成:支持自动化生成前后端代码。CURD关联表、树表、消息队列、定时任务一键生成等。 16. 插件应用:支持一键生成插件模板,每个插件之间开发隔离,拥有独立多应用入口、独立配置。完美支持多人协同开发、插件插拔不会对原系统产生影响等。 17. 服务监控:监视当前系统CPU、内存、磁盘、网络、堆栈等相关信息。 -18. 附件管理:文件图片上传,支持本地、阿里云oss、腾讯云cos、ucloud对象存储、七牛云对象存储等多种上传驱动,后台一键切换配置。 -19. TCP服务:基于gtcp的应用实例,支持长连接、断线重连、自动维护心跳、签名、服务登录、服务授权等。主要用于C/S服务器和服务进程之间的数据通讯。 +18. 附件管理:文件图片上传,支持本地、阿里云oss、腾讯云cos、ucloud对象存储、七牛云对象存储等多种上传驱动,后台一键切换配置,并集成了文件选择器。 +19. TCP服务:基于gtcp的服务应用,支持长连接、断线重连、服务认证、路由分发、RPC消息、拦截器和数据绑定等。简化和规范了服务器开发流程。 20. 消息队列:同时兼容 kafka、redis、rocketmq、磁盘队列,一键配置切换到场景适用的MQ。 21. 通知公告:采用websocket实时推送在线用户最新通知、公告、私信消息。 22. 地区编码:整合国内通用省市区编码,运用于项目于一身,支持动态省市区选项。 diff --git a/docs/guide-zh-CN/README.md b/docs/guide-zh-CN/README.md index b9291e9..3c2721b 100644 --- a/docs/guide-zh-CN/README.md +++ b/docs/guide-zh-CN/README.md @@ -22,15 +22,16 @@ - [支付网关](sys-payment.md) - [数据库](sys-db.md) - [代码生成](sys-code.md) -- 定时任务 +- [定时任务](sys-cron.md) - [消息队列](sys-queue.md) - [功能扩展库](sys-library.md) -- 工具方法 +- [工具方法](sys-utility.md) - RESTful Api - Websocket服务器 -- TCP服务器 -- 单元测试 +- [TCP服务器](sys-tcp-server.md) +- [单元测试](sys-test.md) + #### 插件模块开发 - [模块介绍及目录](addon-introduce-catalog.md) @@ -38,16 +39,12 @@ - [模块辅助说明](addon-helper.md) -#### 实战开发 -- 服务端 -- web前端 - - ### 前端开发 - [表单组件](web-form.md) - Websocket客户端 - 工具库 - [独立部署](web-deploy.md) + #### 附录 - [网址收录](append-website.md) \ No newline at end of file diff --git a/docs/guide-zh-CN/images/sys-middleware-com-response.png b/docs/guide-zh-CN/images/sys-middleware-com-response.png index 86e86d0..26d46a5 100644 Binary files a/docs/guide-zh-CN/images/sys-middleware-com-response.png and b/docs/guide-zh-CN/images/sys-middleware-com-response.png differ diff --git a/docs/guide-zh-CN/start-update-log.md b/docs/guide-zh-CN/start-update-log.md index 67888b0..b865c7c 100644 --- a/docs/guide-zh-CN/start-update-log.md +++ b/docs/guide-zh-CN/start-update-log.md @@ -12,6 +12,19 @@ > 如果升级(覆盖)代码后打开会出现 sql 报错, 请检查更新的数据库格式或自行调整 +### v2.8.4 +updated 2023.07.22 + +- 增加:增加输入预处理中间件 +- 增加:增加文件选择器 +- 增加:增加在线服务监控,服务许可证管理 +- 增加:TCP服务器增加RPC路由消息注册、路由分发、拦截器注册,更方便的进行应用开发 +- 优化:gf版本升级到v2.5.0 +- 优化:优化CURD代码生成,简化控制器代码逻辑,升级列表数据查询方式 +- 修复:修复角色菜单子权限取消导致父级权限失效问题 +- 修复:修复上传驱动路径错误 +- 修复:修复CURD代码生成时间类型字段默认值获取异常 + ### v2.7.6 updated 2023.06.19 @@ -19,7 +32,6 @@ updated 2023.06.19 - 修复:部门管理查询空指针问题 - 优化:附件md5生成方式调整(与更新前已上传的文件md5不兼容,如果业务有影响请注意调整) - ### v2.7.3 updated 2023.05.14 diff --git a/docs/guide-zh-CN/sys-code.md b/docs/guide-zh-CN/sys-code.md index 5d9e9b6..c233d2a 100644 --- a/docs/guide-zh-CN/sys-code.md +++ b/docs/guide-zh-CN/sys-code.md @@ -5,9 +5,11 @@ - 使用条件 - 生成配置 - 一个生成增删改查列表例子 -- 内置gf-cli - 多数据库生成配置 - 自定义生成模板 +- 内置gf-cli +- 指定gf-cli版本 +- 指定数据库驱动类型 > 在HotGo中可以通过后台开发工具快速的一键生成CRUD,自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。 @@ -263,13 +265,6 @@ INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `conte -### 内置gf-cli - -> 由于gf版本更新较常出现向下不兼容的情况,所以我们为了保证生成代码的依赖稳定性,我们将gf-cli工具内置到了系统中并做了一些在线执行的调整。 - -- 后续我们也将开放在线运行`gf gen dao`、`gf gen service`功能。在做插件开发时也会支持到在线生成插件下的service接口,这将会使得插件开发更加方便 - - ## 多数据库生成配置 #### 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码 @@ -339,3 +334,53 @@ hggen: - 如果你在实际的开发过程中默认模板需要调整的地方较多时,HotGo允许你新建新的模板分组来满足你的需求。新的模板可根据现有模板基础拷贝一份出来做改造,默认模板目录:[server/resource/generate/default](../../server/resource/generate/default) + + +### 内置gf-cli + +> 为了确保生成代码的依赖稳定性,在面对`gf`版本更新可能导致向下不兼容情况时,HotGo将`gf-cli`工具内置到系统中并进行在线执行调整,从而提供更可靠和一致的生成代码功能。 + +- 后续我们也将开放在线运行`gf gen ...`功能。在做插件开发时也会支持到在线生成插件下的service接口,这将会使得插件开发更加方便 + + + +### 指定gf-cli版本 + +> HotGo多数情况下会和最新版本的gf-cli保持同步,如果更新不及时或你不想使用最新版本的gf-cli来生成代码,可以找到自己想要的版本进行替换即可。 + +- 下面大致做一些替换步骤说明: + +1. 打开https://github.com/gogf/gf,找到你想要使用的版本`clone`下来 +2. 将`clone`代码中`gf/cmd/gf/internal/`目录覆盖到`server/internal/library/hggen/internal` +3. 将覆盖过来的目录文件中引入包名`github.com/gogf/gf/cmd/gf/v2/`批量改为`hotgo/internal/library/hggen/` +4. 运行`go mod tidy` +5. 运行`go run main.go`,如果没有报错,那么恭喜你已经完成了。如果有报错一般都是版本差异带来的影响,需要根据情况自行调整 + + + +### 指定数据库驱动类型 + +> HotGo默认使用mysql驱动,如果你想用其他数据库驱动打开下方文件中注释即可 + +修改文件路径:server/internal/library/hggen/internal/cmd/cmd_gen_dao.go +```go +package cmd + +import ( + //_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2" + //_ "github.com/gogf/gf/contrib/drivers/mssql/v2" + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + //_ "github.com/gogf/gf/contrib/drivers/oracle/v2" + //_ "github.com/gogf/gf/contrib/drivers/pgsql/v2" + //_ "github.com/gogf/gf/contrib/drivers/sqlite/v2" + + "hotgo/internal/library/hggen/internal/cmd/gendao" +) + +type ( + cGenDao = gendao.CGenDao +) + +``` + +修改完成后运行`go mod tidy` diff --git a/docs/guide-zh-CN/sys-cron.md b/docs/guide-zh-CN/sys-cron.md new file mode 100644 index 0000000..10835d7 --- /dev/null +++ b/docs/guide-zh-CN/sys-cron.md @@ -0,0 +1,71 @@ +## 定时任务 + +目录 + +- 实现接口 +- 一个例子 +- 更多 + +> 在实际的项目开发中,定时任务几乎成为不可或缺的一部分。HotGo为定时任务提供一个方便的后台操作界面,让您能够轻松地进行在线启停、修改和立即执行等操作。这样的设计可以极大地改善您在使用定时任务过程中的体验,让整个过程更加顺畅、高效。 + + +### 实现接口 +- 为了提供高度的扩展性,定时任务在设计上采用了接口化的思路。只需要实现以下接口,您就可以在任何地方注册和使用定时任务功能,从而实现更大的灵活性和可扩展性。 + +```go +// Cron 定时任务接口 +type Cron interface { + // GetName 获取任务名称 + GetName() string + // Execute 执行一次任务 + Execute(ctx context.Context) +} +``` + + +### 一个例子 + +定时任务的文件结构可以根据具体需要进行调整,以下是一个常见的参考结构: + +- 文件路径:server/internal/crons/test.go + +```go +package crons + +import ( + "context" + "hotgo/internal/library/cron" + "time" +) + +func init() { + cron.Register(Test) +} + +// Test 测试任务(无参数) +var Test = &cTest{name: "test"} + +type cTest struct { + name string +} + +func (c *cTest) GetName() string { + return c.name +} + +// Execute 执行任务 +func (c *cTest) Execute(ctx context.Context) { + cron.Logger().Infof(ctx, "cron test Execute:%v", time.Now()) +} + + +``` + +继续在后台系统设置-定时任务-添加任务,填写的任务名称需要和上面的名称保持一致,再进行简单的策略配置以后,一个后台可控的定时任务就添加好了! + + +### 更多 + +定时任务源码路径:server/internal/library/cron/cron.go + +更多文档请参考:https://goframe.org/pages/viewpage.action?pageId=1114187 \ No newline at end of file diff --git a/docs/guide-zh-CN/sys-middleware.md b/docs/guide-zh-CN/sys-middleware.md index 01deb94..53e49b7 100644 --- a/docs/guide-zh-CN/sys-middleware.md +++ b/docs/guide-zh-CN/sys-middleware.md @@ -33,6 +33,9 @@ func main() { // 演示系統操作限制,当开启演示模式时,所有POST请求将被拒绝 service.Middleware().DemoLimit() + + // 请求输入预处理,api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可隐式预处理 + service.Middleware().PreFilter() // HTTP响应预处理,在业务处理完成后,对响应结果进行格式化和错误过滤,将处理后的数据发送给请求方 service.Middleware().ResponseHandler() @@ -143,7 +146,6 @@ func main() { 2. 在`server/internal/logic/middleware/response.go`中根据请求的独有特征进行单独的处理,兼容后续http处理。 - #### 重写响应错误提示 - 在实际开发中,我们可能想要隐藏一些敏感错误,返回给客户端友好的错误提示,但开发者同时又想需要看到真实的敏感错误。对此hotgo已经进行了过滤处理,下面是一个简单的例子: diff --git a/docs/guide-zh-CN/sys-tcp-server.md b/docs/guide-zh-CN/sys-tcp-server.md new file mode 100644 index 0000000..8ef612f --- /dev/null +++ b/docs/guide-zh-CN/sys-tcp-server.md @@ -0,0 +1,271 @@ +## TCP服务器 + +目录 + +- 配置文件 +- 一个基本的消息收发例子 +- 注册路由 +- 拦截器 +- 服务认证 +- 更多 + +> HotGo基于GF框架的TCP服务器组件,提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能,如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等,大大简化和规范了服务器开发流程。 + +### 配置文件 +- 配置文件:server/manifest/config/config.yaml + +```yaml +tcp: + # 服务器 + server: + address: ":8099" + # 客户端 + client: + # 定时任务 + cron: + group: "cron" # 分组名称 + name: "cron1" # 客户端名称 + address: "127.0.0.1:8099" # 服务器地址 + appId: "1002" # 应用名称 + secretKey: "hotgo" # 密钥 + # 系统授权 + auth: + group: "auth" # 分组名称 + name: "auth1" # 客户端名称 + address: "127.0.0.1:8099" # 服务器地址 + appId: "mengshuai" # 应用名称 + secretKey: "123456" # 密钥 + +``` +- 可以看到,除了服务器配置外,还有两个客户端配置`cron` 和`auth` +- `cron`是HotGo内置的定时任务服务,和http服务通过RPC通讯以实现和后台交互,使其可以独立、集群部署。 +- `auth`可以为第三方平台提供授权服务。如果你需要他,可以将它部署在第三方程序中,在重要的位置进行授权验证。 + +### 一个基本的消息收发测试用例 + +- 文件路径:server/internal/library/network/tcp/tcp_example_test.go + +```go +package tcp_test + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" + "hotgo/internal/library/network/tcp" + "testing" + "time" +) + +var T *testing.T // 声明一个全局的 *testing.T 变量 + +type TestMsgReq struct { + Name string `json:"name"` +} + +type TestMsgRes struct { + tcp.ServerRes +} + +type TestRPCMsgReq struct { + Name string `json:"name"` +} + +type TestRPCMsgRes struct { + tcp.ServerRes +} + +func onTestMsg(ctx context.Context, req *TestMsgReq) { + fmt.Printf("服务器收到消息 ==> onTestMsg:%+v\n", req) + conn := tcp.ConnFromCtx(ctx) + gtest.C(T, func(t *gtest.T) { + t.AssertNE(conn, nil) + }) + + res := new(TestMsgRes) + res.Message = fmt.Sprintf("你的名字:%v", req.Name) + conn.Send(ctx, res) +} + +func onResponseTestMsg(ctx context.Context, req *TestMsgRes) { + fmt.Printf("客户端收到响应消息 ==> TestMsgRes:%+v\n", req) + err := req.GetError() + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) +} + +func onTestRPCMsg(ctx context.Context, req *TestRPCMsgReq) (res *TestRPCMsgRes, err error) { + fmt.Printf("服务器收到消息 ==> onTestRPCMsg:%+v\n", req) + res = new(TestRPCMsgRes) + res.Message = fmt.Sprintf("你的名字:%v", req.Name) + return +} + +func startTCPServer() { + serv := tcp.NewServer(&tcp.ServerConfig{ + Name: "hotgo", + Addr: ":8002", + }) + + // 注册路由 + serv.RegisterRouter( + onTestMsg, + ) + + // 注册RPC路由 + serv.RegisterRPCRouter( + onTestRPCMsg, + ) + + // 服务监听 + err := serv.Listen() + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) +} + +// 一个基本的消息收发 +func TestSendMsg(t *testing.T) { + T = t + go startTCPServer() + + ctx := gctx.New() + client := tcp.NewClient(&tcp.ClientConfig{ + Addr: "127.0.0.1:8002", + }) + + // 注册路由 + client.RegisterRouter( + onResponseTestMsg, + ) + + go func() { + err := client.Start() + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) + }() + + // 确保服务都启动完成 + time.Sleep(time.Second * 1) + + // 拿到客户端的连接 + conn := client.Conn() + gtest.C(T, func(t *gtest.T) { + t.AssertNE(conn, nil) + }) + + // 向服务器发送tcp消息,不会阻塞程序执行 + err := conn.Send(ctx, &TestMsgReq{Name: "Tom"}) + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) + + // 向服务器发送rpc消息,会等待服务器响应结果,直到拿到结果或响应超时才会继续 + var res TestRPCMsgRes + if err = conn.RequestScan(ctx, &TestRPCMsgReq{Name: "Tony"}, &res); err != nil { + gtest.C(T, func(t *gtest.T) { + t.AssertNil(err) + }) + } + + fmt.Printf("客户端收到RPC消息响应 ==> TestRPCMsgRes:%+v\n", res) + time.Sleep(time.Second * 1) +} + +``` + + +### 注册路由 + +- 从上面的例子可以看到,不管是普通TCP消息和RPC消息的请求/响应结构体都采用类似GF框架的规范路由的结构,请求`XxxRes`/响应`XxxRes`的格式,是不是很亲切? + + +### 拦截器 + +- 不管是服务端还是客户端,在初始化时都可以注册多个拦截器来满足更多场景的服务开发,下面是一个使用例子: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "hotgo/internal/library/network/tcp" +) + +func main() { + serv = tcp.NewServer(&tcp.ServerConfig{ + Name: "hotgo", + Addr: ":8002", + }) + + // 注册拦截器 + // 执行顺序是从前到后,即Interceptor -> Interceptor2 -> Interceptor3。如果中间有任意一个抛出错误,则会中断后续处理 + serv.RegisterInterceptor(Interceptor, Interceptor2, Interceptor3) + + // 服务监听 + if err := serv.Listen(); err != nil { + if !serv.IsClose() { + g.Log().Warningf(ctx, "TCPServer Listen err:%v", err) + } + } +} + +func Interceptor(ctx context.Context, msg *tcp.Message) (err error) { + // 可以在拦截器中通过上下文拿到连接 + conn := tcp.ConnFromCtx(ctx) + + // 拿到原始请求消息 + g.Dump(msg) + + // 如果想要中断后续处理只需返回一个错误即可,但注意两种情况 + // tcp消息:如果你还想对该消息进行回复应在拦截器中进行处理,例如:conn.Send(ctx, 回复消息内容) + // rpc消息:返回一个错误后系统会将错误自动回复到rpc响应中,无需单独处理 + return +} + +func Interceptor2(ctx context.Context, msg *tcp.Message) (err error) { + // ... + return +} + +func Interceptor3(ctx context.Context, msg *tcp.Message) (err error) { + // ... + return +} + +``` + + +### 服务认证 + +- 一般情况下,建议客户端连接到服务器时都通过`授权许可证`的方式进行登录认证,当初始化客户端配置认证数据时,连接成功后会自动进行登录认证。 + +```go + // 创建客户端配置 + clientConfig := &tcp.ClientConfig{ + Addr: "127.0.0.1:8002", + AutoReconnect: true, + // 认证数据 + // 认证数据可以在后台-系统监控-在线服务-许可证列表中添加,同一个授权支持多个服务使用,但多个服务不能使用相同的名称进行连接 + Auth: &tcp.AuthMeta{ + Name: "服务名称", + Group: "服务分组", + AppId: "APPID", + SecretKey: "SecretKey", + }, + } + + // 初始化客户端 + client = tcp.NewClient(clientConfig) +``` + + +### 更多 + +TCP服务器源码路径:server/internal/library/network/tcp + +更多文档请参考:https://goframe.org/pages/viewpage.action?pageId=1114625 diff --git a/docs/guide-zh-CN/sys-test.md b/docs/guide-zh-CN/sys-test.md new file mode 100644 index 0000000..0e19710 --- /dev/null +++ b/docs/guide-zh-CN/sys-test.md @@ -0,0 +1,3 @@ +## 单元测试 + +请参考:https://goframe.org/pages/viewpage.action?pageId=1114153 \ No newline at end of file diff --git a/docs/guide-zh-CN/sys-utility.md b/docs/guide-zh-CN/sys-utility.md new file mode 100644 index 0000000..ee5ef2e --- /dev/null +++ b/docs/guide-zh-CN/sys-utility.md @@ -0,0 +1,20 @@ +## 工具方法 + +HotGo还提供一些系统中常用的工具库方法,在这里简单说明: + +``` +/server +├── utility +│ ├── charset # 字符串处理 +│ ├── convert # 数据类型转换 +│ ├── encrypt # 数据加密/解密 +│ ├── excel # 电子表格导出/导入 +│ ├── file # 文件/目录处理 +│ ├── format # 数据格式化 +│ ├── simple # 一些简捷函数 +│ ├── tree # 树形结构 +│ ├── url # URL处理 +│ ├── useragent # 请求头代理处理 +└── └── validate # 数据验证 +``` + diff --git a/docs/guide-zh-CN/web-form.md b/docs/guide-zh-CN/web-form.md index 243ba1d..d23b812 100644 --- a/docs/guide-zh-CN/web-form.md +++ b/docs/guide-zh-CN/web-form.md @@ -20,6 +20,7 @@ - 多图上传 UploadImage - 单文件上传 UploadFile - 多文件上传 UploadFile +- 文件选择器 FileChooser - 开关 Switch - 评分 Rate - 省市区选择器 CitySelector @@ -765,6 +766,35 @@ const value = ref(null); ``` +### 文件选择器 FileChooser +- 基础用法 +```vue + + + +``` + +- 指定fileType,支持多种选择器类型,默认情况是全部都可以选择 +```ts +type FileType = 'image' | 'doc' | 'audio' | 'video' | 'zip' | 'other' | 'default'; +``` + +- 图片选择器 +```vue + +``` + +- 多选支持,指定`maxNumber`多选数量 +```vue + +``` + ### 开关 Switch ```vue diff --git a/web/src/views/home/account/BasicSetting.vue b/web/src/views/home/account/BasicSetting.vue index f30722b..adad530 100644 --- a/web/src/views/home/account/BasicSetting.vue +++ b/web/src/views/home/account/BasicSetting.vue @@ -73,7 +73,7 @@ style="margin-top: 15px" > - + @@ -122,13 +122,13 @@ + + diff --git a/web/src/views/monitor/netconn/modal/edit.vue b/web/src/views/monitor/netconn/modal/edit.vue new file mode 100644 index 0000000..6b33182 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/edit.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/index.vue b/web/src/views/monitor/netconn/modal/index.vue new file mode 100644 index 0000000..1ee83b1 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/index.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/modal.vue b/web/src/views/monitor/netconn/modal/modal.vue new file mode 100644 index 0000000..cbdd692 --- /dev/null +++ b/web/src/views/monitor/netconn/modal/modal.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/web/src/views/monitor/netconn/modal/model.ts b/web/src/views/monitor/netconn/modal/model.ts new file mode 100644 index 0000000..1b9dadf --- /dev/null +++ b/web/src/views/monitor/netconn/modal/model.ts @@ -0,0 +1,307 @@ +import { h, ref } from 'vue'; +import { NTag } from 'naive-ui'; +import { cloneDeep } from 'lodash-es'; +import { FormSchema } from '@/components/Form'; +import { Dicts } from '@/api/dict/dict'; +import { isNullObject } from '@/utils/is'; +import { defRangeShortcuts, formatBefore } from '@/utils/dateUtil'; +import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo'; +import { NetOption } from '@/api/monitor/monitor'; + +export interface State { + id: number; + group: string; + name: string; + appid: string; + secretKey: string; + remoteAddr: string; + onlineLimit: number; + loginTimes: number; + lastLoginAt: string; + lastActiveAt: string; + routes: any; + allowedIps: string; + endAt: string; + remark: string; + status: number; + createdAt: string; + updatedAt: string; +} + +export const defaultState = { + id: 0, + group: '', + name: '', + appid: '', + secretKey: '', + remoteAddr: '', + onlineLimit: 1, + loginTimes: 0, + lastLoginAt: '', + lastActiveAt: '', + routes: null, + allowedIps: '', + endAt: '', + remark: '', + status: 1, + createdAt: '', + updatedAt: '', +}; + +export function newState(state: State | null): State { + if (state !== null) { + return cloneDeep(state); + } + return cloneDeep(defaultState); +} + +export const options = ref({ + sys_normal_disable: [], + group: [], + routes: [], +}); + +export const rules = { + group: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入分组', + }, + name: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入许可名称', + }, + appid: { + required: true, + trigger: ['blur', 'input'], + type: 'string', + message: '请输入应用ID', + }, + endAt: { + required: true, + trigger: ['blur', 'input', 'focus'], + type: 'string', + message: '请输入授权结束时间', + }, +}; + +export const schemas = ref([ + { + field: 'id', + component: 'NInput', + label: '许可ID', + componentProps: { + placeholder: '请输入许可ID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'group', + component: 'NSelect', + label: '授权分组', + defaultValue: null, + componentProps: { + placeholder: '请选择授权分组', + options: options.value.group, + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'name', + component: 'NInput', + label: '许可名称', + componentProps: { + placeholder: '请输入许可名称', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'appid', + component: 'NInput', + label: 'APPID', + componentProps: { + placeholder: '请输入APPID', + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'status', + component: 'NSelect', + label: '状态', + defaultValue: null, + componentProps: { + placeholder: '请选择状态', + options: [], + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'endAt', + component: 'NDatePicker', + label: '过期时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, + { + field: 'createdAt', + component: 'NDatePicker', + label: '创建时间', + componentProps: { + type: 'datetimerange', + clearable: true, + shortcuts: defRangeShortcuts(), + onUpdateValue: (e: any) => { + console.log(e); + }, + }, + }, +]); + +export const columns = [ + { + title: '许可ID', + key: 'id', + width: 100, + }, + { + title: '授权分组', + key: 'group', + width: 100, + render(row) { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => getOptionLabel(options.value.group, row.group), + } + ); + }, + }, + { + title: '授权许可', + key: 'name', + render(row) { + return h('p', { id: 'app' }, [ + h('div', { + innerHTML: '

名称:' + row.name + '

', + }), + h('div', { + innerHTML: '

APPID:' + row.appid + '

', + }), + ]); + }, + width: 180, + }, + { + title: '在线', + key: 'online', + render(row) { + return row.online + ' / ' + row.onlineLimit; + }, + width: 100, + }, + { + title: '授权有效期', + key: 'endAt', + width: 150, + }, + { + title: '状态', + key: 'status', + width: 100, + render(row) { + if (isNullObject(row.status)) { + return ``; + } + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: getOptionTag(options.value.sys_normal_disable, row.status), + bordered: false, + }, + { + default: () => getOptionLabel(options.value.sys_normal_disable, row.status), + } + ); + }, + }, + { + title: '最后连接', + key: 'remoteAddr', + width: 150, + }, + { + title: '最近登录 / 心跳', + key: 'name', + render(row) { + if (row.lastLoginAt === null) { + return '从未登录'; + } + return ( + formatBefore(new Date(row.lastLoginAt)) + ' / ' + formatBefore(new Date(row.lastActiveAt)) + ); + }, + width: 180, + }, + { + title: '累计登录', + key: 'loginTimes', + width: 100, + }, + { + title: '创建时间', + key: 'createdAt', + width: 150, + }, +]; + +async function loadOptions() { + options.value = await Dicts({ + types: ['sys_normal_disable'], + }); + + const netOption = await NetOption(); + options.value.group = netOption.licenseGroup; + options.value.routes = netOption.routes; + + for (const item of schemas.value) { + switch (item.field) { + case 'status': + item.componentProps.options = options.value.sys_normal_disable; + break; + case 'group': + item.componentProps.options = options.value.group; + break; + } + } +} + +await loadOptions(); diff --git a/web/src/views/monitor/online/index.vue b/web/src/views/monitor/online/index.vue index 8224362..d581eb7 100644 --- a/web/src/views/monitor/online/index.vue +++ b/web/src/views/monitor/online/index.vue @@ -1,20 +1,25 @@ diff --git a/web/src/views/org/user/model.ts b/web/src/views/org/user/model.ts index aaf80c1..b595b73 100644 --- a/web/src/views/org/user/model.ts +++ b/web/src/views/org/user/model.ts @@ -178,7 +178,7 @@ export const options = ref({ post: [], }); -async function loadOptions() { +export async function loadOptions() { const dept = await getDeptOption(); if (dept.list !== undefined) { options.value.dept = dept.list; @@ -187,6 +187,7 @@ async function loadOptions() { const role = await getRoleOption(); if (role.list !== undefined) { options.value.role = role.list; + options.value.roleTabs = [{ id: -1, name: '全部' }]; treeDataToCompressed(role.list); } @@ -207,8 +208,5 @@ function treeDataToCompressed(source) { ? treeDataToCompressed(source[i].children) : ''; // 子级递归 } - return options.value.roleTabs; } - -await loadOptions(); diff --git a/web/src/views/org/user/user.vue b/web/src/views/org/user/user.vue index c82054e..705a35f 100644 --- a/web/src/views/org/user/user.vue +++ b/web/src/views/org/user/user.vue @@ -39,7 +39,8 @@ } }); - function handleBeforeLeave(tabName: string) { + function handleBeforeLeave(tabName: string): boolean | Promise { defaultTab.value = tabName; + return true; } diff --git a/web/src/views/permission/menu/CreateDrawer.vue b/web/src/views/permission/menu/CreateDrawer.vue index 8f7612c..656b6e9 100644 --- a/web/src/views/permission/menu/CreateDrawer.vue +++ b/web/src/views/permission/menu/CreateDrawer.vue @@ -296,7 +296,7 @@ }, }, emits: ['loadData'], - setup(props, context) { + setup(_props, context) { const message = useMessage(); const formRef: any = ref(null); const state = reactive({ diff --git a/web/src/views/permission/role/columns.ts b/web/src/views/permission/role/columns.ts index 2ed0b91..198db0c 100644 --- a/web/src/views/permission/role/columns.ts +++ b/web/src/views/permission/role/columns.ts @@ -40,7 +40,7 @@ export const columns = [ { title: '角色编码', key: 'key', - // width: 150, + width: 150, }, // { // title: '上级角色', @@ -60,22 +60,22 @@ export const columns = [ } ); }, - // width: 80, + width: 80, }, { title: '排序', key: 'sort', - // width: 100, + width: 100, }, { title: '备注', key: 'remark', - // width: 300, + width: 180, }, { title: '状态', key: 'status', - // width: 80, + width: 80, render(row) { return h( NTag, diff --git a/web/src/views/permission/role/role.vue b/web/src/views/permission/role/role.vue index 8b81402..e816e82 100644 --- a/web/src/views/permission/role/role.vue +++ b/web/src/views/permission/role/role.vue @@ -31,8 +31,8 @@