first commit

This commit is contained in:
2025-03-21 11:05:42 +08:00
commit 7dffc94035
1717 changed files with 724764 additions and 0 deletions

View File

@@ -0,0 +1,267 @@
package budget
import (
"errors"
"net/http"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
"management/internal/db/model/form"
db "management/internal/db/sqlc"
"management/internal/global"
"management/internal/global/html"
"management/internal/global/know"
"management/internal/middleware/manage/auth"
"management/internal/pkg/convertor"
"management/internal/router/manage/util"
categoryservice "management/internal/service/category"
projectservice "management/internal/service/project"
"management/internal/tpl"
"github.com/jackc/pgx/v5/pgtype"
)
func List(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pp := projectservice.AllProjects(ctx)
cc, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.BudgetCategory)
tpl.HTML(w, r, "budget/list.tmpl", map[string]any{
"Statuses": html.NewSelectControls(global.Statuses, 0),
"Projects": html.NewSelectStringControls(pp, "0"),
"Categories": html.NewSelectStringControls(cc, "0"),
})
}
func PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
project := convertor.ConvertInt[int64](r.PostFormValue("project"), 9999)
if project == 0 {
project = 9999
}
budgetType := convertor.ConvertInt[int32](r.PostFormValue("budgetType"), 9999)
if budgetType == 0 {
budgetType = 9999
}
category := convertor.ConvertInt[int32](r.PostFormValue("category"), 9999)
if category == 0 {
category = 9999
}
title := strings.TrimSpace(r.PostFormValue("title"))
var search string
if len(title) > 0 {
search = "%" + title + "%"
if strings.HasSuffix(title, ":") {
search = title[:len(title)-1] + "%"
}
}
arg := &db.ListBudgetConditionParam{
ProjectID: project,
BudgetType: budgetType,
Category: category,
IsTitle: len(search) > 0,
Title: search,
Status: util.ConvertInt16(r.PostFormValue("status"), 9999),
PageID: util.ConvertInt32(r.PostFormValue("page"), 1),
PageSize: util.ConvertInt32(r.PostFormValue("rows"), 10),
}
arg.TimeBegin, arg.TimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
res, total, err := db.Engine.ListBudgetCondition(ctx, arg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: total,
Data: res,
}
tpl.JSON(w, data)
}
func Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "budget/edit.tmpl", map[string]any{
"Item": &form.BudgetForm{
BudgetType: 1,
},
"Statuses": html.NewSelectControls(global.Statuses, 0),
})
}
func Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.ConvertInt[int64](vars.Get("id"), 0)
budget := &form.BudgetForm{}
ctx := r.Context()
if id > 0 {
if cus, err := db.Engine.GetBudget(ctx, id); err == nil {
budget = budget.ToForm(cus)
if u, err := db.Engine.GetSysUser(ctx, cus.CreatedUserID); err == nil {
budget.CreatedName = u.Username
}
if u, err := db.Engine.GetSysUser(ctx, cus.UpdatedUserID); err == nil {
budget.UpdatedName = u.Username
}
}
}
tpl.HTML(w, r, "budget/edit.tmpl", map[string]any{
"Item": budget,
"Statuses": html.NewSelectControls(global.Statuses, budget.Status),
})
}
func Save(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
data, err := validForm(r)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
authUser := auth.AuthUser(ctx)
if data.ID > 0 {
arg := &db.UpdateBudgetParams{
ID: data.ID,
ProjectID: pgtype.Int8{
Int64: data.ProjectID,
Valid: true,
},
Name: pgtype.Text{
String: data.Name,
Valid: true,
},
BudgetType: pgtype.Int4{
Int32: data.BudgetType,
Valid: true,
},
Category: pgtype.Int4{
Int32: data.Category,
Valid: true,
},
StartAt: pgtype.Timestamptz{
Time: data.StartAt,
Valid: true,
},
EndAt: pgtype.Timestamptz{
Time: data.EndAt,
Valid: true,
},
Amount: data.AmountF,
UsedAmount: data.UsedAmountF,
RemainingAmount: data.RemainingAmountF,
Remark: pgtype.Text{
String: data.Remark,
Valid: true,
},
Status: pgtype.Int2{
Int16: data.Status,
Valid: true,
},
UpdatedUserID: pgtype.Int4{
Int32: authUser.ID,
Valid: true,
},
}
_, err := db.Engine.UpdateBudget(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "更新成功")
} else {
arg := &db.CreateBudgetParams{
ProjectID: data.ProjectID,
Name: data.Name,
BudgetType: data.BudgetType,
Category: data.Category,
StartAt: data.StartAt,
EndAt: data.EndAt,
Amount: data.AmountF,
UsedAmount: data.UsedAmountF,
RemainingAmount: data.RemainingAmountF,
Remark: data.Remark,
CreatedUserID: authUser.ID,
}
_, err := db.Engine.CreateBudget(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "添加成功")
}
}
func XmSelect(w http.ResponseWriter, r *http.Request) {
projectID := convertor.ConvertInt[int64](r.URL.Query().Get("projectId"), 0)
all, err := db.Engine.ListBudgets(r.Context(), projectID)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
res := []*dto.XmSelectStrDto{}
for _, v := range all {
res = append(res, &dto.XmSelectStrDto{
Name: v.Name,
Value: strconv.FormatInt(v.ID, 10),
})
}
tpl.JSON(w, res)
}
func validForm(r *http.Request) (form.BudgetForm, error) {
var err error
data := form.BudgetForm{}
data.ID = convertor.ConvertInt[int64](r.PostFormValue("ID"), 0)
data.ProjectID, err = strconv.ParseInt(r.PostFormValue("ProjectID"), 10, 64)
if err != nil || data.ProjectID == 0 {
return data, errors.New("项目不能为空")
}
data.Name = r.PostFormValue("Name")
if len(data.Name) == 0 {
return data, errors.New("预算名称不能为空")
}
budgetType, err := strconv.ParseInt(r.PostFormValue("BudgetType"), 10, 32)
if err != nil {
return data, errors.New("预算类型不能为空")
}
data.BudgetType = int32(budgetType)
category, err := strconv.ParseInt(r.PostFormValue("Category"), 10, 32)
if err != nil {
return data, errors.New("预算类别不能为空")
}
data.Category = int32(category)
data.StartAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("StartAt"), time.Local)
if err != nil {
return data, errors.New("开始时间格式错误")
}
data.EndAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("EndAt"), time.Local)
if err != nil {
return data, errors.New("结束时间格式错误")
}
if err := data.AmountF.Scan(r.PostFormValue("Amount")); err != nil {
return data, errors.New("预算金额格式错误")
}
if err := data.UsedAmountF.Scan(r.PostFormValue("UsedAmount")); err != nil {
return data, errors.New("已支付金额格式错误")
}
if err := data.RemainingAmountF.Scan(r.PostFormValue("RemainingAmount")); err != nil {
return data, errors.New("剩余金额格式错误")
}
data.Remark = r.PostFormValue("Remark")
data.Status = convertor.ConvertInt[int16](r.PostFormValue("Status"), 9999)
return data, nil
}

63
internal/router/manage/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,63 @@
package cache
import (
"net/http"
"sort"
"strings"
"management/internal/pkg/redis"
"management/internal/router/manage/util"
"management/internal/tpl"
)
func List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "cache/list.tmpl", nil)
}
type Key struct {
Name string `json:"name"`
}
func PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
keyword := strings.TrimSpace(r.PostFormValue("title"))
pageID := util.ConvertInt(r.PostFormValue("page"), 1)
pageSize := util.ConvertInt(r.PostFormValue("rows"), 10)
if len(keyword) == 0 {
keyword = "*"
} else {
keyword = keyword + "*"
}
result, count, err := redis.ListKeys(ctx, keyword, pageID, pageSize)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sort.Sort(sort.StringSlice(result))
var res []Key
for _, key := range result {
res = append(res, Key{
Name: key,
})
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: int64(count),
Data: res,
}
tpl.JSON(w, data)
}
func Refresh(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
name := strings.TrimSpace(r.PostFormValue("name"))
err := redis.Del(ctx, name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}

View File

@@ -0,0 +1,233 @@
package category
import (
"fmt"
"net/http"
"strconv"
"strings"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/router/manage/util"
categoryservice "management/internal/service/category"
"management/internal/tpl"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type CategoryHandler struct{}
func NewCategoryHandler() *CategoryHandler {
return &CategoryHandler{}
}
func (h *CategoryHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "category/list.tmpl", nil)
}
func (h *CategoryHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchStatus = util.ConvertInt(r.PostFormValue("SearchStatus"), 9999)
q.SearchParentID = util.ConvertInt(r.PostFormValue("SearchParentID"), 0)
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
res, count, err := categoryservice.ListCategoriesCondition(r.Context(), q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}
func (h *CategoryHandler) Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "category/edit.tmpl", map[string]any{
"Item": &db.Category{Sort: 6666},
})
}
func (h *CategoryHandler) AddChildren(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
parentID := util.DefaultInt(vars, "parentID", 0)
vm := &db.Category{ParentID: int32(parentID), Sort: 6666}
tpl.HTML(w, r, "category/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *CategoryHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
vm := &db.Category{Sort: 6666}
if id > 0 {
ctx := r.Context()
vm, _ = db.Engine.GetCategory(ctx, int32(id))
}
tpl.HTML(w, r, "category/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *CategoryHandler) Save(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
parentID := util.ConvertInt(r.PostFormValue("ParentID"), 0)
name := r.PostFormValue("Name")
icon := r.PostFormValue("File")
if len(icon) > 0 && !strings.HasPrefix(icon, "/") {
icon = "/" + icon
}
description := r.PostFormValue("Description")
letter := r.PostFormValue("Letter")
if len(letter) == 0 {
letter = uuid.New().String()
}
sort := util.ConvertInt(r.PostFormValue("Sort"), 6666)
status := util.ConvertInt(r.PostFormValue("Status"), 9999)
ctx := r.Context()
var parent *db.Category
if parentID > 0 {
var err error
parent, err = db.Engine.GetCategory(ctx, int32(parentID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "父级节点错误"})
return
}
}
path := fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID)
path = strings.ReplaceAll(path, ",,", ",")
if id == 0 {
arg := &db.CreateCategoryParams{
Name: name,
Icon: icon,
Description: description,
Letter: letter,
ParentID: int32(parentID),
ParentPath: path,
Status: int16(status),
Sort: int32(sort),
}
_, err := db.Engine.CreateCategory(ctx, arg)
if err != nil {
if db.IsUniqueViolation(err) {
tpl.JSON(w, tpl.Response{Success: false, Message: "名称已存在"})
return
}
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "添加成功"})
} else {
arg := &db.UpdateCategoryParams{
ID: int32(id),
Name: pgtype.Text{
String: name,
Valid: true,
},
Icon: pgtype.Text{
String: icon,
Valid: len(icon) > 0,
},
Description: pgtype.Text{
String: description,
Valid: len(description) > 0,
},
Letter: pgtype.Text{
String: letter,
Valid: len(letter) > 0,
},
ParentID: pgtype.Int4{
Int32: int32(parentID),
Valid: true,
},
ParentPath: pgtype.Text{
String: path,
Valid: true,
},
Sort: pgtype.Int4{
Int32: int32(sort),
Valid: true,
},
Status: pgtype.Int2{
Int16: int16(status),
Valid: true,
},
}
_, err := db.Engine.UpdateCategory(ctx, arg)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "更新成功"})
}
}
func (h *CategoryHandler) DTree(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
res, err := categoryservice.DTreeCategory(ctx, int32(id))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
rsp := tpl.ResponseDtree{
Status: tpl.ResponseDtreeStatus{
Code: 200,
Message: "OK",
},
Data: res,
}
tpl.JSON(w, rsp)
}
func (h *CategoryHandler) XmSelect(w http.ResponseWriter, r *http.Request) {
all, err := categoryservice.ListByLetter(r.Context(), r.URL.Query().Get("letter"))
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
var res []*dto.XmSelectStrDto
for _, v := range all {
res = append(res, &dto.XmSelectStrDto{
Name: v.Name,
Value: strconv.FormatInt(int64(v.ID), 10),
})
}
tpl.JSON(w, res)
}
func (h *CategoryHandler) Refresh(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := categoryservice.RefreshCategory(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}
func (h *CategoryHandler) RebuildParentPath(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := db.Engine.CategoryRebuildPath(ctx)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "重建成功"})
}

