diff --git a/server/.gitignore b/server/.gitignore index 752433a..e48bf87 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -24,6 +24,7 @@ resource/ssl/server.key temp/ main.exe main.exe~ +*.exe *.log *.zip .idea diff --git a/server/api/home/base/site.go b/server/api/home/base/site.go new file mode 100644 index 0000000..6d06972 --- /dev/null +++ b/server/api/home/base/site.go @@ -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:""` +} diff --git a/server/internal/cmd/cmd.go b/server/internal/cmd/cmd.go index 938bddd..b6e6db5 100644 --- a/server/internal/cmd/cmd.go +++ b/server/internal/cmd/cmd.go @@ -17,7 +17,7 @@ var ( serverCloseSignal chan struct{} Main = &gcmd.Command{ Description: ` - 欢迎使用HotGo! + 命令提示符 --------------------------------------------------------------------------------- 启动服务 >> HTTP服务 [go run main.go http] @@ -34,8 +34,8 @@ var ( Name: "help", Brief: "查看帮助", Description: ` - 欢迎使用 HotGo - 当前版本:v2.0.0 + github地址:https://github.com/bufanyun/hotgo + 文档地址:文档正在书写中,请耐心等一等。 `, } diff --git a/server/internal/cmd/http.go b/server/internal/cmd/http.go index a17baa9..ed02e62 100644 --- a/server/internal/cmd/http.go +++ b/server/internal/cmd/http.go @@ -11,6 +11,8 @@ import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gcmd" + baseApi "hotgo/api/home/base" + "hotgo/internal/controller/home/base" "hotgo/internal/library/casbin" "hotgo/internal/model" "hotgo/internal/router" @@ -55,7 +57,7 @@ var ( // 注册默认首页路由 group.ALL("/", func(r *ghttp.Request) { - r.Response.Write("hello hotGo!!") + _, _ = base.Site.Index(r.Context(), &baseApi.SiteIndexReq{}) return }) @@ -72,6 +74,8 @@ var ( // 注册websocket路由 router.WebSocket(ctx, group) + // 注册前台页面路由 + router.Home(ctx, group) }) // 启动定时任务 diff --git a/server/internal/controller/home/base/site.go b/server/internal/controller/home/base/site.go new file mode 100644 index 0000000..98cd63c --- /dev/null +++ b/server/internal/controller/home/base/site.go @@ -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 +} diff --git a/server/internal/logic/logic.go b/server/internal/logic/logic.go index dcd6856..8e2ca5a 100644 --- a/server/internal/logic/logic.go +++ b/server/internal/logic/logic.go @@ -10,4 +10,5 @@ import ( _ "hotgo/internal/logic/hook" _ "hotgo/internal/logic/middleware" _ "hotgo/internal/logic/sys" + _ "hotgo/internal/logic/view" ) diff --git a/server/internal/logic/middleware/response.go b/server/internal/logic/middleware/response.go index f99ff03..f4cb78e 100644 --- a/server/internal/logic/middleware/response.go +++ b/server/internal/logic/middleware/response.go @@ -29,6 +29,12 @@ func (s *sMiddleware) ResponseHandler(r *ghttp.Request) { err error ) + // 模板页面响应 + if "text/html" == r.Response.Header().Get("Content-Type") { + r.Middleware.Next() + return + } + if err := r.GetError(); err != nil { g.Log().Print(ctx, err) // 记录到自定义错误日志文件 diff --git a/server/internal/logic/view/view.go b/server/internal/logic/view/view.go new file mode 100644 index 0000000..a0193c3 --- /dev/null +++ b/server/internal/logic/view/view.go @@ -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, "/") +} diff --git a/server/internal/logic/view/view_buildin.go b/server/internal/logic/view/view_buildin.go new file mode 100644 index 0000000..92686fc --- /dev/null +++ b/server/internal/logic/view/view_buildin.go @@ -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>", + "": "/a>", + }) + 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 +} diff --git a/server/internal/model/view.go b/server/internal/model/view.go new file mode 100644 index 0000000..76c52fb --- /dev/null +++ b/server/internal/model/view.go @@ -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 // (可选)当前名称 +} diff --git a/server/internal/router/home.go b/server/internal/router/home.go new file mode 100644 index 0000000..efed567 --- /dev/null +++ b/server/internal/router/home.go @@ -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, // 基础 + ) + + }) +} diff --git a/server/internal/service/admin.go b/server/internal/service/admin.go index 3ace13f..66ec6a4 100644 --- a/server/internal/service/admin.go +++ b/server/internal/service/admin.go @@ -92,13 +92,13 @@ type ( ) var ( + localAdminMemberPost IAdminMemberPost + localAdminMenu IAdminMenu localAdminNotice IAdminNotice localAdminPost IAdminPost localAdminRole IAdminRole localAdminDept IAdminDept localAdminMember IAdminMember - localAdminMemberPost IAdminMemberPost - localAdminMenu IAdminMenu ) func AdminMemberPost() IAdminMemberPost { diff --git a/server/internal/service/sys.go b/server/internal/service/sys.go index d8783f5..b5fbca0 100644 --- a/server/internal/service/sys.go +++ b/server/internal/service/sys.go @@ -16,12 +16,6 @@ import ( ) 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 { Export(ctx context.Context, in sysin.LogListInp) (err error) RealWrite(ctx context.Context, commonLog entity.SysLog) error @@ -32,6 +26,15 @@ type ( Delete(ctx context.Context, in sysin.LogDeleteInp) 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 { Delete(ctx context.Context, in sysin.BlacklistDeleteInp) 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) 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 { StartCron(ctx context.Context) 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) 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 { Delete(ctx context.Context, in sysin.CronGroupDeleteInp) 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) 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 { Delete(ctx context.Context, in sysin.ProvincesDeleteInp) error Edit(ctx context.Context, in sysin.ProvincesEditInp) (err error) @@ -89,50 +89,17 @@ type ( ) var ( - localSysConfig ISysConfig + localSysBlacklist ISysBlacklist localSysCron ISysCron - localSysCronGroup ISysCronGroup localSysDictData ISysDictData localSysDictType ISysDictType localSysLog ISysLog - localSysBlacklist ISysBlacklist - localSysProvinces ISysProvinces 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 { if localSysConfig == nil { panic("implement not found for interface ISysConfig, forgot register?") @@ -144,17 +111,6 @@ func RegisterSysConfig(i ISysConfig) { 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 { if localSysCronGroup == nil { panic("implement not found for interface ISysCronGroup, forgot register?") @@ -166,6 +122,17 @@ func RegisterSysCronGroup(i ISysCronGroup) { 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 { if localSysDictData == nil { panic("implement not found for interface ISysDictData, forgot register?") @@ -198,3 +165,36 @@ func SysLog() ISysLog { func RegisterSysLog(i ISysLog) { 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 +} diff --git a/server/internal/service/view.go b/server/internal/service/view.go new file mode 100644 index 0000000..a757343 --- /dev/null +++ b/server/internal/service/view.go @@ -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 +} diff --git a/server/internal/websocket/client.go b/server/internal/websocket/client.go index 6465e57..f4e2f25 100644 --- a/server/internal/websocket/client.go +++ b/server/internal/websocket/client.go @@ -46,6 +46,7 @@ type Client struct { Socket *websocket.Conn // 用户连接 Send chan *WResponse // 待发送的数据 SendClose bool // 发送是否关闭 + closeSignal chan struct{} // 关闭信号 FirstTime uint64 // 首次连接时间 HeartbeatTime uint64 // 用户上次心跳时间 Tags garray.StrArray // 标签 @@ -63,6 +64,7 @@ func NewClient(r *ghttp.Request, socket *websocket.Conn, firstTime uint64) (clie Socket: socket, Send: make(chan *WResponse, 100), SendClose: false, + closeSignal: make(chan struct{}, 1), FirstTime: firstTime, HeartbeatTime: firstTime, User: contexts.Get(r.Context()).User, @@ -107,6 +109,9 @@ func (c *Client) write() { }() for { select { + case <-c.closeSignal: + g.Log().Infof(ctxManager, "websocket client quit, user:%+v", c.User) + return case message, ok := <-c.Send: if !ok { // 发送数据错误 关闭连接 @@ -159,12 +164,13 @@ func (c *Client) close() { return } c.SendClose = true - if _, ok := <-c.Send; !ok { - g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID) - } else { - // 关闭 chan - close(c.Send) - } + //if _, ok := <-c.Send; !ok { + // g.Log().Warningf(ctxManager, "close of closed channel, client.id:%v", c.ID) + //} else { + // // 关闭 chan + // close(c.Send) + //} + c.closeSignal <- struct{}{} } // Close 关闭指定客户端连接 diff --git a/server/manifest/config/config.example.yaml b/server/manifest/config/config.example.yaml index 3f6b8c0..58402f2 100644 --- a/server/manifest/config/config.example.yaml +++ b/server/manifest/config/config.example.yaml @@ -50,6 +50,18 @@ server: pprofEnabled: true # 是否开启PProf性能调试特性。默认为false 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: @@ -82,6 +94,12 @@ router: # 不需要验证登录的路由地址 exceptLogin: [ ] + # 前台页面 + home: + # 前缀 + prefix: "/home" + # 不需要验证登录的路由地址 + exceptPath: [ ] #JWT jwt: diff --git a/server/resource/public/resource/home/js/jquery-3.6.0.min.js b/server/resource/public/resource/home/js/jquery-3.6.0.min.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/server/resource/public/resource/home/js/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 + + + + + + + + @{.Title} + + + + +
+

Hello,@{.Data.name}!!

+

当前版本:@{.Data.version}

+
+ + + + + + \ No newline at end of file