mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-02-02 18:28:41 +08:00
增加前台模块,添加实例html模板页面
This commit is contained in:
parent
6b3333340f
commit
a7658b9b8b
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@ -24,6 +24,7 @@ resource/ssl/server.key
|
|||||||
temp/
|
temp/
|
||||||
main.exe
|
main.exe
|
||||||
main.exe~
|
main.exe~
|
||||||
|
*.exe
|
||||||
*.log
|
*.log
|
||||||
*.zip
|
*.zip
|
||||||
.idea
|
.idea
|
||||||
|
16
server/api/home/base/site.go
Normal file
16
server/api/home/base/site.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Package base
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
package base
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
|
type SiteIndexReq struct {
|
||||||
|
g.Meta `path:"/index" method:"get" summary:"首页" tags:"首页"`
|
||||||
|
}
|
||||||
|
type SiteIndexRes struct {
|
||||||
|
g.Meta `mime:"text/html" type:"string" example:"<html/>"`
|
||||||
|
}
|
@ -17,7 +17,7 @@ var (
|
|||||||
serverCloseSignal chan struct{}
|
serverCloseSignal chan struct{}
|
||||||
Main = &gcmd.Command{
|
Main = &gcmd.Command{
|
||||||
Description: `
|
Description: `
|
||||||
欢迎使用HotGo!
|
命令提示符
|
||||||
---------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------
|
||||||
启动服务
|
启动服务
|
||||||
>> HTTP服务 [go run main.go http]
|
>> HTTP服务 [go run main.go http]
|
||||||
@ -34,8 +34,8 @@ var (
|
|||||||
Name: "help",
|
Name: "help",
|
||||||
Brief: "查看帮助",
|
Brief: "查看帮助",
|
||||||
Description: `
|
Description: `
|
||||||
欢迎使用 HotGo
|
github地址:https://github.com/bufanyun/hotgo
|
||||||
当前版本:v2.0.0
|
文档地址:文档正在书写中,请耐心等一等。
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/net/ghttp"
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
"github.com/gogf/gf/v2/os/gcmd"
|
"github.com/gogf/gf/v2/os/gcmd"
|
||||||
|
baseApi "hotgo/api/home/base"
|
||||||
|
"hotgo/internal/controller/home/base"
|
||||||
"hotgo/internal/library/casbin"
|
"hotgo/internal/library/casbin"
|
||||||
"hotgo/internal/model"
|
"hotgo/internal/model"
|
||||||
"hotgo/internal/router"
|
"hotgo/internal/router"
|
||||||
@ -55,7 +57,7 @@ var (
|
|||||||
|
|
||||||
// 注册默认首页路由
|
// 注册默认首页路由
|
||||||
group.ALL("/", func(r *ghttp.Request) {
|
group.ALL("/", func(r *ghttp.Request) {
|
||||||
r.Response.Write("hello hotGo!!")
|
_, _ = base.Site.Index(r.Context(), &baseApi.SiteIndexReq{})
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -72,6 +74,8 @@ var (
|
|||||||
// 注册websocket路由
|
// 注册websocket路由
|
||||||
router.WebSocket(ctx, group)
|
router.WebSocket(ctx, group)
|
||||||
|
|
||||||
|
// 注册前台页面路由
|
||||||
|
router.Home(ctx, group)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 启动定时任务
|
// 启动定时任务
|
||||||
|
29
server/internal/controller/home/base/site.go
Normal file
29
server/internal/controller/home/base/site.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Package base
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"hotgo/api/home/base"
|
||||||
|
"hotgo/internal/consts"
|
||||||
|
"hotgo/internal/model"
|
||||||
|
"hotgo/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Site 基础
|
||||||
|
var Site = cSite{}
|
||||||
|
|
||||||
|
type cSite struct{}
|
||||||
|
|
||||||
|
func (a *cSite) Index(ctx context.Context, req *base.SiteIndexReq) (res *base.SiteIndexRes, err error) {
|
||||||
|
service.View().Render(ctx, model.View{Data: g.Map{
|
||||||
|
"name": "HotGo",
|
||||||
|
"version": consts.VersionApp,
|
||||||
|
}})
|
||||||
|
return
|
||||||
|
}
|
@ -10,4 +10,5 @@ import (
|
|||||||
_ "hotgo/internal/logic/hook"
|
_ "hotgo/internal/logic/hook"
|
||||||
_ "hotgo/internal/logic/middleware"
|
_ "hotgo/internal/logic/middleware"
|
||||||
_ "hotgo/internal/logic/sys"
|
_ "hotgo/internal/logic/sys"
|
||||||
|
_ "hotgo/internal/logic/view"
|
||||||
)
|
)
|
||||||
|
@ -29,6 +29,12 @@ func (s *sMiddleware) ResponseHandler(r *ghttp.Request) {
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 模板页面响应
|
||||||
|
if "text/html" == r.Response.Header().Get("Content-Type") {
|
||||||
|
r.Middleware.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.GetError(); err != nil {
|
if err := r.GetError(); err != nil {
|
||||||
g.Log().Print(ctx, err)
|
g.Log().Print(ctx, err)
|
||||||
// 记录到自定义错误日志文件
|
// 记录到自定义错误日志文件
|
||||||
|
192
server/internal/logic/view/view.go
Normal file
192
server/internal/logic/view/view.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// Package view
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/os/gfile"
|
||||||
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"hotgo/internal/model"
|
||||||
|
"hotgo/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sView struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
service.RegisterView(New())
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *sView {
|
||||||
|
return &sView{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBreadCrumb 前台系统-获取面包屑列表
|
||||||
|
func (s *sView) GetBreadCrumb(ctx context.Context, in *model.ViewGetBreadCrumbInput) []model.ViewBreadCrumb {
|
||||||
|
breadcrumb := []model.ViewBreadCrumb{
|
||||||
|
{Name: "首页", Url: "/"},
|
||||||
|
}
|
||||||
|
return breadcrumb
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTitle 前台系统-获取标题
|
||||||
|
func (s *sView) GetTitle(ctx context.Context, in *model.ViewGetTitleInput) string {
|
||||||
|
return "title"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderTpl 渲染指定模板页面
|
||||||
|
func (s *sView) RenderTpl(ctx context.Context, tpl string, data ...model.View) {
|
||||||
|
var (
|
||||||
|
viewObj = model.View{}
|
||||||
|
viewData = make(g.Map)
|
||||||
|
request = g.RequestFromCtx(ctx)
|
||||||
|
)
|
||||||
|
if len(data) > 0 {
|
||||||
|
viewObj = data[0]
|
||||||
|
}
|
||||||
|
if viewObj.Title == "" {
|
||||||
|
viewObj.Title = g.Cfg().MustGet(ctx, `setting.title`).String()
|
||||||
|
} else {
|
||||||
|
viewObj.Title = viewObj.Title + ` - ` + g.Cfg().MustGet(ctx, `setting.title`).String()
|
||||||
|
}
|
||||||
|
if viewObj.Keywords == "" {
|
||||||
|
viewObj.Keywords = g.Cfg().MustGet(ctx, `setting.keywords`).String()
|
||||||
|
}
|
||||||
|
if viewObj.Description == "" {
|
||||||
|
viewObj.Description = g.Cfg().MustGet(ctx, `setting.description`).String()
|
||||||
|
}
|
||||||
|
if viewObj.IpcCode == "" {
|
||||||
|
viewObj.IpcCode = g.Cfg().MustGet(ctx, `setting.icpCode`).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewObj.GET == nil {
|
||||||
|
viewObj.GET = request.GetQueryMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉空数据
|
||||||
|
viewData = gconv.Map(viewObj)
|
||||||
|
for k, v := range viewData {
|
||||||
|
if g.IsEmpty(v) {
|
||||||
|
delete(viewData, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 内置对象
|
||||||
|
viewData["BuildIn"] = &viewBuildIn{httpRequest: request}
|
||||||
|
|
||||||
|
// 渲染模板
|
||||||
|
_ = request.Response.WriteTpl(tpl, viewData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render 渲染默认模板页面
|
||||||
|
func (s *sView) Render(ctx context.Context, data ...model.View) {
|
||||||
|
s.RenderTpl(ctx, g.Cfg().MustGet(ctx, "viewer.homeLayout").String(), data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render302 跳转中间页面
|
||||||
|
func (s *sView) Render302(ctx context.Context, data ...model.View) {
|
||||||
|
view := model.View{}
|
||||||
|
if len(data) > 0 {
|
||||||
|
view = data[0]
|
||||||
|
}
|
||||||
|
if view.Title == "" {
|
||||||
|
view.Title = "页面跳转中"
|
||||||
|
}
|
||||||
|
//view.MainTpl = s.getViewFolderName(ctx) + "/pages/302.html"
|
||||||
|
//s.Render(ctx, view)
|
||||||
|
s.RenderTpl(ctx, "default/pages/302.html", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render401 401页面
|
||||||
|
func (s *sView) Render401(ctx context.Context, data ...model.View) {
|
||||||
|
view := model.View{}
|
||||||
|
if len(data) > 0 {
|
||||||
|
view = data[0]
|
||||||
|
}
|
||||||
|
if view.Title == "" {
|
||||||
|
view.Title = "无访问权限"
|
||||||
|
}
|
||||||
|
s.RenderTpl(ctx, "default/pages/401.html", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render403 403页面
|
||||||
|
func (s *sView) Render403(ctx context.Context, data ...model.View) {
|
||||||
|
view := model.View{}
|
||||||
|
if len(data) > 0 {
|
||||||
|
view = data[0]
|
||||||
|
}
|
||||||
|
if view.Title == "" {
|
||||||
|
view.Title = "无访问权限"
|
||||||
|
}
|
||||||
|
s.RenderTpl(ctx, "default/pages/403.html", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render404 404页面
|
||||||
|
func (s *sView) Render404(ctx context.Context, data ...model.View) {
|
||||||
|
view := model.View{}
|
||||||
|
if len(data) > 0 {
|
||||||
|
view = data[0]
|
||||||
|
}
|
||||||
|
if view.Title == "" {
|
||||||
|
view.Title = "资源不存在"
|
||||||
|
}
|
||||||
|
s.RenderTpl(ctx, "default/pages/404.html", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render500 500页面
|
||||||
|
func (s *sView) Render500(ctx context.Context, data ...model.View) {
|
||||||
|
view := model.View{}
|
||||||
|
if len(data) > 0 {
|
||||||
|
view = data[0]
|
||||||
|
}
|
||||||
|
if view.Title == "" {
|
||||||
|
view.Title = "请求执行错误"
|
||||||
|
}
|
||||||
|
s.RenderTpl(ctx, "default/pages/500.html", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sView) Error(ctx context.Context, err error) {
|
||||||
|
view := model.View{
|
||||||
|
Title: "错误提示",
|
||||||
|
Error: err.Error(),
|
||||||
|
}
|
||||||
|
s.RenderTpl(ctx, "default/pages/500.html", view)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取视图存储目录
|
||||||
|
func (s *sView) getViewFolderName(ctx context.Context) string {
|
||||||
|
return gstr.Split(g.Cfg().MustGet(ctx, "viewer.indexLayout").String(), "/")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取自动设置的MainTpl
|
||||||
|
func (s *sView) getDefaultMainTpl(ctx context.Context) string {
|
||||||
|
var (
|
||||||
|
viewFolderPrefix = s.getViewFolderName(ctx)
|
||||||
|
urlPathArray = gstr.SplitAndTrim(g.RequestFromCtx(ctx).URL.Path, "/")
|
||||||
|
mainTpl string
|
||||||
|
)
|
||||||
|
if len(urlPathArray) > 0 && urlPathArray[0] == viewFolderPrefix {
|
||||||
|
urlPathArray = urlPathArray[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(urlPathArray) == 2:
|
||||||
|
// 如果2级路由为数字,那么为模块的详情页面,那么路由固定为/xxx/detail。
|
||||||
|
// 如果需要定制化内容模板,请在具体路由方法中设置MainTpl。
|
||||||
|
if gstr.IsNumeric(urlPathArray[1]) {
|
||||||
|
urlPathArray[1] = "detail"
|
||||||
|
}
|
||||||
|
mainTpl = viewFolderPrefix + "/" + gfile.Join(urlPathArray[0], urlPathArray[1]) + ".html"
|
||||||
|
case len(urlPathArray) == 1:
|
||||||
|
mainTpl = viewFolderPrefix + "/" + urlPathArray[0] + "/index.html"
|
||||||
|
default:
|
||||||
|
// 默认首页内容
|
||||||
|
mainTpl = viewFolderPrefix + "/index/index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
return gstr.TrimLeft(mainTpl, "/")
|
||||||
|
}
|
91
server/internal/logic/view/view_buildin.go
Normal file
91
server/internal/logic/view/view_buildin.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Package view
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hotgo/internal/consts"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"github.com/gogf/gf/v2/util/gmode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 视图自定义方法管理对象
|
||||||
|
type viewBuildIn struct {
|
||||||
|
httpRequest *ghttp.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page 创建分页HTML内容
|
||||||
|
func (s *viewBuildIn) Page(total, size int) string {
|
||||||
|
page := s.httpRequest.GetPage(total, size)
|
||||||
|
page.LinkStyle = "page-link"
|
||||||
|
page.SpanStyle = "page-link"
|
||||||
|
page.PrevPageTag = "«"
|
||||||
|
page.NextPageTag = "»"
|
||||||
|
content := page.PrevPage() + page.PageBar() + page.NextPage()
|
||||||
|
content = gstr.ReplaceByMap(content, map[string]string{
|
||||||
|
"<span": "<li class=\"page-item disabled\"><span",
|
||||||
|
"/span>": "/span></li>",
|
||||||
|
"<a": "<li class=\"page-item\"><a",
|
||||||
|
"/a>": "/a></li>",
|
||||||
|
})
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// UrlPath 获取当前页面的Url Path.
|
||||||
|
func (s *viewBuildIn) UrlPath() string {
|
||||||
|
return s.httpRequest.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatTime 格式化时间
|
||||||
|
func (s *viewBuildIn) FormatTime(gt *gtime.Time) string {
|
||||||
|
if gt == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
n := gtime.Now().Timestamp()
|
||||||
|
t := gt.Timestamp()
|
||||||
|
|
||||||
|
var ys int64 = 31536000
|
||||||
|
var ds int64 = 86400
|
||||||
|
var hs int64 = 3600
|
||||||
|
var ms int64 = 60
|
||||||
|
var ss int64 = 1
|
||||||
|
|
||||||
|
var rs string
|
||||||
|
|
||||||
|
d := n - t
|
||||||
|
switch {
|
||||||
|
case d > ys:
|
||||||
|
rs = fmt.Sprintf("%d年前", int(d/ys))
|
||||||
|
case d > ds:
|
||||||
|
rs = fmt.Sprintf("%d天前", int(d/ds))
|
||||||
|
case d > hs:
|
||||||
|
rs = fmt.Sprintf("%d小时前", int(d/hs))
|
||||||
|
case d > ms:
|
||||||
|
rs = fmt.Sprintf("%d分钟前", int(d/ms))
|
||||||
|
case d > ss:
|
||||||
|
rs = fmt.Sprintf("%d秒前", int(d/ss))
|
||||||
|
default:
|
||||||
|
rs = "刚刚"
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version 随机数 开发环境时间戳,线上为前端版本号
|
||||||
|
func (s *viewBuildIn) Version() string {
|
||||||
|
var rand string
|
||||||
|
if gmode.IsDevelop() {
|
||||||
|
rand = gconv.String(gtime.TimestampMilli())
|
||||||
|
} else {
|
||||||
|
rand = consts.VersionApp
|
||||||
|
}
|
||||||
|
return rand
|
||||||
|
}
|
42
server/internal/model/view.go
Normal file
42
server/internal/model/view.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Package model
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
package model
|
||||||
|
|
||||||
|
// View 视图渲染内容对象
|
||||||
|
type View struct {
|
||||||
|
Title string // 页面标题
|
||||||
|
Keywords string // 页面Keywords
|
||||||
|
Description string // 页面Description
|
||||||
|
IpcCode string // ICP备案号
|
||||||
|
Error string // 错误信息
|
||||||
|
MainTpl string // 自定义MainTpl展示模板文件
|
||||||
|
Redirect string // 引导页面跳转
|
||||||
|
ContentType string // 内容模型
|
||||||
|
BreadCrumb []ViewBreadCrumb // 面包屑
|
||||||
|
GET map[string]interface{} // GET参数
|
||||||
|
Data interface{} // 页面参数
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewBreadCrumb 视图面包屑结构
|
||||||
|
type ViewBreadCrumb struct {
|
||||||
|
Name string // 显示名称
|
||||||
|
Url string // 链接地址,当为空时表示被选中
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewGetBreadCrumbInput 获取面包屑请求
|
||||||
|
type ViewGetBreadCrumbInput struct {
|
||||||
|
ContentId uint // (可选)内容ID
|
||||||
|
ContentType string // (可选)内容类型
|
||||||
|
CategoryId uint // (可选)栏目ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewGetTitleInput 获取title请求
|
||||||
|
type ViewGetTitleInput struct {
|
||||||
|
ContentType string // (可选)内容类型
|
||||||
|
CategoryId uint // (可选)栏目ID
|
||||||
|
CurrentName string // (可选)当前名称
|
||||||
|
}
|
26
server/internal/router/home.go
Normal file
26
server/internal/router/home.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Package router
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
//
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
|
"hotgo/internal/controller/home/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Home 前台页面路由
|
||||||
|
func Home(ctx context.Context, group *ghttp.RouterGroup) {
|
||||||
|
routerPrefix, _ := g.Cfg().Get(ctx, "router.home.prefix", "/home")
|
||||||
|
|
||||||
|
group.Group(routerPrefix.String(), func(group *ghttp.RouterGroup) {
|
||||||
|
group.Bind(
|
||||||
|
base.Site, // 基础
|
||||||
|
)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -92,13 +92,13 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
localAdminMemberPost IAdminMemberPost
|
||||||
|
localAdminMenu IAdminMenu
|
||||||
localAdminNotice IAdminNotice
|
localAdminNotice IAdminNotice
|
||||||
localAdminPost IAdminPost
|
localAdminPost IAdminPost
|
||||||
localAdminRole IAdminRole
|
localAdminRole IAdminRole
|
||||||
localAdminDept IAdminDept
|
localAdminDept IAdminDept
|
||||||
localAdminMember IAdminMember
|
localAdminMember IAdminMember
|
||||||
localAdminMemberPost IAdminMemberPost
|
|
||||||
localAdminMenu IAdminMenu
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func AdminMemberPost() IAdminMemberPost {
|
func AdminMemberPost() IAdminMemberPost {
|
||||||
|
@ -16,12 +16,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ISysDictType interface {
|
|
||||||
Tree(ctx context.Context) (list []g.Map, err error)
|
|
||||||
Delete(ctx context.Context, in sysin.DictTypeDeleteInp) error
|
|
||||||
Edit(ctx context.Context, in sysin.DictTypeEditInp) (err error)
|
|
||||||
Select(ctx context.Context, in sysin.DictTypeSelectInp) (list sysin.DictTypeSelectModel, err error)
|
|
||||||
}
|
|
||||||
ISysLog interface {
|
ISysLog interface {
|
||||||
Export(ctx context.Context, in sysin.LogListInp) (err error)
|
Export(ctx context.Context, in sysin.LogListInp) (err error)
|
||||||
RealWrite(ctx context.Context, commonLog entity.SysLog) error
|
RealWrite(ctx context.Context, commonLog entity.SysLog) error
|
||||||
@ -32,6 +26,15 @@ type (
|
|||||||
Delete(ctx context.Context, in sysin.LogDeleteInp) error
|
Delete(ctx context.Context, in sysin.LogDeleteInp) error
|
||||||
List(ctx context.Context, in sysin.LogListInp) (list []*sysin.LogListModel, totalCount int64, err error)
|
List(ctx context.Context, in sysin.LogListInp) (list []*sysin.LogListModel, totalCount int64, err error)
|
||||||
}
|
}
|
||||||
|
ISysAttachment interface {
|
||||||
|
Delete(ctx context.Context, in sysin.AttachmentDeleteInp) error
|
||||||
|
Edit(ctx context.Context, in sysin.AttachmentEditInp) (err error)
|
||||||
|
Status(ctx context.Context, in sysin.AttachmentStatusInp) (err error)
|
||||||
|
MaxSort(ctx context.Context, in sysin.AttachmentMaxSortInp) (*sysin.AttachmentMaxSortModel, error)
|
||||||
|
View(ctx context.Context, in sysin.AttachmentViewInp) (res *sysin.AttachmentViewModel, err error)
|
||||||
|
List(ctx context.Context, in sysin.AttachmentListInp) (list []*sysin.AttachmentListModel, totalCount int64, err error)
|
||||||
|
Add(ctx context.Context, meta *sysin.UploadFileMeta, fullPath, drive string) (data *entity.SysAttachment, err error)
|
||||||
|
}
|
||||||
ISysBlacklist interface {
|
ISysBlacklist interface {
|
||||||
Delete(ctx context.Context, in sysin.BlacklistDeleteInp) error
|
Delete(ctx context.Context, in sysin.BlacklistDeleteInp) error
|
||||||
Edit(ctx context.Context, in sysin.BlacklistEditInp) (err error)
|
Edit(ctx context.Context, in sysin.BlacklistEditInp) (err error)
|
||||||
@ -40,12 +43,6 @@ type (
|
|||||||
View(ctx context.Context, in sysin.BlacklistViewInp) (res *sysin.BlacklistViewModel, err error)
|
View(ctx context.Context, in sysin.BlacklistViewInp) (res *sysin.BlacklistViewModel, err error)
|
||||||
List(ctx context.Context, in sysin.BlacklistListInp) (list []*sysin.BlacklistListModel, totalCount int64, err error)
|
List(ctx context.Context, in sysin.BlacklistListInp) (list []*sysin.BlacklistListModel, totalCount int64, err error)
|
||||||
}
|
}
|
||||||
ISysConfig interface {
|
|
||||||
GetSmtp(ctx context.Context) (conf *model.EmailConfig, err error)
|
|
||||||
GetConfigByGroup(ctx context.Context, in sysin.GetConfigInp) (*sysin.GetConfigModel, error)
|
|
||||||
ConversionType(ctx context.Context, models *entity.SysConfig) (value interface{}, err error)
|
|
||||||
UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error
|
|
||||||
}
|
|
||||||
ISysCron interface {
|
ISysCron interface {
|
||||||
StartCron(ctx context.Context)
|
StartCron(ctx context.Context)
|
||||||
Delete(ctx context.Context, in sysin.CronDeleteInp) error
|
Delete(ctx context.Context, in sysin.CronDeleteInp) error
|
||||||
@ -55,6 +52,23 @@ type (
|
|||||||
View(ctx context.Context, in sysin.CronViewInp) (res *sysin.CronViewModel, err error)
|
View(ctx context.Context, in sysin.CronViewInp) (res *sysin.CronViewModel, err error)
|
||||||
List(ctx context.Context, in sysin.CronListInp) (list []*sysin.CronListModel, totalCount int64, err error)
|
List(ctx context.Context, in sysin.CronListInp) (list []*sysin.CronListModel, totalCount int64, err error)
|
||||||
}
|
}
|
||||||
|
ISysDictData interface {
|
||||||
|
Delete(ctx context.Context, in sysin.DictDataDeleteInp) error
|
||||||
|
Edit(ctx context.Context, in sysin.DictDataEditInp) (err error)
|
||||||
|
List(ctx context.Context, in sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int64, err error)
|
||||||
|
}
|
||||||
|
ISysDictType interface {
|
||||||
|
Tree(ctx context.Context) (list []g.Map, err error)
|
||||||
|
Delete(ctx context.Context, in sysin.DictTypeDeleteInp) error
|
||||||
|
Edit(ctx context.Context, in sysin.DictTypeEditInp) (err error)
|
||||||
|
Select(ctx context.Context, in sysin.DictTypeSelectInp) (list sysin.DictTypeSelectModel, err error)
|
||||||
|
}
|
||||||
|
ISysConfig interface {
|
||||||
|
GetSmtp(ctx context.Context) (conf *model.EmailConfig, err error)
|
||||||
|
GetConfigByGroup(ctx context.Context, in sysin.GetConfigInp) (*sysin.GetConfigModel, error)
|
||||||
|
ConversionType(ctx context.Context, models *entity.SysConfig) (value interface{}, err error)
|
||||||
|
UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error
|
||||||
|
}
|
||||||
ISysCronGroup interface {
|
ISysCronGroup interface {
|
||||||
Delete(ctx context.Context, in sysin.CronGroupDeleteInp) error
|
Delete(ctx context.Context, in sysin.CronGroupDeleteInp) error
|
||||||
Edit(ctx context.Context, in sysin.CronGroupEditInp) (err error)
|
Edit(ctx context.Context, in sysin.CronGroupEditInp) (err error)
|
||||||
@ -64,20 +78,6 @@ type (
|
|||||||
List(ctx context.Context, in sysin.CronGroupListInp) (list []*sysin.CronGroupListModel, totalCount int64, err error)
|
List(ctx context.Context, in sysin.CronGroupListInp) (list []*sysin.CronGroupListModel, totalCount int64, err error)
|
||||||
Select(ctx context.Context, in sysin.CronGroupSelectInp) (list sysin.CronGroupSelectModel, err error)
|
Select(ctx context.Context, in sysin.CronGroupSelectInp) (list sysin.CronGroupSelectModel, err error)
|
||||||
}
|
}
|
||||||
ISysDictData interface {
|
|
||||||
Delete(ctx context.Context, in sysin.DictDataDeleteInp) error
|
|
||||||
Edit(ctx context.Context, in sysin.DictDataEditInp) (err error)
|
|
||||||
List(ctx context.Context, in sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int64, err error)
|
|
||||||
}
|
|
||||||
ISysAttachment interface {
|
|
||||||
Delete(ctx context.Context, in sysin.AttachmentDeleteInp) error
|
|
||||||
Edit(ctx context.Context, in sysin.AttachmentEditInp) (err error)
|
|
||||||
Status(ctx context.Context, in sysin.AttachmentStatusInp) (err error)
|
|
||||||
MaxSort(ctx context.Context, in sysin.AttachmentMaxSortInp) (*sysin.AttachmentMaxSortModel, error)
|
|
||||||
View(ctx context.Context, in sysin.AttachmentViewInp) (res *sysin.AttachmentViewModel, err error)
|
|
||||||
List(ctx context.Context, in sysin.AttachmentListInp) (list []*sysin.AttachmentListModel, totalCount int64, err error)
|
|
||||||
Add(ctx context.Context, meta *sysin.UploadFileMeta, fullPath, drive string) (data *entity.SysAttachment, err error)
|
|
||||||
}
|
|
||||||
ISysProvinces interface {
|
ISysProvinces interface {
|
||||||
Delete(ctx context.Context, in sysin.ProvincesDeleteInp) error
|
Delete(ctx context.Context, in sysin.ProvincesDeleteInp) error
|
||||||
Edit(ctx context.Context, in sysin.ProvincesEditInp) (err error)
|
Edit(ctx context.Context, in sysin.ProvincesEditInp) (err error)
|
||||||
@ -89,50 +89,17 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
localSysConfig ISysConfig
|
localSysBlacklist ISysBlacklist
|
||||||
localSysCron ISysCron
|
localSysCron ISysCron
|
||||||
localSysCronGroup ISysCronGroup
|
|
||||||
localSysDictData ISysDictData
|
localSysDictData ISysDictData
|
||||||
localSysDictType ISysDictType
|
localSysDictType ISysDictType
|
||||||
localSysLog ISysLog
|
localSysLog ISysLog
|
||||||
localSysBlacklist ISysBlacklist
|
|
||||||
localSysProvinces ISysProvinces
|
|
||||||
localSysAttachment ISysAttachment
|
localSysAttachment ISysAttachment
|
||||||
|
localSysCronGroup ISysCronGroup
|
||||||
|
localSysProvinces ISysProvinces
|
||||||
|
localSysConfig ISysConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
func SysAttachment() ISysAttachment {
|
|
||||||
if localSysAttachment == nil {
|
|
||||||
panic("implement not found for interface ISysAttachment, forgot register?")
|
|
||||||
}
|
|
||||||
return localSysAttachment
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterSysAttachment(i ISysAttachment) {
|
|
||||||
localSysAttachment = i
|
|
||||||
}
|
|
||||||
|
|
||||||
func SysProvinces() ISysProvinces {
|
|
||||||
if localSysProvinces == nil {
|
|
||||||
panic("implement not found for interface ISysProvinces, forgot register?")
|
|
||||||
}
|
|
||||||
return localSysProvinces
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterSysProvinces(i ISysProvinces) {
|
|
||||||
localSysProvinces = i
|
|
||||||
}
|
|
||||||
|
|
||||||
func SysBlacklist() ISysBlacklist {
|
|
||||||
if localSysBlacklist == nil {
|
|
||||||
panic("implement not found for interface ISysBlacklist, forgot register?")
|
|
||||||
}
|
|
||||||
return localSysBlacklist
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterSysBlacklist(i ISysBlacklist) {
|
|
||||||
localSysBlacklist = i
|
|
||||||
}
|
|
||||||
|
|
||||||
func SysConfig() ISysConfig {
|
func SysConfig() ISysConfig {
|
||||||
if localSysConfig == nil {
|
if localSysConfig == nil {
|
||||||
panic("implement not found for interface ISysConfig, forgot register?")
|
panic("implement not found for interface ISysConfig, forgot register?")
|
||||||
@ -144,17 +111,6 @@ func RegisterSysConfig(i ISysConfig) {
|
|||||||
localSysConfig = i
|
localSysConfig = i
|
||||||
}
|
}
|
||||||
|
|
||||||
func SysCron() ISysCron {
|
|
||||||
if localSysCron == nil {
|
|
||||||
panic("implement not found for interface ISysCron, forgot register?")
|
|
||||||
}
|
|
||||||
return localSysCron
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterSysCron(i ISysCron) {
|
|
||||||
localSysCron = i
|
|
||||||
}
|
|
||||||
|
|
||||||
func SysCronGroup() ISysCronGroup {
|
func SysCronGroup() ISysCronGroup {
|
||||||
if localSysCronGroup == nil {
|
if localSysCronGroup == nil {
|
||||||
panic("implement not found for interface ISysCronGroup, forgot register?")
|
panic("implement not found for interface ISysCronGroup, forgot register?")
|
||||||
@ -166,6 +122,17 @@ func RegisterSysCronGroup(i ISysCronGroup) {
|
|||||||
localSysCronGroup = i
|
localSysCronGroup = i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SysProvinces() ISysProvinces {
|
||||||
|
if localSysProvinces == nil {
|
||||||
|
panic("implement not found for interface ISysProvinces, forgot register?")
|
||||||
|
}
|
||||||
|
return localSysProvinces
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSysProvinces(i ISysProvinces) {
|
||||||
|
localSysProvinces = i
|
||||||
|
}
|
||||||
|
|
||||||
func SysDictData() ISysDictData {
|
func SysDictData() ISysDictData {
|
||||||
if localSysDictData == nil {
|
if localSysDictData == nil {
|
||||||
panic("implement not found for interface ISysDictData, forgot register?")
|
panic("implement not found for interface ISysDictData, forgot register?")
|
||||||
@ -198,3 +165,36 @@ func SysLog() ISysLog {
|
|||||||
func RegisterSysLog(i ISysLog) {
|
func RegisterSysLog(i ISysLog) {
|
||||||
localSysLog = i
|
localSysLog = i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SysAttachment() ISysAttachment {
|
||||||
|
if localSysAttachment == nil {
|
||||||
|
panic("implement not found for interface ISysAttachment, forgot register?")
|
||||||
|
}
|
||||||
|
return localSysAttachment
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSysAttachment(i ISysAttachment) {
|
||||||
|
localSysAttachment = i
|
||||||
|
}
|
||||||
|
|
||||||
|
func SysBlacklist() ISysBlacklist {
|
||||||
|
if localSysBlacklist == nil {
|
||||||
|
panic("implement not found for interface ISysBlacklist, forgot register?")
|
||||||
|
}
|
||||||
|
return localSysBlacklist
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSysBlacklist(i ISysBlacklist) {
|
||||||
|
localSysBlacklist = i
|
||||||
|
}
|
||||||
|
|
||||||
|
func SysCron() ISysCron {
|
||||||
|
if localSysCron == nil {
|
||||||
|
panic("implement not found for interface ISysCron, forgot register?")
|
||||||
|
}
|
||||||
|
return localSysCron
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSysCron(i ISysCron) {
|
||||||
|
localSysCron = i
|
||||||
|
}
|
||||||
|
41
server/internal/service/view.go
Normal file
41
server/internal/service/view.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// ================================================================================
|
||||||
|
// Code generated by GoFrame CLI tool. DO NOT EDIT.
|
||||||
|
// You can delete these comments if you wish manually maintain this interface file.
|
||||||
|
// ================================================================================
|
||||||
|
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"hotgo/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
IView interface {
|
||||||
|
GetBreadCrumb(ctx context.Context, in *model.ViewGetBreadCrumbInput) []model.ViewBreadCrumb
|
||||||
|
GetTitle(ctx context.Context, in *model.ViewGetTitleInput) string
|
||||||
|
RenderTpl(ctx context.Context, tpl string, data ...model.View)
|
||||||
|
Render(ctx context.Context, data ...model.View)
|
||||||
|
Render302(ctx context.Context, data ...model.View)
|
||||||
|
Render401(ctx context.Context, data ...model.View)
|
||||||
|
Render403(ctx context.Context, data ...model.View)
|
||||||
|
Render404(ctx context.Context, data ...model.View)
|
||||||
|
Render500(ctx context.Context, data ...model.View)
|
||||||
|
Error(ctx context.Context, err error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localView IView
|
||||||
|
)
|
||||||
|
|
||||||
|
func View() IView {
|
||||||
|
if localView == nil {
|
||||||
|
panic("implement not found for interface IView, forgot register?")
|
||||||
|
}
|
||||||
|
return localView
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterView(i IView) {
|
||||||
|
localView = i
|
||||||
|
}
|
@ -46,6 +46,7 @@ type Client struct {
|
|||||||
Socket *websocket.Conn // 用户连接
|
Socket *websocket.Conn // 用户连接
|
||||||
Send chan *WResponse // 待发送的数据
|
Send chan *WResponse // 待发送的数据
|
||||||
SendClose bool // 发送是否关闭
|
SendClose bool // 发送是否关闭
|
||||||
|
closeSignal chan struct{} // 关闭信号
|
||||||
FirstTime uint64 // 首次连接时间
|
FirstTime uint64 // 首次连接时间
|
||||||
HeartbeatTime uint64 // 用户上次心跳时间
|
HeartbeatTime uint64 // 用户上次心跳时间
|
||||||
Tags garray.StrArray // 标签
|
Tags garray.StrArray // 标签
|
||||||
@ -63,6 +64,7 @@ func NewClient(r *ghttp.Request, socket *websocket.Conn, firstTime uint64) (clie
|
|||||||
Socket: socket,
|
Socket: socket,
|
||||||
Send: make(chan *WResponse, 100),
|
Send: make(chan *WResponse, 100),
|
||||||
SendClose: false,
|
SendClose: false,
|
||||||
|
closeSignal: make(chan struct{}, 1),
|
||||||
FirstTime: firstTime,
|
FirstTime: firstTime,
|
||||||
HeartbeatTime: firstTime,
|
HeartbeatTime: firstTime,
|
||||||
User: contexts.Get(r.Context()).User,
|
User: contexts.Get(r.Context()).User,
|
||||||
@ -107,6 +109,9 @@ func (c *Client) write() {
|
|||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case <-c.closeSignal:
|
||||||
|
g.Log().Infof(ctxManager, "websocket client quit, user:%+v", c.User)
|
||||||
|
return
|
||||||
case message, ok := <-c.Send:
|
case message, ok := <-c.Send:
|
||||||
if !ok {
|
if !ok {
|
||||||
// 发送数据错误 关闭连接
|
// 发送数据错误 关闭连接
|
||||||
@ -159,12 +164,13 @@ func (c *Client) close() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SendClose = true
|
c.SendClose = true
|
||||||
if _, ok := <-c.Send; !ok {
|
//if _, ok := <-c.Send; !ok {
|
||||||
g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID)
|
// g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID)
|
||||||
} else {
|
//} else {
|
||||||
// 关闭 chan
|
// // 关闭 chan
|
||||||
close(c.Send)
|
// close(c.Send)
|
||||||
}
|
//}
|
||||||
|
c.closeSignal <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close 关闭指定客户端连接
|
// Close 关闭指定客户端连接
|
||||||
|
@ -50,6 +50,18 @@ server:
|
|||||||
pprofEnabled: true # 是否开启PProf性能调试特性。默认为false
|
pprofEnabled: true # 是否开启PProf性能调试特性。默认为false
|
||||||
pprofPattern: "/pprof" # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。
|
pprofPattern: "/pprof" # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。
|
||||||
|
|
||||||
|
viewer:
|
||||||
|
paths: "resource/template"
|
||||||
|
defaultFile: "index.html"
|
||||||
|
delimiters: ["@{", "}"]
|
||||||
|
homeLayout: "home/index.html"
|
||||||
|
|
||||||
|
# 内容设置
|
||||||
|
setting:
|
||||||
|
title: "HotGo"
|
||||||
|
keywords: "中后台解决方案,gf框架,vue3"
|
||||||
|
description: "hotgo 是一个基于 goframe2,vue3,vite2,TypeScript,uinapp 的中后台解决方案,它可以帮助你快速搭建企业级中后台项目,相信不管是从新技术使用还是其他方面,都能帮助到你,持续更新中。"
|
||||||
|
|
||||||
|
|
||||||
# 路由配置
|
# 路由配置
|
||||||
router:
|
router:
|
||||||
@ -82,6 +94,12 @@ router:
|
|||||||
# 不需要验证登录的路由地址
|
# 不需要验证登录的路由地址
|
||||||
exceptLogin: [
|
exceptLogin: [
|
||||||
]
|
]
|
||||||
|
# 前台页面
|
||||||
|
home:
|
||||||
|
# 前缀
|
||||||
|
prefix: "/home"
|
||||||
|
# 不需要验证登录的路由地址
|
||||||
|
exceptPath: [ ]
|
||||||
|
|
||||||
#JWT
|
#JWT
|
||||||
jwt:
|
jwt:
|
||||||
|
2
server/resource/public/resource/home/js/jquery-3.6.0.min.js
vendored
Normal file
2
server/resource/public/resource/home/js/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
33
server/resource/template/home/index.html
Normal file
33
server/resource/template/home/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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,user-scalable=no">
|
||||||
|
<meta name="keywords" content="@{.Keywords}"/>
|
||||||
|
<meta name="description" content="@{.Description}"/>
|
||||||
|
<title>@{.Title}</title>
|
||||||
|
<script type="text/javascript" src="/resource/home/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="padding-top: 100px;text-align:center;">
|
||||||
|
<h1><p>Hello,@{.Data.name}!!</p></h1>
|
||||||
|
<h2><p>当前版本:@{.Data.version}</p></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user