View File

@@ -0,0 +1,34 @@
package common
import (
"net/http"
"management/internal/config"
captchaservice "management/internal/service/captcha"
"management/internal/tpl"
)
type CaptchaResponse struct {
CaptchaID string `json:"captcha_id"`
PicPath string `json:"pic_path"`
CaptchaLength int `json:"captcha_length"`
OpenCaptcha int `json:"open_captcha"`
}
func Captcha(w http.ResponseWriter, r *http.Request) {
keyLong := config.File.Captcha.KeyLong
oc := config.File.Captcha.OpenCaptcha
id, b64s, _, err := captchaservice.Generate(config.File.Captcha.ImgHeight, config.File.Captcha.ImgWidth, keyLong, 0.7, 80)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "获取验证码失败"})
return
}
rsp := CaptchaResponse{
CaptchaID: id,
PicPath: b64s,
CaptchaLength: keyLong,
OpenCaptcha: oc,
}
tpl.JSON(w, tpl.Response{Success: true, Message: "ok", Data: rsp})
}

View File

@@ -0,0 +1,97 @@
package common
import (
"io"
"mime/multipart"
"net/http"
"management/internal/tpl"
fileutil "management/internal/pkg/file"
)
const maxImageSize = 100 << 20 // 100 MB
func UploadImg(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
_, fh, err := r.FormFile("files")
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
path, err := fileutil.UploadFile(fh, fileutil.IMG)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "ok", Data: path})
}
func UploadFile(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
_, fh, err := r.FormFile("files")
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
path, err := fileutil.UploadFile(fh, fileutil.ALL)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "ok", Data: path})
}
type UploadFileRes struct {
Name string `json:"name"`
Path string `json:"path"`
}
func UploadMutilFiles(w http.ResponseWriter, r *http.Request) {
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(r.Body)
err := r.ParseMultipartForm(int64(maxImageSize))
if err != nil {
return
}
files := r.MultipartForm.File["files"]
var res []UploadFileRes
c := make(chan UploadFileRes, 2)
defer close(c)
for i, file := range files {
go func(item *multipart.FileHeader, key int) {
ufr := UploadFileRes{Name: item.Filename}
filePath, err := fileutil.UploadFile(item, fileutil.ALL)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
ufr.Path = filePath
c <- ufr
}(file, i)
}
for {
v, ok := <-c
if ok {
res = append(res, v)
}
if len(files) == len(res) {
break
}
}
tpl.JSON(w, tpl.Response{Success: true, Message: "ok", Data: res})
}

View File

@@ -0,0 +1,202 @@
package customer
import (
"net/http"
"strconv"
"strings"
"management/internal/db/model/dto"
"management/internal/db/model/form"
db "management/internal/db/sqlc"
"management/internal/global"
"management/internal/global/html"
"management/internal/global/know"
"management/internal/middleware/manage/auth"
"management/internal/pkg/snowflake"
"management/internal/router/manage/util"
categoryservice "management/internal/service/category"
"management/internal/tpl"
"github.com/jackc/pgx/v5/pgtype"
)
func List(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
cc, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.CustomerCategory)
ss, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.CustomerSource)
tpl.HTML(w, r, "customer/list.tmpl", map[string]any{
"Statuses": html.NewSelectControls(global.Statuses, 0),
"Categories": html.NewSelectStringControls(cc, "0"),
"Sources": html.NewSelectStringControls(ss, "0"),
})
}
func PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
category := util.ConvertInt32(r.PostFormValue("category"), 9999)
if category == 0 {
category = 9999
}
source := util.ConvertInt32(r.PostFormValue("source"), 9999)
if source == 0 {
source = 9999
}
title := strings.TrimSpace(r.PostFormValue("title"))
var search string
if len(title) > 0 {
search = "%" + title + "%"
if strings.HasSuffix(title, ":") {
search = title[:len(title)-1] + "%"
}
}
arg := &db.ListCustomerConditionParam{
IsTitle: len(search) > 0,
Title: search,
Status: util.ConvertInt16(r.PostFormValue("status"), 9999),
Category: category,
Source: source,
PageID: util.ConvertInt32(r.PostFormValue("page"), 1),
PageSize: util.ConvertInt32(r.PostFormValue("rows"), 10),
}
arg.TimeBegin, arg.TimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
res, total, err := db.Engine.ListCustomerCondition(ctx, arg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: total,
Data: res,
}
tpl.JSON(w, data)
}
func Add(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
cc, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.CustomerCategory)
ss, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.CustomerSource)
tpl.HTML(w, r, "customer/edit.tmpl", map[string]any{
"Item": &form.CustomerForm{},
"Statuses": html.NewSelectControls(global.Statuses, 0),
"Categories": html.NewSelectStringControls(cc, "0"),
"Sources": html.NewSelectStringControls(ss, "0"),
})
}
func Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.ConvertInt[int64](vars.Get("id"), 0)
customer := &form.CustomerForm{}
ctx := r.Context()
if id > 0 {
if cus, err := db.Engine.GetCustomer(ctx, id); err == nil {
customer = customer.ToForm(cus)
if u, err := db.Engine.GetSysUser(ctx, cus.CreatedBy); err == nil {
customer.CreatedBy = u.Username
}
if u, err := db.Engine.GetSysUser(ctx, cus.UpdatedBy); err == nil {
customer.UpdatedBy = u.Username
}
}
}
cc, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.CustomerCategory)
ss, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.CustomerSource)
tpl.HTML(w, r, "customer/edit.tmpl", map[string]any{
"Item": customer,
"Statuses": html.NewSelectControls(global.Statuses, customer.Status),
"Categories": html.NewSelectStringControls(cc, strconv.Itoa(int(customer.Category))),
"Sources": html.NewSelectStringControls(ss, strconv.Itoa(int(customer.Source))),
})
}
func Save(w http.ResponseWriter, r *http.Request) {
data := &form.CustomerForm{}
if err := form.BindForm(r, data); err != nil {
tpl.JSONERR(w, err.Error())
return
}
ctx := r.Context()
authUser := auth.AuthUser(ctx)
if data.ID > 0 {
arg := &db.UpdateCustomerParams{
ID: data.ID,
Name: pgtype.Text{
String: data.Name,
Valid: true,
},
Category: pgtype.Int4{
Int32: data.Category,
Valid: true,
},
Source: pgtype.Int4{
Int32: data.Source,
Valid: true,
},
Address: pgtype.Text{
String: data.Address,
Valid: true,
},
ContactName: pgtype.Text{
String: data.ContactName,
Valid: true,
},
ContactPhone: pgtype.Text{
String: data.ContactPhone,
Valid: true,
},
Status: pgtype.Int2{
Int16: data.Status,
Valid: true,
},
UpdatedBy: pgtype.Int4{
Int32: authUser.ID,
Valid: true,
},
}
_, err := db.Engine.UpdateCustomer(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "更新成功")
} else {
arg := &db.CreateCustomerParams{
ID: snowflake.GetId(),
Name: data.Name,
Category: data.Category,
Source: data.Source,
Address: data.Address,
ContactName: data.ContactName,
ContactPhone: data.ContactPhone,
CreatedBy: authUser.ID,
}
_, err := db.Engine.CreateCustomer(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "添加成功")
}
}
func XmSelect(w http.ResponseWriter, r *http.Request) {
all, err := db.Engine.AllCustomers(r.Context())
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
var res []*dto.XmSelectStrDto
for _, v := range all {
res = append(res, &dto.XmSelectStrDto{
Name: v.Name,
Value: strconv.FormatInt(v.ID, 10),
})
}
tpl.JSON(w, res)
}

View File

