v1
This commit is contained in:
@@ -2,6 +2,7 @@ package erpserver
|
||||
|
||||
import (
|
||||
"management/internal/erpserver/model/system"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
@@ -16,6 +17,8 @@ type App struct {
|
||||
LoginLogRepo system.LoginLogRepository
|
||||
AuditLogRepo system.AuditLogRepository
|
||||
|
||||
AuditLogService v1.AuditLogService
|
||||
|
||||
Router *chi.Mux
|
||||
}
|
||||
|
||||
@@ -29,6 +32,8 @@ func NewApp(
|
||||
LoginLogRepo system.LoginLogRepository,
|
||||
AuditLogRepo system.AuditLogRepository,
|
||||
|
||||
AuditLogService v1.AuditLogService,
|
||||
|
||||
Router *chi.Mux,
|
||||
) App {
|
||||
return App{
|
||||
@@ -41,6 +46,8 @@ func NewApp(
|
||||
LoginLogRepo: LoginLogRepo,
|
||||
AuditLogRepo: AuditLogRepo,
|
||||
|
||||
AuditLogService: AuditLogService,
|
||||
|
||||
Router: Router,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"management/internal/erpserver/model/dto"
|
||||
@@ -10,7 +11,9 @@ import (
|
||||
"management/internal/pkg/render"
|
||||
"management/internal/pkg/session"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/drhin/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
//type RouterGroup interface {
|
||||
@@ -40,14 +43,14 @@ func NewHandler(
|
||||
}
|
||||
|
||||
// =====================================================================================================================
|
||||
// middleware 帮助方法
|
||||
// mid 帮助方法
|
||||
|
||||
func (h *Handler) AuthUser(ctx context.Context) dto.AuthorizeUser {
|
||||
u, err := h.session.GetUser(ctx, know.StoreName)
|
||||
if err != nil {
|
||||
return dto.AuthorizeUser{}
|
||||
}
|
||||
return *u
|
||||
return u
|
||||
}
|
||||
|
||||
func (h *Handler) RenewToken(ctx context.Context) error {
|
||||
@@ -61,6 +64,14 @@ func (h *Handler) Destroy(ctx context.Context) error {
|
||||
// =====================================================================================================================
|
||||
// render 帮助方法
|
||||
|
||||
func (h *Handler) Render(ctx context.Context, w http.ResponseWriter, t templ.Component) {
|
||||
if err := t.Render(ctx, w); err != nil {
|
||||
h.Log.Error(err.Error(), err,
|
||||
zap.String("templ render", fmt.Sprintf("%v", t)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) HTML(w http.ResponseWriter, r *http.Request, name string, data map[string]any) {
|
||||
h.render.HTML(w, r, name, data)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"management/internal/erpserver/handler"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/erpserver/templ/home"
|
||||
)
|
||||
|
||||
type HomeHandler struct {
|
||||
@@ -26,19 +27,22 @@ func NewHomeHandler(
|
||||
}
|
||||
|
||||
func (h *HomeHandler) Home(w http.ResponseWriter, r *http.Request) {
|
||||
h.HTML(w, r, "home/home.tmpl", nil)
|
||||
ctx := r.Context()
|
||||
h.Render(ctx, w, home.Home(ctx))
|
||||
//h.HTML(w, r, "home/home.tmpl", nil)
|
||||
}
|
||||
|
||||
func (h *HomeHandler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
auth := h.AuthUser(ctx)
|
||||
user, _ := h.userService.Get(ctx, auth.ID)
|
||||
//user, _ := h.userService.Get(ctx, auth.ID)
|
||||
lt, _ := h.loginLogService.LoginTime(ctx, auth.Email)
|
||||
c := h.loginLogService.LoginCount(ctx, auth.Email)
|
||||
h.HTML(w, r, "home/dashboard.tmpl", map[string]any{
|
||||
"Auth": auth,
|
||||
"User": user,
|
||||
"LoginTime": lt,
|
||||
"LoginCount": c,
|
||||
})
|
||||
h.Render(ctx, w, home.Dashboard(ctx, int(c), lt.ThisLoginTime, lt.LastLoginTime))
|
||||
//h.HTML(w, r, "home/dashboard.tmpl", map[string]any{
|
||||
// "Auth": auth,
|
||||
// "User": user,
|
||||
// "LoginTime": lt,
|
||||
// "LoginCount": c,
|
||||
//})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"management/internal/erpserver/model/form"
|
||||
"management/internal/erpserver/model/system"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/erpserver/templ/system/role"
|
||||
"management/internal/pkg/binding"
|
||||
"management/internal/pkg/convertor"
|
||||
"management/internal/pkg/render"
|
||||
@@ -31,7 +32,9 @@ func NewRoleHandler(handler *handler.Handler, roleService v1.RoleService, menuSe
|
||||
func (h *RoleHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.HTML(w, r, "role/list.tmpl", nil)
|
||||
ctx := r.Context()
|
||||
h.Render(ctx, w, role.List(ctx))
|
||||
//h.HTML(w, r, "role/list.tmpl", nil)
|
||||
case http.MethodPost:
|
||||
var q dto.SearchDto
|
||||
q.SearchTimeBegin, q.SearchTimeEnd = convertor.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
|
||||
@@ -60,30 +63,36 @@ func (h *RoleHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *RoleHandler) Add(w http.ResponseWriter, r *http.Request) {
|
||||
h.HTML(w, r, "role/edit.tmpl", map[string]any{
|
||||
"Item": &system.Role{Sort: 6666},
|
||||
})
|
||||
ctx := r.Context()
|
||||
h.Render(ctx, w, role.Edit(ctx, &system.Role{Sort: 6666}))
|
||||
//h.HTML(w, r, "role/edit.tmpl", map[string]any{
|
||||
// "Item": &system.Role{Sort: 6666},
|
||||
//})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) AddChildren(w http.ResponseWriter, r *http.Request) {
|
||||
vars := r.URL.Query()
|
||||
parentID := convertor.QueryInt[int32](vars, "parentID", 0)
|
||||
vm := &system.Role{ParentID: parentID, Sort: 6666}
|
||||
h.HTML(w, r, "role/edit.tmpl", map[string]any{
|
||||
"Item": vm,
|
||||
})
|
||||
ctx := r.Context()
|
||||
h.Render(ctx, w, role.Edit(ctx, vm))
|
||||
//h.HTML(w, r, "role/edit.tmpl", map[string]any{
|
||||
// "Item": vm,
|
||||
//})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) Edit(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vars := r.URL.Query()
|
||||
id := convertor.QueryInt[int32](vars, "id", 0)
|
||||
vm := &system.Role{Sort: 6666}
|
||||
if id > 0 {
|
||||
vm, _ = h.roleService.Get(r.Context(), id)
|
||||
vm, _ = h.roleService.Get(ctx, id)
|
||||
}
|
||||
h.HTML(w, r, "role/edit.tmpl", map[string]any{
|
||||
"Item": vm,
|
||||
})
|
||||
h.Render(ctx, w, role.Edit(ctx, vm))
|
||||
//h.HTML(w, r, "role/edit.tmpl", map[string]any{
|
||||
// "Item": vm,
|
||||
//})
|
||||
}
|
||||
|
||||
func (h *RoleHandler) Save(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -158,32 +167,32 @@ func (h *RoleHandler) RefreshRoleMenus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 获取需要刷新的角色ID
|
||||
roleID := convertor.ConvertInt[int32](r.PostFormValue("roleID"), 0)
|
||||
role, err := h.roleService.Get(ctx, roleID)
|
||||
roleModel, err := h.roleService.Get(ctx, roleID)
|
||||
if err != nil {
|
||||
h.JSONErr(w, err.Error())
|
||||
return
|
||||
}
|
||||
if role == nil {
|
||||
if roleModel == nil {
|
||||
h.JSONErr(w, "角色不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新角色菜单 (角色所拥有的菜单集合)
|
||||
_, err = h.menuService.SetListByRoleID(ctx, role.ID)
|
||||
_, err = h.menuService.SetListByRoleID(ctx, roleModel.ID)
|
||||
if err != nil {
|
||||
h.JSONErr(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新角色菜单 (角色所拥有的菜单集合)
|
||||
_, err = h.menuService.SetListByRoleIDToMap(ctx, role.ID)
|
||||
_, err = h.menuService.SetListByRoleIDToMap(ctx, roleModel.ID)
|
||||
if err != nil {
|
||||
h.JSONErr(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新角色菜单树 (pear admin layui 使用的格式)
|
||||
_, err = h.menuService.SetOwerMenus(ctx, role.ID)
|
||||
_, err = h.menuService.SetOwerMenus(ctx, roleModel.ID)
|
||||
if err != nil {
|
||||
h.JSONErr(w, err.Error())
|
||||
return
|
||||
@@ -220,7 +229,7 @@ func (h *RoleHandler) SetMenu(w http.ResponseWriter, r *http.Request) {
|
||||
h.JSONErr(w, "角色异常, 请刷新重试")
|
||||
return
|
||||
}
|
||||
role, err := h.roleService.Get(ctx, id)
|
||||
roleModel, err := h.roleService.Get(ctx, id)
|
||||
if err != nil {
|
||||
h.JSONErr(w, err.Error())
|
||||
return
|
||||
@@ -248,7 +257,7 @@ func (h *RoleHandler) SetMenu(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
rms = append(rms, &system.RoleMenu{
|
||||
RoleID: role.ID,
|
||||
RoleID: roleModel.ID,
|
||||
MenuID: menu.ID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package system
|
||||
//
|
||||
//import (
|
||||
// v1 "management/internal/erpserver/service/v1"
|
||||
// mi "management/internal/pkg/middleware"
|
||||
// mi "management/internal/pkg/mid"
|
||||
// "management/internal/pkg/session"
|
||||
//
|
||||
// "github.com/go-chi/chi/v5"
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"management/internal/erpserver/model/form"
|
||||
systemmodel "management/internal/erpserver/model/system"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/erpserver/templ/auth"
|
||||
"management/internal/pkg/binding"
|
||||
"management/internal/pkg/convertor"
|
||||
"management/internal/pkg/render"
|
||||
@@ -176,7 +177,9 @@ func (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
_ = h.Destroy(ctx)
|
||||
h.HTML(w, r, "oauth/login.tmpl", nil)
|
||||
component := auth.Login(ctx)
|
||||
h.Render(ctx, w, component)
|
||||
//h.HTML(w, r, "oauth/login.tmpl", nil)
|
||||
case http.MethodPost:
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"management/internal/erpserver/handler/common"
|
||||
"management/internal/erpserver/handler/system"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
mi "management/internal/pkg/middleware"
|
||||
mi "management/internal/pkg/mid"
|
||||
"management/internal/pkg/session"
|
||||
static "management/web/statics"
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewHTTPServer(
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
// r.Use(middleware.Logger)
|
||||
// r.Use(mid.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
//staticServer := http.FileServer(http.Dir("./web/statics/"))
|
||||
@@ -47,6 +47,7 @@ func NewHTTPServer(
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(mi.NoSurf) // CSRF
|
||||
r.Use(mi.NoSurfContext) // CSRF Store Context
|
||||
r.Use(mi.LoadSession(sm)) // Session
|
||||
|
||||
r.Get("/", userHandler.Login)
|
||||
@@ -63,14 +64,14 @@ func NewHTTPServer(
|
||||
r.Get("/pear.json", configHandler.Pear)
|
||||
|
||||
r.Route("/upload", func(r chi.Router) {
|
||||
r.Use(mi.Audit(sm, auditLogService, log))
|
||||
r.Use(mi.Audit(sm, log))
|
||||
r.Get("/img", uploadHandler.Img)
|
||||
r.Get("/file", uploadHandler.File)
|
||||
r.Get("/multi_files", uploadHandler.MultiFiles)
|
||||
})
|
||||
|
||||
r.Route("/system", func(r chi.Router) {
|
||||
r.Use(mi.Audit(sm, auditLogService, log))
|
||||
r.Use(mi.Audit(sm, log))
|
||||
|
||||
r.Get("/menus", menuHandler.Menus)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ type AuthorizeUser struct {
|
||||
Uuid uuid.UUID `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
RoleID int32 `json:"role_id"`
|
||||
RoleName string `json:"role_name"`
|
||||
OS string `json:"os"`
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
type AuditLogRepository interface {
|
||||
Create(ctx context.Context, obj *AuditLog) error
|
||||
BatchCreate(ctx context.Context, objs []*AuditLog) error
|
||||
List(ctx context.Context, q dto.SearchDto) ([]*AuditLog, int64, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ func (s *auditLogRepository) Create(ctx context.Context, obj *system.AuditLog) e
|
||||
return s.repo.DB(ctx).Create(obj).Error
|
||||
}
|
||||
|
||||
func (s *auditLogRepository) BatchCreate(ctx context.Context, objs []*system.AuditLog) error {
|
||||
return s.repo.DB(ctx).Create(objs).Error
|
||||
}
|
||||
|
||||
func (s *auditLogRepository) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) {
|
||||
query := s.repo.DB(ctx).
|
||||
Model(&system.AuditLog{}).
|
||||
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
"time"
|
||||
|
||||
"management/internal/erpserver/model/view"
|
||||
"management/internal/pkg/redis"
|
||||
"management/internal/pkg/cache"
|
||||
)
|
||||
|
||||
func GetCacheExpire() time.Duration {
|
||||
return time.Hour*6 + time.Duration(rand.Intn(600))*time.Second // 6小时±10分钟
|
||||
}
|
||||
|
||||
func GetOrSetCache(ctx context.Context, redis redis.Cache, key string, expire time.Duration, getData func() (any, error), result any) error {
|
||||
func GetOrSetCache(ctx context.Context, redis cache.Cache, key string, expire time.Duration, getData func() (any, error), result any) error {
|
||||
if data, err := redis.GetBytes(ctx, key); err == nil {
|
||||
return json.Unmarshal(data, result)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"management/internal/erpserver/model/system"
|
||||
"management/internal/erpserver/model/view"
|
||||
"management/internal/erpserver/repository"
|
||||
"management/internal/pkg/redis"
|
||||
"management/internal/pkg/cache"
|
||||
"management/internal/pkg/session"
|
||||
|
||||
"github.com/drhin/logger"
|
||||
@@ -18,14 +18,14 @@ type Service struct {
|
||||
Log *logger.Logger
|
||||
Tx repository.Transaction
|
||||
Session session.Manager
|
||||
Redis redis.Cache
|
||||
Redis cache.Cache
|
||||
}
|
||||
|
||||
func NewService(
|
||||
log *logger.Logger,
|
||||
tx repository.Transaction,
|
||||
session session.Manager,
|
||||
redis redis.Cache,
|
||||
redis cache.Cache,
|
||||
) *Service {
|
||||
return &Service{
|
||||
Log: log,
|
||||
@@ -72,6 +72,7 @@ type LoginLogService interface {
|
||||
|
||||
type AuditLogService interface {
|
||||
Create(ctx context.Context, req *system.AuditLog) error
|
||||
BatchCreate(ctx context.Context, objs []*system.AuditLog) error
|
||||
List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ func (b *auditLogService) Create(ctx context.Context, req *system.AuditLog) erro
|
||||
return b.repo.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (b *auditLogService) BatchCreate(ctx context.Context, objs []*system.AuditLog) error {
|
||||
return b.repo.BatchCreate(ctx, objs)
|
||||
}
|
||||
|
||||
func (b *auditLogService) List(ctx context.Context, q dto.SearchDto) ([]*system.AuditLog, int64, error) {
|
||||
return b.repo.List(ctx, q)
|
||||
}
|
||||
|
||||
@@ -179,11 +179,12 @@ func (s *userService) login(ctx context.Context, req *form.Login) error {
|
||||
}
|
||||
|
||||
func (s *userService) loginSuccess(ctx context.Context, user *system.User, req *form.Login) error {
|
||||
return s.Session.PutUser(ctx, know.StoreName, &dto.AuthorizeUser{
|
||||
return s.Session.PutUser(ctx, know.StoreName, dto.AuthorizeUser{
|
||||
ID: user.ID,
|
||||
Uuid: user.Uuid,
|
||||
Email: user.Email,
|
||||
Username: user.Username,
|
||||
Avatar: user.Avatar,
|
||||
RoleID: user.Role.ID,
|
||||
RoleName: user.Role.DisplayName,
|
||||
OS: req.Os,
|
||||
|
||||
150
internal/erpserver/templ/auth/login.templ
Normal file
150
internal/erpserver/templ/auth/login.templ
Normal file
@@ -0,0 +1,150 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
templ Login(ctx context.Context) {
|
||||
{{ token := mid.GetCsrfToken(ctx) }}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>登录</title>
|
||||
<link rel="icon" type="image/x-icon" href="/statics/favicon.ico">
|
||||
<link rel="stylesheet" href="/statics/component/pear/css/pear.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/other/login.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/variables.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-page" style="background-image: url('/statics/admin/images/background.svg')">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-sm6 login-bg layui-hide-xs">
|
||||
<img class="login-bg-img" src="/statics/admin/images/banner.png" alt="" />
|
||||
</div>
|
||||
<div class="layui-col-sm6 layui-col-xs12 login-form">
|
||||
<div class="layui-form">
|
||||
<div class="form-center">
|
||||
<div class="form-center-box">
|
||||
<div class="top-log-title">
|
||||
<img class="top-log" src="/statics/favicon.ico" alt="" />
|
||||
<span>Pear Admin 4.0</span>
|
||||
</div>
|
||||
<div class="top-desc">
|
||||
以 超 乎 想 象 的 速 度 构 建 内 部 工 具
|
||||
</div>
|
||||
<div style="margin-top: 30px;">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-username"></i>
|
||||
</div>
|
||||
<input placeholder="邮 箱 " name="email" type="email" value="1185230223@qq.com"
|
||||
lay-verify="required" hover class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-password"></i>
|
||||
</div>
|
||||
<input placeholder="密 码 " name="password" type="password" value="secret"
|
||||
lay-verify="required" hover class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-log-verification">
|
||||
<div class="verification-text">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-auz"></i>
|
||||
</div>
|
||||
<input name="captcha" type="text" lay-verify="required" value=""
|
||||
placeholder="验证码" autocomplete="off" class="layui-input">
|
||||
<input type="hidden" id="captcha_id" name="captcha_id" />
|
||||
</div>
|
||||
</div>
|
||||
<img id="captcha" src="/captcha" class="verification-img"
|
||||
style="cursor: pointer;" />
|
||||
</div>
|
||||
<div class="login-btn" style="margin-top: 15px;">
|
||||
<button type="button" lay-submit lay-filter="login"
|
||||
class="layui-btn pear-btn-success login">
|
||||
登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 资 源 引 入 -->
|
||||
<script src="/statics/component/layui/layui.js"></script>
|
||||
<script src="/statics/component/pear/pear.js"></script>
|
||||
<script>
|
||||
layui.use(['jquery', 'form', 'button', 'popup'], function () {
|
||||
var $ = layui.jquery;
|
||||
var form = layui.form;
|
||||
var button = layui.button;
|
||||
var popup = layui.popup;
|
||||
|
||||
$(function () {
|
||||
$('#captcha').click();
|
||||
})
|
||||
|
||||
$('#captcha').click(function () {
|
||||
$.ajax({
|
||||
url: '/captcha',
|
||||
method: 'get',
|
||||
success: function (j) {
|
||||
if (j.success) {
|
||||
$('#captcha').attr('src', j.data.pic_path);
|
||||
$('#captcha_id').val(j.data.captcha_id);
|
||||
} else {
|
||||
console.log(j)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 登 录 提 交
|
||||
form.on('submit(login)', function (data) {
|
||||
let loading = button.load({
|
||||
elem: '.login',
|
||||
});
|
||||
|
||||
data.field.csrf_token = '{{ token }}';
|
||||
$.ajax({
|
||||
url: '/login',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
data: data.field,
|
||||
success: function (obj) {
|
||||
if (obj.success) {
|
||||
loading.stop(function () {
|
||||
popup.success("登录成功", function () {
|
||||
location.href = "/home.html"
|
||||
});
|
||||
});
|
||||
} else {
|
||||
loading.stop(function () {
|
||||
popup.failure(obj.msg);
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function (ex) {
|
||||
loading.stop(function () {
|
||||
popup.failure('网络异常,请刷新重试');
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
59
internal/erpserver/templ/auth/login_templ.go
Normal file
59
internal/erpserver/templ/auth/login_templ.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
package auth
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
func Login(ctx context.Context) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
token := mid.GetCsrfToken(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"><title>登录</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/statics/favicon.ico\"><link rel=\"stylesheet\" href=\"/statics/component/pear/css/pear.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/other/login.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/variables.css\"></head><body><div class=\"login-page\" style=\"background-image: url('/statics/admin/images/background.svg')\"><div class=\"layui-row\"><div class=\"layui-col-sm6 login-bg layui-hide-xs\"><img class=\"login-bg-img\" src=\"/statics/admin/images/banner.png\" alt=\"\"></div><div class=\"layui-col-sm6 layui-col-xs12 login-form\"><div class=\"layui-form\"><div class=\"form-center\"><div class=\"form-center-box\"><div class=\"top-log-title\"><img class=\"top-log\" src=\"/statics/favicon.ico\" alt=\"\"> <span>Pear Admin 4.0</span></div><div class=\"top-desc\">以 超 乎 想 象 的 速 度 构 建 内 部 工 具</div><div style=\"margin-top: 30px;\"><div class=\"layui-form-item\"><div class=\"layui-input-wrap\"><div class=\"layui-input-prefix\"><i class=\"layui-icon layui-icon-username\"></i></div><input placeholder=\"邮 箱 \" name=\"email\" type=\"email\" value=\"1185230223@qq.com\" lay-verify=\"required\" hover class=\"layui-input\"></div></div><div class=\"layui-form-item\"><div class=\"layui-input-wrap\"><div class=\"layui-input-prefix\"><i class=\"layui-icon layui-icon-password\"></i></div><input placeholder=\"密 码 \" name=\"password\" type=\"password\" value=\"secret\" lay-verify=\"required\" hover class=\"layui-input\"></div></div><div class=\"tab-log-verification\"><div class=\"verification-text\"><div class=\"layui-input-wrap\"><div class=\"layui-input-prefix\"><i class=\"layui-icon layui-icon-auz\"></i></div><input name=\"captcha\" type=\"text\" lay-verify=\"required\" value=\"\" placeholder=\"验证码\" autocomplete=\"off\" class=\"layui-input\"> <input type=\"hidden\" id=\"captcha_id\" name=\"captcha_id\"></div></div><img id=\"captcha\" src=\"/captcha\" class=\"verification-img\" style=\"cursor: pointer;\"></div><div class=\"login-btn\" style=\"margin-top: 15px;\"><button type=\"button\" lay-submit lay-filter=\"login\" class=\"layui-btn pear-btn-success login\">登录</button></div></div></div></div></div></div></div></div><!-- 资 源 引 入 --><script src=\"/statics/component/layui/layui.js\"></script><script src=\"/statics/component/pear/pear.js\"></script><script>\n \t layui.use(['jquery', 'form', 'button', 'popup'], function () {\n \t\t\tvar $ = layui.jquery;\n \t\t\tvar form = layui.form;\n \t\t\tvar button = layui.button;\n \t\t\tvar popup = layui.popup;\n\n \t\t\t$(function () {\n \t\t\t\t$('#captcha').click();\n \t\t\t})\n\n \t\t\t$('#captcha').click(function () {\n \t\t\t\t$.ajax({\n \t\t\t\t\turl: '/captcha',\n \t\t\t\t\tmethod: 'get',\n \t\t\t\t\tsuccess: function (j) {\n \t\t\t\t\t\tif (j.success) {\n \t\t\t\t\t\t\t$('#captcha').attr('src', j.data.pic_path);\n \t\t\t\t\t\t\t$('#captcha_id').val(j.data.captcha_id);\n \t\t\t\t\t\t} else {\n \t\t\t\t\t\t\tconsole.log(j)\n \t\t\t\t\t\t}\n \t\t\t\t\t}\n \t\t\t\t});\n \t\t\t});\n\n \t\t\t// 登 录 提 交\n \t\t\tform.on('submit(login)', function (data) {\n \t\t\t\tlet loading = button.load({\n \t\t\t\t\telem: '.login',\n \t\t\t\t});\n\n \t\t\t\tdata.field.csrf_token = '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/auth/login.templ`, Line: 119, Col: 41}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "';\n \t\t\t\t$.ajax({\n \t\t\t\t\turl: '/login',\n \t\t\t\t\ttype: 'post',\n \t\t\t\t\tdataType: 'json',\n \t\t\t\t\tdata: data.field,\n \t\t\t\t\tsuccess: function (obj) {\n \t\t\t\t\t\tif (obj.success) {\n \t\t\t\t\t\t\tloading.stop(function () {\n \t\t\t\t\t\t\t\tpopup.success(\"登录成功\", function () {\n \t\t\t\t\t\t\t\t\tlocation.href = \"/home.html\"\n \t\t\t\t\t\t\t\t});\n \t\t\t\t\t\t\t});\n \t\t\t\t\t\t} else {\n \t\t\t\t\t\t\tloading.stop(function () {\n \t\t\t\t\t\t\t\tpopup.failure(obj.msg);\n \t\t\t\t\t\t\t});\n \t\t\t\t\t\t}\n \t\t\t\t\t},\n \t\t\t\t\terror: function (ex) {\n \t\t\t\t\t\tloading.stop(function () {\n \t\t\t\t\t\t\tpopup.failure('网络异常,请刷新重试');\n \t\t\t\t\t\t});\n \t\t\t\t\t}\n \t\t\t\t});\n \t\t\t\treturn false;\n \t\t\t});\n \t\t})\n </script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
79
internal/erpserver/templ/base/base.templ
Normal file
79
internal/erpserver/templ/base/base.templ
Normal file
@@ -0,0 +1,79 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
tpl "github.com/a-h/templ"
|
||||
)
|
||||
|
||||
templ Base(ctx context.Context, style, script tpl.Component) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>component</title>
|
||||
<link rel="icon" type="image/x-icon" href="/statics/favicon.ico">
|
||||
<link rel="stylesheet" href="/statics/component/pear/css/pear.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/admin.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/admin.dark.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/variables.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/reset.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/style.css" />
|
||||
@style
|
||||
</head>
|
||||
|
||||
<body class="pear-container">
|
||||
{ children... }
|
||||
<script src="/statics/component/layui/layui.js"></script>
|
||||
<script src="/statics/component/pear/pear.js"></script>
|
||||
<script src="/statics/component/pear/help.js"></script>
|
||||
<script src="/statics/js/function/public.js"></script>
|
||||
<script src="/statics/js/upload.js"></script>
|
||||
<script>
|
||||
layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {
|
||||
var $ = layui.jquery;
|
||||
var admin = layui.admin;
|
||||
var popup = layui.popup;
|
||||
|
||||
// 读取 localStorage 主题颜色值,初始化 iframe 主题
|
||||
darkTheme();
|
||||
themeColor();
|
||||
|
||||
// iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果
|
||||
window.addEventListener('storage', function (event) {
|
||||
if (event.key === 'dark') {
|
||||
darkTheme();
|
||||
}
|
||||
if (event.key === 'theme-color') {
|
||||
themeColor();
|
||||
}
|
||||
});
|
||||
|
||||
// 夜间模式切换
|
||||
function darkTheme() {
|
||||
var $pearAdmin = $(".pear-admin"); // 主体
|
||||
var $layuiCard = $('.layui-card'); // 编辑页
|
||||
|
||||
if (localStorage.getItem('dark') === 'true') {
|
||||
$pearAdmin.addClass("pear-admin-dark");
|
||||
$layuiCard.addClass('layui-card-dark');
|
||||
} else {
|
||||
$pearAdmin.removeClass("pear-admin-dark");
|
||||
$layuiCard.removeClass('layui-card-dark');
|
||||
}
|
||||
}
|
||||
|
||||
// 主题颜色切换
|
||||
function themeColor() {
|
||||
const variableKey = "--global-primary-color";
|
||||
const variableVal = localStorage.getItem("theme-color-color");
|
||||
document.documentElement.style.setProperty(variableKey, variableVal);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@script
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
70
internal/erpserver/templ/base/base_templ.go
Normal file
70
internal/erpserver/templ/base/base_templ.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
package base
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
tpl "github.com/a-h/templ"
|
||||
)
|
||||
|
||||
func Base(ctx context.Context, style, script tpl.Component) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"><title>component</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/statics/favicon.ico\"><link rel=\"stylesheet\" href=\"/statics/component/pear/css/pear.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.dark.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/variables.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/reset.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/style.css\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = style.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</head><body class=\"pear-container\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script src=\"/statics/component/layui/layui.js\"></script><script src=\"/statics/component/pear/pear.js\"></script><script src=\"/statics/component/pear/help.js\"></script><script src=\"/statics/js/function/public.js\"></script><script src=\"/statics/js/upload.js\"></script><script>\n layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {\n var $ = layui.jquery;\n var admin = layui.admin;\n var popup = layui.popup;\n\n // 读取 localStorage 主题颜色值,初始化 iframe 主题\n darkTheme();\n themeColor();\n\n // iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果\n window.addEventListener('storage', function (event) {\n if (event.key === 'dark') {\n darkTheme();\n }\n if (event.key === 'theme-color') {\n themeColor();\n }\n });\n\n // 夜间模式切换\n function darkTheme() {\n var $pearAdmin = $(\".pear-admin\"); // 主体\n var $layuiCard = $('.layui-card'); // 编辑页\n\n if (localStorage.getItem('dark') === 'true') {\n $pearAdmin.addClass(\"pear-admin-dark\");\n $layuiCard.addClass('layui-card-dark');\n } else {\n $pearAdmin.removeClass(\"pear-admin-dark\");\n $layuiCard.removeClass('layui-card-dark');\n }\n }\n\n // 主题颜色切换\n function themeColor() {\n const variableKey = \"--global-primary-color\";\n const variableVal = localStorage.getItem(\"theme-color-color\");\n document.documentElement.style.setProperty(variableKey, variableVal);\n }\n })\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = script.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
140
internal/erpserver/templ/component/btn.go
Normal file
140
internal/erpserver/templ/component/btn.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"management/internal/erpserver/model/dto"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
func TemplBtn(buttons []dto.OwnerMenuDto, searchBtn bool, actionNames ...string) templ.Component {
|
||||
var res string
|
||||
if len(actionNames) == 0 {
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
if len(buttons) == 0 {
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
res = `<script type="text/html" id="toolbar">`
|
||||
res += GenBtn(buttons, actionNames...)
|
||||
|
||||
if searchBtn {
|
||||
res += `
|
||||
<button type="button" lay-event="search" lay-on="search" class="layui-btn layui-btn-primary layui-btn-sm">
|
||||
<i class="layui-icon layui-icon-search"></i>
|
||||
</button>`
|
||||
}
|
||||
|
||||
res += `</script>`
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
func TemplLink(buttons []dto.OwnerMenuDto, actionNames ...string) templ.Component {
|
||||
var res string
|
||||
if len(actionNames) == 0 {
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
if len(buttons) == 0 {
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
res = `<script type="text/html" id="actionBox">`
|
||||
res += GenLink(buttons, actionNames...)
|
||||
|
||||
res += `</script>`
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
func GenBtn(buttons []dto.OwnerMenuDto, actionNames ...string) string {
|
||||
var res string
|
||||
if len(buttons) == 0 {
|
||||
return res
|
||||
}
|
||||
|
||||
if len(actionNames) == 0 {
|
||||
return res
|
||||
}
|
||||
|
||||
for _, action := range actionNames {
|
||||
for _, btn := range buttons {
|
||||
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 res
|
||||
}
|
||||
|
||||
func GenLink(buttons []dto.OwnerMenuDto, actionNames ...string) string {
|
||||
if len(buttons) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(actionNames) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var res string
|
||||
for _, action := range actionNames {
|
||||
for _, btn := range buttons {
|
||||
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 res
|
||||
}
|
||||
|
||||
func SubmitBtn(buttons []dto.OwnerMenuDto, actionNames ...string) templ.Component {
|
||||
var res string
|
||||
if len(buttons) == 0 {
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
if len(actionNames) == 0 {
|
||||
return templ.Raw(res)
|
||||
}
|
||||
|
||||
for _, action := range actionNames {
|
||||
for _, btn := range buttons {
|
||||
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 templ.Raw(res)
|
||||
}
|
||||
|
||||
func firstLower(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
return strings.ToLower(s[:1]) + s[1:]
|
||||
}
|
||||
38
internal/erpserver/templ/home/dashboard.templ
Normal file
38
internal/erpserver/templ/home/dashboard.templ
Normal file
@@ -0,0 +1,38 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
templ Dashboard(ctx context.Context, loginCount int, thisLoginTime, lastLoginTime time.Time) {
|
||||
{{ user := mid.GetUser(ctx) }}
|
||||
<div class="layui-row h-all">
|
||||
<div class="layui-col-md12" style="padding: 10px;">
|
||||
<div class="layui-panel userinfo">
|
||||
<div class="logo">
|
||||
@avatar(user.Avatar)
|
||||
</div>
|
||||
<div class="info">
|
||||
@info(user.Username, user.RoleName, loginCount, thisLoginTime, lastLoginTime)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ avatar(avatar string) {
|
||||
<img src={ avatar } alt="logo">
|
||||
}
|
||||
|
||||
templ info(username, roleName string, loginCount int, thisLoginTime, lastLoginTime time.Time) {
|
||||
<p class="name">欢迎您,{ username } ({ roleName })</p>
|
||||
if loginCount == 1 {
|
||||
<p>这是您第 1 次登录,本次登录日期:{ thisLoginTime.Format(time.DateTime) },如果不是您本人登录,请及时修改密码 。</p>
|
||||
} else {
|
||||
<p>这是您第 { loginCount } 次登录,本次登录日期:{ thisLoginTime.Format(time.DateTime) },
|
||||
上次登录日期:{ lastLoginTime.Format(time.DateTime) },如果不是您本人登录,请及时修改密码 。</p>
|
||||
}
|
||||
}
|
||||
224
internal/erpserver/templ/home/dashboard_templ.go
Normal file
224
internal/erpserver/templ/home/dashboard_templ.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
package home
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
func Dashboard(ctx context.Context, loginCount int, thisLoginTime, lastLoginTime time.Time) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
user := mid.GetUser(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"layui-row h-all\"><div class=\"layui-col-md12\" style=\"padding: 10px;\"><div class=\"layui-panel userinfo\"><div class=\"logo\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = avatar(user.Avatar).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div class=\"info\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = info(user.Username, user.RoleName, loginCount, thisLoginTime, lastLoginTime).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func avatar(avatar string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(avatar)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 27, Col: 21}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" alt=\"logo\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func info(username, roleName string, loginCount int, thisLoginTime, lastLoginTime time.Time) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<p class=\"name\">欢迎您,")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(username)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 31, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " (")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(roleName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 31, Col: 56}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, ")</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if loginCount == 1 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p>这是您第 1 次登录,本次登录日期:")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(thisLoginTime.Format(time.DateTime))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 33, Col: 96}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, ",如果不是您本人登录,请及时修改密码 。</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p>这是您第 ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(loginCount)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 35, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " 次登录,本次登录日期:")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(thisLoginTime.Format(time.DateTime))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 35, Col: 109}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, ", 上次登录日期:")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(lastLoginTime.Format(time.DateTime))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/dashboard.templ`, Line: 36, Col: 70}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ",如果不是您本人登录,请及时修改密码 。</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
174
internal/erpserver/templ/home/home.templ
Normal file
174
internal/erpserver/templ/home/home.templ
Normal file
@@ -0,0 +1,174 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
templ Home(ctx context.Context) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>管理后台</title>
|
||||
<link rel="icon" type="image/x-icon" href="/statics/favicon.ico">
|
||||
<link rel="stylesheet" href="/statics/component/pear/css/pear.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/admin.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/admin.dark.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/variables.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/reset.css" />
|
||||
<link rel="stylesheet" href="/statics/admin/css/style.css" />
|
||||
</head>
|
||||
|
||||
<body class="layui-layout-body pear-admin">
|
||||
<!-- 布 局 框 架 -->
|
||||
<div class="layui-layout layui-layout-admin">
|
||||
<!-- 顶 部 样 式 -->
|
||||
<div class="layui-header">
|
||||
<!-- 菜 单 顶 部 -->
|
||||
<div class="layui-logo">
|
||||
<!-- 图 标 -->
|
||||
<img class="logo" alt="" src="">
|
||||
<!-- 标 题 -->
|
||||
<span class="title"></span>
|
||||
</div>
|
||||
<!-- 顶 部 左 侧 功 能 -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<li class="collapse layui-nav-item">
|
||||
<a href="#" class="layui-icon layui-icon-shrink-right"> </a>
|
||||
</li>
|
||||
<li class="refresh layui-nav-item">
|
||||
<a href="#" class="layui-icon layui-icon-refresh-1"> </a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- 多 系 统 菜 单 -->
|
||||
<div id="control" class="layui-layout-control"></div>
|
||||
<!-- 顶 部 右 侧 菜 单 -->
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<!-- <li class="layui-nav-item layui-hide-xs"><a href="#"
|
||||
class="menuSearch layui-icon layui-icon-search"></a></li>
|
||||
<li class="layui-nav-item layui-hide-xs message"></li> -->
|
||||
<li class="layui-nav-item layui-hide-xs"><a href="#"
|
||||
class="fullScreen layui-icon layui-icon-screen-full"></a></li>
|
||||
<li class="layui-nav-item user">
|
||||
<!-- 头 像 -->
|
||||
<a class="layui-icon layui-icon-username" href="javascript:;"></a>
|
||||
<!-- 功 能 菜 单 -->
|
||||
<dl class="layui-nav-child">
|
||||
{{ user := mid.GetUser(ctx) }}
|
||||
if user.ID > 0 {
|
||||
<dd>
|
||||
<a user-menu-url="/system/user/profile" user-menu-id="profile"
|
||||
user-menu-title="基本资料">基本资料</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="javascript:void(0);" title={ user.Username } class="logout">注销登录</a>
|
||||
</dd>
|
||||
}
|
||||
</dl>
|
||||
</li>
|
||||
<!-- 主 题 配 置 -->
|
||||
<li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- 侧 边 区 域 -->
|
||||
<div class="layui-side layui-bg-black">
|
||||
<!-- 菜 单 顶 部 -->
|
||||
<div class="layui-logo">
|
||||
<!-- 图 标 -->
|
||||
<img class="logo">
|
||||
<!-- 标 题 -->
|
||||
<span class="title"></span>
|
||||
</div>
|
||||
<!-- 菜 单 内 容 -->
|
||||
<div class="layui-side-scroll">
|
||||
<div id="side"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 视 图 页 面 -->
|
||||
<div class="layui-body" style="padding: 0">
|
||||
<!-- 内 容 页 面 -->
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
<!-- 页脚 -->
|
||||
<div class="layui-footer layui-text"></div>
|
||||
<!-- 遮 盖 层 -->
|
||||
<div class="pear-cover"></div>
|
||||
<!-- 加 载 动 画 -->
|
||||
<div class="loader-wrapper">
|
||||
<!-- 动 画 对 象 -->
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移 动 端 便 捷 操 作 -->
|
||||
<div class="pear-collapsed-pe collapse">
|
||||
<a href="#" class="layui-icon layui-icon-shrink-right"></a>
|
||||
</div>
|
||||
|
||||
<script src="/statics/component/layui/layui.js"></script>
|
||||
<script src="/statics/component/pear/pear.js"></script>
|
||||
<script>
|
||||
layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {
|
||||
const $ = layui.jquery;
|
||||
const admin = layui.admin;
|
||||
const popup = layui.popup;
|
||||
|
||||
// yml | json | api
|
||||
admin.setConfigurationPath("/pear.json");
|
||||
|
||||
admin.render();
|
||||
|
||||
// 登出逻辑
|
||||
admin.logout(function () {
|
||||
window.location.href = '/logout';
|
||||
// 清空 tabs 缓存
|
||||
return new Promise((resolve) => {
|
||||
resolve(true)
|
||||
});
|
||||
})
|
||||
|
||||
// 读取 localStorage 主题颜色值,初始化 iframe 主题
|
||||
//darkTheme();
|
||||
//themeColor();
|
||||
|
||||
// iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果
|
||||
// window.addEventListener('storage', function (event) {
|
||||
// if (event.key === 'dark') {
|
||||
// darkTheme();
|
||||
// }
|
||||
// if (event.key === 'theme-color') {
|
||||
// themeColor();
|
||||
// }
|
||||
// });
|
||||
|
||||
// 夜间模式切换
|
||||
// function darkTheme() {
|
||||
// var $pearAdmin = $(".pear-admin"); // 主体
|
||||
// var $layuiCard = $('.layui-card'); // 编辑页
|
||||
|
||||
// if (localStorage.getItem('dark') === 'true') {
|
||||
// $pearAdmin.addClass("pear-admin-dark");
|
||||
// $layuiCard.addClass('layui-card-dark');
|
||||
// } else {
|
||||
// $pearAdmin.removeClass("pear-admin-dark");
|
||||
// $layuiCard.removeClass('layui-card-dark');
|
||||
// }
|
||||
// }
|
||||
|
||||
// 主题颜色切换
|
||||
// function themeColor() {
|
||||
// const variableKey = "--global-primary-color";
|
||||
// const variableVal = localStorage.getItem("theme-color-color");
|
||||
// document.documentElement.style.setProperty(variableKey, variableVal);
|
||||
// }
|
||||
|
||||
// 消息点击回调
|
||||
// admin.message(function (id, title, context, form) { });
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
70
internal/erpserver/templ/home/home_templ.go
Normal file
70
internal/erpserver/templ/home/home_templ.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
package home
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
func Home(ctx context.Context) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\"><title>管理后台</title><link rel=\"icon\" type=\"image/x-icon\" href=\"/statics/favicon.ico\"><link rel=\"stylesheet\" href=\"/statics/component/pear/css/pear.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/admin.dark.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/variables.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/reset.css\"><link rel=\"stylesheet\" href=\"/statics/admin/css/style.css\"></head><body class=\"layui-layout-body pear-admin\"><!-- 布 局 框 架 --><div class=\"layui-layout layui-layout-admin\"><!-- 顶 部 样 式 --><div class=\"layui-header\"><!-- 菜 单 顶 部 --><div class=\"layui-logo\"><!-- 图 标 --><img class=\"logo\" alt=\"\" src=\"\"><!-- 标 题 --><span class=\"title\"></span></div><!-- 顶 部 左 侧 功 能 --><ul class=\"layui-nav layui-layout-left\"><li class=\"collapse layui-nav-item\"><a href=\"#\" class=\"layui-icon layui-icon-shrink-right\"></a></li><li class=\"refresh layui-nav-item\"><a href=\"#\" class=\"layui-icon layui-icon-refresh-1\"></a></li></ul><!-- 多 系 统 菜 单 --><div id=\"control\" class=\"layui-layout-control\"></div><!-- 顶 部 右 侧 菜 单 --><ul class=\"layui-nav layui-layout-right\"><!-- <li class=\"layui-nav-item layui-hide-xs\"><a href=\"#\"\n class=\"menuSearch layui-icon layui-icon-search\"></a></li>\n <li class=\"layui-nav-item layui-hide-xs message\"></li> --><li class=\"layui-nav-item layui-hide-xs\"><a href=\"#\" class=\"fullScreen layui-icon layui-icon-screen-full\"></a></li><li class=\"layui-nav-item user\"><!-- 头 像 --><a class=\"layui-icon layui-icon-username\" href=\"javascript:;\"></a><!-- 功 能 菜 单 --><dl class=\"layui-nav-child\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
user := mid.GetUser(ctx)
|
||||
if user.ID > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<dd><a user-menu-url=\"/system/user/profile\" user-menu-id=\"profile\" user-menu-title=\"基本资料\">基本资料</a></dd><dd><a href=\"javascript:void(0);\" title=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/home/home.templ`, Line: 68, Col: 91}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"logout\">注销登录</a></dd>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</dl></li><!-- 主 题 配 置 --><li class=\"layui-nav-item setting\"><a href=\"#\" class=\"layui-icon layui-icon-more-vertical\"></a></li></ul></div><!-- 侧 边 区 域 --><div class=\"layui-side layui-bg-black\"><!-- 菜 单 顶 部 --><div class=\"layui-logo\"><!-- 图 标 --><img class=\"logo\"><!-- 标 题 --><span class=\"title\"></span></div><!-- 菜 单 内 容 --><div class=\"layui-side-scroll\"><div id=\"side\"></div></div></div><!-- 视 图 页 面 --><div class=\"layui-body\" style=\"padding: 0\"><!-- 内 容 页 面 --><div id=\"content\"></div></div><!-- 页脚 --><div class=\"layui-footer layui-text\"></div><!-- 遮 盖 层 --><div class=\"pear-cover\"></div><!-- 加 载 动 画 --><div class=\"loader-wrapper\"><!-- 动 画 对 象 --><div class=\"loader\"></div></div></div><!-- 移 动 端 便 捷 操 作 --><div class=\"pear-collapsed-pe collapse\"><a href=\"#\" class=\"layui-icon layui-icon-shrink-right\"></a></div><script src=\"/statics/component/layui/layui.js\"></script><script src=\"/statics/component/pear/pear.js\"></script><script>\n layui.use(['admin', 'jquery', 'popup', 'drawer'], function () {\n const $ = layui.jquery;\n const admin = layui.admin;\n const popup = layui.popup;\n\n // yml | json | api\n admin.setConfigurationPath(\"/pear.json\");\n\n admin.render();\n\n // 登出逻辑\n admin.logout(function () {\n window.location.href = '/logout';\n // 清空 tabs 缓存\n return new Promise((resolve) => {\n resolve(true)\n });\n })\n\n // 读取 localStorage 主题颜色值,初始化 iframe 主题\n //darkTheme();\n //themeColor();\n\n // iframe 页监听 localStorage [pear 框架主题颜色] 变化。发生变化,同步更新 iframe 显示效果\n // window.addEventListener('storage', function (event) {\n // if (event.key === 'dark') {\n // darkTheme();\n // }\n // if (event.key === 'theme-color') {\n // themeColor();\n // }\n // });\n\n // 夜间模式切换\n // function darkTheme() {\n // var $pearAdmin = $(\".pear-admin\"); // 主体\n // var $layuiCard = $('.layui-card'); // 编辑页\n\n // if (localStorage.getItem('dark') === 'true') {\n // $pearAdmin.addClass(\"pear-admin-dark\");\n // $layuiCard.addClass('layui-card-dark');\n // } else {\n // $pearAdmin.removeClass(\"pear-admin-dark\");\n // $layuiCard.removeClass('layui-card-dark');\n // }\n // }\n\n // 主题颜色切换\n // function themeColor() {\n // const variableKey = \"--global-primary-color\";\n // const variableVal = localStorage.getItem(\"theme-color-color\");\n // document.documentElement.style.setProperty(variableKey, variableVal);\n // }\n\n // 消息点击回调\n // admin.message(function (id, title, context, form) { });\n })\n </script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
17
internal/erpserver/templ/minifyjs/compress.go
Normal file
17
internal/erpserver/templ/minifyjs/compress.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package minifyjs
|
||||
|
||||
import (
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/minify/js"
|
||||
)
|
||||
|
||||
func Compile(raw string) string {
|
||||
m := minify.New()
|
||||
m.AddFunc("text/javascript", js.Minify)
|
||||
|
||||
minified, err := m.String("text/javascript", raw)
|
||||
if err != nil {
|
||||
return raw // 失败时返回原始代码
|
||||
}
|
||||
return minified
|
||||
}
|
||||
212
internal/erpserver/templ/system/role/edit.templ
Normal file
212
internal/erpserver/templ/system/role/edit.templ
Normal file
@@ -0,0 +1,212 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"management/internal/erpserver/templ/base"
|
||||
"management/internal/pkg/mid"
|
||||
"management/internal/erpserver/templ/component"
|
||||
"management/internal/erpserver/model/system"
|
||||
)
|
||||
|
||||
templ Edit(ctx context.Context, item *system.Role) {
|
||||
@base.Base(ctx, editCss(), editJs(ctx, item)) {
|
||||
{{ meuns := mid.GetCurMenus(ctx) }}
|
||||
{{ ht := mid.GetHtmlCsrfToken(ctx) }}
|
||||
<div class="layui-body layui-bg-gray">
|
||||
<div class="layui-card">
|
||||
<form class="layui-form">
|
||||
@ht
|
||||
<input type="hidden" id="id" name="id" value={ item.ID } />
|
||||
|
||||
<div class="layui-tab layui-tab-card">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">基础信息</li>
|
||||
<li>其它</li>
|
||||
</ul>
|
||||
|
||||
<div class="layui-tab-content">
|
||||
<!-- 基础信息 -->
|
||||
<div class="layui-tab-item layui-show">
|
||||
if item.ID > 0 {
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-form-label">ID</div>
|
||||
<div class="layui-form-label" style="width:400px;text-align:left;">
|
||||
{ item.ID }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-form-label">上级</div>
|
||||
<div class="layui-input-inline" style="width:300px;">
|
||||
<ul id="roleTree" class="dtree organizationTree"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-form-label">名称</div>
|
||||
<div class="layui-input-inline" style="width:300px;">
|
||||
<input type="text" id="name" name="name" value={ item.Name } lay-verify="required"
|
||||
autocomplete="off" placeholder="请输入名称" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-form-label">显示名称</div>
|
||||
<div class="layui-input-inline" style="width:300px;">
|
||||
<input type="text" id="display_name" name="display_name" value={ item.DisplayName }
|
||||
lay-verify="required" autocomplete="off" placeholder="请输入显示名称"
|
||||
class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">状态标识</label>
|
||||
<div class="layui-input-inline">
|
||||
<select id="status" name="status">
|
||||
if item.Status == 0 {
|
||||
<option value="0" selected>正常</option>
|
||||
<option value="-1">删除</option>
|
||||
} else if item.Status == -1 {
|
||||
<option value="0">正常</option>
|
||||
<option value="-1" selected>删除</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">排序</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="number" id="sort" name="sort" value={ item.Sort } lay-affix="number"
|
||||
min="1" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其它 -->
|
||||
<div class="layui-tab-item">
|
||||
if item.ID > 0 {
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-form-label">创建时间</div>
|
||||
<div class="layui-form-label" style="width:400px;text-align:left;">
|
||||
{ item.CreatedAt.Format(time.DateTime) }
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-form-label">更新时间</div>
|
||||
<div class="layui-form-label" style="width:400px;text-align:left;">
|
||||
{ item.UpdatedAt.Format(time.DateTime) }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item layui-fixbar btn-fixbar-box">
|
||||
<div class="layui-input-block">
|
||||
@component.SubmitBtn(meuns, "save")
|
||||
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" lay-on="close">
|
||||
<i class="layui-icon layui-icon-close"></i>关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ editCss() {
|
||||
<style>
|
||||
.layui-body {
|
||||
padding: 15px;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
templ editJs(ctx context.Context, item *system.Role) {
|
||||
{{ token := mid.GetCsrfToken(ctx) }}
|
||||
<script>
|
||||
layui.use(['jquery', 'form', 'xmSelect', 'util'], function () {
|
||||
let $ = layui.jquery;
|
||||
let form = layui.form;
|
||||
let xmSelect = layui.xmSelect;
|
||||
let util = layui.util;
|
||||
|
||||
getRoleTree();
|
||||
|
||||
// 表单提交
|
||||
form.on('submit(save)', function (data) {
|
||||
$.ajax({
|
||||
url: '/system/role/save',
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
data: data.field,
|
||||
success: function (result) {
|
||||
if (result.success) {
|
||||
layer.msg(result.msg, { icon: 1, time: 1000 }, function () {
|
||||
parent.layer.close(parent.layer.getFrameIndex(window.name)); // 关闭当前页
|
||||
parent.layui.table.reload("tablelist", {
|
||||
page: { curr: 1 },
|
||||
});
|
||||
});
|
||||
} else {
|
||||
parent.layer.msg(result.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// 事件绑定
|
||||
util.on("lay-on", {
|
||||
"close": function () {
|
||||
window.parent.layer.close(parent.layer.getFrameIndex(window.name));
|
||||
}
|
||||
});
|
||||
|
||||
function getRoleTree() {
|
||||
$.ajax({
|
||||
url: "/system/role/data?type=xm_select_tree",
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
headers: { 'X-CSRF-Token': '{{ token }}' },
|
||||
success: function (res) {
|
||||
xmSelect.render({
|
||||
el: '#roleTree',
|
||||
// 工具栏
|
||||
toolbar: {
|
||||
show: true
|
||||
},
|
||||
radio: true,
|
||||
clickClose: true,
|
||||
tips: '请选择父级',
|
||||
filterable: true,
|
||||
data: res,
|
||||
name: 'parent_id',
|
||||
initValue: ['{{ item.ParentID }}'],
|
||||
tree: {
|
||||
show: true,
|
||||
//非严格模式
|
||||
strict: false,
|
||||
//默认展开节点的数组, 为 true 时, 展开所有节点
|
||||
expandedKeys: [1],
|
||||
},
|
||||
on: function (data) { },
|
||||
});
|
||||
},
|
||||
error: function (err) {
|
||||
// 处理请求错误
|
||||
console.log('请求出错:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
295
internal/erpserver/templ/system/role/edit_templ.go
Normal file
295
internal/erpserver/templ/system/role/edit_templ.go
Normal file
@@ -0,0 +1,295 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
package role
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"management/internal/erpserver/model/system"
|
||||
"management/internal/erpserver/templ/base"
|
||||
"management/internal/erpserver/templ/component"
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
func Edit(ctx context.Context, item *system.Role) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
meuns := mid.GetCurMenus(ctx)
|
||||
ht := mid.GetHtmlCsrfToken(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"layui-body layui-bg-gray\"><div class=\"layui-card\"><form class=\"layui-form\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = ht.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<input type=\"hidden\" id=\"id\" name=\"id\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(item.ID)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 21, Col: 74}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><div class=\"layui-tab layui-tab-card\"><ul class=\"layui-tab-title\"><li class=\"layui-this\">基础信息</li><li>其它</li></ul><div class=\"layui-tab-content\"><!-- 基础信息 --><div class=\"layui-tab-item layui-show\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if item.ID > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"layui-form-item\"><div class=\"layui-form-label\">ID</div><div class=\"layui-form-label\" style=\"width:400px;text-align:left;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.ID)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 36, Col: 53}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"layui-form-item\"><div class=\"layui-form-label\">上级</div><div class=\"layui-input-inline\" style=\"width:300px;\"><ul id=\"roleTree\" class=\"dtree organizationTree\"></ul></div></div><div class=\"layui-form-item\"><div class=\"layui-form-label\">名称</div><div class=\"layui-input-inline\" style=\"width:300px;\"><input type=\"text\" id=\"name\" name=\"name\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 51, Col: 98}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入名称\" class=\"layui-input\"></div></div><div class=\"layui-form-item\"><div class=\"layui-form-label\">显示名称</div><div class=\"layui-input-inline\" style=\"width:300px;\"><input type=\"text\" id=\"display_name\" name=\"display_name\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.DisplayName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 59, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" lay-verify=\"required\" autocomplete=\"off\" placeholder=\"请输入显示名称\" class=\"layui-input\"></div></div><div class=\"layui-form-item\"><label class=\"layui-form-label\">状态标识</label><div class=\"layui-input-inline\"><select id=\"status\" name=\"status\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if item.Status == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<option value=\"0\" selected>正常</option> <option value=\"-1\">删除</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else if item.Status == -1 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<option value=\"0\">正常</option> <option value=\"-1\" selected>删除</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</select></div></div><div class=\"layui-form-item\"><label class=\"layui-form-label\">排序</label><div class=\"layui-input-inline\"><input type=\"number\" id=\"sort\" name=\"sort\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.Sort)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 83, Col: 100}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" lay-affix=\"number\" min=\"1\" class=\"layui-input\"></div></div></div><!-- 其它 --><div class=\"layui-tab-item\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if item.ID > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"layui-form-item\"><div class=\"layui-form-label\">创建时间</div><div class=\"layui-form-label\" style=\"width:400px;text-align:left;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.CreatedAt.Format(time.DateTime))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 95, Col: 78}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div><div class=\"layui-form-item\"><div class=\"layui-form-label\">更新时间</div><div class=\"layui-form-label\" style=\"width:400px;text-align:left;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.UpdatedAt.Format(time.DateTime))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 101, Col: 78}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div></div><div class=\"layui-form-item layui-fixbar btn-fixbar-box\"><div class=\"layui-input-block\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = component.SubmitBtn(meuns, "save").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<button type=\"button\" class=\"layui-btn layui-btn-primary layui-btn-sm\" lay-on=\"close\"><i class=\"layui-icon layui-icon-close\"></i>关闭</button></div></div></form></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = base.Base(ctx, editCss(), editJs(ctx, item)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func editCss() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var10 == nil {
|
||||
templ_7745c5c3_Var10 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<style>\n .layui-body {\n padding: 15px;\n left: 0;\n }\n </style>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func editJs(ctx context.Context, item *system.Role) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var11 == nil {
|
||||
templ_7745c5c3_Var11 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
token := mid.GetCsrfToken(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<script>\n layui.use(['jquery', 'form', 'xmSelect', 'util'], function () {\n let $ = layui.jquery;\n let form = layui.form;\n let xmSelect = layui.xmSelect;\n let util = layui.util;\n\n getRoleTree();\n\n // 表单提交\n form.on('submit(save)', function (data) {\n $.ajax({\n url: '/system/role/save',\n type: 'post',\n dataType: 'json',\n data: data.field,\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 1000 }, function () {\n parent.layer.close(parent.layer.getFrameIndex(window.name)); // 关闭当前页\n parent.layui.table.reload(\"tablelist\", {\n page: { curr: 1 },\n });\n });\n } else {\n parent.layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n\n return false;\n });\n\n // 事件绑定\n util.on(\"lay-on\", {\n \"close\": function () {\n window.parent.layer.close(parent.layer.getFrameIndex(window.name));\n }\n });\n\n function getRoleTree() {\n $.ajax({\n url: \"/system/role/data?type=xm_select_tree\",\n type: 'post',\n dataType: 'json',\n headers: { 'X-CSRF-Token': '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 179, Col: 56}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "' },\n success: function (res) {\n xmSelect.render({\n el: '#roleTree',\n // 工具栏\n toolbar: {\n show: true\n },\n radio: true,\n clickClose: true,\n tips: '请选择父级',\n filterable: true,\n data: res,\n name: 'parent_id',\n initValue: ['")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(item.ParentID)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/edit.templ`, Line: 193, Col: 57}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "'],\n tree: {\n show: true,\n //非严格模式\n strict: false,\n //默认展开节点的数组, 为 true 时, 展开所有节点\n expandedKeys: [1],\n },\n on: function (data) { },\n });\n },\n error: function (err) {\n // 处理请求错误\n console.log('请求出错:', err);\n }\n });\n }\n });\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
314
internal/erpserver/templ/system/role/list.templ
Normal file
314
internal/erpserver/templ/system/role/list.templ
Normal file
@@ -0,0 +1,314 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/erpserver/templ/base"
|
||||
"management/internal/pkg/mid"
|
||||
"management/internal/erpserver/templ/component"
|
||||
)
|
||||
|
||||
templ List(ctx context.Context) {
|
||||
@base.Base(ctx, listCss(), listJs(ctx)) {
|
||||
{{ meuns := mid.GetCurMenus(ctx) }}
|
||||
@component.TemplBtn(meuns, true, "add", "refresh_cache", "rebuild_parent_path")
|
||||
@component.TemplLink(meuns, "edit", "set_menu", "refresh_role_menus")
|
||||
|
||||
<div class="search-layer" id="search-layer" style="display: none;">
|
||||
<input type="hidden" id="parentId" name="parentId" value="0" />
|
||||
<div class="layui-form layui-row">
|
||||
<div class="layui-col-xs12 layui-col-sm12 layui-col-md4">
|
||||
<div class="layui-form-column">
|
||||
<label for="status" class="tips">状态</label>
|
||||
<select id="status">
|
||||
<option value="0">正常</option>
|
||||
<option value="-1">删除</option>
|
||||
<option value="9999">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs12 layui-col-sm12 layui-col-md4">
|
||||
<div class="layui-form-column">
|
||||
<label for="id" class="tips">编号</label>
|
||||
<input type="text" id="id" placeholder="请输入编号" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs12 layui-col-sm12 layui-col-md4">
|
||||
<div class="layui-form-column">
|
||||
<label for="name" class="tips">名称</label>
|
||||
<input type="text" id="name" placeholder="请输入名称" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-row h-all">
|
||||
<div class="layui-col-md2 h-all">
|
||||
<div class="own-left-pannel">
|
||||
<div id="roleTree" class="own-tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-md10">
|
||||
<div class="own-pannel">
|
||||
<table id="tablelist" lay-filter="tablelist"></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ listCss() {
|
||||
}
|
||||
|
||||
templ listJs(ctx context.Context) {
|
||||
{{ token := mid.GetCsrfToken(ctx) }}
|
||||
<script>
|
||||
layui.use(['jquery', 'table', 'form', 'tree', 'util'], function () {
|
||||
let $ = layui.jquery;
|
||||
let table = layui.table;
|
||||
let form = layui.form;
|
||||
let tree = layui.tree;
|
||||
let util = layui.util;
|
||||
|
||||
getRoleTree();
|
||||
|
||||
// 加载列表
|
||||
table.render({
|
||||
elem: '#tablelist',
|
||||
url: "/system/role/list",
|
||||
method: "POST",
|
||||
headers: { 'X-CSRF-Token': '{{ token }}' },
|
||||
where: getQueryParams(),
|
||||
height: function () {
|
||||
return $(window).height() - 22;
|
||||
},
|
||||
page: true,
|
||||
limit: 15,
|
||||
limits: [15, 30, 45, 60, 75, 90],
|
||||
cols: [[
|
||||
{ field: 'display_name', title: '显示名称', align: 'left', width: 160, fixed: 'left' },
|
||||
{ field: 'name', title: '名称', align: 'left', width: 150, fixed: 'left' },
|
||||
{
|
||||
field: 'status', title: '状态', align: 'center', width: 90, templet: function (row) {
|
||||
switch (row.status) {
|
||||
case 0: return '正常';
|
||||
case -1: return '删除';
|
||||
default: return '其它';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
{ field: 'sort', title: '排序', align: 'center', width: 60 },
|
||||
{ field: 'created_at', title: '创建时间', align: 'center', width: 160, templet: function (d) { return !d.created_at ? '' : util.toDateString(d.created_at) } },
|
||||
{ field: 'updated_at', title: '更新时间', align: 'center', width: 160, templet: function (d) { return !d.updated_at ? '' : util.toDateString(d.updated_at) } },
|
||||
{ title: '操作', toolbar: '#actionBox', align: 'center', width: 300, fixed: 'right' },
|
||||
]],
|
||||
skin: 'grid',
|
||||
toolbar: '#toolbar',
|
||||
defaultToolbar: [{
|
||||
title: '刷新',
|
||||
layEvent: 'refresh',
|
||||
icon: 'layui-icon-refresh',
|
||||
}, 'filter', 'exports'],
|
||||
request: {
|
||||
pageName: 'page',
|
||||
limitName: 'rows'
|
||||
}
|
||||
});
|
||||
|
||||
// 工具栏
|
||||
table.on('toolbar(tablelist)', function (obj) {
|
||||
switch (obj.event) {
|
||||
case 'add': add(); break;
|
||||
case 'refresh': table.reload('tablelist'); break;
|
||||
case 'refresh_cache': refreshCache(); break;
|
||||
case 'rebuild_parent_path': rebuildParentPath(); break;
|
||||
case 'search': search(); break;
|
||||
}
|
||||
});
|
||||
|
||||
function add(obj) {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '新增',
|
||||
shade: 0.1,
|
||||
area: ['99%', '98%'],
|
||||
content: "/system/role/add"
|
||||
});
|
||||
}
|
||||
|
||||
function refreshCache() {
|
||||
layer.confirm('确定要刷新角色数据吗?', { title: '提示' }, function (index, layero) {
|
||||
$.ajax({
|
||||
url: '/system/role/refresh_cache',
|
||||
type: 'post',
|
||||
headers: { 'X-CSRF-Token': '{{ token }}' },
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
if (result.success) {
|
||||
layer.msg(result.msg, { icon: 1, time: 2000 });
|
||||
search_btn();
|
||||
} else {
|
||||
layer.msg(result.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
});
|
||||
layer.close(index); // 关闭弹窗
|
||||
}, function (index, layero) {
|
||||
layer.close(index); // 关闭弹窗
|
||||
});
|
||||
}
|
||||
|
||||
function rebuildParentPath() {
|
||||
layer.confirm('确定要重建父路径吗?', { title: '提示' }, function (index, layero) {
|
||||
$.ajax({
|
||||
url: '/system/role/rebuild_parent_path',
|
||||
type: 'post',
|
||||
headers: { 'X-CSRF-Token': '{{ token }}' },
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
if (result.success) {
|
||||
layer.msg(result.msg, { icon: 1, time: 2000 });
|
||||
search_btn();
|
||||
} else {
|
||||
layer.msg(result.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
});
|
||||
layer.close(index); // 关闭弹窗
|
||||
}, function (index, layero) {
|
||||
layer.close(index); // 关闭弹窗
|
||||
});
|
||||
}
|
||||
|
||||
function search() {
|
||||
layer.open({
|
||||
type: 1,
|
||||
offset: '20px',
|
||||
title: '搜索',
|
||||
content: $('#search-layer'), // 捕获的元素
|
||||
shade: 0.1,
|
||||
shadeClose: false,
|
||||
scrollbar: false,
|
||||
resize: false,
|
||||
move: false,
|
||||
skin: 'search-layer-open',
|
||||
area: ['50%', '350px'],
|
||||
btn: ['搜索', '重置'],
|
||||
btn1: function (index, layero) {
|
||||
search_btn();
|
||||
layer.close(index);
|
||||
},
|
||||
btn2: function (index, layero) {
|
||||
$('#status').val(0);
|
||||
$('#id').val('');
|
||||
$('#name').val('');
|
||||
$('#parentId').val(0);
|
||||
form.render('select');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 表格项操作按钮
|
||||
table.on('tool(tablelist)', function (obj) {
|
||||
switch (obj.event) {
|
||||
case 'edit': edit(obj); break;
|
||||
case 'set_menu': setMenu(obj); break;
|
||||
case 'refresh_role_menus': refreshRoleMenus(obj); break;
|
||||
}
|
||||
});
|
||||
|
||||
function edit(obj) {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '修改',
|
||||
shade: 0.1,
|
||||
area: ['99%', '98%'],
|
||||
content: "/system/role/edit?id=" + obj.data['id']
|
||||
});
|
||||
}
|
||||
|
||||
function setMenu(obj) {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '为 ' + obj.data['display_name'] + ' 分配权限',
|
||||
shade: 0.1,
|
||||
area: ['99%', '98%'],
|
||||
content: "/system/role/set_menu?id=" + obj.data['id']
|
||||
});
|
||||
}
|
||||
|
||||
function refreshRoleMenus(obj) {
|
||||
layer.confirm('确定要刷新角色权限吗?', { title: '提示' }, function (index, layero) {
|
||||
$.ajax({
|
||||
url: '/system/role/refresh_role_menus',
|
||||
type: 'post',
|
||||
headers: { 'X-CSRF-Token': '{{ token }}' },
|
||||
dataType: 'json',
|
||||
data: { "roleID": obj.data['id'] },
|
||||
success: function (result) {
|
||||
if (result.success) {
|
||||
layer.msg(result.msg, { icon: 1, time: 2000 });
|
||||
} else {
|
||||
layer.msg(result.msg, { icon: 2 })
|
||||
}
|
||||
}
|
||||
});
|
||||
layer.close(index); // 关闭弹窗
|
||||
}, function (index, layero) {
|
||||
layer.close(index); // 关闭弹窗
|
||||
});
|
||||
}
|
||||
|
||||
// 搜索条件
|
||||
function getQueryParams() {
|
||||
return {
|
||||
status: $('#status').val(),
|
||||
name: $('#name').val(),
|
||||
id: $('#id').val(),
|
||||
parentId: $('#parentId').val()
|
||||
};
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function search_btn() {
|
||||
table.reload('tablelist', {
|
||||
where: getQueryParams(),
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getRoleTree() {
|
||||
$.ajax({
|
||||
url: "/system/role/data?type=tree",
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
headers: { 'X-CSRF-Token': '{{ token }}' },
|
||||
success: function (res) {
|
||||
tree.render({
|
||||
elem: '#roleTree',
|
||||
data: res,
|
||||
onlyIconControl: true, // 是否仅允许节点左侧图标控制展开收缩
|
||||
showLine: true,
|
||||
click: function (obj) {
|
||||
// console.log(obj.data); // 得到当前点击的节点数据
|
||||
// console.log(obj.state); // 得到当前节点的展开状态:open、close、normal
|
||||
// console.log(obj.elem); // 得到当前节点元素
|
||||
// console.log(obj.data.children); // 当前节点下是否有子节点
|
||||
$('#parentId').val(obj.data.id);
|
||||
search_btn();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function (err) {
|
||||
// 处理请求错误
|
||||
console.log('请求出错:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
194
internal/erpserver/templ/system/role/list_templ.go
Normal file
194
internal/erpserver/templ/system/role/list_templ.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.898
|
||||
package role
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/erpserver/templ/base"
|
||||
"management/internal/erpserver/templ/component"
|
||||
"management/internal/pkg/mid"
|
||||
)
|
||||
|
||||
func List(ctx context.Context) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
meuns := mid.GetCurMenus(ctx)
|
||||
templ_7745c5c3_Err = component.TemplBtn(meuns, true, "add", "refresh_cache", "rebuild_parent_path").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = component.TemplLink(meuns, "edit", "set_menu", "refresh_role_menus").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " <div class=\"search-layer\" id=\"search-layer\" style=\"display: none;\"><input type=\"hidden\" id=\"parentId\" name=\"parentId\" value=\"0\"><div class=\"layui-form layui-row\"><div class=\"layui-col-xs12 layui-col-sm12 layui-col-md4\"><div class=\"layui-form-column\"><label for=\"status\" class=\"tips\">状态</label> <select id=\"status\"><option value=\"0\">正常</option> <option value=\"-1\">删除</option> <option value=\"9999\">全部</option></select></div></div><div class=\"layui-col-xs12 layui-col-sm12 layui-col-md4\"><div class=\"layui-form-column\"><label for=\"id\" class=\"tips\">编号</label> <input type=\"text\" id=\"id\" placeholder=\"请输入编号\" class=\"layui-input\"></div></div><div class=\"layui-col-xs12 layui-col-sm12 layui-col-md4\"><div class=\"layui-form-column\"><label for=\"name\" class=\"tips\">名称</label> <input type=\"text\" id=\"name\" placeholder=\"请输入名称\" class=\"layui-input\"></div></div></div></div><div class=\"layui-row h-all\"><div class=\"layui-col-md2 h-all\"><div class=\"own-left-pannel\"><div id=\"roleTree\" class=\"own-tree\"></div></div></div><div class=\"layui-col-md10\"><div class=\"own-pannel\"><table id=\"tablelist\" lay-filter=\"tablelist\"></table></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = base.Base(ctx, listCss(), listJs(ctx)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func listCss() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func listJs(ctx context.Context) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
token := mid.GetCsrfToken(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script>\n layui.use(['jquery', 'table', 'form', 'tree', 'util'], function () {\n let $ = layui.jquery;\n let table = layui.table;\n let form = layui.form;\n let tree = layui.tree;\n let util = layui.util;\n\n getRoleTree();\n\n // 加载列表\n table.render({\n elem: '#tablelist',\n url: \"/system/role/list\",\n method: \"POST\",\n headers: { 'X-CSRF-Token': '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 80, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "' },\n where: getQueryParams(),\n height: function () {\n return $(window).height() - 22;\n },\n page: true,\n limit: 15,\n limits: [15, 30, 45, 60, 75, 90],\n cols: [[\n { field: 'display_name', title: '显示名称', align: 'left', width: 160, fixed: 'left' },\n { field: 'name', title: '名称', align: 'left', width: 150, fixed: 'left' },\n {\n field: 'status', title: '状态', align: 'center', width: 90, templet: function (row) {\n switch (row.status) {\n case 0: return '正常';\n case -1: return '删除';\n default: return '其它';\n }\n return '';\n }\n },\n { field: 'sort', title: '排序', align: 'center', width: 60 },\n { field: 'created_at', title: '创建时间', align: 'center', width: 160, templet: function (d) { return !d.created_at ? '' : util.toDateString(d.created_at) } },\n { field: 'updated_at', title: '更新时间', align: 'center', width: 160, templet: function (d) { return !d.updated_at ? '' : util.toDateString(d.updated_at) } },\n { title: '操作', toolbar: '#actionBox', align: 'center', width: 300, fixed: 'right' },\n ]],\n skin: 'grid',\n toolbar: '#toolbar',\n defaultToolbar: [{\n title: '刷新',\n layEvent: 'refresh',\n icon: 'layui-icon-refresh',\n }, 'filter', 'exports'],\n request: {\n pageName: 'page',\n limitName: 'rows'\n }\n });\n\n // 工具栏\n table.on('toolbar(tablelist)', function (obj) {\n switch (obj.event) {\n case 'add': add(); break;\n case 'refresh': table.reload('tablelist'); break;\n case 'refresh_cache': refreshCache(); break;\n case 'rebuild_parent_path': rebuildParentPath(); break;\n case 'search': search(); break;\n }\n });\n\n function add(obj) {\n layer.open({\n type: 2,\n title: '新增',\n shade: 0.1,\n area: ['99%', '98%'],\n content: \"/system/role/add\"\n });\n }\n\n function refreshCache() {\n layer.confirm('确定要刷新角色数据吗?', { title: '提示' }, function (index, layero) {\n $.ajax({\n url: '/system/role/refresh_cache',\n type: 'post',\n headers: { 'X-CSRF-Token': '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 145, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "' },\n dataType: 'json',\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 2000 });\n search_btn();\n } else {\n layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n layer.close(index); // 关闭弹窗\n }, function (index, layero) {\n layer.close(index); // 关闭弹窗\n });\n }\n\n function rebuildParentPath() {\n layer.confirm('确定要重建父路径吗?', { title: '提示' }, function (index, layero) {\n $.ajax({\n url: '/system/role/rebuild_parent_path',\n type: 'post',\n headers: { 'X-CSRF-Token': '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 167, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "' },\n dataType: 'json',\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 2000 });\n search_btn();\n } else {\n layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n layer.close(index); // 关闭弹窗\n }, function (index, layero) {\n layer.close(index); // 关闭弹窗\n });\n }\n\n function search() {\n layer.open({\n type: 1,\n offset: '20px',\n title: '搜索',\n content: $('#search-layer'), // 捕获的元素\n shade: 0.1,\n shadeClose: false,\n scrollbar: false,\n resize: false,\n move: false,\n skin: 'search-layer-open',\n area: ['50%', '350px'],\n btn: ['搜索', '重置'],\n btn1: function (index, layero) {\n search_btn();\n layer.close(index);\n },\n btn2: function (index, layero) {\n $('#status').val(0);\n $('#id').val('');\n $('#name').val('');\n $('#parentId').val(0);\n form.render('select');\n return false;\n }\n });\n }\n\n // 表格项操作按钮\n table.on('tool(tablelist)', function (obj) {\n switch (obj.event) {\n case 'edit': edit(obj); break;\n case 'set_menu': setMenu(obj); break;\n case 'refresh_role_menus': refreshRoleMenus(obj); break;\n }\n });\n\n function edit(obj) {\n layer.open({\n type: 2,\n title: '修改',\n shade: 0.1,\n area: ['99%', '98%'],\n content: \"/system/role/edit?id=\" + obj.data['id']\n });\n }\n\n function setMenu(obj) {\n layer.open({\n type: 2,\n title: '为 ' + obj.data['display_name'] + ' 分配权限',\n shade: 0.1,\n area: ['99%', '98%'],\n content: \"/system/role/set_menu?id=\" + obj.data['id']\n });\n }\n\n function refreshRoleMenus(obj) {\n layer.confirm('确定要刷新角色权限吗?', { title: '提示' }, function (index, layero) {\n $.ajax({\n url: '/system/role/refresh_role_menus',\n type: 'post',\n headers: { 'X-CSRF-Token': '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 247, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "' },\n dataType: 'json',\n data: { \"roleID\": obj.data['id'] },\n success: function (result) {\n if (result.success) {\n layer.msg(result.msg, { icon: 1, time: 2000 });\n } else {\n layer.msg(result.msg, { icon: 2 })\n }\n }\n });\n layer.close(index); // 关闭弹窗\n }, function (index, layero) {\n layer.close(index); // 关闭弹窗\n });\n }\n\n // 搜索条件\n function getQueryParams() {\n return {\n status: $('#status').val(),\n name: $('#name').val(),\n id: $('#id').val(),\n parentId: $('#parentId').val()\n };\n }\n\n // 搜索\n function search_btn() {\n table.reload('tablelist', {\n where: getQueryParams(),\n page: {\n curr: 1\n }\n })\n }\n\n function getRoleTree() {\n $.ajax({\n url: \"/system/role/data?type=tree\",\n type: 'post',\n dataType: 'json',\n headers: { 'X-CSRF-Token': '")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err := templruntime.ScriptContentInsideStringLiteral(token)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/erpserver/templ/system/role/list.templ`, Line: 289, Col: 56}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "' },\n success: function (res) {\n tree.render({\n elem: '#roleTree',\n data: res,\n onlyIconControl: true, // 是否仅允许节点左侧图标控制展开收缩\n showLine: true,\n click: function (obj) {\n // console.log(obj.data); // 得到当前点击的节点数据\n // console.log(obj.state); // 得到当前节点的展开状态:open、close、normal\n // console.log(obj.elem); // 得到当前节点元素\n // console.log(obj.data.children); // 当前节点下是否有子节点\n $('#parentId').val(obj.data.id);\n search_btn();\n }\n });\n },\n error: function (err) {\n // 处理请求错误\n console.log('请求出错:', err);\n }\n });\n }\n });\n </script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"management/internal/erpserver/service/v1"
|
||||
commonService "management/internal/erpserver/service/v1/common"
|
||||
systemService "management/internal/erpserver/service/v1/system"
|
||||
"management/internal/pkg/cache"
|
||||
"management/internal/pkg/config"
|
||||
"management/internal/pkg/redis"
|
||||
"management/internal/pkg/render"
|
||||
"management/internal/pkg/session"
|
||||
|
||||
@@ -70,7 +70,8 @@ var serverSet = wire.NewSet(
|
||||
func NewWire(*config.Config, *logger.Logger) (App, func(), error) {
|
||||
panic(wire.Build(
|
||||
repositorySet,
|
||||
redis.New,
|
||||
cache.ConnectRedis,
|
||||
cache.NewRedisCache,
|
||||
session.NewSCSManager,
|
||||
serviceSet,
|
||||
render.New,
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"management/internal/erpserver/service/v1"
|
||||
"management/internal/erpserver/service/v1/common"
|
||||
system2 "management/internal/erpserver/service/v1/system"
|
||||
"management/internal/pkg/cache"
|
||||
"management/internal/pkg/config"
|
||||
"management/internal/pkg/redis"
|
||||
"management/internal/pkg/render"
|
||||
"management/internal/pkg/session"
|
||||
)
|
||||
@@ -39,22 +39,24 @@ func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (App, fun
|
||||
configRepository := system.NewConfigRepository(repositoryRepository)
|
||||
loginLogRepository := system.NewLoginLogRepository(repositoryRepository)
|
||||
auditLogRepository := system.NewAuditLogRepository(repositoryRepository)
|
||||
manager, err := session.NewSCSManager(db, configConfig)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return App{}, nil, err
|
||||
}
|
||||
transaction := repository.NewTransaction(repositoryRepository)
|
||||
cache, cleanup2, err := redis.New(configConfig, loggerLogger)
|
||||
client, cleanup2, err := cache.ConnectRedis(configConfig, loggerLogger)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return App{}, nil, err
|
||||
}
|
||||
service := v1.NewService(loggerLogger, transaction, manager, cache)
|
||||
manager, err := session.NewSCSManager(client, configConfig)
|
||||
if err != nil {
|
||||
cleanup2()
|
||||
cleanup()
|
||||
return App{}, nil, err
|
||||
}
|
||||
cacheCache := cache.NewRedisCache(client)
|
||||
service := v1.NewService(loggerLogger, transaction, manager, cacheCache)
|
||||
auditLogService := system2.NewAuditLogService(service, auditLogRepository)
|
||||
roleService := system2.NewRoleService(service, roleRepository)
|
||||
roleMenuService := system2.NewRoleMenuService(service, roleMenuRepository)
|
||||
menuService := system2.NewMenuService(service, menuRepository, roleService, roleMenuService)
|
||||
auditLogService := system2.NewAuditLogService(service, auditLogRepository)
|
||||
renderRender, err := render.New(manager, menuService)
|
||||
if err != nil {
|
||||
cleanup2()
|
||||
@@ -78,7 +80,7 @@ func NewWire(configConfig *config.Config, loggerLogger *logger.Logger) (App, fun
|
||||
roleHandler := system3.NewRoleHandler(handlerHandler, roleService, menuService)
|
||||
departmentHandler := system3.NewDepartmentHandler(handlerHandler, departmentService)
|
||||
mux := NewHTTPServer(manager, loggerLogger, menuService, auditLogService, captchaHandler, uploadHandler, configHandler, homeHandler, userHandler, loginLogHandler, auditHandler, menuHandler, roleHandler, departmentHandler)
|
||||
app := NewApp(userRepository, roleRepository, menuRepository, roleMenuRepository, departmentRepository, configRepository, loginLogRepository, auditLogRepository, mux)
|
||||
app := NewApp(userRepository, roleRepository, menuRepository, roleMenuRepository, departmentRepository, configRepository, loginLogRepository, auditLogRepository, auditLogService, mux)
|
||||
return app, func() {
|
||||
cleanup2()
|
||||
cleanup()
|
||||
|
||||
19
internal/pkg/cache/cache.go
vendored
Normal file
19
internal/pkg/cache/cache.go
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type Cache interface {
|
||||
Encode(a any) ([]byte, error)
|
||||
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
|
||||
Del(ctx context.Context, keys ...string) error
|
||||
Get(ctx context.Context, key string) (string, error)
|
||||
GetBytes(ctx context.Context, key string) ([]byte, error)
|
||||
Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd
|
||||
Keys(ctx context.Context, pattern string) ([]string, error)
|
||||
ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]string, int, error)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package redis
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -16,28 +16,17 @@ import (
|
||||
|
||||
var ErrRedisKeyNotFound = errors.New("redis key not found")
|
||||
|
||||
type Cache interface {
|
||||
Encode(a any) ([]byte, error)
|
||||
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
|
||||
Del(ctx context.Context, keys ...string) error
|
||||
Get(ctx context.Context, key string) (string, error)
|
||||
GetBytes(ctx context.Context, key string) ([]byte, error)
|
||||
Scan(ctx context.Context, cursor uint64, match string, count int64) *redis.ScanCmd
|
||||
Keys(ctx context.Context, pattern string) ([]string, error)
|
||||
ListKeys(ctx context.Context, pattern string, pageID int, pageSize int) ([]string, int, error)
|
||||
}
|
||||
|
||||
type redisCache struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func New(conf *config.Config, log *logger.Logger) (Cache, func(), error) {
|
||||
func ConnectRedis(conf *config.Config, log *logger.Logger) (*redis.Client, func(), error) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%d", conf.Redis.Host, conf.Redis.Port),
|
||||
Password: conf.Redis.Password,
|
||||
DB: conf.Redis.DB,
|
||||
})
|
||||
_, err := rdb.Ping(context.Background()).Result()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||
defer cancel()
|
||||
|
||||
_, err := rdb.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -48,9 +37,17 @@ func New(conf *config.Config, log *logger.Logger) (Cache, func(), error) {
|
||||
}
|
||||
}
|
||||
|
||||
return rdb, cleanup, nil
|
||||
}
|
||||
|
||||
type redisCache struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func NewRedisCache(client *redis.Client) Cache {
|
||||
return &redisCache{
|
||||
client: rdb,
|
||||
}, cleanup, nil
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *redisCache) Encode(a any) ([]byte, error) {
|
||||
58
internal/pkg/mid/audit_v1.go
Normal file
58
internal/pkg/mid/audit_v1.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package mid
|
||||
|
||||
//import (
|
||||
// "context"
|
||||
// "errors"
|
||||
// "net/http"
|
||||
// "time"
|
||||
//
|
||||
// systemmodel "management/internal/erpserver/model/system"
|
||||
// v1 "management/internal/erpserver/service/v1"
|
||||
// "management/internal/pkg/know"
|
||||
// "management/internal/pkg/session"
|
||||
//
|
||||
// "github.com/drhin/logger"
|
||||
// "go.uber.org/zap"
|
||||
//)
|
||||
//
|
||||
//func Audit(sess session.Manager, auditLogService v1.AuditLogService, log *logger.Logger) func(http.Handler) http.Handler {
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// start := time.Now()
|
||||
//
|
||||
// // 提前获取用户信息(同步操作)
|
||||
// user, err := sess.GetUser(r.Context(), know.StoreName)
|
||||
// if err != nil {
|
||||
// log.Error("获取用户会话失败", err)
|
||||
// next.ServeHTTP(w, r) // 继续处理请求
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// defer func() {
|
||||
// go func() {
|
||||
// if user.ID == 0 {
|
||||
// log.Error("用户信息为空", errors.New("scs get user is empty"))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
// defer cancel()
|
||||
//
|
||||
// al := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
|
||||
// if err := auditLogService.Create(ctx, al); err != nil {
|
||||
// log.Error(err.Error(), err,
|
||||
// zap.Int32("user_id", user.ID),
|
||||
// zap.String("user", user.Email),
|
||||
// zap.String("ip", al.Ip),
|
||||
// zap.String("os", al.Os),
|
||||
// zap.String("method", al.Method),
|
||||
// zap.String("path", al.Url),
|
||||
// )
|
||||
// }
|
||||
// }()
|
||||
// }()
|
||||
//
|
||||
// next.ServeHTTP(w, r)
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
228
internal/pkg/mid/audit_v2.go
Normal file
228
internal/pkg/mid/audit_v2.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package mid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
systemmodel "management/internal/erpserver/model/system"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/session"
|
||||
|
||||
"github.com/drhin/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AuditBuffer 审计日志缓冲器
|
||||
type AuditBuffer struct {
|
||||
auditLogService v1.AuditLogService
|
||||
log *logger.Logger
|
||||
buffer chan *systemmodel.AuditLog
|
||||
stopCh chan struct{}
|
||||
wg sync.WaitGroup
|
||||
batchSize int
|
||||
flushInterval time.Duration
|
||||
}
|
||||
|
||||
// NewAuditBuffer 创建审计日志缓冲器
|
||||
func NewAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) *AuditBuffer {
|
||||
return &AuditBuffer{
|
||||
auditLogService: auditLogService,
|
||||
log: log,
|
||||
buffer: make(chan *systemmodel.AuditLog, 10000), // 缓冲区大小
|
||||
stopCh: make(chan struct{}),
|
||||
batchSize: 50, // 批量大小
|
||||
flushInterval: 3 * time.Second, // 刷新间隔
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动缓冲器
|
||||
func (ab *AuditBuffer) Start() {
|
||||
ab.wg.Add(1)
|
||||
go ab.processBuffer()
|
||||
}
|
||||
|
||||
// Stop 停止缓冲器
|
||||
func (ab *AuditBuffer) Stop() {
|
||||
close(ab.stopCh)
|
||||
ab.wg.Wait()
|
||||
close(ab.buffer)
|
||||
}
|
||||
|
||||
// Add 添加审计日志到缓冲区
|
||||
func (ab *AuditBuffer) Add(auditLog *systemmodel.AuditLog) {
|
||||
select {
|
||||
case ab.buffer <- auditLog:
|
||||
// 成功添加到缓冲区
|
||||
default:
|
||||
// 缓冲区满,记录警告但不阻塞
|
||||
ab.log.Warn("审计日志缓冲区已满,丢弃日志")
|
||||
}
|
||||
}
|
||||
|
||||
// processBuffer 处理缓冲区中的日志
|
||||
func (ab *AuditBuffer) processBuffer() {
|
||||
defer ab.wg.Done()
|
||||
|
||||
ticker := time.NewTicker(ab.flushInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
batch := make([]*systemmodel.AuditLog, 0, ab.batchSize)
|
||||
|
||||
flushBatch := func() {
|
||||
if len(batch) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 批量插入
|
||||
if err := ab.batchInsert(ctx, batch); err != nil {
|
||||
ab.log.Error("批量插入审计日志失败", err, zap.Int("count", len(batch)))
|
||||
} else {
|
||||
ab.log.Debug("批量插入审计日志成功", zap.Int("count", len(batch)))
|
||||
}
|
||||
|
||||
// 清空批次
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ab.stopCh:
|
||||
// 停止信号,处理剩余的日志
|
||||
for len(ab.buffer) > 0 {
|
||||
select {
|
||||
case auditLog := <-ab.buffer:
|
||||
batch = append(batch, auditLog)
|
||||
if len(batch) >= ab.batchSize {
|
||||
flushBatch()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
flushBatch() // 处理最后一批
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
// 定时刷新
|
||||
flushBatch()
|
||||
|
||||
case auditLog := <-ab.buffer:
|
||||
// 收到新的审计日志
|
||||
batch = append(batch, auditLog)
|
||||
if len(batch) >= ab.batchSize {
|
||||
flushBatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// batchInsert 批量插入数据库
|
||||
func (ab *AuditBuffer) batchInsert(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
|
||||
maxRetries := 3
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
// 假设你的服务有批量创建方法,如果没有,需要添加
|
||||
if err := ab.auditLogService.BatchCreate(ctx, auditLogs); err != nil {
|
||||
if i == maxRetries-1 {
|
||||
return err
|
||||
}
|
||||
ab.log.Error("批量插入失败,准备重试", err, zap.Int("retry", i+1))
|
||||
time.Sleep(time.Duration(i+1) * time.Second)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 全局缓冲器实例
|
||||
var globalAuditBuffer *AuditBuffer
|
||||
|
||||
// InitAuditBuffer 初始化全局缓冲器
|
||||
func InitAuditBuffer(auditLogService v1.AuditLogService, log *logger.Logger) {
|
||||
globalAuditBuffer = NewAuditBuffer(auditLogService, log)
|
||||
globalAuditBuffer.Start()
|
||||
}
|
||||
|
||||
// StopAuditBuffer 停止全局缓冲器
|
||||
func StopAuditBuffer() {
|
||||
if globalAuditBuffer != nil {
|
||||
globalAuditBuffer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Audit 优化后的中间件
|
||||
func Audit(sess session.Manager, log *logger.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// 提前获取用户信息
|
||||
user, err := sess.GetUser(r.Context(), know.StoreName)
|
||||
if err != nil {
|
||||
log.Error("获取用户会话失败", err)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理请求
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
// 异步添加到缓冲区
|
||||
go func() {
|
||||
if user.ID == 0 {
|
||||
log.Error("用户信息为空", errors.New("user is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
auditLog := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
|
||||
|
||||
// 添加到缓冲区,不会阻塞
|
||||
if globalAuditBuffer != nil {
|
||||
globalAuditBuffer.Add(auditLog)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果你的AuditLogService没有BatchCreate方法,需要添加这个接口
|
||||
// 在你的service接口中添加:
|
||||
/*
|
||||
type AuditLogService interface {
|
||||
Create(ctx context.Context, auditLog *systemmodel.AuditLog) error
|
||||
BatchCreate(ctx context.Context, auditLogs []*systemmodel.AuditLog) error
|
||||
// ... 其他方法
|
||||
}
|
||||
*/
|
||||
|
||||
// 以及对应的实现(PostgreSQL批量插入示例)
|
||||
/*
|
||||
func (s *auditLogService) BatchCreate(ctx context.Context, auditLogs []*systemmodel.AuditLog) error {
|
||||
if len(auditLogs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 构建批量插入SQL
|
||||
query := `INSERT INTO audit_logs (user_id, email, ip, os, browser, method, url, start_time, end_time, duration) VALUES `
|
||||
values := make([]interface{}, 0, len(auditLogs)*10)
|
||||
|
||||
for i, log := range auditLogs {
|
||||
if i > 0 {
|
||||
query += ", "
|
||||
}
|
||||
query += "($" + strconv.Itoa(i*10+1) + ", $" + strconv.Itoa(i*10+2) + ", $" + strconv.Itoa(i*10+3) + ", $" + strconv.Itoa(i*10+4) + ", $" + strconv.Itoa(i*10+5) + ", $" + strconv.Itoa(i*10+6) + ", $" + strconv.Itoa(i*10+7) + ", $" + strconv.Itoa(i*10+8) + ", $" + strconv.Itoa(i*10+9) + ", $" + strconv.Itoa(i*10+10) + ")"
|
||||
|
||||
values = append(values, log.UserID, log.Email, log.Ip, log.Os, log.Browser, log.Method, log.Url, log.StartTime, log.EndTime, log.Duration)
|
||||
}
|
||||
|
||||
_, err := s.db.ExecContext(ctx, query, values...)
|
||||
return err
|
||||
}
|
||||
*/
|
||||
99
internal/pkg/mid/authorize_v1.go
Normal file
99
internal/pkg/mid/authorize_v1.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package mid
|
||||
|
||||
//import (
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "time"
|
||||
//
|
||||
// "management/internal/erpserver/model/dto"
|
||||
// v1 "management/internal/erpserver/service/v1"
|
||||
// "management/internal/pkg/know"
|
||||
// "management/internal/pkg/session"
|
||||
//)
|
||||
//
|
||||
//var publicRoutes = map[string]bool{
|
||||
// "/home.html": true,
|
||||
// "/dashboard": true,
|
||||
// "/system/menus": true,
|
||||
// "/upload/img": true,
|
||||
// "/upload/file": true,
|
||||
// "/upload/multi_files": true,
|
||||
// "/pear.json": true,
|
||||
// "/logout": true,
|
||||
//}
|
||||
//
|
||||
//func Authorize(
|
||||
// sess session.Manager,
|
||||
// menuService v1.MenuService,
|
||||
//) func(http.Handler) http.Handler {
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
// path := r.URL.Path
|
||||
//
|
||||
// // 登陆检查
|
||||
// n := time.Now()
|
||||
// user, err := sess.GetUser(ctx, know.StoreName)
|
||||
// if err != nil || user.ID == 0 {
|
||||
// http.Redirect(w, r, "/", http.StatusFound)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// log.Printf("scs get user: %s", time.Since(n).String())
|
||||
//
|
||||
// // 公共路由放行
|
||||
// if publicRoutes[path] {
|
||||
// ctx = setUser(ctx, user)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// n1 := time.Now()
|
||||
// // 权限检查
|
||||
// menus, err := menuService.ListByRoleIDToMap(ctx, user.RoleID)
|
||||
// if err != nil || !hasPermission(menus, path) {
|
||||
// http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// log.Printf("listByRoleIDToMap: %s", time.Since(n1).String())
|
||||
//
|
||||
// n2 := time.Now()
|
||||
// cur := getCurrentMenus(menus, path)
|
||||
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
|
||||
//
|
||||
// ctx = setUser(ctx, user)
|
||||
// ctx = setCurMenus(ctx, cur)
|
||||
//
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
|
||||
// _, ok := menus[path]
|
||||
// return ok
|
||||
//}
|
||||
//
|
||||
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
|
||||
// var res []dto.OwnerMenuDto
|
||||
//
|
||||
// menu, ok := data[path]
|
||||
// if !ok {
|
||||
// return res
|
||||
// }
|
||||
//
|
||||
// for _, item := range data {
|
||||
// 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
|
||||
//}
|
||||
402
internal/pkg/mid/authorize_v2.go
Normal file
402
internal/pkg/mid/authorize_v2.go
Normal file
@@ -0,0 +1,402 @@
|
||||
package mid
|
||||
|
||||
//
|
||||
//import (
|
||||
// "context"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "sync"
|
||||
// "time"
|
||||
//
|
||||
// "management/internal/erpserver/model/dto"
|
||||
// v1 "management/internal/erpserver/service/v1"
|
||||
// "management/internal/pkg/know"
|
||||
// "management/internal/pkg/session"
|
||||
//)
|
||||
//
|
||||
//var publicRoutes = map[string]bool{
|
||||
// "/home.html": true,
|
||||
// "/dashboard": true,
|
||||
// "/system/menus": true,
|
||||
// "/upload/img": true,
|
||||
// "/upload/file": true,
|
||||
// "/upload/multi_files": true,
|
||||
// "/pear.json": true,
|
||||
// "/logout": true,
|
||||
//}
|
||||
//
|
||||
//// MenuCacheItem 菜单缓存项
|
||||
//type MenuCacheItem struct {
|
||||
// Data map[string]*dto.OwnerMenuDto
|
||||
// ExpireAt time.Time
|
||||
// LoadTime time.Time
|
||||
// Version int64
|
||||
//}
|
||||
//
|
||||
//// IsExpired 检查是否过期
|
||||
//func (item *MenuCacheItem) IsExpired() bool {
|
||||
// return time.Now().After(item.ExpireAt)
|
||||
//}
|
||||
//
|
||||
//// MenuCache 内存缓存管理器
|
||||
//type MenuCache struct {
|
||||
// mu sync.RWMutex
|
||||
// cache map[int32]*MenuCacheItem // roleID -> MenuCacheItem
|
||||
// maxSize int // 最大缓存条目数
|
||||
// ttl time.Duration // 缓存TTL
|
||||
// refreshTTL time.Duration // 刷新TTL (提前刷新时间)
|
||||
// stats CacheStats // 缓存统计
|
||||
// cleanupTick *time.Ticker // 清理定时器
|
||||
// stopCh chan struct{} // 停止信号
|
||||
//}
|
||||
//
|
||||
//// CacheStats 缓存统计信息
|
||||
//type CacheStats struct {
|
||||
// mu sync.RWMutex
|
||||
// Hits int64
|
||||
// Misses int64
|
||||
// Evictions int64
|
||||
// RefreshCount int64
|
||||
//}
|
||||
//
|
||||
//// NewMenuCache 创建新的菜单缓存
|
||||
//func NewMenuCache(maxSize int, ttl time.Duration) *MenuCache {
|
||||
// cache := &MenuCache{
|
||||
// cache: make(map[int32]*MenuCacheItem),
|
||||
// maxSize: maxSize,
|
||||
// ttl: ttl,
|
||||
// refreshTTL: ttl - time.Duration(float64(ttl)*0.1), // 提前10%刷新
|
||||
// stopCh: make(chan struct{}),
|
||||
// cleanupTick: time.NewTicker(time.Minute * 5), // 每5分钟清理一次
|
||||
// }
|
||||
//
|
||||
// // 启动后台清理协程
|
||||
// go cache.cleanup()
|
||||
//
|
||||
// return cache
|
||||
//}
|
||||
//
|
||||
//// Get 获取缓存数据
|
||||
//func (mc *MenuCache) Get(roleID int32) (map[string]*dto.OwnerMenuDto, bool) {
|
||||
// mc.mu.RLock()
|
||||
// item, exists := mc.cache[roleID]
|
||||
// mc.mu.RUnlock()
|
||||
//
|
||||
// if !exists {
|
||||
// mc.recordMiss()
|
||||
// return nil, false
|
||||
// }
|
||||
//
|
||||
// // 检查是否过期
|
||||
// if item.IsExpired() {
|
||||
// mc.recordMiss()
|
||||
// // 异步删除过期项
|
||||
// go func() {
|
||||
// mc.mu.Lock()
|
||||
// delete(mc.cache, roleID)
|
||||
// mc.mu.Unlock()
|
||||
// }()
|
||||
// return nil, false
|
||||
// }
|
||||
//
|
||||
// mc.recordHit()
|
||||
// return item.Data, true
|
||||
//}
|
||||
//
|
||||
//// Set 设置缓存数据
|
||||
//func (mc *MenuCache) Set(roleID int32, data map[string]*dto.OwnerMenuDto, version int64) {
|
||||
// mc.mu.Lock()
|
||||
// defer mc.mu.Unlock()
|
||||
//
|
||||
// // 如果缓存已满,执行LRU淘汰
|
||||
// if len(mc.cache) >= mc.maxSize {
|
||||
// mc.evictLRU()
|
||||
// }
|
||||
//
|
||||
// item := &MenuCacheItem{
|
||||
// Data: data,
|
||||
// ExpireAt: time.Now().Add(mc.ttl),
|
||||
// LoadTime: time.Now(),
|
||||
// Version: version,
|
||||
// }
|
||||
//
|
||||
// mc.cache[roleID] = item
|
||||
//}
|
||||
//
|
||||
//// NeedRefresh 检查是否需要提前刷新
|
||||
//func (mc *MenuCache) NeedRefresh(roleID int32) bool {
|
||||
// mc.mu.RLock()
|
||||
// item, exists := mc.cache[roleID]
|
||||
// mc.mu.RUnlock()
|
||||
//
|
||||
// if !exists {
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// // 提前刷新策略:在过期前10%时间开始刷新
|
||||
// refreshTime := item.LoadTime.Add(mc.refreshTTL)
|
||||
// return time.Now().After(refreshTime)
|
||||
//}
|
||||
//
|
||||
//// evictLRU 淘汰最久未使用的缓存项
|
||||
//func (mc *MenuCache) evictLRU() {
|
||||
// var oldestRoleID int32
|
||||
// var oldestTime time.Time = time.Now()
|
||||
//
|
||||
// for roleID, item := range mc.cache {
|
||||
// if item.LoadTime.Before(oldestTime) {
|
||||
// oldestTime = item.LoadTime
|
||||
// oldestRoleID = roleID
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if oldestRoleID != 0 {
|
||||
// delete(mc.cache, oldestRoleID)
|
||||
// mc.stats.mu.Lock()
|
||||
// mc.stats.Evictions++
|
||||
// mc.stats.mu.Unlock()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// cleanup 后台清理过期缓存
|
||||
//func (mc *MenuCache) cleanup() {
|
||||
// for {
|
||||
// select {
|
||||
// case <-mc.cleanupTick.C:
|
||||
// mc.cleanupExpired()
|
||||
// case <-mc.stopCh:
|
||||
// mc.cleanupTick.Stop()
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// cleanupExpired 清理过期缓存
|
||||
//func (mc *MenuCache) cleanupExpired() {
|
||||
// mc.mu.Lock()
|
||||
// defer mc.mu.Unlock()
|
||||
//
|
||||
// now := time.Now()
|
||||
// for roleID, item := range mc.cache {
|
||||
// if now.After(item.ExpireAt) {
|
||||
// delete(mc.cache, roleID)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// GetStats 获取缓存统计信息
|
||||
//func (mc *MenuCache) GetStats() CacheStats {
|
||||
// mc.stats.mu.RLock()
|
||||
// defer mc.stats.mu.RUnlock()
|
||||
// return mc.stats
|
||||
//}
|
||||
//
|
||||
//// recordHit 记录缓存命中
|
||||
//func (mc *MenuCache) recordHit() {
|
||||
// mc.stats.mu.Lock()
|
||||
// mc.stats.Hits++
|
||||
// mc.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// recordMiss 记录缓存未命中
|
||||
//func (mc *MenuCache) recordMiss() {
|
||||
// mc.stats.mu.Lock()
|
||||
// mc.stats.Misses++
|
||||
// mc.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// Close 关闭缓存
|
||||
//func (mc *MenuCache) Close() {
|
||||
// close(mc.stopCh)
|
||||
//}
|
||||
//
|
||||
//// CachedMenuService 带缓存的菜单服务包装器
|
||||
//type CachedMenuService struct {
|
||||
// menuService v1.MenuService
|
||||
// cache *MenuCache
|
||||
// mu sync.RWMutex
|
||||
// refreshing map[int32]bool // 正在刷新的roleID
|
||||
//}
|
||||
//
|
||||
//// NewCachedMenuService 创建带缓存的菜单服务
|
||||
//func NewCachedMenuService(menuService v1.MenuService, cache *MenuCache) *CachedMenuService {
|
||||
// return &CachedMenuService{
|
||||
// menuService: menuService,
|
||||
// cache: cache,
|
||||
// refreshing: make(map[int32]bool),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// ListByRoleIDToMap 获取菜单数据(带缓存)
|
||||
//func (cms *CachedMenuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
|
||||
// // 先尝试从缓存获取
|
||||
// if data, hit := cms.cache.Get(roleID); hit {
|
||||
// // 检查是否需要异步刷新
|
||||
// if cms.cache.NeedRefresh(roleID) {
|
||||
// go cms.asyncRefresh(ctx, roleID)
|
||||
// }
|
||||
// return data, nil
|
||||
// }
|
||||
//
|
||||
// // 缓存未命中,同步获取数据
|
||||
// return cms.loadAndCache(ctx, roleID)
|
||||
//}
|
||||
//
|
||||
//// loadAndCache 加载数据并缓存
|
||||
//func (cms *CachedMenuService) loadAndCache(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
|
||||
// // 防止并发重复加载
|
||||
// cms.mu.Lock()
|
||||
// if cms.refreshing[roleID] {
|
||||
// cms.mu.Unlock()
|
||||
// // 如果正在加载,等待一小段时间后重试缓存
|
||||
// time.Sleep(time.Millisecond * 10)
|
||||
// if data, hit := cms.cache.Get(roleID); hit {
|
||||
// return data, nil
|
||||
// }
|
||||
// // 重试失败,继续执行加载逻辑
|
||||
// cms.mu.Lock()
|
||||
// }
|
||||
// cms.refreshing[roleID] = true
|
||||
// cms.mu.Unlock()
|
||||
//
|
||||
// defer func() {
|
||||
// cms.mu.Lock()
|
||||
// delete(cms.refreshing, roleID)
|
||||
// cms.mu.Unlock()
|
||||
// }()
|
||||
//
|
||||
// // 从原始服务获取数据
|
||||
// data, err := cms.menuService.ListByRoleIDToMap(ctx, roleID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // 缓存数据
|
||||
// version := time.Now().UnixNano()
|
||||
// cms.cache.Set(roleID, data, version)
|
||||
//
|
||||
// return data, nil
|
||||
//}
|
||||
//
|
||||
//// asyncRefresh 异步刷新缓存
|
||||
//func (cms *CachedMenuService) asyncRefresh(ctx context.Context, roleID int32) {
|
||||
// // 使用背景上下文,避免原请求取消影响刷新
|
||||
// bgCtx := context.Background()
|
||||
//
|
||||
// cms.mu.RLock()
|
||||
// if cms.refreshing[roleID] {
|
||||
// cms.mu.RUnlock()
|
||||
// return
|
||||
// }
|
||||
// cms.mu.RUnlock()
|
||||
//
|
||||
// _, err := cms.loadAndCache(bgCtx, roleID)
|
||||
// if err != nil {
|
||||
// log.Printf("async refresh menu cache failed for roleID %d: %v", roleID, err)
|
||||
// }
|
||||
//
|
||||
// cms.cache.stats.mu.Lock()
|
||||
// cms.cache.stats.RefreshCount++
|
||||
// cms.cache.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// 全局缓存实例
|
||||
//var (
|
||||
// menuCache *MenuCache
|
||||
// cachedMenuSvc *CachedMenuService
|
||||
// cacheInitOnce sync.Once
|
||||
//)
|
||||
//
|
||||
//// InitMenuCache 初始化菜单缓存(在应用启动时调用)
|
||||
//func InitMenuCache(menuService v1.MenuService) {
|
||||
// cacheInitOnce.Do(func() {
|
||||
// // 配置参数:最大1000个角色的缓存,TTL 10分钟
|
||||
// menuCache = NewMenuCache(1000, time.Minute*10)
|
||||
// cachedMenuSvc = NewCachedMenuService(menuService, menuCache)
|
||||
// })
|
||||
//}
|
||||
//
|
||||
//// GetCachedMenuService 获取缓存菜单服务实例
|
||||
//func GetCachedMenuService() *CachedMenuService {
|
||||
// return cachedMenuSvc
|
||||
//}
|
||||
//
|
||||
//// Authorize 修改后的授权中间件
|
||||
//func Authorize(
|
||||
// sess session.Manager,
|
||||
// menuService v1.MenuService,
|
||||
//) func(http.Handler) http.Handler {
|
||||
// // 初始化缓存
|
||||
// InitMenuCache(menuService)
|
||||
//
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
// path := r.URL.Path
|
||||
//
|
||||
// // 登陆检查
|
||||
// n := time.Now()
|
||||
// user, err := sess.GetUser(ctx, know.StoreName)
|
||||
// if err != nil || user.ID == 0 {
|
||||
// http.Redirect(w, r, "/", http.StatusFound)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// log.Printf("scs get user: %s", time.Since(n).String())
|
||||
//
|
||||
// // 公共路由放行
|
||||
// if publicRoutes[path] {
|
||||
// ctx = setUser(ctx, user)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// n1 := time.Now()
|
||||
// // 权限检查 - 使用缓存服务
|
||||
// menus, err := GetCachedMenuService().ListByRoleIDToMap(ctx, user.RoleID)
|
||||
// if err != nil || !hasPermission(menus, path) {
|
||||
// http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// log.Printf("listByRoleIDToMap (cached): %s", time.Since(n1).String())
|
||||
//
|
||||
// n2 := time.Now()
|
||||
// cur := getCurrentMenus(menus, path)
|
||||
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
|
||||
//
|
||||
// ctx = setUser(ctx, user)
|
||||
// ctx = setCurMenus(ctx, cur)
|
||||
//
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
|
||||
// _, ok := menus[path]
|
||||
// return ok
|
||||
//}
|
||||
//
|
||||
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
|
||||
// var res []dto.OwnerMenuDto
|
||||
//
|
||||
// menu, ok := data[path]
|
||||
// if !ok {
|
||||
// return res
|
||||
// }
|
||||
//
|
||||
// for _, item := range data {
|
||||
// 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
|
||||
//}
|
||||
537
internal/pkg/mid/authorize_v3.go
Normal file
537
internal/pkg/mid/authorize_v3.go
Normal file
@@ -0,0 +1,537 @@
|
||||
package mid
|
||||
|
||||
//import (
|
||||
// "context"
|
||||
// "encoding/json"
|
||||
// "errors"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "net/http"
|
||||
// "strconv"
|
||||
// "sync"
|
||||
// "time"
|
||||
//
|
||||
// "management/internal/erpserver/model/dto"
|
||||
// v1 "management/internal/erpserver/service/v1"
|
||||
// "management/internal/pkg/know"
|
||||
// "management/internal/pkg/session"
|
||||
//
|
||||
// "github.com/allegro/bigcache/v3"
|
||||
//)
|
||||
//
|
||||
//var publicRoutes = map[string]bool{
|
||||
// "/home.html": true,
|
||||
// "/dashboard": true,
|
||||
// "/system/menus": true,
|
||||
// "/upload/img": true,
|
||||
// "/upload/file": true,
|
||||
// "/upload/multi_files": true,
|
||||
// "/pear.json": true,
|
||||
// "/logout": true,
|
||||
//}
|
||||
//
|
||||
//// MenuCacheEntry 菜单缓存条目
|
||||
//type MenuCacheEntry struct {
|
||||
// Data map[string]*dto.OwnerMenuDto `json:"data"`
|
||||
// Timestamp int64 `json:"timestamp"`
|
||||
// Version int64 `json:"version"`
|
||||
//}
|
||||
//
|
||||
//// CacheStats 缓存统计信息
|
||||
//type CacheStats struct {
|
||||
// mu sync.RWMutex
|
||||
// Hits int64
|
||||
// Misses int64
|
||||
// Errors int64
|
||||
// RefreshCount int64
|
||||
// AsyncRefreshHits int64
|
||||
// LastRefreshTime time.Time
|
||||
//}
|
||||
//
|
||||
//// GetHitRate 获取命中率
|
||||
//func (cs *CacheStats) GetHitRate() float64 {
|
||||
// cs.mu.RLock()
|
||||
// defer cs.mu.RUnlock()
|
||||
//
|
||||
// total := cs.Hits + cs.Misses
|
||||
// if total == 0 {
|
||||
// return 0
|
||||
// }
|
||||
// return float64(cs.Hits) / float64(total) * 100
|
||||
//}
|
||||
//
|
||||
//// MenuCacheManager BigCache菜单缓存管理器
|
||||
//type MenuCacheManager struct {
|
||||
// cache *bigcache.BigCache
|
||||
// refreshTTL time.Duration
|
||||
// stats *CacheStats
|
||||
// mu sync.RWMutex
|
||||
// refreshing map[int64]bool
|
||||
// stopCh chan struct{}
|
||||
// monitorTicker *time.Ticker
|
||||
//}
|
||||
//
|
||||
//// NewMenuCacheManager 创建菜单缓存管理器
|
||||
//func NewMenuCacheManager(ttl time.Duration) (*MenuCacheManager, error) {
|
||||
// config := bigcache.DefaultConfig(ttl)
|
||||
//
|
||||
// // 生产环境优化配置
|
||||
// config.Shards = 256 // 分片数,减少锁竞争
|
||||
// config.MaxEntriesInWindow = 10000 // 窗口内最大条目数
|
||||
// config.MaxEntrySize = 1024 * 50 // 最大条目50KB
|
||||
// config.HardMaxCacheSize = 512 // 最大缓存512MB
|
||||
// config.StatsEnabled = true // 启用统计
|
||||
// config.Verbose = false // 关闭详细日志
|
||||
// config.CleanWindow = 5 * time.Minute // 清理窗口
|
||||
//
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
// defer cancel()
|
||||
//
|
||||
// cache, err := bigcache.New(ctx, config)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to create BigCache: %w", err)
|
||||
// }
|
||||
//
|
||||
// manager := &MenuCacheManager{
|
||||
// cache: cache,
|
||||
// refreshTTL: time.Duration(float64(ttl) * 0.8), // 80%时间后开始刷新
|
||||
// stats: &CacheStats{},
|
||||
// refreshing: make(map[int64]bool),
|
||||
// stopCh: make(chan struct{}),
|
||||
// monitorTicker: time.NewTicker(time.Minute), // 每分钟监控一次
|
||||
// }
|
||||
//
|
||||
// // 启动监控协程
|
||||
// go manager.monitor()
|
||||
//
|
||||
// return manager, nil
|
||||
//}
|
||||
//
|
||||
//// Get 获取缓存数据
|
||||
//func (mcm *MenuCacheManager) Get(roleID int32) (map[string]*dto.OwnerMenuDto, bool, bool) {
|
||||
// key := mcm.makeKey(roleID)
|
||||
//
|
||||
// data, err := mcm.cache.Get(key)
|
||||
// if err != nil {
|
||||
// if !errors.Is(err, bigcache.ErrEntryNotFound) {
|
||||
// mcm.recordError()
|
||||
// log.Printf("BigCache get error for roleID %d: %v", roleID, err)
|
||||
// }
|
||||
// mcm.recordMiss()
|
||||
// return nil, false, false
|
||||
// }
|
||||
//
|
||||
// // 反序列化
|
||||
// var entry MenuCacheEntry
|
||||
// if err := json.Unmarshal(data, &entry); err != nil {
|
||||
// mcm.recordError()
|
||||
// log.Printf("Failed to unmarshal cache entry for roleID %d: %v", roleID, err)
|
||||
// mcm.recordMiss()
|
||||
// return nil, false, false
|
||||
// }
|
||||
//
|
||||
// mcm.recordHit()
|
||||
//
|
||||
// // 检查是否需要刷新
|
||||
// needRefresh := time.Since(time.Unix(entry.Timestamp, 0)) > mcm.refreshTTL
|
||||
//
|
||||
// return entry.Data, true, needRefresh
|
||||
//}
|
||||
//
|
||||
//// Set 设置缓存数据
|
||||
//func (mcm *MenuCacheManager) Set(roleID int32, data map[string]*dto.OwnerMenuDto) error {
|
||||
// key := mcm.makeKey(roleID)
|
||||
//
|
||||
// entry := MenuCacheEntry{
|
||||
// Data: data,
|
||||
// Timestamp: time.Now().Unix(),
|
||||
// Version: time.Now().UnixNano(),
|
||||
// }
|
||||
//
|
||||
// // 序列化
|
||||
// entryData, err := json.Marshal(entry)
|
||||
// if err != nil {
|
||||
// mcm.recordError()
|
||||
// return fmt.Errorf("failed to marshal cache entry: %w", err)
|
||||
// }
|
||||
//
|
||||
// // 存储到BigCache
|
||||
// err = mcm.cache.Set(key, entryData)
|
||||
// if err != nil {
|
||||
// mcm.recordError()
|
||||
// return fmt.Errorf("failed to set cache: %w", err)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// Delete 删除缓存数据
|
||||
//func (mcm *MenuCacheManager) Delete(roleID int32) error {
|
||||
// key := mcm.makeKey(roleID)
|
||||
// err := mcm.cache.Delete(key)
|
||||
// if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
|
||||
// mcm.recordError()
|
||||
// return fmt.Errorf("failed to delete cache: %w", err)
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// makeKey 生成缓存key
|
||||
//func (mcm *MenuCacheManager) makeKey(roleID int32) string {
|
||||
// return "menu:role:" + strconv.Itoa(int(roleID))
|
||||
//}
|
||||
//
|
||||
//// GetStats 获取统计信息
|
||||
//func (mcm *MenuCacheManager) GetStats() *CacheStats {
|
||||
// mcm.stats.mu.RLock()
|
||||
// defer mcm.stats.mu.RUnlock()
|
||||
//
|
||||
// // 复制统计数据
|
||||
// stats := *mcm.stats
|
||||
// return &stats
|
||||
//}
|
||||
//
|
||||
//// GetBigCacheStats 获取BigCache原生统计
|
||||
//func (mcm *MenuCacheManager) GetBigCacheStats() bigcache.Stats {
|
||||
// return mcm.cache.Stats()
|
||||
//}
|
||||
//
|
||||
//// recordHit 记录命中
|
||||
//func (mcm *MenuCacheManager) recordHit() {
|
||||
// mcm.stats.mu.Lock()
|
||||
// mcm.stats.Hits++
|
||||
// mcm.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// recordMiss 记录未命中
|
||||
//func (mcm *MenuCacheManager) recordMiss() {
|
||||
// mcm.stats.mu.Lock()
|
||||
// mcm.stats.Misses++
|
||||
// mcm.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// recordError 记录错误
|
||||
//func (mcm *MenuCacheManager) recordError() {
|
||||
// mcm.stats.mu.Lock()
|
||||
// mcm.stats.Errors++
|
||||
// mcm.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// recordRefresh 记录刷新
|
||||
//func (mcm *MenuCacheManager) recordRefresh() {
|
||||
// mcm.stats.mu.Lock()
|
||||
// mcm.stats.RefreshCount++
|
||||
// mcm.stats.LastRefreshTime = time.Now()
|
||||
// mcm.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// recordAsyncRefreshHit 记录异步刷新命中
|
||||
//func (mcm *MenuCacheManager) recordAsyncRefreshHit() {
|
||||
// mcm.stats.mu.Lock()
|
||||
// mcm.stats.AsyncRefreshHits++
|
||||
// mcm.stats.mu.Unlock()
|
||||
//}
|
||||
//
|
||||
//// monitor 监控协程
|
||||
//func (mcm *MenuCacheManager) monitor() {
|
||||
// for {
|
||||
// select {
|
||||
// case <-mcm.monitorTicker.C:
|
||||
// mcm.logStats()
|
||||
// case <-mcm.stopCh:
|
||||
// mcm.monitorTicker.Stop()
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// logStats 记录统计信息
|
||||
//func (mcm *MenuCacheManager) logStats() {
|
||||
// stats := mcm.GetStats()
|
||||
// bigCacheStats := mcm.GetBigCacheStats()
|
||||
//
|
||||
// log.Printf("MenuCache Stats - Hits: %d, Misses: %d, Errors: %d, HitRate: %.2f%%, "+
|
||||
// "RefreshCount: %d, AsyncRefreshHits: %d, BigCache Hits: %d, BigCache Misses: %d",
|
||||
// stats.Hits, stats.Misses, stats.Errors, stats.GetHitRate(),
|
||||
// stats.RefreshCount, stats.AsyncRefreshHits,
|
||||
// bigCacheStats.Hits, bigCacheStats.Misses)
|
||||
//}
|
||||
//
|
||||
//// Close 关闭缓存管理器
|
||||
//func (mcm *MenuCacheManager) Close() error {
|
||||
// close(mcm.stopCh)
|
||||
// return mcm.cache.Close()
|
||||
//}
|
||||
//
|
||||
//// CachedMenuService 带BigCache的菜单服务
|
||||
//type CachedMenuService struct {
|
||||
// menuService v1.MenuService
|
||||
// cache *MenuCacheManager
|
||||
// mu sync.RWMutex
|
||||
// refreshing map[int32]bool
|
||||
//}
|
||||
//
|
||||
//// NewCachedMenuService 创建带缓存的菜单服务
|
||||
//func NewCachedMenuService(menuService v1.MenuService, cache *MenuCacheManager) *CachedMenuService {
|
||||
// return &CachedMenuService{
|
||||
// menuService: menuService,
|
||||
// cache: cache,
|
||||
// refreshing: make(map[int32]bool),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// ListByRoleIDToMap 获取菜单数据(带BigCache缓存)
|
||||
//func (cms *CachedMenuService) ListByRoleIDToMap(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
|
||||
// // 尝试从缓存获取
|
||||
// data, hit, needRefresh := cms.cache.Get(roleID)
|
||||
// if hit {
|
||||
// // 如果需要刷新且当前没有在刷新中,启动异步刷新
|
||||
// if needRefresh && !cms.isRefreshing(roleID) {
|
||||
// go cms.asyncRefresh(roleID)
|
||||
// }
|
||||
// return data, nil
|
||||
// }
|
||||
//
|
||||
// // 缓存未命中,同步获取
|
||||
// return cms.loadAndCache(ctx, roleID)
|
||||
//}
|
||||
//
|
||||
//// loadAndCache 加载数据并缓存
|
||||
//func (cms *CachedMenuService) loadAndCache(ctx context.Context, roleID int32) (map[string]*dto.OwnerMenuDto, error) {
|
||||
// // 防止并发重复加载
|
||||
// cms.mu.Lock()
|
||||
// if cms.refreshing[roleID] {
|
||||
// cms.mu.Unlock()
|
||||
// // 等待一小段时间后重试缓存
|
||||
// time.Sleep(time.Millisecond * 5)
|
||||
// if data, hit, _ := cms.cache.Get(roleID); hit {
|
||||
// return data, nil
|
||||
// }
|
||||
// // 重试失败,继续加载
|
||||
// cms.mu.Lock()
|
||||
// }
|
||||
// cms.refreshing[roleID] = true
|
||||
// cms.mu.Unlock()
|
||||
//
|
||||
// defer func() {
|
||||
// cms.mu.Lock()
|
||||
// delete(cms.refreshing, roleID)
|
||||
// cms.mu.Unlock()
|
||||
// }()
|
||||
//
|
||||
// // 从原始服务获取数据
|
||||
// data, err := cms.menuService.ListByRoleIDToMap(ctx, roleID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// // 缓存数据
|
||||
// if cacheErr := cms.cache.Set(roleID, data); cacheErr != nil {
|
||||
// log.Printf("Failed to cache menu data for roleID %d: %v", roleID, cacheErr)
|
||||
// // 缓存失败不影响业务逻辑,继续返回数据
|
||||
// }
|
||||
//
|
||||
// return data, nil
|
||||
//}
|
||||
//
|
||||
//// asyncRefresh 异步刷新缓存
|
||||
//func (cms *CachedMenuService) asyncRefresh(roleID int32) {
|
||||
// // 检查是否已在刷新中
|
||||
// if cms.isRefreshing(roleID) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 使用背景上下文
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
// defer cancel()
|
||||
//
|
||||
// _, err := cms.loadAndCache(ctx, roleID)
|
||||
// if err != nil {
|
||||
// log.Printf("Async refresh menu cache failed for roleID %d: %v", roleID, err)
|
||||
// } else {
|
||||
// cms.cache.recordRefresh()
|
||||
// cms.cache.recordAsyncRefreshHit()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// isRefreshing 检查是否正在刷新
|
||||
//func (cms *CachedMenuService) isRefreshing(roleID int32) bool {
|
||||
// cms.mu.RLock()
|
||||
// refreshing := cms.refreshing[roleID]
|
||||
// cms.mu.RUnlock()
|
||||
// return refreshing
|
||||
//}
|
||||
//
|
||||
//// InvalidateRole 使指定角色缓存失效
|
||||
//func (cms *CachedMenuService) InvalidateRole(roleID int32) error {
|
||||
// return cms.cache.Delete(roleID)
|
||||
//}
|
||||
//
|
||||
//// GetCacheStats 获取缓存统计
|
||||
//func (cms *CachedMenuService) GetCacheStats() *CacheStats {
|
||||
// return cms.cache.GetStats()
|
||||
//}
|
||||
//
|
||||
//// 全局实例
|
||||
//var (
|
||||
// menuCacheManager *MenuCacheManager
|
||||
// cachedMenuSvc *CachedMenuService
|
||||
// cacheInitOnce sync.Once
|
||||
// cacheInitErr error
|
||||
//)
|
||||
//
|
||||
//// InitMenuCache 初始化菜单缓存
|
||||
//func InitMenuCache(menuService v1.MenuService) error {
|
||||
// cacheInitOnce.Do(func() {
|
||||
// // 缓存TTL设置为15分钟
|
||||
// manager, err := NewMenuCacheManager(15 * time.Minute)
|
||||
// if err != nil {
|
||||
// cacheInitErr = fmt.Errorf("failed to initialize menu cache: %w", err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// menuCacheManager = manager
|
||||
// cachedMenuSvc = NewCachedMenuService(menuService, manager)
|
||||
//
|
||||
// log.Println("MenuCache initialized successfully with BigCache")
|
||||
// })
|
||||
//
|
||||
// return cacheInitErr
|
||||
//}
|
||||
//
|
||||
//// GetCachedMenuService 获取缓存菜单服务
|
||||
//func GetCachedMenuService() *CachedMenuService {
|
||||
// return cachedMenuSvc
|
||||
//}
|
||||
//
|
||||
//// GetMenuCacheManager 获取缓存管理器
|
||||
//func GetMenuCacheManager() *MenuCacheManager {
|
||||
// return menuCacheManager
|
||||
//}
|
||||
//
|
||||
//// CloseMenuCache 关闭菜单缓存
|
||||
//func CloseMenuCache() error {
|
||||
// if menuCacheManager != nil {
|
||||
// return menuCacheManager.Close()
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//// Authorize 修改后的授权中间件
|
||||
//func Authorize(
|
||||
// sess session.Manager,
|
||||
// menuService v1.MenuService,
|
||||
//) func(http.Handler) http.Handler {
|
||||
// // 初始化缓存
|
||||
// if err := InitMenuCache(menuService); err != nil {
|
||||
// log.Printf("Failed to initialize menu cache: %v", err)
|
||||
// // 缓存初始化失败,降级到原始服务
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
// path := r.URL.Path
|
||||
//
|
||||
// // 登陆检查
|
||||
// n := time.Now()
|
||||
// user, err := sess.GetUser(ctx, know.StoreName)
|
||||
// if err != nil || user.ID == 0 {
|
||||
// http.Redirect(w, r, "/", http.StatusFound)
|
||||
// return
|
||||
// }
|
||||
// log.Printf("scs get user: %s", time.Since(n).String())
|
||||
//
|
||||
// // 公共路由放行
|
||||
// if publicRoutes[path] {
|
||||
// ctx = setUser(ctx, user)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// n1 := time.Now()
|
||||
// // 权限检查 - 使用原始服务
|
||||
// menus, err := menuService.ListByRoleIDToMap(ctx, user.RoleID)
|
||||
// if err != nil || !hasPermission(menus, path) {
|
||||
// http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
// return
|
||||
// }
|
||||
// log.Printf("listByRoleIDToMap (fallback): %s", time.Since(n1).String())
|
||||
//
|
||||
// n2 := time.Now()
|
||||
// cur := getCurrentMenus(menus, path)
|
||||
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
|
||||
//
|
||||
// ctx = setUser(ctx, user)
|
||||
// ctx = setCurMenus(ctx, cur)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return func(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx := r.Context()
|
||||
// path := r.URL.Path
|
||||
//
|
||||
// // 登陆检查
|
||||
// n := time.Now()
|
||||
// user, err := sess.GetUser(ctx, know.StoreName)
|
||||
// if err != nil || user.ID == 0 {
|
||||
// http.Redirect(w, r, "/", http.StatusFound)
|
||||
// return
|
||||
// }
|
||||
// log.Printf("scs get user: %s", time.Since(n).String())
|
||||
//
|
||||
// // 公共路由放行
|
||||
// if publicRoutes[path] {
|
||||
// ctx = setUser(ctx, user)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// n1 := time.Now()
|
||||
// // 权限检查 - 使用BigCache缓存服务
|
||||
// menus, err := GetCachedMenuService().ListByRoleIDToMap(ctx, user.RoleID)
|
||||
// if err != nil || !hasPermission(menus, path) {
|
||||
// http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
// return
|
||||
// }
|
||||
// log.Printf("listByRoleIDToMap (BigCache): %s", time.Since(n1).String())
|
||||
//
|
||||
// n2 := time.Now()
|
||||
// cur := getCurrentMenus(menus, path)
|
||||
// log.Printf("getCurrentMenus: %s", time.Since(n2).String())
|
||||
//
|
||||
// ctx = setUser(ctx, user)
|
||||
// ctx = setCurMenus(ctx, cur)
|
||||
// next.ServeHTTP(w, r.WithContext(ctx))
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
|
||||
// _, ok := menus[path]
|
||||
// return ok
|
||||
//}
|
||||
//
|
||||
//func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
|
||||
// var res []dto.OwnerMenuDto
|
||||
//
|
||||
// menu, ok := data[path]
|
||||
// if !ok {
|
||||
// return res
|
||||
// }
|
||||
//
|
||||
// for _, item := range data {
|
||||
// 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
|
||||
//}
|
||||
121
internal/pkg/mid/authorize_v4.go
Normal file
121
internal/pkg/mid/authorize_v4.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package mid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"management/internal/erpserver/model/dto"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/session"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
var publicRoutes = map[string]bool{
|
||||
"/home.html": true,
|
||||
"/dashboard": true,
|
||||
"/system/menus": true,
|
||||
"/upload/img": true,
|
||||
"/upload/file": true,
|
||||
"/upload/multi_files": true,
|
||||
"/pear.json": true,
|
||||
"/logout": true,
|
||||
}
|
||||
|
||||
// 定义一个全局的go-cache实例
|
||||
var menuCache *cache.Cache
|
||||
|
||||
func init() {
|
||||
// 初始化go-cache,设置默认过期时间为5分钟,每10分钟清理一次过期项
|
||||
menuCache = cache.New(5*time.Minute, 10*time.Minute)
|
||||
}
|
||||
|
||||
func Authorize(
|
||||
sess session.Manager,
|
||||
menuService v1.MenuService,
|
||||
) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
path := r.URL.Path
|
||||
|
||||
// 登陆检查
|
||||
user, err := sess.GetUser(ctx, know.StoreName)
|
||||
if err != nil || user.ID == 0 {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// 公共路由放行
|
||||
if publicRoutes[path] {
|
||||
ctx = setUser(ctx, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
|
||||
n1 := time.Now()
|
||||
// 权限检查
|
||||
var menus map[string]*dto.OwnerMenuDto
|
||||
cacheKey := fmt.Sprintf("user_menus:%d", user.RoleID) // 使用用户RoleID作为缓存key
|
||||
|
||||
// 尝试从内存缓存中获取菜单数据
|
||||
if cachedMenus, found := menuCache.Get(cacheKey); found {
|
||||
menus = cachedMenus.(map[string]*dto.OwnerMenuDto)
|
||||
log.Printf("listByRoleIDToMap (from cache): %s", time.Since(n1).String())
|
||||
|
||||
} else {
|
||||
// 内存缓存未命中,从menuService获取,并存入内存缓存
|
||||
menus, err = menuService.ListByRoleIDToMap(ctx, user.RoleID)
|
||||
if err != nil {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
menuCache.Set(cacheKey, menus, cache.DefaultExpiration) // 使用默认过期时间
|
||||
log.Printf("listByRoleIDToMap (from service, then cached): %s", time.Since(n1).String())
|
||||
}
|
||||
|
||||
if !hasPermission(menus, path) {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
cur := getCurrentMenus(menus, path)
|
||||
|
||||
ctx = setUser(ctx, user)
|
||||
ctx = setCurMenus(ctx, cur)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
|
||||
_, ok := menus[path]
|
||||
return ok
|
||||
}
|
||||
|
||||
func getCurrentMenus(data map[string]*dto.OwnerMenuDto, path string) []dto.OwnerMenuDto {
|
||||
var res []dto.OwnerMenuDto
|
||||
|
||||
menu, ok := data[path]
|
||||
if !ok {
|
||||
return res
|
||||
}
|
||||
|
||||
for _, item := range data {
|
||||
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
|
||||
}
|
||||
22
internal/pkg/mid/csrf.go
Normal file
22
internal/pkg/mid/csrf.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package mid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/justinas/nosurf"
|
||||
)
|
||||
|
||||
func NoSurf(next http.Handler) http.Handler {
|
||||
return nosurf.New(next)
|
||||
}
|
||||
|
||||
func NoSurfContext(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token := nosurf.Token(r)
|
||||
|
||||
ctx := setCsrfToken(r.Context(), token)
|
||||
ctx = setHtmlCsrfToken(ctx, fmt.Sprintf(`<input type="hidden" name="csrf_token" value="%s" />`, token))
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
75
internal/pkg/mid/mid.go
Normal file
75
internal/pkg/mid/mid.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package mid
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"management/internal/erpserver/model/dto"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
type userKey struct{}
|
||||
|
||||
func setUser(ctx context.Context, usr dto.AuthorizeUser) context.Context {
|
||||
return context.WithValue(ctx, userKey{}, usr)
|
||||
}
|
||||
|
||||
// GetUser returns the user from the context.
|
||||
func GetUser(ctx context.Context) dto.AuthorizeUser {
|
||||
v, ok := ctx.Value(userKey{}).(dto.AuthorizeUser)
|
||||
if !ok {
|
||||
return dto.AuthorizeUser{}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
type menuKey struct{}
|
||||
|
||||
func setCurMenus(ctx context.Context, ms []dto.OwnerMenuDto) context.Context {
|
||||
return context.WithValue(ctx, menuKey{}, ms)
|
||||
}
|
||||
|
||||
func GetCurMenus(ctx context.Context) []dto.OwnerMenuDto {
|
||||
v, ok := ctx.Value(menuKey{}).([]dto.OwnerMenuDto)
|
||||
if !ok {
|
||||
return []dto.OwnerMenuDto{}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
type NoSurfToken struct {
|
||||
Token string
|
||||
HtmlToken string
|
||||
}
|
||||
|
||||
type csrfKey struct{}
|
||||
|
||||
func setCsrfToken(ctx context.Context, token string) context.Context {
|
||||
return context.WithValue(ctx, csrfKey{}, token)
|
||||
}
|
||||
|
||||
func GetCsrfToken(ctx context.Context) string {
|
||||
v, ok := ctx.Value(csrfKey{}).(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
type htmlCsrfKey struct{}
|
||||
|
||||
func setHtmlCsrfToken(ctx context.Context, token string) context.Context {
|
||||
return context.WithValue(ctx, htmlCsrfKey{}, templ.Raw(token))
|
||||
}
|
||||
|
||||
func GetHtmlCsrfToken(ctx context.Context) templ.Component {
|
||||
v, ok := ctx.Value(htmlCsrfKey{}).(templ.Component)
|
||||
if !ok {
|
||||
return templ.Raw("")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package middleware
|
||||
package mid
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -1,58 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
systemmodel "management/internal/erpserver/model/system"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/session"
|
||||
|
||||
"github.com/drhin/logger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Audit(sess session.Manager, auditLogService v1.AuditLogService, log *logger.Logger) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
// 提前获取用户信息(同步操作)
|
||||
user, err := sess.GetUser(r.Context(), know.StoreName)
|
||||
if err != nil {
|
||||
log.Error("获取用户会话失败", err)
|
||||
next.ServeHTTP(w, r) // 继续处理请求
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
go func() {
|
||||
if user.ID == 0 {
|
||||
log.Error("用户信息为空", errors.New("scs get user is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
al := systemmodel.NewAuditLog(r, user.Email, user.OS, user.Browser, start, time.Now())
|
||||
if err := auditLogService.Create(ctx, al); err != nil {
|
||||
log.Error(err.Error(), err,
|
||||
zap.Int32("user_id", user.ID),
|
||||
zap.String("user", user.Email),
|
||||
zap.String("ip", al.Ip),
|
||||
zap.String("os", al.Os),
|
||||
zap.String("method", al.Method),
|
||||
zap.String("path", al.Url),
|
||||
)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"management/internal/erpserver/model/dto"
|
||||
v1 "management/internal/erpserver/service/v1"
|
||||
"management/internal/pkg/know"
|
||||
"management/internal/pkg/session"
|
||||
)
|
||||
|
||||
var publicRoutes = map[string]bool{
|
||||
"/home.html": true,
|
||||
"/dashboard": true,
|
||||
"/system/menus": true,
|
||||
"/upload/img": true,
|
||||
"/upload/file": true,
|
||||
"/upload/multi_files": true,
|
||||
"/pear.json": true,
|
||||
"/logout": true,
|
||||
}
|
||||
|
||||
func Authorize(
|
||||
sess session.Manager,
|
||||
menuService v1.MenuService,
|
||||
) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
path := r.URL.Path
|
||||
|
||||
// 登陆检查
|
||||
user, err := sess.GetUser(ctx, know.StoreName)
|
||||
if err != nil || user.ID == 0 {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// 公共路由放行
|
||||
if publicRoutes[path] {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
menus, err := menuService.ListByRoleIDToMap(ctx, user.RoleID)
|
||||
if err != nil || !hasPermission(menus, path) {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func hasPermission(menus map[string]*dto.OwnerMenuDto, path string) bool {
|
||||
_, ok := menus[path]
|
||||
return ok
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/justinas/nosurf"
|
||||
)
|
||||
|
||||
func NoSurf(next http.Handler) http.Handler {
|
||||
return nosurf.New(next)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (r *render) setDefaultData(req *http.Request, data map[string]any) map[stri
|
||||
ctx := req.Context()
|
||||
|
||||
authUser, err := r.session.GetUser(ctx, know.StoreName)
|
||||
if err != nil || authUser == nil {
|
||||
if err != nil || authUser.ID == 0 {
|
||||
data["IsAuthenticated"] = false
|
||||
} else {
|
||||
data["IsAuthenticated"] = true
|
||||
|
||||
@@ -1,83 +1,86 @@
|
||||
package session
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "time"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
// "management/internal/pkg/redis"
|
||||
// )
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// var (
|
||||
// storePrefix = "scs:session:"
|
||||
// ctx = context.Background()
|
||||
// DefaultRedisStore = newRedisStore()
|
||||
// )
|
||||
// 为所有 Redis 操作定义一个合理的超时时间。
|
||||
// 这个值应该根据你的服务SLA和网络状况来定,通常在50-500毫秒之间是比较合理的。
|
||||
// 最佳实践:这个值应该来自配置文件,而不是硬编码。
|
||||
const redisTimeout = 200 * time.Millisecond
|
||||
|
||||
// type redisStore struct{}
|
||||
// RedisStore 表示一个使用 go-redis/v9 客户端的 scs.Store 实现。
|
||||
type RedisStore struct {
|
||||
// 内嵌 go-redis 客户端
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
// func newRedisStore() *redisStore {
|
||||
// return &redisStore{}
|
||||
// }
|
||||
// NewRedisStore 是 RedisStore 的构造函数。
|
||||
func NewRedisStore(client *redis.Client) *RedisStore {
|
||||
return &RedisStore{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// // Delete should remove the session token and corresponding data from the
|
||||
// // session store. If the token does not exist then Delete should be a no-op
|
||||
// // and return nil (not an error).
|
||||
// func (s *redisStore) Delete(token string) error {
|
||||
// return redis.Del(ctx, storePrefix+token)
|
||||
// }
|
||||
// Find 方法根据 session token 从 Redis 中查找 session 数据。
|
||||
// 如果 token 不存在或已过期,exists 返回 false。
|
||||
func (s *RedisStore) Find(token string) ([]byte, bool, error) {
|
||||
// ✅ 最佳实践: 为数据库操作创建带超时的上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
|
||||
// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
|
||||
defer cancel()
|
||||
|
||||
// // Find should return the data for a session token from the store. If the
|
||||
// // session token is not found or is expired, the found return value should
|
||||
// // be false (and the err return value should be nil). Similarly, tampered
|
||||
// // or malformed tokens should result in a found return value of false and a
|
||||
// // nil err value. The err return value should be used for system errors only.
|
||||
// func (s *redisStore) Find(token string) (b []byte, found bool, err error) {
|
||||
// val, err := redis.GetBytes(ctx, storePrefix+token)
|
||||
// if err != nil {
|
||||
// return nil, false, err
|
||||
// } else {
|
||||
// return val, true, nil
|
||||
// }
|
||||
// }
|
||||
// 使用 go-redis 的 Get 方法
|
||||
data, err := s.client.Get(ctx, token).Bytes()
|
||||
if err != nil {
|
||||
// 如果 key 不存在,go-redis 会返回 redis.Nil 错误
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// // Commit should add the session token and data to the store, with the given
|
||||
// // expiry time. If the session token already exists, then the data and
|
||||
// // expiry time should be overwritten.
|
||||
// func (s *redisStore) Commit(token string, b []byte, expiry time.Time) error {
|
||||
// // TODO: 这边可以调整时间
|
||||
// exp, err := time.ParseInLocation(time.DateTime, time.Now().Format("2006-01-02")+" 23:59:59", time.Local)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
// t := time.Now()
|
||||
// expired := exp.Sub(t)
|
||||
// return redis.Set(ctx, storePrefix+token, b, expired)
|
||||
// }
|
||||
// Commit 方法将 session 数据和过期时间存入 Redis。
|
||||
// 如果 token 已存在,则更新其数据和过期时间。
|
||||
func (s *RedisStore) Commit(token string, b []byte, expiry time.Time) error {
|
||||
// ✅ 最佳实践: 为数据库操作创建带超时的上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
|
||||
// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
|
||||
defer cancel()
|
||||
|
||||
// // All should return a map containing data for all active sessions (i.e.
|
||||
// // sessions which have not expired). The map key should be the session
|
||||
// // token and the map value should be the session data. If no active
|
||||
// // sessions exist this should return an empty (not nil) map.
|
||||
// func (s *redisStore) All() (map[string][]byte, error) {
|
||||
// sessions := make(map[string][]byte)
|
||||
// 计算 Redis 的 TTL (Time To Live)
|
||||
// time.Until(expiry) 会计算出当前时间到 expiry 之间的时间差
|
||||
ttl := time.Until(expiry)
|
||||
|
||||
// iter := redis.Scan(ctx, 0, storePrefix+"*", 0).Iterator()
|
||||
// for iter.Next(ctx) {
|
||||
// key := iter.Val()
|
||||
// token := key[len(storePrefix):]
|
||||
// data, exists, err := s.Find(token)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// 使用 go-redis 的 Set 方法,并设置过期时间
|
||||
// 如果 expiry 时间已经过去,ttl 会是负数,Redis 会立即删除这个 key,这正是我们期望的行为。
|
||||
err := s.client.Set(ctx, token, b, ttl).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if exists {
|
||||
// sessions[token] = data
|
||||
// }
|
||||
// }
|
||||
// if err := iter.Err(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// return sessions, nil
|
||||
// }
|
||||
// Delete 方法根据 session token 从 Redis 中删除 session 数据。
|
||||
func (s *RedisStore) Delete(token string) error {
|
||||
// ✅ 最佳实践: 为数据库操作创建带超时的上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), redisTimeout)
|
||||
// ✅ 必须: 无论函数如何返回,都调用 cancel() 来释放上下文资源
|
||||
defer cancel()
|
||||
|
||||
// 使用 go-redis 的 Del 方法
|
||||
err := s.client.Del(ctx, token).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,9 +10,8 @@ import (
|
||||
"management/internal/erpserver/model/dto"
|
||||
"management/internal/pkg/config"
|
||||
|
||||
"github.com/alexedwards/scs/postgresstore"
|
||||
"github.com/alexedwards/scs/v2"
|
||||
"gorm.io/gorm"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ErrNoSession = errors.New("session user not found")
|
||||
@@ -20,8 +19,8 @@ var ErrNoSession = errors.New("session user not found")
|
||||
// Manager 抽象核心会话操作
|
||||
type Manager interface {
|
||||
Load(next http.Handler) http.Handler
|
||||
GetUser(ctx context.Context, key string) (*dto.AuthorizeUser, error)
|
||||
PutUser(ctx context.Context, key string, user *dto.AuthorizeUser) error
|
||||
GetUser(ctx context.Context, key string) (dto.AuthorizeUser, error)
|
||||
PutUser(ctx context.Context, key string, user dto.AuthorizeUser) error
|
||||
RenewToken(ctx context.Context) error
|
||||
Destroy(ctx context.Context) error
|
||||
}
|
||||
@@ -30,7 +29,7 @@ type SCSSession struct {
|
||||
manager *scs.SessionManager
|
||||
}
|
||||
|
||||
func NewSCSManager(db *gorm.DB, config *config.Config) (Manager, error) {
|
||||
func NewSCSManager(client *redis.Client, conf *config.Config) (Manager, error) {
|
||||
sessionManager := scs.New()
|
||||
sessionManager.Lifetime = 24 * time.Hour
|
||||
sessionManager.IdleTimeout = 2 * time.Hour
|
||||
@@ -38,21 +37,21 @@ func NewSCSManager(db *gorm.DB, config *config.Config) (Manager, error) {
|
||||
sessionManager.Cookie.HttpOnly = true
|
||||
sessionManager.Cookie.Persist = true
|
||||
sessionManager.Cookie.SameSite = http.SameSiteStrictMode
|
||||
sessionManager.Cookie.Secure = config.App.Prod
|
||||
sessionManager.Cookie.Secure = conf.App.Prod
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//sqlDB, err := db.DB()
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
|
||||
// postgres
|
||||
// github.com/alexedwards/scs/postgresstore
|
||||
sessionManager.Store = postgresstore.New(sqlDB)
|
||||
// sessionManager.Store = postgresstore.New(sqlDB)
|
||||
// pgx
|
||||
// github.com/alexedwards/scs/pgxstore
|
||||
// sessionManager.Store = pgxstore.New(pool)
|
||||
// redis
|
||||
// sessionManager.Store = newRedisStore()
|
||||
sessionManager.Store = NewRedisStore(client)
|
||||
return &SCSSession{manager: sessionManager}, nil
|
||||
}
|
||||
|
||||
@@ -60,21 +59,21 @@ func (s *SCSSession) Load(next http.Handler) http.Handler {
|
||||
return s.manager.LoadAndSave(next)
|
||||
}
|
||||
|
||||
func (s *SCSSession) GetUser(ctx context.Context, key string) (*dto.AuthorizeUser, error) {
|
||||
func (s *SCSSession) GetUser(ctx context.Context, key string) (dto.AuthorizeUser, error) {
|
||||
data, ok := s.manager.Get(ctx, key).([]byte)
|
||||
if !ok || len(data) == 0 {
|
||||
return nil, ErrNoSession
|
||||
return dto.AuthorizeUser{}, ErrNoSession
|
||||
}
|
||||
|
||||
var user dto.AuthorizeUser
|
||||
if err := json.Unmarshal(data, &user); err != nil {
|
||||
return nil, err
|
||||
return dto.AuthorizeUser{}, err
|
||||
}
|
||||
return &user, nil
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *SCSSession) PutUser(ctx context.Context, key string, user *dto.AuthorizeUser) error {
|
||||
data, err := json.Marshal(user)
|
||||
func (s *SCSSession) PutUser(ctx context.Context, key string, user dto.AuthorizeUser) error {
|
||||
data, err := json.Marshal(&user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user