This commit is contained in:
2025-03-28 17:51:34 +08:00
parent da612380e0
commit 5c8802d2f0
68 changed files with 3422 additions and 630 deletions

48
internal/pkg/tpl/html.go Normal file
View File

@@ -0,0 +1,48 @@
package tpl
import (
"bytes"
"net/http"
"path/filepath"
"strings"
"management/internal/db/model/dto"
)
type TemplateConfig struct {
Root string
Extension string
Layout string
Partial string
}
type HtmlData struct {
IsAuthenticated bool
AuthorizeUser dto.AuthorizeUser
AuthorizeMenus []*dto.OwnerMenuDto
Data any
}
func (r *render) HTML(w http.ResponseWriter, req *http.Request, tpl string, data map[string]any) {
name := strings.ReplaceAll(tpl, "/", "_")
t, ok := r.templates[name]
if !ok {
http.Error(w, "template is empty", http.StatusInternalServerError)
return
}
hd := r.setDefaultData(req, data)
buf := new(bytes.Buffer)
err := t.ExecuteTemplate(buf, filepath.Base(tpl), hd)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = buf.WriteTo(w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,91 @@
package tpl
import (
"html/template"
"path/filepath"
"strings"
"management/internal/db/model/dto"
)
func (r *render) btnFuncs() map[string]any {
res := make(map[string]any, 3)
res["genBtn"] = func(btns []*dto.OwnerMenuDto, actionNames ...string) template.HTML {
if len(btns) == 0 {
return template.HTML("")
}
var res string
for _, action := range actionNames {
for _, btn := range btns {
btn.Style = strings.ReplaceAll(btn.Style, "pear", "layui")
base := filepath.Base(btn.Url)
if base == action {
res += `<button type="button" class="layui-btn ` + btn.Style + `" lay-event="` + firstLower(action) + `" lay-on="` + firstLower(action) + `">`
if len(btn.Avatar) > 0 {
res += `<i class="` + btn.Avatar + `"></i> `
}
res += btn.DisplayName + `</button>`
}
}
}
return template.HTML(res)
}
res["genLink"] = func(btns []*dto.OwnerMenuDto, actionNames ...string) template.HTML {
if len(btns) == 0 {
return template.HTML("")
}
var res string
for _, action := range actionNames {
for _, btn := range btns {
btn.Style = strings.ReplaceAll(btn.Style, "pear", "layui")
base := filepath.Base(btn.Url)
if base == action {
res += `<button type="button" style="font-size:12px !important;" class="layui-btn ` + btn.Style + `" lay-event="` + firstLower(action) + `" lay-on="` + firstLower(action) + `">`
if len(btn.Avatar) > 0 {
res += `<i class="` + btn.Avatar + `"></i> `
}
res += btn.DisplayName + `</button>`
}
}
}
return template.HTML(res)
}
res["previewPicture"] = func(name string) template.HTML {
var res string
res += `<a><img src="https://school-1251542740.cos.ap-shanghai.myqcloud.com{{` + name + `}}" data-type="1" height="30" width="30" class="preview-all screenshot" onclick="previewPicture('https://school-1251542740.cos.ap-shanghai.myqcloud.com/{{` + name + `}}')"/></a>`
return template.HTML(res)
}
res["submitBtn"] = func(btns []*dto.OwnerMenuDto, actionNames ...string) template.HTML {
if len(btns) == 0 {
return template.HTML("")
}
var res string
for _, action := range actionNames {
for _, btn := range btns {
btn.Style = strings.ReplaceAll(btn.Style, "pear", "layui")
base := filepath.Base(btn.Url)
if base == action {
res += `<button type="submit" class="layui-btn ` + btn.Style + `" lay-submit lay-filter="` + firstLower(action) + `">`
if len(btn.Avatar) > 0 {
res += `<i class="` + btn.Avatar + `"></i> `
}
res += btn.DisplayName + `</button>`
}
}
}
return template.HTML(res)
}
return res
}

View File

@@ -0,0 +1,57 @@
package tpl
import (
"html/template"
"strings"
"time"
)
func (r *render) Methods() map[string]any {
res := make(map[string]any, 1)
res["dateFormat"] = func(dt time.Time) template.HTML {
return template.HTML(dt.Format(time.DateTime))
}
res["today"] = func() template.HTML {
return template.HTML(time.Now().Format("2006-01-02"))
}
res["threeMonth"] = func() template.HTML {
return template.HTML(time.Now().AddDate(0, 3, 0).Format("2006-01-02"))
}
res["yearBegin"] = func() template.HTML {
dt := time.Now()
t := dt.AddDate(0, -int(dt.Month())+1, -dt.Day()+1)
return template.HTML(t.Format("2006-01-02") + " 00:00:00")
}
res["monthBegin"] = func() template.HTML {
dt := time.Now()
t := dt.AddDate(0, 0, -dt.Day()+1)
return template.HTML(t.Format("2006-01-02") + " 00:00:00")
}
res["monthEnd"] = func() template.HTML {
dt := time.Now()
t := dt.AddDate(0, 0, -dt.Day()+1).AddDate(0, 1, -1)
return template.HTML(t.Format("2006-01-02") + " 23:59:59")
}
res["trimSpace"] = func(s string) template.HTML {
return template.HTML(strings.TrimSpace(s))
}
res["expandTags"] = func(s []string) template.HTML {
if len(s) == 0 {
return template.HTML("")
}
if len(s) == 1 && s[0] == "all" {
return template.HTML("")
}
return template.HTML(strings.Join(s, ","))
}
return res
}

57
internal/pkg/tpl/json.go Normal file
View File

@@ -0,0 +1,57 @@
package tpl
import (
"encoding/json"
"net/http"
)
type Response struct {
Success bool `json:"success"`
Message string `json:"msg"`
Data any `json:"data"`
}
type ResponseDtree struct {
Status ResponseDtreeStatus `json:"status"`
Data any `json:"data"`
}
type ResponseDtreeStatus struct {
Code int `json:"code"`
Message string `json:"message"`
}
type ResponseList struct {
Code int `json:"code"`
Message string `json:"msg"`
Count int64 `json:"count"`
Data any `json:"data"`
}
func (r *render) JSONF(w http.ResponseWriter, success bool, message string) {
r.JSON(w, Response{Success: success, Message: message})
}
func (r *render) JSONOK(w http.ResponseWriter, message string) {
r.JSON(w, Response{Success: true, Message: message})
}
func (r *render) JSONERR(w http.ResponseWriter, message string) {
r.JSON(w, Response{Success: false, Message: message})
}
func (r *render) JSON(w http.ResponseWriter, data any) {
v, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err = w.Write(v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@@ -0,0 +1,46 @@
package tpl
import (
"html/template"
"net/http"
systemv1 "management/internal/erpserver/biz/v1/system"
"management/internal/pkg/session"
)
type Renderer interface {
HTML(w http.ResponseWriter, req *http.Request, name string, data map[string]any)
JSON(w http.ResponseWriter, data any)
JSONF(w http.ResponseWriter, success bool, message string)
JSONOK(w http.ResponseWriter, message string)
JSONERR(w http.ResponseWriter, message string)
}
type render struct {
session session.ISession
config *TemplateConfig
templates map[string]*template.Template
menuBiz systemv1.MenuBiz
}
func New(session session.ISession, menuBiz systemv1.MenuBiz) (Renderer, error) {
render := &render{
session: session,
menuBiz: menuBiz,
config: &TemplateConfig{
Root: ".",
Extension: ".tmpl",
Layout: "base",
Partial: "partial",
},
}
templates, err := render.createTemplateCache()
if err != nil {
return nil, err
}
render.templates = templates
return render, nil
}

181
internal/pkg/tpl/util.go Normal file
View File

@@ -0,0 +1,181 @@
package tpl
import (
"context"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net/http"
"os"
"path/filepath"
"slices"
"strings"
"management/internal/db/model/dto"
"management/internal/global/auth"
templates "management/web/templates/manage"
"github.com/justinas/nosurf"
)
func (r *render) setDefaultData(req *http.Request, data map[string]any) map[string]any {
if data == nil {
data = make(map[string]any)
}
ctx := req.Context()
isAuth := r.session.Exists(ctx, auth.StoreName)
data["IsAuthenticated"] = isAuth
if isAuth {
var authUser dto.AuthorizeUser
u := r.session.GetBytes(ctx, auth.StoreName)
_ = json.Unmarshal(u, &authUser)
data["AuthorizeMenus"] = r.getCurrentPathBtns(ctx, authUser.RoleID, req.URL.Path)
}
token := nosurf.Token(req)
data["CsrfToken"] = token
data["CsrfTokenField"] = template.HTML(fmt.Sprintf(`<input type="hidden" name="csrf_token" value="%s" />`, token))
return data
}
func (r *render) getCurrentPathBtns(ctx context.Context, roleID int32, path string) []*dto.OwnerMenuDto {
var res []*dto.OwnerMenuDto
// 获取当前path的菜单
menu, err := r.menuBiz.GetSysMenuByUrl(ctx, path)
if err != nil {
return res
}
// 获取权限
menus, err := r.menuBiz.ListOwnerMenuByRoleID(ctx, roleID)
if err != nil {
return res
}
for _, item := range menus {
if menu.IsList {
if item.ParentID == menu.ID || item.ID == menu.ID {
res = append(res, item)
}
} else {
if item.ParentID == menu.ParentID {
res = append(res, item)
}
}
}
return res
}
func (r *render) createTemplateCache() (map[string]*template.Template, error) {
cache := make(map[string]*template.Template)
pages, err := getFiles(r.config.Root, r.config.Extension)
if err != nil {
return nil, err
}
layoutAndPartial, err := r.getLayoutAndPartials()
if err != nil {
return nil, err
}
for _, page := range pages {
if strings.HasPrefix(page, "base") || strings.HasSuffix(page, "partial") {
continue
}
name := filepath.Base(page)
pathArr := strings.Split(page, "/")
dir := pathArr[len(pathArr)-2 : len(pathArr)-1]
templateName := fmt.Sprintf("%s_%s", dir[0], name)
ts := template.Must(template.New(templateName).Funcs(r.btnFuncs()).Funcs(r.Methods()).ParseFS(templates.TemplateFS, page))
if err != nil {
return nil, err
}
ts, err = ts.ParseFS(templates.TemplateFS, layoutAndPartial...)
if err != nil {
return nil, err
}
cache[templateName] = ts
}
return cache, nil
}
func (r *render) getLayoutAndPartials() ([]string, error) {
layouts, err := getFiles(r.config.Layout, r.config.Extension)
if err != nil {
return nil, err
}
partials, err := getFiles(r.config.Partial, r.config.Extension)
if err != nil {
return nil, err
}
return slices.Concat(layouts, partials), nil
}
func getFiles(path string, stuffix string) ([]string, error) {
files := make([]string, 0)
b, err := pathExists(templates.TemplateFS, path)
if err != nil {
return nil, err
}
if !b {
return files, nil
}
err = fs.WalkDir(templates.TemplateFS, path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if strings.HasSuffix(path, stuffix) {
files = append(files, path)
}
return nil
})
// err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
// if info == nil {
// return err
// }
// if info.IsDir() {
// return nil
// }
// // 将模板后缀的文件放到列表
// if strings.HasSuffix(path, stuffix) {
// files = append(files, path)
// }
// return nil
// })
return files, err
}
func pathExists(fs fs.FS, path string) (bool, error) {
_, err := fs.Open(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, err
}
func firstLower(s string) string {
if len(s) == 0 {
return s
}
return strings.ToLower(s[:1]) + s[1:]
}