@@ -0,0 +1,208 @@
package expense
import (
"errors"
"net/http"
"strconv"
"strings"
"time"
"management/internal/db/model/form"
db "management/internal/db/sqlc"
"management/internal/global"
"management/internal/global/html"
"management/internal/global/know"
"management/internal/middleware/manage/auth"
"management/internal/pkg/convertor"
"management/internal/router/manage/util"
categoryservice "management/internal/service/category"
projectservice "management/internal/service/project"
"management/internal/tpl"
"github.com/jackc/pgx/v5/pgtype"
)
func List(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pp := projectservice.AllProjects(ctx)
cc, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.ExpenseCategory)
tpl.HTML(w, r, "expense/list.tmpl", map[string]any{
"Statuses": html.NewSelectControls(global.Statuses, 0),
"Projects": html.NewSelectStringControls(pp, "0"),
"Categories": html.NewSelectStringControls(cc, "0"),
})
}
func PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
project := convertor.ConvertInt[int64](r.PostFormValue("project"), 9999)
if project == 0 {
project = 9999
}
budget := convertor.ConvertInt[int64](r.PostFormValue("budget"), 9999)
if budget == 0 {
budget = 9999
}
category := convertor.ConvertInt[int32](r.PostFormValue("category"), 9999)
if category == 0 {
category = 9999
}
title := strings.TrimSpace(r.PostFormValue("title"))
var search string
if len(title) > 0 {
search = "%" + title + "%"
if strings.HasSuffix(title, ":") {
search = title[:len(title)-1] + "%"
}
}
arg := &db.ListExpenseConditionParam{
ProjectID: project,
BudgetID: budget,
ExpenseType: category,
IsTitle: len(search) > 0,
Title: search,
Status: util.ConvertInt16(r.PostFormValue("status"), 9999),
PageID: util.ConvertInt32(r.PostFormValue("page"), 1),
PageSize: util.ConvertInt32(r.PostFormValue("rows"), 10),
}
arg.TimeBegin, arg.TimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
res, total, err := db.Engine.ListExpenseCondition(ctx, arg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: total,
Data: res,
}
tpl.JSON(w, data)
}
func Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "expense/edit.tmpl", map[string]any{
"Item": &form.ExpenseForm{},
"Statuses": html.NewSelectControls(global.Statuses, 0),
})
}
func Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.ConvertInt[int64](vars.Get("id"), 0)
expense := &form.ExpenseForm{}
ctx := r.Context()
if id > 0 {
if cus, err := db.Engine.GetExpense(ctx, id); err == nil {
expense = expense.ToForm(cus)
if u, err := db.Engine.GetSysUser(ctx, cus.CreatedUserID); err == nil {
expense.CreatedName = u.Username
}
if u, err := db.Engine.GetSysUser(ctx, cus.UpdatedUserID); err == nil {
expense.UpdatedName = u.Username
}
}
}
tpl.HTML(w, r, "expense/edit.tmpl", map[string]any{
"Item": expense,
"Statuses": html.NewSelectControls(global.Statuses, expense.Status),
})
}
func Save(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
data, err := validForm(r)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
authUser := auth.AuthUser(ctx)
if data.ID > 0 {
arg := &db.UpdateExpenseParams{
ID: data.ID,
ProjectID: pgtype.Int8{
Int64: data.ProjectID,
Valid: true,
},
BudgetID: pgtype.Int8{
Int64: data.BudgetID,
Valid: true,
},
Amount: data.AmountF,
ExpensesAt: pgtype.Timestamptz{
Time: data.ExpensesAt,
Valid: true,
},
ExpensesType: pgtype.Int4{
Int32: data.ExpensesType,
Valid: true,
},
Remark: pgtype.Text{
String: data.Remark,
Valid: true,
},
Status: pgtype.Int2{
Int16: data.Status,
Valid: true,
},
UpdatedUserID: pgtype.Int4{
Int32: authUser.ID,
Valid: true,
},
}
_, err := db.Engine.UpdateExpense(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "更新成功")
} else {
arg := &db.CreateExpenseParams{
ProjectID: data.ProjectID,
BudgetID: data.BudgetID,
Amount: data.AmountF,
ExpensesAt: data.ExpensesAt,
ExpensesType: data.ExpensesType,
Remark: data.Remark,
Status: data.Status,
CreatedUserID: authUser.ID,
}
_, err := db.Engine.CreateExpense(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "添加成功")
}
}
func validForm(r *http.Request) (form.ExpenseForm, error) {
var err error
data := form.ExpenseForm{}
data.ID = convertor.ConvertInt[int64](r.PostFormValue("ID"), 0)
data.ProjectID, err = strconv.ParseInt(r.PostFormValue("ProjectID"), 10, 64)
if err != nil || data.ProjectID == 0 {
return data, errors.New("项目不能为空")
}
data.BudgetID, err = strconv.ParseInt(r.PostFormValue("BudgetID"), 10, 64)
if err := data.AmountF.Scan(r.PostFormValue("Amount")); err != nil {
return data, errors.New("报销金额格式错误")
}
data.ExpensesAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("ExpensesAt"), time.Local)
if err != nil {
return data, errors.New("报销时间格式错误")
}
expensesType, err := strconv.ParseInt(r.PostFormValue("ExpensesType"), 10, 64)
if err != nil || expensesType == 0 {
return data, errors.New("报销类型不能为空")
}
data.ExpensesType = int32(expensesType)
data.Status = convertor.ConvertInt[int16](r.PostFormValue("Status"), 9999)
return data, nil
}

View File

@@ -0,0 +1,11 @@
package router
import (
"net/http"
"management/internal/tpl"
)
func Home(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "home/home.tmpl", nil)
}

View File

@@ -0,0 +1,226 @@
package expense
import (
"errors"
"net/http"
"strconv"
"strings"
"time"
"management/internal/db/model/form"
db "management/internal/db/sqlc"
"management/internal/global"
"management/internal/global/html"
"management/internal/global/know"
"management/internal/middleware/manage/auth"
"management/internal/pkg/convertor"
"management/internal/router/manage/util"
categoryservice "management/internal/service/category"
projectservice "management/internal/service/project"
"management/internal/tpl"
"github.com/jackc/pgx/v5/pgtype"
)
func List(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pp := projectservice.AllProjects(ctx)
cc, _ := categoryservice.GetParentCategorySelectLetter(ctx, know.IncomeCategory)
tpl.HTML(w, r, "income/list.tmpl", map[string]any{
"Statuses": html.NewSelectControls(global.Statuses, 0),
"Projects": html.NewSelectStringControls(pp, "0"),
"Categories": html.NewSelectStringControls(cc, "0"),
})
}
func PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
project := convertor.ConvertInt[int64](r.PostFormValue("project"), 9999)
if project == 0 {
project = 9999
}
budget := convertor.ConvertInt[int64](r.PostFormValue("budget"), 9999)
if budget == 0 {
budget = 9999
}
category := convertor.ConvertInt[int32](r.PostFormValue("category"), 9999)
if category == 0 {
category = 9999
}
title := strings.TrimSpace(r.PostFormValue("title"))
var search string
if len(title) > 0 {
search = "%" + title + "%"
if strings.HasSuffix(title, ":") {
search = title[:len(title)-1] + "%"
}
}
arg := &db.ListIncomeConditionParam{
ProjectID: project,
BudgetID: budget,
IncomeType: category,
IsTitle: len(search) > 0,
Title: search,
Status: util.ConvertInt16(r.PostFormValue("status"), 9999),
PageID: util.ConvertInt32(r.PostFormValue("page"), 1),
PageSize: util.ConvertInt32(r.PostFormValue("rows"), 10),
}
arg.TimeBegin, arg.TimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
res, total, err := db.Engine.ListIncomeCondition(ctx, arg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: total,
Data: res,
}
tpl.JSON(w, data)
}
func Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "income/edit.tmpl", map[string]any{
"Item": &form.IncomeForm{},
"Statuses": html.NewSelectControls(global.Statuses, 0),
})
}
func Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.ConvertInt[int64](vars.Get("id"), 0)
income := &form.IncomeForm{}
ctx := r.Context()
if id > 0 {
if cus, err := db.Engine.GetIncome(ctx, id); err == nil {
income = income.ToForm(cus)
if u, err := db.Engine.GetSysUser(ctx, cus.CreatedUserID); err == nil {
income.CreatedName = u.Username
}
if u, err := db.Engine.GetSysUser(ctx, cus.UpdatedUserID); err == nil {
income.UpdatedName = u.Username
}
}
}
tpl.HTML(w, r, "income/edit.tmpl", map[string]any{
"Item": income,
"Statuses": html.NewSelectControls(global.Statuses, income.Status),
})
}
func Save(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
data, err := validForm(r)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
authUser := auth.AuthUser(ctx)
if data.ID > 0 {
arg := &db.UpdateIncomeParams{
ID: data.ID,
ProjectID: pgtype.Int8{
Int64: data.ProjectID,
Valid: true,
},
BudgetID: pgtype.Int8{
Int64: data.BudgetID,
Valid: true,
},
Amount: data.AmountF,
IncomeAt: pgtype.Timestamptz{
Time: data.IncomeAt,
Valid: true,
},
IncomeType: pgtype.Int4{
Int32: data.IncomeType,
Valid: true,
},
IncomeBank: pgtype.Int4{
Int32: data.IncomeBank,
Valid: true,
},
Remark: pgtype.Text{
String: data.Remark,
Valid: true,
},
Status: pgtype.Int2{
Int16: data.Status,
Valid: true,
},
UpdatedUserID: pgtype.Int4{
Int32: authUser.ID,
Valid: true,
},
}
_, err := db.Engine.UpdateIncome(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "更新成功")
} else {
arg := &db.CreateIncomeParams{
ProjectID: data.ProjectID,
BudgetID: data.BudgetID,
Amount: data.AmountF,
IncomeAt: data.IncomeAt,
IncomeType: data.IncomeType,
IncomeBank: data.IncomeBank,
Remark: data.Remark,
Status: data.Status,
CreatedUserID: authUser.ID,
}
_, err := db.Engine.CreateIncome(ctx, arg)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "添加成功")
}
}
func validForm(r *http.Request) (form.IncomeForm, error) {
var err error
data := form.IncomeForm{}
data.ID = convertor.ConvertInt[int64](r.PostFormValue("ID"), 0)
data.ProjectID, err = strconv.ParseInt(r.PostFormValue("ProjectID"), 10, 64)
if err != nil || data.ProjectID == 0 {
return data, errors.New("项目不能为空")
}
data.BudgetID, err = strconv.ParseInt(r.PostFormValue("BudgetID"), 10, 64)
if err != nil {
data.BudgetID = 0
}
if err := data.AmountF.Scan(r.PostFormValue("Amount")); err != nil {
return data, errors.New("报销金额格式错误")
}
data.IncomeAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("IncomeAt"), time.Local)
if err != nil {
return data, errors.New("回款时间格式错误")
}
incomeType, err := strconv.ParseInt(r.PostFormValue("IncomeType"), 10, 64)
if err != nil {
return data, errors.New("收入类型数据错误")
}
data.IncomeType = int32(incomeType)
incomeBank, err := strconv.ParseInt(r.PostFormValue("IncomeBank"), 10, 64)
if err != nil {
return data, errors.New("收入银行数据错误")
}
data.IncomeBank = int32(incomeBank)
data.Remark = r.PostFormValue("Remark")
data.Status = convertor.ConvertInt[int16](r.PostFormValue("Status"), 9999)
return data, nil
}

View File

