mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: support custom menu
This commit is contained in:
		@@ -217,6 +217,7 @@ func needLogin(c *gin.Context) bool {
 | 
			
		||||
		c.Request.URL.Path == "/api/sd/client" ||
 | 
			
		||||
		c.Request.URL.Path == "/api/config/get" ||
 | 
			
		||||
		c.Request.URL.Path == "/api/product/list" ||
 | 
			
		||||
		c.Request.URL.Path == "/api/menu/list" ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
 | 
			
		||||
 
 | 
			
		||||
@@ -144,13 +144,13 @@ type SystemConfig struct {
 | 
			
		||||
	PowerPrice    float64 `json:"power_price,omitempty"`    // 算力单价
 | 
			
		||||
 | 
			
		||||
	OrderPayTimeout int    `json:"order_pay_timeout,omitempty"` //订单支付超时时间
 | 
			
		||||
	VipInfoText     string `json:"vip_info_text"`               // 会员页面充值说明
 | 
			
		||||
	VipInfoText     string `json:"vip_info_text,omitempty"`     // 会员页面充值说明
 | 
			
		||||
	DefaultModels   []int  `json:"default_models,omitempty"`    // 默认开通的 AI 模型
 | 
			
		||||
 | 
			
		||||
	MjPower       int `json:"mj_power,omitempty"`   // MJ 绘画消耗算力
 | 
			
		||||
	MjActionPower int `json:"mj_action_power"`      // MJ 操作(放大,变换)消耗算力
 | 
			
		||||
	SdPower       int `json:"sd_power,omitempty"`   // SD 绘画消耗算力
 | 
			
		||||
	DallPower     int `json:"dall_power,omitempty"` // DALLE3 绘图消耗算力
 | 
			
		||||
	MjPower       int `json:"mj_power,omitempty"`        // MJ 绘画消耗算力
 | 
			
		||||
	MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
 | 
			
		||||
	SdPower       int `json:"sd_power,omitempty"`        // SD 绘画消耗算力
 | 
			
		||||
	DallPower     int `json:"dall_power,omitempty"`      // DALLE3 绘图消耗算力
 | 
			
		||||
 | 
			
		||||
	WechatCardURL string `json:"wechat_card_url,omitempty"` // 微信客服地址
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										121
									
								
								api/handler/admin/menu_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								api/handler/admin/menu_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/core/types"
 | 
			
		||||
	"chatplus/handler"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MenuHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMenuHandler(app *core.AppServer, db *gorm.DB) *MenuHandler {
 | 
			
		||||
	return &MenuHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *MenuHandler) Save(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id      uint   `json:"id"`
 | 
			
		||||
		Name    string `json:"name"`
 | 
			
		||||
		Icon    string `json:"icon"`
 | 
			
		||||
		URL     string `json:"url"`
 | 
			
		||||
		SortNum int    `json:"sort_num"`
 | 
			
		||||
		Enabled bool   `json:"enabled"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res := h.DB.Save(&model.Menu{
 | 
			
		||||
		Id:      data.Id,
 | 
			
		||||
		Name:    data.Name,
 | 
			
		||||
		Icon:    data.Icon,
 | 
			
		||||
		URL:     data.URL,
 | 
			
		||||
		SortNum: data.SortNum,
 | 
			
		||||
		Enabled: data.Enabled,
 | 
			
		||||
	})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List 数据列表
 | 
			
		||||
func (h *MenuHandler) List(c *gin.Context) {
 | 
			
		||||
	var items []model.Menu
 | 
			
		||||
	var list = make([]vo.Menu, 0)
 | 
			
		||||
	res := h.DB.Order("sort_num ASC").Find(&items)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			var product vo.Menu
 | 
			
		||||
			err := utils.CopyObject(item, &product)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				list = append(list, product)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, list)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *MenuHandler) Enable(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id      uint `json:"id"`
 | 
			
		||||
		Enabled bool `json:"enabled"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res := h.DB.Model(&model.Menu{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *MenuHandler) Sort(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Ids   []uint `json:"ids"`
 | 
			
		||||
		Sorts []int  `json:"sorts"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for index, id := range data.Ids {
 | 
			
		||||
		res := h.DB.Model(&model.Menu{}).Where("id", id).Update("sort_num", data.Sorts[index])
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *MenuHandler) Remove(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
 | 
			
		||||
	if id > 0 {
 | 
			
		||||
		res := h.DB.Where("id", id).Delete(&model.Menu{})
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
@@ -65,21 +65,11 @@ func (h *ProductHandler) Save(c *gin.Context) {
 | 
			
		||||
	resp.SUCCESS(c, itemVo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List 模型列表
 | 
			
		||||
// List 数据列表
 | 
			
		||||
func (h *ProductHandler) List(c *gin.Context) {
 | 
			
		||||
	if err := utils.CheckPermission(c, h.DB); err != nil {
 | 
			
		||||
		resp.NotPermission(c)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{})
 | 
			
		||||
	enable := h.GetBool(c, "enable")
 | 
			
		||||
	if enable {
 | 
			
		||||
		session = session.Where("enabled", enable)
 | 
			
		||||
	}
 | 
			
		||||
	var items []model.Product
 | 
			
		||||
	var list = make([]vo.Product, 0)
 | 
			
		||||
	res := session.Order("sort_num ASC").Find(&items)
 | 
			
		||||
	res := h.DB.Order("sort_num ASC").Find(&items)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			var product vo.Product
 | 
			
		||||
@@ -128,7 +118,7 @@ func (h *ProductHandler) Sort(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for index, id := range data.Ids {
 | 
			
		||||
		res := h.DB.Model(&model.Product{}).Where("id = ?", id).Update("sort_num", data.Sorts[index])
 | 
			
		||||
		res := h.DB.Model(&model.Product{}).Where("id", id).Update("sort_num", data.Sorts[index])
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
			return
 | 
			
		||||
@@ -142,7 +132,7 @@ func (h *ProductHandler) Remove(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
 | 
			
		||||
	if id > 0 {
 | 
			
		||||
		res := h.DB.Where("id = ?", id).Delete(&model.Product{})
 | 
			
		||||
		res := h.DB.Where("id", id).Delete(&model.Product{})
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "更新数据库失败!")
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,10 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
 | 
			
		||||
	all := h.GetBool(c, "all")
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	var roles []model.ChatRole
 | 
			
		||||
	var roleVos = make([]vo.ChatRole, 0)
 | 
			
		||||
	res := h.DB.Where("enable", true).Order("sort_num ASC").Find(&roles)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "No roles found,"+res.Error.Error())
 | 
			
		||||
		resp.SUCCESS(c, roleVos)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -55,8 +56,7 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, "角色解析失败!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// 转成 vo
 | 
			
		||||
	var roleVos = make([]vo.ChatRole, 0)
 | 
			
		||||
 | 
			
		||||
	for _, r := range roles {
 | 
			
		||||
		if !utils.ContainsStr(roleKeys, r.Key) {
 | 
			
		||||
			continue
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								api/handler/menu_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								api/handler/menu_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
package handler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MenuHandler struct {
 | 
			
		||||
	BaseHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMenuHandler(app *core.AppServer, db *gorm.DB) *MenuHandler {
 | 
			
		||||
	return &MenuHandler{BaseHandler: BaseHandler{App: app, DB: db}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List 数据列表
 | 
			
		||||
func (h *MenuHandler) List(c *gin.Context) {
 | 
			
		||||
	var items []model.Menu
 | 
			
		||||
	var list = make([]vo.Menu, 0)
 | 
			
		||||
	res := h.DB.Where("enabled", true).Order("sort_num ASC").Find(&items)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			var product vo.Menu
 | 
			
		||||
			err := utils.CopyObject(item, &product)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				list = append(list, product)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, list)
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ package handler
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/core/types"
 | 
			
		||||
	"chatplus/service"
 | 
			
		||||
	"chatplus/service/oss"
 | 
			
		||||
	"chatplus/service/sd"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
@@ -23,15 +24,17 @@ import (
 | 
			
		||||
 | 
			
		||||
type SdJobHandler struct {
 | 
			
		||||
	BaseHandler
 | 
			
		||||
	redis    *redis.Client
 | 
			
		||||
	pool     *sd.ServicePool
 | 
			
		||||
	uploader *oss.UploaderManager
 | 
			
		||||
	redis     *redis.Client
 | 
			
		||||
	pool      *sd.ServicePool
 | 
			
		||||
	uploader  *oss.UploaderManager
 | 
			
		||||
	snowflake *service.Snowflake
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSdJobHandler(app *core.AppServer, db *gorm.DB, pool *sd.ServicePool, manager *oss.UploaderManager) *SdJobHandler {
 | 
			
		||||
func NewSdJobHandler(app *core.AppServer, db *gorm.DB, pool *sd.ServicePool, manager *oss.UploaderManager, snowflake *service.Snowflake) *SdJobHandler {
 | 
			
		||||
	return &SdJobHandler{
 | 
			
		||||
		pool:     pool,
 | 
			
		||||
		uploader: manager,
 | 
			
		||||
		pool:      pool,
 | 
			
		||||
		uploader:  manager,
 | 
			
		||||
		snowflake: snowflake,
 | 
			
		||||
		BaseHandler: BaseHandler{
 | 
			
		||||
			App: app,
 | 
			
		||||
			DB:  db,
 | 
			
		||||
@@ -116,8 +119,13 @@ func (h *SdJobHandler) Image(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	idValue, _ := c.Get(types.LoginUserID)
 | 
			
		||||
	userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
 | 
			
		||||
	taskId, err := h.snowflake.Next(true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, "error with generate task id: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	params := types.SdTaskParams{
 | 
			
		||||
		TaskId:         fmt.Sprintf("task(%s)", utils.RandString(15)),
 | 
			
		||||
		TaskId:         taskId,
 | 
			
		||||
		Prompt:         data.Prompt,
 | 
			
		||||
		NegativePrompt: data.NegativePrompt,
 | 
			
		||||
		Steps:          data.Steps,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								api/main.go
									
									
									
									
									
								
							@@ -417,6 +417,20 @@ func main() {
 | 
			
		||||
			group := s.Engine.Group("/api/admin/powerLog/")
 | 
			
		||||
			group.POST("list", h.List)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Provide(admin.NewMenuHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.MenuHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/admin/menu/")
 | 
			
		||||
			group.POST("save", h.Save)
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
			group.POST("enable", h.Enable)
 | 
			
		||||
			group.POST("sort", h.Sort)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Provide(handler.NewMenuHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.MenuHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/menu/")
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
 | 
			
		||||
			err := s.Run(db)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -181,6 +181,7 @@ func (p *ServicePool) SyncTaskProgress() {
 | 
			
		||||
							CreatedAt: time.Now(),
 | 
			
		||||
						})
 | 
			
		||||
					}
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,13 @@ func (s *Service) Run() {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var job model.MidJourneyJob
 | 
			
		||||
		tx := s.db.Where("id = ?", task.Id).First(&job)
 | 
			
		||||
		if tx.Error != nil {
 | 
			
		||||
			logger.Error("任务不存在,任务ID:", task.TaskId)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logger.Infof("%s handle a new MidJourney task: %+v", s.Name, task)
 | 
			
		||||
		var res ImageRes
 | 
			
		||||
		switch task.Type {
 | 
			
		||||
@@ -97,8 +104,6 @@ func (s *Service) Run() {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var job model.MidJourneyJob
 | 
			
		||||
		s.db.Where("id = ?", task.Id).First(&job)
 | 
			
		||||
		if err != nil || (res.Code != 1 && res.Code != 22) {
 | 
			
		||||
			errMsg := fmt.Sprintf("%v,%s", err, res.Description)
 | 
			
		||||
			logger.Error("绘画任务执行失败:", errMsg)
 | 
			
		||||
@@ -127,14 +132,17 @@ func (s *Service) canHandleTask() bool {
 | 
			
		||||
	return handledNum < s.maxHandleTaskNum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// remove the expired tasks
 | 
			
		||||
// remove the timeout tasks
 | 
			
		||||
func (s *Service) checkTasks() {
 | 
			
		||||
	for k, t := range s.taskStartTimes {
 | 
			
		||||
		if time.Now().Unix()-t.Unix() > s.taskTimeout {
 | 
			
		||||
			delete(s.taskStartTimes, k)
 | 
			
		||||
			atomic.AddInt32(&s.HandledTaskNum, -1)
 | 
			
		||||
			// delete task from database
 | 
			
		||||
			s.db.Delete(&model.MidJourneyJob{Id: uint(k)}, "progress < 100")
 | 
			
		||||
 | 
			
		||||
			s.db.Model(&model.MidJourneyJob{Id: uint(k)}).UpdateColumns(map[string]interface{}{
 | 
			
		||||
				"progress": -1,
 | 
			
		||||
				"err_msg":  "任务超时",
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -114,6 +114,7 @@ func (s *Service) Txt2Img(task types.SdTask) error {
 | 
			
		||||
		Width:          task.Params.Width,
 | 
			
		||||
		Height:         task.Params.Height,
 | 
			
		||||
		SamplerName:    task.Params.Sampler,
 | 
			
		||||
		ForceTaskId:    task.Params.TaskId,
 | 
			
		||||
	}
 | 
			
		||||
	if task.Params.Seed > 0 {
 | 
			
		||||
		body.Seed = task.Params.Seed
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								api/store/model/menu.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/store/model/menu.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
// Menu 系统菜单
 | 
			
		||||
type Menu struct {
 | 
			
		||||
	Id      uint   `gorm:"primarykey;column:id"`
 | 
			
		||||
	Name    string // 菜单名称
 | 
			
		||||
	Icon    string // 菜单图标
 | 
			
		||||
	URL     string // 菜单跳转地址
 | 
			
		||||
	SortNum int    // 排序
 | 
			
		||||
	Enabled bool   // 启用状态
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								api/store/vo/menu.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/store/vo/menu.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
// Menu 系统菜单
 | 
			
		||||
type Menu struct {
 | 
			
		||||
	Id      uint   `json:"id"`
 | 
			
		||||
	Name    string `json:"name"`
 | 
			
		||||
	Icon    string `json:"icon"`
 | 
			
		||||
	URL     string `json:"url"`
 | 
			
		||||
	SortNum int    `json:"sort_num"`
 | 
			
		||||
	Enabled bool   `json:"enabled"`
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user