@@ -0,0 +1,144 @@
package oauth
import (
"encoding/json"
"net/http"
"strings"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
authglobal "management/internal/global/auth"
"management/internal/pkg/crypto"
"management/internal/pkg/session"
captchaservice "management/internal/service/captcha"
systemservice "management/internal/service/system"
"management/internal/tpl"
"github.com/zhang2092/browser"
)
func Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var user dto.AuthorizeUser
u := session.GetBytes(ctx, authglobal.StoreName)
if err := json.Unmarshal(u, &user); err == nil {
// 判断租户是否一致, 一致则刷新令牌,跳转到首页
if err := session.RenewToken(ctx); err == nil {
session.Put(ctx, authglobal.StoreName, u)
http.Redirect(w, r, "/home.html", http.StatusFound)
return
}
}
session.Destroy(ctx)
tpl.HTML(w, r, "oauth/login.tmpl", nil)
}
func PostLogin(w http.ResponseWriter, r *http.Request) {
email := strings.TrimSpace(r.PostFormValue("email"))
password := strings.TrimSpace(r.PostFormValue("password"))
captchaID := strings.TrimSpace(r.PostFormValue("captcha_id"))
captcha := strings.TrimSpace(r.PostFormValue("captcha"))
if len(email) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "请填写邮箱"})
return
}
if len(password) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "请填写密码"})
return
}
if len(captcha) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "请填写验证码"})
return
}
if !captchaservice.Verify(captchaID, captcha, true) {
tpl.JSON(w, tpl.Response{Success: false, Message: "验证码错误"})
return
}
br, err := browser.NewBrowser(r.Header.Get("User-Agent"))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "平台信息获取错误"})
return
}
ctx := r.Context()
log := &db.CreateSysUserLoginLogParams{
CreatedAt: time.Now(),
Email: email,
IsSuccess: false,
RefererUrl: r.Header.Get("Referer"),
Url: r.URL.RequestURI(),
Os: br.Platform().Name(),
Ip: r.RemoteAddr,
Browser: br.Name(),
}
user, err := systemservice.GetSysUserByEmail(ctx, email)
if err != nil {
log.Message = err.Error()
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
log.UserUuid = user.Uuid
log.Username = user.Username
err = crypto.BcryptComparePassword(user.HashedPassword, password+user.Salt)
if err != nil {
log.Message = "compare password failed"
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: false, Message: "compare password failed"})
return
}
// 登陆成功
if user.RoleID == 0 {
log.Message = "账号没有配置角色, 请联系管理员"
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: false, Message: "账号没有配置角色, 请联系管理员"})
return
}
sysRole, err := systemservice.GetSysRole(ctx, user.RoleID)
if err != nil {
log.Message = "账号配置的角色错误, 请联系管理员"
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: false, Message: "账号配置的角色错误, 请联系管理员"})
return
}
auth := dto.AuthorizeUser{
ID: user.ID,
Uuid: user.Uuid,
Email: user.Email,
Username: user.Username,
RoleID: sysRole.ID,
RoleName: sysRole.Name,
OS: log.Os,
IP: log.Ip,
Browser: log.Browser,
}
b, err := json.Marshal(auth)
if err != nil {
log.Message = err.Error()
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
session.Put(ctx, authglobal.StoreName, b)
log.IsSuccess = true
log.Message = "登陆成功"
_ = systemservice.CreateSysUserLoginLog(ctx, log)
tpl.JSON(w, tpl.Response{Success: true, Message: "login successful"})
}
func Logout(w http.ResponseWriter, r *http.Request) {
session.Destroy(r.Context())
http.Redirect(w, r, "/", http.StatusFound)
}

View File

@@ -0,0 +1,738 @@
package project
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
formDto "management/internal/db/model/form"
"management/internal/db/model/view"
db "management/internal/db/sqlc"
"management/internal/global"
"management/internal/global/html"
"management/internal/middleware/manage/auth"
"management/internal/pkg/convertor"
"management/internal/pkg/snowflake"
"management/internal/router/manage/util"
projectservice "management/internal/service/project"
"management/internal/tpl"
"github.com/jackc/pgx/v5/pgtype"
)
func List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "project/list.tmpl", map[string]any{
"Statuses": html.NewSelectControls(global.ProjectStatuses, 0),
})
}
func PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
title := strings.TrimSpace(r.PostFormValue("title"))
var search string
if len(title) > 0 {
search = "%" + title + "%"
if strings.HasSuffix(title, ":") {
search = title[:len(title)-1] + "%"
}
}
arg := &db.ListProjectConditionParam{
IsTitle: len(search) > 0,
Title: search,
Status: util.ConvertInt16(r.PostFormValue("status"), 9999),
PageID: util.ConvertInt32(r.PostFormValue("page"), 1),
PageSize: util.ConvertInt32(r.PostFormValue("rows"), 10),
}
arg.TimeBegin, arg.TimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("timeBegin"), r.PostFormValue("timeEnd"))
res, total, err := db.Engine.ListProjectCondition(ctx, arg)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: total,
Data: res,
}
tpl.JSON(w, data)
}
func Add(w http.ResponseWriter, r *http.Request) {
authUser := auth.AuthUser(r.Context())
tpl.HTML(w, r, "project/edit.tmpl", map[string]any{
"Item": &formDto.ProjectForm{
ApplyUserID: authUser.ID,
ProjectFiles: &formDto.ProjectFileForm{
ProjectFileItems: []*formDto.ProjectFileItemForm{},
},
},
"Statuses": html.NewSelectControls(global.ProjectStatuses, 0),
})
}
func Edit(w http.ResponseWriter, r *http.Request) {
form := &formDto.ProjectForm{}
id := convertor.ConvertInt[int64](r.URL.Query().Get("id"), 0)
if id > 0 {
ctx := r.Context()
if po, err := db.Engine.GetProject(ctx, id); err == nil {
pfs, err := db.Engine.ListProjectFiles(ctx, po.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
form = form.ToForm(po, pfs)
if form.ApplyUserID == 0 {
authUser := auth.AuthUser(ctx)
form.ApplyUserID = authUser.ID
}
if c, err := db.Engine.GetCustomer(ctx, po.CustomerID); err == nil {
form.CustomerName = c.Name
}
// if u, err := db.Engine.GetSysUser(ctx, po.ManagerID); err == nil {
// form.ManagerName = u.Username
// }
// if len(po.Members) > 0 {
// arr := strings.Split(po.Members, ",")
// if len(arr) > 0 {
// var ids []int32
// for _, v := range arr {
// ids = append(ids, convertor.ConvertInt[int32](v, 0))
// }
// if rows, err := db.Engine.ListSysUserByIds(ctx, ids); err == nil && len(rows) > 0 {
// log.Println("rows: ", rows)
// var names []string
// for _, v := range rows {
// names = append(names, v.Username)
// }
// form.MembersName = strings.Join(names, ",")
// }
// }
// }
// if u, err := db.Engine.GetSysUser(ctx, po.ApplyUserID); err == nil {
// form.ApplyUserName = u.Username
// }
if u, err := db.Engine.GetSysUser(ctx, po.CreatedUserID); err == nil {
form.CreatedName = u.Username
}
if u, err := db.Engine.GetSysUser(ctx, po.UpdatedUserID); err == nil {
form.UpdatedName = u.Username
}
}
}
tpl.HTML(w, r, "project/edit.tmpl", map[string]any{
"Item": form,
"Statuses": html.NewSelectControls(global.ProjectStatuses, form.Status),
})
}
func Save(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
form, err := validForm(r)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
authUser := auth.AuthUser(ctx)
if form.ID > 0 {
p := &db.UpdateProjectParams{
ID: form.ID,
Name: pgtype.Text{
String: form.Name,
Valid: true,
},
StartAt: pgtype.Timestamptz{
Time: form.StartAt,
Valid: true,
},
EndAt: pgtype.Timestamptz{
Time: form.EndAt,
Valid: true,
},
CustomerID: pgtype.Int8{
Int64: form.CustomerID,
Valid: true,
},
TotalMoney: form.TotalMoneyF,
Description: pgtype.Text{
String: form.Description,
Valid: true,
},
ApplyAt: pgtype.Timestamptz{
Time: form.ApplyAt,
Valid: true,
},
ApplyUserID: pgtype.Int4{
Int32: form.ApplyUserID,
Valid: true,
},
ManagerID: pgtype.Int4{
Int32: form.ManagerID,
Valid: true,
},
Members: pgtype.Text{
String: form.Members,
Valid: true,
},
Status: pgtype.Int2{
Int16: form.Status,
Valid: true,
},
UpdatedUserID: pgtype.Int4{
Int32: authUser.ID,
Valid: true,
},
}
cpfs := []*db.CreateProjectFileParams{}
for _, pfile := range form.ProjectFiles.ProjectFileItems {
cpfs = append(cpfs, &db.CreateProjectFileParams{
ID: snowflake.GetId(),
Name: pfile.Name,
Path: pfile.Path,
ProjectID: form.ID,
CreatedUserID: authUser.ID,
})
}
err := projectservice.UpdateProject(ctx, p, cpfs)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "更新成功")
} else {
p := &db.CreateProjectParams{
ID: snowflake.GetId(),
Name: form.Name,
StartAt: form.StartAt,
EndAt: form.EndAt,
CustomerID: form.CustomerID,
TotalMoney: form.TotalMoneyF,
Description: form.Description,
ApplyAt: form.ApplyAt,
ApplyUserID: form.ApplyUserID,
ManagerID: form.ManagerID,
Members: form.Members,
Status: form.Status,
CreatedUserID: authUser.ID,
}
cpfs := []*db.CreateProjectFileParams{}
for _, pfile := range form.ProjectFiles.ProjectFileItems {
cpfs = append(cpfs, &db.CreateProjectFileParams{
ID: snowflake.GetId(),
Name: pfile.Name,
Path: pfile.Path,
ProjectID: p.ID,
CreatedUserID: authUser.ID,
})
}
err := projectservice.CreateProject(ctx, p, cpfs)
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
tpl.JSONOK(w, "添加成功")
}
}
func XmSelect(w http.ResponseWriter, r *http.Request) {
all, err := db.Engine.AllProjects(r.Context())
if err != nil {
tpl.JSONERR(w, err.Error())
return
}
var res []*dto.XmSelectStrDto
for _, v := range all {
res = append(res, &dto.XmSelectStrDto{
Name: v.Name,
Value: strconv.FormatInt(v.ID, 10),
})
}
tpl.JSON(w, res)
}
func Dashboard(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "project/dashboard.tmpl", map[string]any{
"Projects": projectservice.AllProjects(r.Context()),
})
}
func PostDashboard(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
t := r.PostFormValue("type")
if t == "project" {
var projectIncome float64
var projectExpense float64
projectID := convertor.ConvertInt[int64](r.PostFormValue("projectID"), 0)
if projectID == 0 {
si, _ := db.Engine.SumIncome(ctx)
projectIncome = convertor.NumericToFloat64(si)
se, _ := db.Engine.SumExpense(ctx)
projectExpense = convertor.NumericToFloat64(se)
} else {
pi, _ := db.Engine.SumIncomeByProjectID(ctx, projectID)
projectIncome = convertor.NumericToFloat64(pi)
pe, _ := db.Engine.SumExpenseByProjectID(ctx, projectID)
projectExpense = convertor.NumericToFloat64(pe)
}
projectProfit := projectIncome - projectExpense
var projectProfitRate float64 = 0
if projectExpense > 0 {
projectProfitRate = projectProfit / projectExpense
}
res := &view.DashboardProject{
ProjectIncome: projectIncome,
ProjectExpense: projectExpense,
ProjectProfit: projectProfit,
ProjectProfitRate: fmt.Sprintf("%0.2f%%", projectProfitRate*100),
IncomeExpenseEcharts: incomeExpenseEcharts(ctx, projectID),
IncomeEcharts: incomeEcharts(ctx, projectID),
ExpenseEcharts: expenseEcharts(ctx, projectID),
}
tpl.JSON(w, tpl.Response{Success: true, Message: "ok", Data: res})
return
} else if t == "budget" {
}
tpl.JSONERR(w, "failed to valid type")
}
func validForm(r *http.Request) (formDto.ProjectForm, error) {
// data := &form.ProjectForm{}
// if err := form.BindForm(r, data); err != nil {
// return data, err
// }
// if err := data.TotalMoneyF.Scan(data.TotalMoney); err != nil {
// return data, errors.New("总金额格式错误")
// }
// if len(data.Members) > 0 {
// membersSplit := strings.SplitSeq(data.Members, ",")
// for v := range membersSplit {
// m := convertor.ConvertInt[int32](v, 0)
// if m == 0 {
// return data, errors.New("项目成员数据错误")
// }
// }
// }
// data.ProjectFiles = &formDto.ProjectFileForm{
// ProjectFileItems: []*formDto.ProjectFileItemForm{},
// }
// if len(data.Paths) > 0 {
// fnsSplit := strings.SplitSeq(data.Paths, ",")
// for v := range fnsSplit {
// if len(v) > 0 {
// read := strings.Split(v, "|")
// if len(read) != 2 {
// return data, errors.New("文件路径数据错误")
// }
// pff := &formDto.ProjectFileItemForm{
// Name: read[0],
// Path: read[1],
// Combination: v,
// }
// data.ProjectFiles.ProjectFileItems = append(data.ProjectFiles.ProjectFileItems, pff)
// }
// }
// }
// return data, nil
var err error
form := formDto.ProjectForm{}
form.ID = convertor.ConvertInt[int64](r.PostFormValue("ID"), 0)
form.CustomerID, err = strconv.ParseInt(r.PostFormValue("CustomerID"), 10, 64)
if err != nil || form.CustomerID == 0 {
return form, errors.New("客户不能为空")
}
form.Name = r.PostFormValue("Name")
if len(form.Name) == 0 {
return form, errors.New("名称不能为空")
}
form.StartAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("StartAt"), time.Local)
if err != nil {
return form, errors.New("开始时间格式错误")
}
form.EndAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("EndAt"), time.Local)
if err != nil {
return form, errors.New("结束时间格式错误")
}
if err := form.TotalMoneyF.Scan(r.PostFormValue("TotalMoney")); err != nil {
return form, errors.New("总金额格式错误")
}
form.Description = r.PostFormValue("Description")
form.ApplyAt, err = time.ParseInLocation("2006-01-02", r.PostFormValue("ApplyAt"), time.Local)
if err != nil {
return form, errors.New("申请时间格式错误")
}
form.ApplyUserID = convertor.ConvertInt[int32](r.PostFormValue("ApplyUserID"), 0)
if form.ApplyUserID == 0 {
return form, errors.New("申请人不能为空")
}
form.ManagerID = convertor.ConvertInt[int32](r.PostFormValue("ManagerID"), 0)
if form.ManagerID == 0 {
return form, errors.New("项目经理不能为空")
}
form.Members = r.PostFormValue("Members")
if len(form.Members) > 0 {
membersSplit := strings.SplitSeq(form.Members, ",")
for v := range membersSplit {
m := convertor.ConvertInt[int32](v, 0)
if m == 0 {
return form, errors.New("项目成员数据错误")
}
}
}
form.Status = convertor.ConvertInt[int16](r.PostFormValue("Status"), 9999)
form.ProjectFiles = &formDto.ProjectFileForm{
ProjectFileItems: []*formDto.ProjectFileItemForm{},
}
fns := r.PostFormValue("Paths")
if len(fns) > 0 {
fnsSplit := strings.SplitSeq(fns, ",")
for v := range fnsSplit {
if len(v) > 0 {
read := strings.Split(v, "|")
if len(read) != 2 {
return form, errors.New("文件路径数据错误")
}
pff := &formDto.ProjectFileItemForm{
Name: read[0],
Path: read[1],
Combination: v,
}
form.ProjectFiles.ProjectFileItems = append(form.ProjectFiles.ProjectFileItems, pff)
}
}
}
return form, nil
}
func incomeExpenseEcharts(ctx context.Context, projectId int64) view.EchartsOption {
var name []string
var income []float64
var expense []float64
if projectId == 0 {
rows, err := db.Engine.StatisticsProjects(ctx)
if err != nil || len(rows) == 0 {
return view.EchartsOption{}
}
for _, row := range rows {
name = append(name, row.Name)
income = append(income, convertor.NumericToFloat64(row.Income))
expense = append(expense, convertor.NumericToFloat64(row.Expense))
}
} else {
row, err := db.Engine.StatisticsProjectItem(ctx, projectId)
if err != nil || row == nil {
return view.EchartsOption{}
}
name = append(name, row.Name)
income = append(income, convertor.NumericToFloat64(row.Income))
expense = append(expense, convertor.NumericToFloat64(row.Expense))
}
return view.EchartsOption{
Title: view.Title{
Text: "项目收支情况",
Left: "center",
Top: 2,
FontSize: 15,
TextStyle: view.TextStyle{
Color: "#666666",
FontWeight: "normal",
},
},
Color: []string{"#fed46b", "#2194ff"},
ToolTip: view.ToolTip{
Trigger: "axis",
AxisPointer: view.AxisPointer{
Type: "shadow",
},
},
Grid: view.Grid{
Left: "3%",
Right: "4%",
Bottom: "10%",
ContainLabel: true,
},
Legend: view.Legend{
Left: "center",
Top: "bottom",
Data: []string{"支出金额", "收入金额"},
},
XAxis: []view.XAxis{
{
Type: "category",
Data: name,
AxisTick: view.AxisTick{
AlignWithLabel: true,
},
},
},
YAxis: []view.YAxis{
{
Type: "value",
Name: "单位:元",
},
},
BarMaxWidth: "30",
Label: view.Label{
Show: true,
Position: "top",
},
Series: []view.Series{
{
Name: "支出金额",
Type: "bar",
Data: expense,
ItemStyle: view.ItemStyle{
Normal: view.Normal{
Color: "#f89588",
},
},
},
{
Name: "收入金额",
Type: "bar",
Data: income,
ItemStyle: view.ItemStyle{
Normal: view.Normal{
Color: "#76da91",
},
},
},
},
}
}
func incomeEcharts(ctx context.Context, projectId int64) view.EchartsOption {
var name []string
data := []view.DataItem{}
if projectId == 0 {
rows, err := db.Engine.StatisticsIncome(ctx)
if err != nil || len(rows) == 0 {
return view.EchartsOption{}
}
for _, row := range rows {
name = append(name, row.IncomeTypeName)
data = append(data, view.DataItem{
Name: row.IncomeTypeName,
Value: convertor.NumericToFloat64(row.TotalAmount),
})
}
} else {
rows, err := db.Engine.StatisticsIncomeByProjectID(ctx, projectId)
if err != nil || len(rows) == 0 {
return view.EchartsOption{}
}
for _, row := range rows {
name = append(name, row.IncomeTypeName)
data = append(data, view.DataItem{
Name: row.IncomeTypeName,
Value: convertor.NumericToFloat64(row.TotalAmount),
})
}
}
return view.EchartsOption{
Title: view.Title{
Text: "收入分布",
Left: "center",
Orient: "vertical",
FontSize: 15,
TextStyle: view.TextStyle{
Color: "#666666",
FontWeight: "normal",
},
},
ToolTip: view.ToolTip{
Trigger: "item",
},
Legend: view.Legend{
Left: "center",
Top: "bottom",
Data: name,
},
Color: []string{"#63b2ee", "#76da91", "#f8cb7f", "#f89588", "#7cd6cf", "#9192ab", "#7898e1", "#efa666", "#eddd86", "#9987ce", "#63b2ee", "#76da91"},
Series: []view.Series{
{
Type: "pie",
Radius: "50%",
Data: data,
Label: view.Label{
Show: true,
TextStyle: view.TextStyle{
Color: "#666666",
},
Normal: view.LableNormal{
Formatter: "{c} ({d}%)",
TextStyle: view.TextStyle{
Color: "#666666",
FontWeight: "normal",
},
},
},
},
},
}
}
func expenseEcharts(ctx context.Context, projectId int64) view.EchartsOption {
var name []string
data := []view.DataItem{}
if projectId == 0 {
rows, err := db.Engine.StatisticsExpense(ctx)
if err != nil || len(rows) == 0 {
return view.EchartsOption{}
}
for _, row := range rows {
name = append(name, row.ExpensesTypeName)
data = append(data, view.DataItem{
Name: row.ExpensesTypeName,
Value: convertor.NumericToFloat64(row.TotalAmount),
})
}
} else {
rows, err := db.Engine.StatisticsExpenseByProjectID(ctx, projectId)
if err != nil || len(rows) == 0 {
return view.EchartsOption{}
}
for _, row := range rows {
name = append(name, row.ExpensesTypeName)
data = append(data, view.DataItem{
Name: row.ExpensesTypeName,
Value: convertor.NumericToFloat64(row.TotalAmount),
})
}
}
return view.EchartsOption{
Title: view.Title{
Text: "支出分布",
Left: "center",
Orient: "vertical",
FontSize: 15,
TextStyle: view.TextStyle{
Color: "#666666",
FontWeight: "normal",
},
},
ToolTip: view.ToolTip{
Trigger: "item",
},
Legend: view.Legend{
Left: "center",
Top: "bottom",
Data: name,
},
Color: []string{"#63b2ee", "#76da91", "#f8cb7f", "#f89588", "#7cd6cf", "#9192ab", "#7898e1", "#efa666", "#eddd86", "#9987ce", "#63b2ee", "#76da91"},
Series: []view.Series{
{
Type: "pie",
Radius: "50%",
Data: data,
Label: view.Label{
Show: true,
TextStyle: view.TextStyle{
Color: "#666666",
},
Normal: view.LableNormal{
Formatter: "{c} ({d}%)",
TextStyle: view.TextStyle{
Color: "#666666",
FontWeight: "normal",
},
},
},
},
},
}
// return view.EchartsOption{
// Title: view.Title{
// Text: "支出分布",
// Left: "center",
// Top: 2,
// FontSize: 15,
// TextStyle: view.TextStyle{
// Color: "#666666",
// FontWeight: "normal",
// },
// },
// Color: []string{"#fed46b", "#2194ff"},
// ToolTip: view.ToolTip{
// Trigger: "axis",
// AxisPointer: view.AxisPointer{
// Type: "shadow",
// },
// },
// Grid: view.Grid{
// Left: "3%",
// Right: "4%",
// Bottom: "10%",
// ContainLabel: true,
// },
// XAxis: []view.XAxis{
// {
// Type: "category",
// Data: name,
// AxisTick: view.AxisTick{
// AlignWithLabel: true,
// },
// },
// },
// YAxis: []view.YAxis{
// {
// Type: "value",
// Name: "单位:元",
// },
// },
// BarMaxWidth: "30",
// Label: view.Label{
// Show: true,
// Position: "top",
// },
// Series: []view.Series{
// {
// Type: "bar",
// Data: expense,
// ItemStyle: view.ItemStyle{
// Normal: view.Normal{
// Color: "#f89588",
// },
// },
// },
// },
// }
}

View File

@@ -0,0 +1,227 @@
package router
import (
"net/http"
"management/internal/middleware/manage/audit"
"management/internal/middleware/manage/auth"
"management/internal/middleware/manage/nosurf"
"management/internal/middleware/manage/session"
budgethandler "management/internal/router/manage/budget"
cachehandler "management/internal/router/manage/cache"
categoryhandler "management/internal/router/manage/category"
commonhandler "management/internal/router/manage/common"
customerhandler "management/internal/router/manage/customer"
expensehandler "management/internal/router/manage/expense"
incomehandler "management/internal/router/manage/income"
oauthhandler "management/internal/router/manage/oauth"
projecthandler "management/internal/router/manage/project"
systemhandler "management/internal/router/manage/system"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func NewRouter() *chi.Mux {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
// r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
staticServer := http.FileServer(http.Dir("./web/statics/"))
r.Handle("/statics/*", http.StripPrefix("/statics", staticServer))
uploadServer := http.FileServer(http.Dir("./upload/"))
r.Handle("/upload/*", http.StripPrefix("/upload", uploadServer))
r.Group(func(r chi.Router) {
r.Use(nosurf.NoSurf) // CSRF
r.Use(session.LoadSession) // Session
r.Get("/captcha", commonhandler.Captcha)
r.Get("/", oauthhandler.Login)
r.Post("/login", oauthhandler.PostLogin)
r.Get("/logout", oauthhandler.Logout)
r.With(auth.Authorize, audit.Audit).Post("/upload/img", commonhandler.UploadImg)
r.With(auth.Authorize, audit.Audit).Post("/upload/file", commonhandler.UploadFile)
r.With(auth.Authorize, audit.Audit).Post("/upload/mutilfile", commonhandler.UploadMutilFiles)
r.With(auth.Authorize, audit.Audit).Get("/home.html", Home)
configHandler := systemhandler.NewSysConfigHandler()
r.With(auth.Authorize).Get("/pear.json", configHandler.Pear)
r.Route("/system", func(r chi.Router) {
r.Use(auth.Authorize)
r.Route("/config", func(r chi.Router) {
r.Use(audit.Audit)
r.Get("/list", configHandler.List)
r.Post("/list", configHandler.PostList)
r.Get("/add", configHandler.Add)
r.Get("/edit", configHandler.Edit)
r.Post("/save", configHandler.Save)
r.Post("/reset_pear", configHandler.ResetPear)
r.Post("/refresh", configHandler.Refresh)
})
r.Route("/department", func(r chi.Router) {
r.Use(audit.Audit)
departHandler := systemhandler.NewSysDepartmentHandler()
r.Get("/list", departHandler.List)
r.Post("/list", departHandler.PostList)
r.Get("/add", departHandler.Add)
r.Get("/add_children", departHandler.AddChildren)
r.Get("/edit", departHandler.Edit)
r.Post("/save", departHandler.Save)
r.Post("/dtree", departHandler.DTree)
r.Post("/refresh", departHandler.Refresh)
r.Post("/rebuild_parent_path", departHandler.RebuildParentPath)
})
r.Route("/user", func(r chi.Router) {
r.Use(audit.Audit)
userHandler := systemhandler.NewSysUserHandler()
r.Get("/list", userHandler.List)
r.Post("/list", userHandler.PostList)
r.Get("/add", userHandler.Add)
r.Get("/edit", userHandler.Edit)
r.Post("/save", userHandler.Save)
r.Get("/profile", userHandler.Profile)
r.Post("/xmselect", userHandler.XmSelect)
})
r.Route("/login_log", func(r chi.Router) {
// r.Use(audit.Audit)
userLoginLogHandler := systemhandler.NewSysUserLoginLogHandler()
r.Get("/list", userLoginLogHandler.List)
r.Post("/list", userLoginLogHandler.PostList)
})
r.Route("/audit_log", func(r chi.Router) {
// r.Use(audit.Audit)
userAuditLogHandler := systemhandler.NewSysAuditLogHandler()
r.Get("/list", userAuditLogHandler.List)
r.Post("/list", userAuditLogHandler.PostList)
})
r.Route("/role", func(r chi.Router) {
r.Use(audit.Audit)
roleHandler := systemhandler.NewSysRoleHandler()
r.Get("/list", roleHandler.List)
r.Post("/list", roleHandler.PostList)
r.Get("/add", roleHandler.Add)
r.Get("/edit", roleHandler.Edit)
r.Post("/save", roleHandler.Save)
r.Post("/dtree", roleHandler.DTree)
r.Post("/refresh", roleHandler.Refresh)
r.Post("/rebuild_parent_path", roleHandler.RebuildParentPath)
r.Post("/refresh_role_menus", roleHandler.RefreshRoleMenus)
r.Post("/xm_select", roleHandler.XmSelect)
r.Get("/set_menu", roleHandler.SetMenu)
r.Post("/set_menu", roleHandler.PostSetMenu)
})
menuHandler := systemhandler.NewSysMenuHandler()
r.Get("/menus", menuHandler.UserMenus)
r.Route("/menu", func(r chi.Router) {
r.Use(audit.Audit)
r.Get("/tree", menuHandler.Tree)
r.Get("/list", menuHandler.List)
r.Post("/list", menuHandler.PostList)
r.Get("/add", menuHandler.Add)
r.Get("/add_children", menuHandler.AddChildren)
r.Get("/edit", menuHandler.Edit)
r.Post("/save", menuHandler.Save)
r.Post("/xm_select_tree", menuHandler.XmSelectTree)
r.Post("/refresh_cache", menuHandler.Refresh)
})
// 类别
r.Route("/category", func(r chi.Router) {
r.Use(audit.Audit)
categoryHandler := categoryhandler.NewCategoryHandler()
r.Get("/list", categoryHandler.List)
r.Post("/list", categoryHandler.PostList)
r.Get("/add", categoryHandler.Add)
r.Get("/add_children", categoryHandler.AddChildren)
r.Get("/edit", categoryHandler.Edit)
r.Post("/save", categoryHandler.Save)
r.Post("/dtree", categoryHandler.DTree)
r.Post("/xmselect", categoryHandler.XmSelect)
r.Post("/refresh", categoryHandler.Refresh)
r.Post("/rebuild_parent_path", categoryHandler.RebuildParentPath)
})
})
// 客户
r.Route("/customer", func(r chi.Router) {
r.Use(auth.Authorize)
r.Get("/list", customerhandler.List)
r.Post("/list", customerhandler.PostList)
r.Get("/add", customerhandler.Add)
r.Get("/edit", customerhandler.Edit)
r.Post("/save", customerhandler.Save)
r.Post("/xmselect", customerhandler.XmSelect)
})
// 项目
r.Route("/project", func(r chi.Router) {
r.Use(auth.Authorize)
r.Get("/list", projecthandler.List)
r.Post("/list", projecthandler.PostList)
r.Get("/add", projecthandler.Add)
r.Get("/edit", projecthandler.Edit)
r.Post("/save", projecthandler.Save)
r.Post("/xmselect", projecthandler.XmSelect)
r.Get("/dashboard", projecthandler.Dashboard)
r.Post("/dashboard", projecthandler.PostDashboard)
})
// 项目预算
r.Route("/budget", func(r chi.Router) {
r.Use(auth.Authorize)
r.Get("/list", budgethandler.List)
r.Post("/list", budgethandler.PostList)
r.Get("/add", budgethandler.Add)
r.Get("/edit", budgethandler.Edit)
r.Post("/save", budgethandler.Save)
r.Post("/xmselect", budgethandler.XmSelect)
})
// 回款单
r.Route("/income", func(r chi.Router) {
r.Use(auth.Authorize)
r.Get("/list", incomehandler.List)
r.Post("/list", incomehandler.PostList)
r.Get("/add", incomehandler.Add)
r.Get("/edit", incomehandler.Edit)
r.Post("/save", incomehandler.Save)
})
// 费用报销单
r.Route("/expense", func(r chi.Router) {
r.Use(auth.Authorize)
r.Get("/list", expensehandler.List)
r.Post("/list", expensehandler.PostList)
r.Get("/add", expensehandler.Add)
r.Get("/edit", expensehandler.Edit)
r.Post("/save", expensehandler.Save)
})
// 缓存
r.Route("/cache", func(r chi.Router) {
r.Use(auth.Authorize)
r.Get("/list", cachehandler.List)
r.Post("/list", cachehandler.PostList)
r.Post("/refresh", cachehandler.Refresh)
})
})
return r
}

View File

@@ -0,0 +1,43 @@
package system
import (
"net/http"
"management/internal/db/model/dto"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
)
type SysAuditLogHandler struct{}
func NewSysAuditLogHandler() *SysAuditLogHandler {
return &SysAuditLogHandler{}
}
func (h *SysAuditLogHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "audit_log/list.tmpl", nil)
}
func (h *SysAuditLogHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchTimeBegin, q.SearchTimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("SearchTimeBegin"), r.PostFormValue("SearchTimeEnd"))
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
ctx := r.Context()
res, count, err := systemservice.ListSysAuditLog(ctx, q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}

View File

@@ -0,0 +1,166 @@
package system
import (
"encoding/json"
"net/http"
"strings"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/global/pearadmin"
"management/internal/pkg/redis"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
)
type SysConfigHandler struct{}
func NewSysConfigHandler() *SysConfigHandler {
return &SysConfigHandler{}
}
func (h *SysConfigHandler) Pear(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pear, err := systemservice.PearConfig(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, pear)
}
func (h *SysConfigHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "config/list.tmpl", nil)
}
func (h *SysConfigHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
ctx := r.Context()
res, count, err := systemservice.ListSysConfigCondition(ctx, q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}
type EditSysConfig struct {
*db.SysConfig
Result string
}
func (h *SysConfigHandler) Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "config/edit.tmpl", map[string]any{
"Item": &db.SysConfig{},
"Result": "",
})
}
func (h *SysConfigHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
vm := &EditSysConfig{}
if id > 0 {
ctx := r.Context()
if conf, err := systemservice.GetSysConfig(ctx, int32(id)); err == nil {
vm.SysConfig = conf
vm.Result = string(conf.Value)
}
}
tpl.HTML(w, r, "config/edit.tmpl", map[string]any{
"Item": vm.SysConfig,
"Result": vm.Result,
})
}
func (h *SysConfigHandler) Save(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
key := r.PostFormValue("Key")
value := r.PostFormValue("Value")
if len(key) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "Key不能为空"})
return
}
if len(value) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "Value不能为空"})
return
}
ctx := r.Context()
if id == 0 {
arg := &db.CreateSysConfigParams{
Key: key,
Value: []byte(value),
}
err := systemservice.CreateSysConfig(ctx, arg)
if err != nil {
if db.IsUniqueViolation(err) {
tpl.JSON(w, tpl.Response{Success: false, Message: "数据已存在"})
return
}
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "添加成功"})
} else {
res, err := systemservice.GetSysConfig(ctx, int32(id))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg := &db.UpdateSysConfigByKeyParams{
Key: res.Key,
Value: []byte(value),
}
err = systemservice.UpdateSysConfigByKey(ctx, arg)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "更新成功"})
}
}
func (h *SysConfigHandler) ResetPear(w http.ResponseWriter, r *http.Request) {
b, err := json.Marshal(pearadmin.PearJson)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ctx := r.Context()
err = systemservice.UpdateSysConfigByKey(ctx, &db.UpdateSysConfigByKeyParams{
Key: pearadmin.PearKey,
Value: b,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "重置成功"})
}
func (h *SysConfigHandler) Refresh(w http.ResponseWriter, r *http.Request) {
key := r.FormValue("key")
err := redis.Del(r.Context(), strings.ToLower(key))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}

View File

@@ -0,0 +1,177 @@
package system
import (
"fmt"
"net/http"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
)
type SysDepartmentHandler struct{}
func NewSysDepartmentHandler() *SysDepartmentHandler {
return &SysDepartmentHandler{}
}
func (h *SysDepartmentHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "department/list.tmpl", nil)
}
func (h *SysDepartmentHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchStatus = util.ConvertInt(r.PostFormValue("SearchStatus"), 9999)
q.SearchParentID = util.ConvertInt(r.PostFormValue("SearchParentID"), 0)
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
res, count, err := systemservice.ListSysDepartmentCondition(r.Context(), q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}
func (h *SysDepartmentHandler) Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "department/edit.tmpl", map[string]any{
"Item": &db.SysDepartment{Sort: 6666},
})
}
func (h *SysDepartmentHandler) AddChildren(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
parentID := util.DefaultInt(vars, "parentID", 0)
vm := &db.SysDepartment{ParentID: int32(parentID), Sort: 6666}
tpl.HTML(w, r, "department/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysDepartmentHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
vm := &db.SysDepartment{Sort: 6666}
if id > 0 {
vm, _ = systemservice.GetSysDepartment(r.Context(), int32(id))
}
tpl.HTML(w, r, "department/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysDepartmentHandler) Save(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
ParentID := util.ConvertInt(r.PostFormValue("ParentID"), 0)
name := r.PostFormValue("Name")
sort := util.ConvertInt(r.PostFormValue("Sort"), 6666)
status := util.ConvertInt(r.PostFormValue("Status"), 9999)
ctx := r.Context()
var parent *db.SysDepartment
if ParentID > 0 {
var err error
parent, err = systemservice.GetSysDepartment(ctx, int32(ParentID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "父级节点错误"})
return
}
}
if id == 0 {
arg := db.CreateSysDepartmentParams{
Name: name,
ParentID: int32(ParentID),
ParentPath: fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID),
Status: int32(status),
Sort: int32(sort),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err := systemservice.CreateSysDepartment(ctx, &arg)
if err != nil {
if db.IsUniqueViolation(err) {
tpl.JSON(w, tpl.Response{Success: false, Message: "部门名称已存在"})
return
}
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "添加成功"})
} else {
res, err := systemservice.GetSysDepartment(ctx, int32(id))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg := &db.UpdateSysDepartmentParams{
ID: res.ID,
Name: name,
ParentID: int32(ParentID),
ParentPath: fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID),
Status: int32(status),
Sort: int32(sort),
UpdatedAt: time.Now(),
}
_, err = systemservice.UpdateSysDepartment(ctx, arg)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "更新成功"})
}
}
func (h *SysDepartmentHandler) DTree(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
res, err := systemservice.DTreeSysDepartment(ctx, 0)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
rsp := tpl.ResponseDtree{
Status: tpl.ResponseDtreeStatus{
Code: 200,
Message: "OK",
},
Data: res,
}
tpl.JSON(w, rsp)
}
func (h *SysDepartmentHandler) Refresh(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := systemservice.RefreshSysDepartment(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}
func (h *SysDepartmentHandler) RebuildParentPath(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := systemservice.RebuildSysDepartmentParentPath(ctx)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "重建成功"})
}

View File

@@ -0,0 +1,227 @@
package system
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/global/auth"
"management/internal/pkg/session"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
"github.com/google/uuid"
)
type SysMenuHandler struct{}
func NewSysMenuHandler() *SysMenuHandler {
return &SysMenuHandler{}
}
func (h *SysMenuHandler) Tree(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
res, err := systemservice.ToTreeMenu(ctx, id, true)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, res)
}
func (h *SysMenuHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "menu/list.tmpl", nil)
}
func (h *SysMenuHandler) PostList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
res, err := systemservice.ListMenuTree(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: 0,
Data: res,
}
tpl.JSON(w, data)
}
func (h *SysMenuHandler) Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "menu/edit.tmpl", map[string]any{
"Item": &db.SysMenu{Style: "pear-btn-primary pear-btn-sm", Visible: true, Sort: 6666},
})
}
func (h *SysMenuHandler) AddChildren(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
parentID := util.DefaultInt(vars, "parentID", 0)
vm := &db.SysMenu{ParentID: int32(parentID), Style: "pear-btn-primary pear-btn-sm", Visible: true, Sort: 6666}
ctx := r.Context()
parent, err := systemservice.GetSysMenu(ctx, int32(parentID))
if err == nil {
if parent.Type == "node" {
vm.Type = "menu"
} else if parent.Type == "menu" {
vm.Type = "btn"
}
}
tpl.HTML(w, r, "menu/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysMenuHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
vm := &db.SysMenu{Style: "pear-btn-primary pear-btn-sm", Sort: 6666}
if id > 0 {
ctx := r.Context()
vm, _ = systemservice.GetSysMenu(ctx, int32(id))
}
tpl.HTML(w, r, "menu/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysMenuHandler) Save(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
name := r.PostFormValue("Name")
dispalyName := r.PostFormValue("DisplayName")
t := r.PostFormValue("Type")
url := r.PostFormValue("Url")
if len(url) == 0 {
url = uuid.Must(uuid.NewRandom()).String()
}
avatar := r.PostFormValue("Avatar")
style := r.PostFormValue("Style")
parentID := util.ConvertInt(r.PostFormValue("ParentID"), 0)
visible := util.ConvertBool(r.PostFormValue("Visible"), false)
isList := util.ConvertBool(r.PostFormValue("IsList"), false)
sort := util.ConvertInt(r.PostFormValue("Sort"), 6666)
status := util.ConvertInt(r.PostFormValue("Status"), 9999)
ctx := r.Context()
if len(avatar) > 0 && !strings.Contains(avatar, "layui-icon ") {
avatar = "layui-icon " + avatar
}
parentPath := ""
if parentID > 0 {
parent, err := systemservice.GetSysMenu(ctx, int32(parentID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
parentPath = parent.ParentPath + "," + strconv.Itoa(parentID) + ","
parentPath = strings.ReplaceAll(parentPath, ",,", ",")
}
if id == 0 {
arg := &db.CreateSysMenuParams{
Name: name,
DisplayName: dispalyName,
Url: url,
Type: t,
ParentID: int32(parentID),
ParentPath: parentPath,
Avatar: avatar,
Style: style,
Visible: visible,
IsList: isList,
Status: int32(status),
Sort: int32(sort),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err := systemservice.CreateSysMenu(ctx, arg)
if err != nil {
if db.IsUniqueViolation(err) {
tpl.JSON(w, tpl.Response{Success: false, Message: "菜单已存在"})
return
}
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "添加成功"})
} else {
res, err := systemservice.GetSysMenu(ctx, int32(id))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg := &db.UpdateSysMenuParams{
ID: res.ID,
Name: name,
DisplayName: dispalyName,
Url: url,
Type: t,
ParentID: int32(parentID),
ParentPath: parentPath,
Avatar: avatar,
Style: style,
Visible: visible,
IsList: isList,
Status: int32(status),
Sort: int32(sort),
UpdatedAt: time.Now(),
}
_, err = systemservice.UpdateSysMenu(ctx, arg)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "更新成功"})
}
}
func (h *SysMenuHandler) UserMenus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
b := session.GetBytes(ctx, auth.StoreName)
var u dto.AuthorizeUser
if err := json.Unmarshal(b, &u); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
menus, err := systemservice.RecursiveSysMenus(ctx, u.RoleID)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, menus)
}
func (h *SysMenuHandler) XmSelectTree(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
res, err := systemservice.ToTreeMenu(ctx, 0, false)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, res)
}
func (h *SysMenuHandler) Refresh(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := systemservice.RefreshMenus(ctx)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}

View File

@@ -0,0 +1,300 @@
package system
import (
"fmt"
"net/http"
"strings"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
)
type SysRoleHandler struct{}
func NewSysRoleHandler() *SysRoleHandler {
return &SysRoleHandler{}
}
func (h *SysRoleHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "role/list.tmpl", nil)
}
func (h *SysRoleHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchStatus = util.ConvertInt(r.PostFormValue("SearchStatus"), 9999)
q.SearchParentID = util.ConvertInt(r.PostFormValue("SearchParentID"), 0)
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
ctx := r.Context()
res, count, err := systemservice.ListSysRoleCondition(ctx, q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}
func (h *SysRoleHandler) Add(w http.ResponseWriter, r *http.Request) {
vm := &db.SysRole{Sort: 6666}
tpl.HTML(w, r, "role/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysRoleHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
vm := &db.SysRole{Sort: 6666}
if id > 0 {
ctx := r.Context()
vm, _ = systemservice.GetSysRole(ctx, int32(id))
}
tpl.HTML(w, r, "role/edit.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysRoleHandler) Save(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
name := r.PostFormValue("Name")
parentID := util.ConvertInt(r.PostFormValue("ParentID"), 0)
displayName := r.PostFormValue("DisplayName")
sort := util.ConvertInt(r.PostFormValue("Sort"), 6666)
status := util.ConvertInt(r.PostFormValue("Status"), 9999)
ctx := r.Context()
var parent *db.SysRole
if parentID > 0 {
var err error
parent, err = systemservice.GetSysRole(ctx, int32(parentID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "父级节点错误"})
return
}
} else {
parent = &db.SysRole{
ID: 0,
ParentID: 0,
ParentPath: ",0,",
}
}
if id == 0 {
arg := &db.CreateSysRoleParams{
Name: name,
DisplayName: displayName,
Vip: false,
ParentID: parent.ID,
ParentPath: fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID),
Status: int32(status),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err := systemservice.CreateSysRole(ctx, arg)
if err != nil {
if db.IsUniqueViolation(err) {
tpl.JSON(w, tpl.Response{Success: false, Message: "角色名称已存在"})
return
}
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "添加成功"})
} else {
res, err := systemservice.GetSysRole(ctx, int32(id))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg := &db.UpdateSysRoleParams{
ID: res.ID,
DisplayName: displayName,
Sort: int32(sort),
Status: int32(status),
ParentID: parent.ID,
ParentPath: fmt.Sprintf("%s,%d,", parent.ParentPath, parent.ID),
UpdatedAt: time.Now(),
}
_, err = systemservice.UpdateSysRole(ctx, arg)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "更新成功"})
}
}
func (h *SysRoleHandler) XmSelect(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
res, err := systemservice.XmSelectSysRole(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, res)
}
func (h *SysRoleHandler) SetMenu(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
vm := struct {
Role *db.SysRole
Menus []*dto.SetMenuDto
}{}
if id > 0 {
ctx := r.Context()
var err error
vm.Role, err = systemservice.GetSysRole(ctx, int32(id))
if err == nil {
vm.Menus, _ = systemservice.SetMenuViewData(ctx, vm.Role.ID)
}
}
tpl.HTML(w, r, "role/set_menu.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysRoleHandler) PostSetMenu(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
menus := r.PostFormValue("roleMenu")
if id == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "角色异常, 请刷新重试"})
return
}
if len(menus) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "请选择菜单"})
return
}
ctx := r.Context()
_, err := systemservice.GetSysRole(ctx, int32(id))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
menuArr := strings.Split(menus, ",")
if len(menuArr) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "请选择菜单"})
return
}
var menuList []*db.SysMenu
for _, v := range menuArr {
menuID := util.ConvertInt(v, 0)
if menuID > 0 {
menu, err := systemservice.GetSysMenu(ctx, int32(menuID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
menuList = append(menuList, menu)
}
}
if len(menuList) == 0 {
tpl.JSON(w, tpl.Response{Success: false, Message: "请选择正确的菜单"})
return
}
err = systemservice.SetMenu(ctx, int32(id), menuList)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "设置成功"})
}
func (h *SysRoleHandler) DTree(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
res, err := systemservice.DTreeSysRole(ctx, 0)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
rsp := tpl.ResponseDtree{
Status: tpl.ResponseDtreeStatus{
Code: 200,
Message: "OK",
},
Data: res,
}
tpl.JSON(w, rsp)
}
func (h *SysRoleHandler) Refresh(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := systemservice.RefreshSysRole(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}
func (h *SysRoleHandler) RebuildParentPath(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := systemservice.RebuildSysRoleParentPath(ctx)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "重建成功"})
}
func (h *SysRoleHandler) RefreshRoleMenus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// 获取需要刷新的角色ID
roleID := util.ConvertInt(r.PostFormValue("roleID"), 0)
sysRole, err := systemservice.GetSysRole(ctx, int32(roleID))
if err != nil || sysRole == nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
// 刷新角色菜单 (角色所拥有的菜单集合)
_, err = systemservice.SetOwnerListMenuByRoleID(ctx, sysRole.ID)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
// 刷新角色菜单 (角色所拥有的菜单集合)
_, err = systemservice.SetOwnerMapMenuByRoleID(ctx, sysRole.ID)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
// 刷新角色菜单树 (pear admin layui 使用的格式)
_, err = systemservice.SetRecursiveSysMenus(ctx, sysRole.ID)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "刷新成功"})
}

View File

@@ -0,0 +1,213 @@
package system
import (
"net/http"
"time"
"management/internal/db/model/dto"
db "management/internal/db/sqlc"
"management/internal/middleware/manage/auth"
"management/internal/pkg/crypto"
"management/internal/pkg/rand"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
"github.com/google/uuid"
)
type SysUserHandler struct{}
func NewSysUserHandler() *SysUserHandler {
return &SysUserHandler{}
}
func (h *SysUserHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "user/list.tmpl", nil)
}
func (h *SysUserHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchStatus = util.ConvertInt(r.PostFormValue("SearchStatus"), 9999)
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
ctx := r.Context()
res, count, err := systemservice.ListSysUserCondition(ctx, q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}
func (h *SysUserHandler) Add(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "user/edit.tmpl", map[string]any{
"Item": &db.SysUser{
HashedPassword: nil,
},
})
}
func (h *SysUserHandler) Edit(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
id := util.DefaultInt(vars, "id", 0)
sysUser := &db.SysUser{}
if id > 0 {
ctx := r.Context()
if user, err := systemservice.GetSysUser(ctx, int32(id)); err == nil {
user.HashedPassword = nil
sysUser = user
}
}
tpl.HTML(w, r, "user/edit.tmpl", map[string]any{
"Item": sysUser,
})
}
func (h *SysUserHandler) Profile(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := auth.AuthUser(ctx)
vm, _ := systemservice.GetSysUser(ctx, user.ID)
tpl.HTML(w, r, "user/profile.tmpl", map[string]any{
"Item": vm,
})
}
func (h *SysUserHandler) Save(w http.ResponseWriter, r *http.Request) {
id := util.ConvertInt(r.PostFormValue("ID"), 0)
email := r.PostFormValue("Email")
username := r.PostFormValue("Username")
password := r.PostFormValue("Password")
changePassword := r.PostFormValue("ChangePassword")
gender := util.ConvertInt(r.PostFormValue("Gender"), 0)
avatar := r.PostFormValue("File")
status := util.ConvertInt(r.PostFormValue("Status"), 9999)
ctx := r.Context()
departmentID := util.ConvertInt(r.PostFormValue("DepartmentID"), 0)
var department *db.SysDepartment
var err error
if departmentID > 0 {
department, err = systemservice.GetSysDepartment(ctx, int32(departmentID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "部门数据错误"})
return
}
}
var role *db.SysRole
roleID := util.ConvertInt(r.PostFormValue("RoleID"), 0)
if roleID > 0 {
role, err = systemservice.GetSysRole(ctx, int32(roleID))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: "角色数据错误"})
return
}
}
if id == 0 {
salt, err := rand.String(10)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
hashedPassword, err := crypto.BcryptHashPassword(password + salt)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
initTime, err := time.ParseInLocation(time.DateTime, "0001-01-01 00:00:00", time.Local)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg := &db.CreateSysUserParams{
Uuid: uuid.Must(uuid.NewV7()),
Email: email,
Username: username,
HashedPassword: hashedPassword,
Salt: salt,
Avatar: avatar,
Gender: int32(gender),
DepartmentID: department.ID,
RoleID: role.ID,
Status: int32(status),
ChangePasswordAt: initTime,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err = systemservice.CreateSysUser(ctx, arg)
if err != nil {
if db.IsUniqueViolation(err) {
tpl.JSON(w, tpl.Response{Success: false, Message: "数据已存在"})
return
}
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "添加成功"})
} else {
res, err := systemservice.GetSysUser(ctx, int32(id))
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg := &db.UpdateSysUserParams{
ID: res.ID,
Username: username,
HashedPassword: res.HashedPassword,
Avatar: avatar,
Gender: int32(gender),
DepartmentID: department.ID,
RoleID: role.ID,
Status: int32(status),
ChangePasswordAt: res.ChangePasswordAt,
UpdatedAt: time.Now(),
}
if changePassword == "on" {
hashedPassword, err := crypto.BcryptHashPassword(password + res.Salt)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
arg.HashedPassword = hashedPassword
arg.ChangePasswordAt = time.Now()
}
_, err = systemservice.UpdateSysUser(ctx, arg)
if err != nil {
tpl.JSON(w, tpl.Response{Success: false, Message: err.Error()})
return
}
tpl.JSON(w, tpl.Response{Success: true, Message: "更新成功"})
}
}
func (h *SysUserHandler) XmSelect(w http.ResponseWriter, r *http.Request) {
all, err := db.Engine.ListSysUser(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var res []*dto.XmSelectInt32Dto
for _, v := range all {
res = append(res, &dto.XmSelectInt32Dto{
Name: v.Username,
Value: v.ID,
})
}
tpl.JSON(w, res)
}

View File

@@ -0,0 +1,43 @@
package system
import (
"net/http"
"management/internal/db/model/dto"
"management/internal/router/manage/util"
systemservice "management/internal/service/system"
"management/internal/tpl"
)
type SysUserLoginLogHandler struct{}
func NewSysUserLoginLogHandler() *SysUserLoginLogHandler {
return &SysUserLoginLogHandler{}
}
func (h *SysUserLoginLogHandler) List(w http.ResponseWriter, r *http.Request) {
tpl.HTML(w, r, "login_log/list.tmpl", nil)
}
func (h *SysUserLoginLogHandler) PostList(w http.ResponseWriter, r *http.Request) {
var q dto.SearchDto
q.SearchTimeBegin, q.SearchTimeEnd = util.DefaultStartTimeAndEndTime(r.PostFormValue("SearchTimeBegin"), r.PostFormValue("SearchTimeEnd"))
q.SearchName = r.PostFormValue("SearchName")
q.SearchKey = r.PostFormValue("SearchKey")
q.Page = util.ConvertInt(r.PostFormValue("page"), 1)
q.Rows = util.ConvertInt(r.PostFormValue("rows"), 10)
ctx := r.Context()
res, count, err := systemservice.ListSysUserLoginLog(ctx, q)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := tpl.ResponseList{
Code: 0,
Message: "ok",
Count: count,
Data: res,
}
tpl.JSON(w, data)
}

View File

@@ -0,0 +1,83 @@
package util
import (
"net/url"
"strconv"
"time"
"github.com/jackc/pgx/v5/pgtype"
)
func DefaultStartTimeAndEndTime(start string, end string) (string, string) {
if len(start) == 0 {
start = "2000-01-01 00:00:00"
}
if len(end) == 0 {
end = time.Now().Add(time.Hour * 24).Format(time.DateTime)
}
return start, end
}
func DefaultString(values url.Values, key, defaultValue string) string {
v := values.Get(key)
if len(v) == 0 {
return defaultValue
}
return v
}
func DefaultInt(values url.Values, key string, defaultValue int) int {
v := values.Get(key)
if len(v) == 0 {
return defaultValue
}
i, err := strconv.Atoi(v)
if err != nil {
return defaultValue
}
return i
}
func ConvertInt16(value string, defaultValue int16) int16 {
i, err := strconv.Atoi(value)
if err != nil {
return defaultValue
}
return int16(i)
}
func ConvertInt32(value string, defaultValue int32) int32 {
i, err := strconv.Atoi(value)
if err != nil {
return defaultValue
}
return int32(i)
}
func ConvertBool(value string, defaultValue bool) bool {
b, err := strconv.ParseBool(value)
if err != nil {
return defaultValue
}
return b
}
func ConvertInt[T int | int16 | int32 | int64](value string, defaultValue T) T {
i, err := strconv.Atoi(value)
if err != nil {
return defaultValue
}
return T(i)
}
func PgtypeNumericToFloat64(num pgtype.Numeric) float64 {
f1, _ := num.Float64Value()
f2, _ := f1.Value()
return f2.(float64)
}