Browse Source

登录改造

huwei 1 month ago
parent
commit
151ed63298

BIN
server/cadmin


+ 37 - 4
server/config/menu.go

@@ -1,5 +1,12 @@
 package config
 
+import (
+	"context"
+	"gadmin/internal/gorm/model"
+	"gadmin/internal/gorm/query"
+	"github.com/sirupsen/logrus"
+)
+
 var menus = map[string]string{
 	"/api/gm/updatePlayerBase":                  "更新玩家金币、钻石、 经验、体力、精力、天赋点",
 	"/api/gm/updateChapter":                     "更新玩家关卡信息",
@@ -75,17 +82,43 @@ var menus = map[string]string{
 	"/api/gm/abnormalOrderReissue":              "异常订单补发",
 	"/api/gm/add_gift":                          "赠送礼包",
 	"/api/service/select":                       "系统选择",
+	"/api/announcement/add":                     "添加公告",
+	"/api/announcement/del":                     "删除公告",
 }
 
 func GetMenuName(p string) string {
-	v, ok := menus[p]
-	if ok {
-		return v
+	q := query.Use(AdminDB).AdminOperation
+	operation, err := q.WithContext(context.Background()).Where(q.API.Eq(p)).First()
+	if err != nil {
+		logrus.Error("GetMenuName", "err", err)
+		return ""
+	}
+	return operation.Name
+
+	//v, ok := menus[p]
+	//if ok {
+	//	return v
+	//}
+	//return ""
+}
+
+func GetAllOptions() map[string]*model.AdminOperation {
+	menus := make(map[string]*model.AdminOperation)
+	q := AdminDB.Model(&model.AdminOperation{})
+	allOperation := make([]*model.AdminOperation, 0)
+	err := q.Find(&allOperation).Error
+	if err != nil {
+		logrus.Error("GetAllOptions", "err", err)
+		return menus
+	}
+	for _, item := range allOperation {
+		menus[item.API] = item
 	}
-	return ""
+	return menus
 }
 
 func HasMenu(p string) bool {
+	menus := GetAllOptions()
 	_, ok := menus[p]
 	return ok
 }

+ 14 - 0
server/config/menu_test.go

@@ -0,0 +1,14 @@
+package config
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestGetMenuName(t *testing.T) {
+	s := "INSERT INTO `dashboard_admin`.`admin_permissions` (`system_id`, `name`, `api`) VALUES "
+	for api, name := range menus {
+		s += "(1, '" + name + "', '" + api + "'),\n"
+	}
+	fmt.Println(s)
+}

+ 23 - 20
server/config/permissions.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"gadmin/internal/gorm/model"
 	"gadmin/internal/gorm/query"
 	"gadmin/utility/character"
 	jsoniter "github.com/json-iterator/go"
@@ -213,11 +214,9 @@ func ValidityAuth(roleId int64, method, path string, systemId int32) (err error)
 
 	checkFlag := false
 	for _, it := range permissionList {
-		for _, api := range it.Apis {
-			if api == path {
-				checkFlag = true
-				break
-			}
+		if it.API == path {
+			checkFlag = true
+			break
 		}
 	}
 	if !checkFlag {
@@ -271,27 +270,31 @@ func GetAllPermissions() ([]*Permission, error) {
 }
 
 // GetRoleSystemPermissions 获取角色下的权限
-func GetRoleSystemPermissions(roleId, systemId int64) ([]*Permission, error) {
+func GetRoleSystemPermissions(roleId, systemId int64) ([]*model.AdminOperation, error) {
 	rpq := query.Use(AdminDB).AdminRolePermission
-	pq := query.Use(AdminDB).AdminPermission
+	oq := query.Use(AdminDB).AdminOperation
 	pIds := make([]int32, 0)
 	err := rpq.Where(rpq.RoleID.Eq(int32(roleId)), rpq.SystemID.Eq(int32(systemId))).Pluck(rpq.PermissionID, &pIds)
 	if err != nil {
 		return nil, err
 	}
-	permissionList, err := pq.WithContext(context.Background()).Where(pq.ID.In(pIds...)).Find()
-	var result []*Permission
-	for _, v := range permissionList {
-		apis := make([]string, 0)
-		if err := jsoniter.UnmarshalFromString(v.Apis, &apis); err != nil {
-			return nil, err
-		}
-		result = append(result, &Permission{
-			ID:   v.ID,
-			Name: v.Name,
-			Apis: apis,
-		})
+	permissionList, err := oq.WithContext(context.Background()).Where(oq.ID.In(pIds...)).Find()
+	if err != nil {
+		return nil, err
 	}
-	return result, nil
+	return permissionList, nil
+	//var result []*Permission
+	//for _, v := range permissionList {
+	//	apis := make([]string, 0)
+	//	if err := jsoniter.UnmarshalFromString(v.Apis, &apis); err != nil {
+	//		return nil, err
+	//	}
+	//	result = append(result, &Permission{
+	//		ID:   v.ID,
+	//		Name: v.Name,
+	//		Apis: apis,
+	//	})
+	//}
+	//return result, nil
 
 }

+ 1 - 1
server/config/redis.go

@@ -11,7 +11,7 @@ import (
 )
 
 const (
-	TokenExpireTime = 1 * time.Hour
+	TokenExpireTime = 3 * time.Hour
 )
 
 var (

+ 6 - 5
server/internal/admin/api/admin_role.go

@@ -30,12 +30,13 @@ func AdminRoleAuthOption(c *gin.Context) {
 		//keys  []int
 		lists []*forms.Option
 	)
-	permissions, err := config.GetAllPermissions()
+	//permissions, err := config.GetAllPermissions()
+	permissions := config.GetAllOptions()
 
-	if err != nil {
-		c.JSON(200, ErrorResponse(err))
-		return
-	}
+	//if err != nil {
+	//	c.JSON(200, ErrorResponse(err))
+	//	return
+	//}
 
 	//for k, _ := range config.AuthNameMap {
 	//	keys = append(keys, k)

+ 34 - 0
server/internal/admin/api/announcement.go

@@ -0,0 +1,34 @@
+package api
+
+import (
+	"gadmin/internal/admin/forms"
+	"gadmin/internal/admin/service"
+	"github.com/gin-gonic/gin"
+)
+
+func AnnouncementList(c *gin.Context) {
+	c.JSON(200, service.Announcement.List())
+}
+
+func AnnouncementAdd(c *gin.Context) {
+	var req forms.AnnouncementAddReq
+	if err := c.ShouldBind(&req); err != nil {
+		c.JSON(200, ErrorResponse(err))
+		return
+	}
+
+	if err := forms.ParseParams(&req); err != nil {
+		c.JSON(200, ErrorResponse(err))
+		return
+	}
+
+	c.JSON(200, service.Announcement.Add(req))
+}
+
+func AnnouncementDel(c *gin.Context) {
+	c.JSON(200, service.Announcement.Del())
+}
+
+func AnnouncementInfo(c *gin.Context) {
+	service.Get(c)
+}

+ 9 - 8
server/internal/admin/consts/socket.go

@@ -1,14 +1,15 @@
 package consts
 
 const (
-	SocketHeartBeat        = 1
-	SocketQueryUser        = 2
-	SocketBoardCastMsg     = 3
-	SocketQuerySwitcher    = 4
-	SocketQueryEndlessRank = 5
-	SocketSendEmail        = 6
-	SocketQueryUserGuide   = 7
-	SocketDelRank          = 8
+	SocketHeartBeat         = 1
+	SocketQueryUser         = 2
+	SocketBoardCastMsg      = 3
+	SocketQuerySwitcher     = 4
+	SocketQueryEndlessRank  = 5
+	SocketSendEmail         = 6
+	SocketQueryUserGuide    = 7
+	SocketDelRank           = 8
+	SocketQueryAnnouncement = 9
 
 	SocketRestartLog   = 90
 	SocketDeployNotify = 91

+ 20 - 0
server/internal/admin/forms/announcement.go

@@ -0,0 +1,20 @@
+package forms
+
+import (
+	"errors"
+)
+
+type AnnouncementAddReq struct {
+	Title   string `json:"title" form:"title"`
+	Content string `json:"content" form:"content"`
+}
+
+func (req *AnnouncementAddReq) Check() error {
+	if req.Content == "" {
+		return errors.New("公告内容不能为空")
+	}
+	if req.Title == "" {
+		return errors.New("公告标题不能为空")
+	}
+	return nil
+}

+ 13 - 2
server/internal/admin/middleware/token.go

@@ -8,6 +8,7 @@ import (
 	"github.com/gin-gonic/gin"
 	jsoniter "github.com/json-iterator/go"
 	"github.com/sirupsen/logrus"
+	"os"
 	"strconv"
 	"time"
 )
@@ -59,7 +60,6 @@ func Token() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-
 		// 查询登录token是否有效
 		key := config.GetUserTokenKey(user.ID)
 		tokenCTStr := config.TokenRedis.HGet(key, t).Val()
@@ -70,7 +70,18 @@ func Token() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-		tokenCT := time.Unix(int64(tokenCreateTime), 0)
+
+		if tokenCreateTime == 0 {
+			c.JSON(200, serializer.CheckLogin())
+			c.Abort()
+			return
+		}
+
+		tokenCT := time.Now()
+		// 正式环境校验token有效期
+		if os.Getenv("GIN_MODE") == "release" {
+			tokenCT = time.Unix(int64(tokenCreateTime), 0)
+		}
 
 		if tokenCT.Before(time.Now().Add(-config.TokenExpireTime)) {
 			c.JSON(200, serializer.CheckLogin())

+ 4 - 0
server/internal/admin/server/router.go

@@ -64,6 +64,7 @@ func NewEngine() *gin.Engine {
 		peripherals := group.Group("peripherals")
 		peripherals.Use(middleware.ApiToken())
 		peripherals.POST("receiveCdk", api.PeripheralsReceiveCdk)
+		peripherals.GET("announcement/info", api.AnnouncementInfo) // 获取游戏更新公告
 
 		feishu := group.Group("feishu")
 		//feishu.Use(middleware.ApiToken())
@@ -88,6 +89,9 @@ func NewEngine() *gin.Engine {
 			//auth.GET("email/list", api.AdminEmailList)                // V2邮件列表
 			//auth.POST("email/add", api.AdminEmailAdd)                 // V2邮件新增/编辑
 			//auth.POST("email/verify", api.AdminEmailVerify)           // V2邮件审核
+			auth.GET("announcement/list", api.AnnouncementList)       // 游戏更新公告列表
+			auth.POST("announcement/add", api.AnnouncementAdd)        // 游戏更新公告新增
+			auth.POST("announcement/del", api.AnnouncementDel)        // 游戏更新公告删除
 			auth.GET("notice/list", api.NoticeList)                   // V2广播列表
 			auth.POST("notice/add", api.NoticeAdd)                    // V2广播新增/编辑
 			auth.POST("notice/cancel", api.NoticeCancel)              // V2广播取消

+ 42 - 9
server/internal/admin/service/admin_user.go

@@ -1,6 +1,7 @@
 package service
 
 import (
+	"encoding/base64"
 	"gadmin/config"
 	"gadmin/internal/admin/consts"
 	"gadmin/internal/admin/forms"
@@ -8,6 +9,7 @@ import (
 	"gadmin/internal/gorm/query"
 	"gadmin/utility/serializer"
 	"gadmin/utility/token"
+	jsoniter "github.com/json-iterator/go"
 	"time"
 
 	"github.com/gin-gonic/gin"
@@ -131,24 +133,55 @@ func (s *sUser) Login(req forms.UserLoginReq, ip string) serializer.Response {
 	//	return serializer.ParamErr("该账号只允许在测试服登录!", nil)
 	//}
 
-	t, err := token.GenerateToken(&token.UserClaims{
-		ID:       u.ID,
-		UserName: u.UserName,
-		RoleId:   int64(u.RoleID),
-		Avatar:   u.Avatar,
-		Nickname: u.Nickname,
-	})
+	t := token.GenerateTokenUsingUUID()
+
+	// 记录登录token
+	key := config.GetUserTokenKey(u.ID)
+	config.TokenRedis.HSet(key, t, time.Now().Unix())
+	config.TokenRedis.Expire(key, config.TokenExpireTime)
+
+	tokenKey := config.GetTokenKey(t)
+	user := &token.UserClaims{
+		ID:          u.ID,
+		UserName:    u.UserName,
+		RoleId:      int64(u.RoleID),
+		Avatar:      u.Avatar,
+		Nickname:    u.Nickname,
+		AccessToken: t,
+	}
+	userStr, err := jsoniter.MarshalToString(user)
 	if err != nil {
-		return serializer.ParamErr(err.Error(), nil)
+		return serializer.Err(1, "", err)
 	}
+	config.TokenRedis.Set(tokenKey, userStr, config.TokenExpireTime)
+
 	return serializer.Suc(forms.UserLoginRes{
 		ID:       u.ID,
 		UserName: u.UserName,
 		Nickname: u.Nickname,
 		Status:   u.Status,
 		Avatar:   u.Avatar,
-		Token:    t,
+		Token:    base64.URLEncoding.EncodeToString([]byte(t)),
 	})
+
+	//t, err := token.GenerateToken(&token.UserClaims{
+	//	ID:       u.ID,
+	//	UserName: u.UserName,
+	//	RoleId:   int64(u.RoleID),
+	//	Avatar:   u.Avatar,
+	//	Nickname: u.Nickname,
+	//})
+	//if err != nil {
+	//	return serializer.ParamErr(err.Error(), nil)
+	//}
+	//return serializer.Suc(forms.UserLoginRes{
+	//	ID:       u.ID,
+	//	UserName: u.UserName,
+	//	Nickname: u.Nickname,
+	//	Status:   u.Status,
+	//	Avatar:   u.Avatar,
+	//	Token:    t,
+	//})
 }
 
 func (s *sUser) Register(req forms.UserRegisterReq) serializer.Response {

+ 130 - 0
server/internal/admin/service/announcement.go

@@ -0,0 +1,130 @@
+package service
+
+import (
+	"encoding/json"
+	"gadmin/config"
+	"gadmin/internal/admin/forms"
+	"gadmin/utility/serializer"
+	"github.com/gin-gonic/gin"
+	"github.com/patrickmn/go-cache"
+	"github.com/sirupsen/logrus"
+	"net/http"
+	"time"
+)
+
+type sAnnouncement struct{}
+
+var announcementCache = cache.New(10*time.Minute, 10*time.Minute)
+
+var Announcement = new(sAnnouncement)
+
+var AnnouncementKey = "announcement"
+
+type AnnouncementItem struct {
+	Title     string `json:"title"`
+	Content   string `json:"content"`
+	CreatedAt int64  `json:"created_at"`
+}
+
+func (s *sAnnouncement) List() serializer.Response {
+
+	var models forms.ListRes
+	models.List = []*AnnouncementItem{}
+	models.Page = 1
+
+	announcementCacheStr, found := announcementCache.Get(AnnouncementKey)
+	if found {
+		announcementCacheItem, ok := announcementCacheStr.(*AnnouncementItem)
+		if ok {
+			models.PageCount = 0
+			models.List = []*AnnouncementItem{announcementCacheItem}
+			return serializer.Suc(models)
+		}
+	}
+	announcementItem := new(AnnouncementItem)
+	announcementStr := config.LogRedis.Get(AnnouncementKey).Val()
+	if announcementStr == "" {
+		models.PageCount = 0
+		return serializer.Suc(models)
+	}
+	err := json.Unmarshal([]byte(announcementStr), announcementItem)
+	if err != nil {
+		return serializer.Err(1, "json.Unmarshal err", err)
+	}
+
+	announcementCache.Set(AnnouncementKey, announcementItem, -1)
+
+	models.PageCount = 1
+	models.List = []*AnnouncementItem{announcementItem}
+
+	return serializer.Suc(models)
+}
+
+func (s *sAnnouncement) Add(req forms.AnnouncementAddReq) serializer.Response {
+	announcementItem := &AnnouncementItem{
+		Title:     req.Title,
+		Content:   req.Content,
+		CreatedAt: time.Now().Unix(),
+	}
+	announcementStr, err := json.Marshal(announcementItem)
+	if err != nil {
+		return serializer.Err(1, "json.Marshal err", err)
+	}
+
+	err = config.LogRedis.Set(AnnouncementKey, announcementStr, -1).Err()
+	if err != nil {
+		logrus.Errorf("log redis.Set Key:%v,Value:%v. err %v", AnnouncementKey, announcementStr, err)
+	}
+
+	announcementCache.Set(AnnouncementKey, announcementItem, -1)
+
+	return serializer.Suc(nil)
+}
+
+func (s *sAnnouncement) Del() serializer.Response {
+
+	err := config.LogRedis.Del(AnnouncementKey).Err()
+	if err != nil {
+		logrus.Errorf("log redis.Del Key:%v. err %v", AnnouncementKey, err)
+		return serializer.Err(1, "redis.Del err", err)
+	}
+
+	announcementCache.Delete(AnnouncementKey)
+
+	return serializer.Suc(nil)
+}
+
+type ResponseAnnouncement struct {
+	ErrCode int               `json:"errCode"`
+	Msg     string            `json:"msg,omitempty"`
+	Data    *AnnouncementItem `json:"data"`
+}
+
+func Get(c *gin.Context) {
+	res := &ResponseAnnouncement{}
+	cacheData, ok := announcementCache.Get(AnnouncementKey)
+	if ok {
+		res.Data = cacheData.(*AnnouncementItem)
+		c.JSON(http.StatusOK, res)
+		return
+	}
+
+	announcementStr := config.LogRedis.Get(AnnouncementKey).Val()
+	if announcementStr == "" {
+		c.JSON(http.StatusOK, res)
+		return
+	}
+	announcementItem := new(AnnouncementItem)
+	err := json.Unmarshal([]byte(announcementStr), announcementItem)
+	if err != nil {
+		res.ErrCode = 1
+		res.Msg = err.Error()
+		c.JSON(http.StatusOK, res)
+		return
+	}
+
+	announcementCache.Set(AnnouncementKey, announcementItem, -1)
+
+	res.Data = announcementItem
+	c.JSON(http.StatusOK, res)
+}

+ 19 - 0
server/internal/gorm/model/admin_operation.gen.go

@@ -0,0 +1,19 @@
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+
+package model
+
+const TableNameAdminOperation = "admin_operation"
+
+// AdminOperation mapped from table <admin_operation>
+type AdminOperation struct {
+	ID   int32  `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
+	Name string `gorm:"column:name;not null;comment:操作名称" json:"name"` // 操作名称
+	API  string `gorm:"column:api;not null;comment:接口路由" json:"api"`   // 接口路由
+}
+
+// TableName AdminOperation's table name
+func (*AdminOperation) TableName() string {
+	return TableNameAdminOperation
+}

+ 339 - 0
server/internal/gorm/query/admin_operation.gen.go

@@ -0,0 +1,339 @@
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+
+package query
+
+import (
+	"context"
+
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+	"gorm.io/gorm/schema"
+
+	"gorm.io/gen"
+	"gorm.io/gen/field"
+
+	"gorm.io/plugin/dbresolver"
+
+	"gadmin/internal/gorm/model"
+)
+
+func newAdminOperation(db *gorm.DB, opts ...gen.DOOption) adminOperation {
+	_adminOperation := adminOperation{}
+
+	_adminOperation.adminOperationDo.UseDB(db, opts...)
+	_adminOperation.adminOperationDo.UseModel(&model.AdminOperation{})
+
+	tableName := _adminOperation.adminOperationDo.TableName()
+	_adminOperation.ALL = field.NewAsterisk(tableName)
+	_adminOperation.ID = field.NewInt32(tableName, "id")
+	_adminOperation.Name = field.NewString(tableName, "name")
+	_adminOperation.API = field.NewString(tableName, "api")
+
+	_adminOperation.fillFieldMap()
+
+	return _adminOperation
+}
+
+type adminOperation struct {
+	adminOperationDo adminOperationDo
+
+	ALL  field.Asterisk
+	ID   field.Int32
+	Name field.String // 操作名称
+	API  field.String // 接口路由
+
+	fieldMap map[string]field.Expr
+}
+
+func (a adminOperation) Table(newTableName string) *adminOperation {
+	a.adminOperationDo.UseTable(newTableName)
+	return a.updateTableName(newTableName)
+}
+
+func (a adminOperation) As(alias string) *adminOperation {
+	a.adminOperationDo.DO = *(a.adminOperationDo.As(alias).(*gen.DO))
+	return a.updateTableName(alias)
+}
+
+func (a *adminOperation) updateTableName(table string) *adminOperation {
+	a.ALL = field.NewAsterisk(table)
+	a.ID = field.NewInt32(table, "id")
+	a.Name = field.NewString(table, "name")
+	a.API = field.NewString(table, "api")
+
+	a.fillFieldMap()
+
+	return a
+}
+
+func (a *adminOperation) WithContext(ctx context.Context) *adminOperationDo {
+	return a.adminOperationDo.WithContext(ctx)
+}
+
+func (a adminOperation) TableName() string { return a.adminOperationDo.TableName() }
+
+func (a adminOperation) Alias() string { return a.adminOperationDo.Alias() }
+
+func (a adminOperation) Columns(cols ...field.Expr) gen.Columns {
+	return a.adminOperationDo.Columns(cols...)
+}
+
+func (a *adminOperation) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
+	_f, ok := a.fieldMap[fieldName]
+	if !ok || _f == nil {
+		return nil, false
+	}
+	_oe, ok := _f.(field.OrderExpr)
+	return _oe, ok
+}
+
+func (a *adminOperation) fillFieldMap() {
+	a.fieldMap = make(map[string]field.Expr, 3)
+	a.fieldMap["id"] = a.ID
+	a.fieldMap["name"] = a.Name
+	a.fieldMap["api"] = a.API
+}
+
+func (a adminOperation) clone(db *gorm.DB) adminOperation {
+	a.adminOperationDo.ReplaceConnPool(db.Statement.ConnPool)
+	return a
+}
+
+func (a adminOperation) replaceDB(db *gorm.DB) adminOperation {
+	a.adminOperationDo.ReplaceDB(db)
+	return a
+}
+
+type adminOperationDo struct{ gen.DO }
+
+func (a adminOperationDo) Debug() *adminOperationDo {
+	return a.withDO(a.DO.Debug())
+}
+
+func (a adminOperationDo) WithContext(ctx context.Context) *adminOperationDo {
+	return a.withDO(a.DO.WithContext(ctx))
+}
+
+func (a adminOperationDo) ReadDB() *adminOperationDo {
+	return a.Clauses(dbresolver.Read)
+}
+
+func (a adminOperationDo) WriteDB() *adminOperationDo {
+	return a.Clauses(dbresolver.Write)
+}
+
+func (a adminOperationDo) Session(config *gorm.Session) *adminOperationDo {
+	return a.withDO(a.DO.Session(config))
+}
+
+func (a adminOperationDo) Clauses(conds ...clause.Expression) *adminOperationDo {
+	return a.withDO(a.DO.Clauses(conds...))
+}
+
+func (a adminOperationDo) Returning(value interface{}, columns ...string) *adminOperationDo {
+	return a.withDO(a.DO.Returning(value, columns...))
+}
+
+func (a adminOperationDo) Not(conds ...gen.Condition) *adminOperationDo {
+	return a.withDO(a.DO.Not(conds...))
+}
+
+func (a adminOperationDo) Or(conds ...gen.Condition) *adminOperationDo {
+	return a.withDO(a.DO.Or(conds...))
+}
+
+func (a adminOperationDo) Select(conds ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.Select(conds...))
+}
+
+func (a adminOperationDo) Where(conds ...gen.Condition) *adminOperationDo {
+	return a.withDO(a.DO.Where(conds...))
+}
+
+func (a adminOperationDo) Order(conds ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.Order(conds...))
+}
+
+func (a adminOperationDo) Distinct(cols ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.Distinct(cols...))
+}
+
+func (a adminOperationDo) Omit(cols ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.Omit(cols...))
+}
+
+func (a adminOperationDo) Join(table schema.Tabler, on ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.Join(table, on...))
+}
+
+func (a adminOperationDo) LeftJoin(table schema.Tabler, on ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.LeftJoin(table, on...))
+}
+
+func (a adminOperationDo) RightJoin(table schema.Tabler, on ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.RightJoin(table, on...))
+}
+
+func (a adminOperationDo) Group(cols ...field.Expr) *adminOperationDo {
+	return a.withDO(a.DO.Group(cols...))
+}
+
+func (a adminOperationDo) Having(conds ...gen.Condition) *adminOperationDo {
+	return a.withDO(a.DO.Having(conds...))
+}
+
+func (a adminOperationDo) Limit(limit int) *adminOperationDo {
+	return a.withDO(a.DO.Limit(limit))
+}
+
+func (a adminOperationDo) Offset(offset int) *adminOperationDo {
+	return a.withDO(a.DO.Offset(offset))
+}
+
+func (a adminOperationDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *adminOperationDo {
+	return a.withDO(a.DO.Scopes(funcs...))
+}
+
+func (a adminOperationDo) Unscoped() *adminOperationDo {
+	return a.withDO(a.DO.Unscoped())
+}
+
+func (a adminOperationDo) Create(values ...*model.AdminOperation) error {
+	if len(values) == 0 {
+		return nil
+	}
+	return a.DO.Create(values)
+}
+
+func (a adminOperationDo) CreateInBatches(values []*model.AdminOperation, batchSize int) error {
+	return a.DO.CreateInBatches(values, batchSize)
+}
+
+// Save : !!! underlying implementation is different with GORM
+// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
+func (a adminOperationDo) Save(values ...*model.AdminOperation) error {
+	if len(values) == 0 {
+		return nil
+	}
+	return a.DO.Save(values)
+}
+
+func (a adminOperationDo) First() (*model.AdminOperation, error) {
+	if result, err := a.DO.First(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.AdminOperation), nil
+	}
+}
+
+func (a adminOperationDo) Take() (*model.AdminOperation, error) {
+	if result, err := a.DO.Take(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.AdminOperation), nil
+	}
+}
+
+func (a adminOperationDo) Last() (*model.AdminOperation, error) {
+	if result, err := a.DO.Last(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.AdminOperation), nil
+	}
+}
+
+func (a adminOperationDo) Find() ([]*model.AdminOperation, error) {
+	result, err := a.DO.Find()
+	return result.([]*model.AdminOperation), err
+}
+
+func (a adminOperationDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AdminOperation, err error) {
+	buf := make([]*model.AdminOperation, 0, batchSize)
+	err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
+		defer func() { results = append(results, buf...) }()
+		return fc(tx, batch)
+	})
+	return results, err
+}
+
+func (a adminOperationDo) FindInBatches(result *[]*model.AdminOperation, batchSize int, fc func(tx gen.Dao, batch int) error) error {
+	return a.DO.FindInBatches(result, batchSize, fc)
+}
+
+func (a adminOperationDo) Attrs(attrs ...field.AssignExpr) *adminOperationDo {
+	return a.withDO(a.DO.Attrs(attrs...))
+}
+
+func (a adminOperationDo) Assign(attrs ...field.AssignExpr) *adminOperationDo {
+	return a.withDO(a.DO.Assign(attrs...))
+}
+
+func (a adminOperationDo) Joins(fields ...field.RelationField) *adminOperationDo {
+	for _, _f := range fields {
+		a = *a.withDO(a.DO.Joins(_f))
+	}
+	return &a
+}
+
+func (a adminOperationDo) Preload(fields ...field.RelationField) *adminOperationDo {
+	for _, _f := range fields {
+		a = *a.withDO(a.DO.Preload(_f))
+	}
+	return &a
+}
+
+func (a adminOperationDo) FirstOrInit() (*model.AdminOperation, error) {
+	if result, err := a.DO.FirstOrInit(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.AdminOperation), nil
+	}
+}
+
+func (a adminOperationDo) FirstOrCreate() (*model.AdminOperation, error) {
+	if result, err := a.DO.FirstOrCreate(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.AdminOperation), nil
+	}
+}
+
+func (a adminOperationDo) FindByPage(offset int, limit int) (result []*model.AdminOperation, count int64, err error) {
+	result, err = a.Offset(offset).Limit(limit).Find()
+	if err != nil {
+		return
+	}
+
+	if size := len(result); 0 < limit && 0 < size && size < limit {
+		count = int64(size + offset)
+		return
+	}
+
+	count, err = a.Offset(-1).Limit(-1).Count()
+	return
+}
+
+func (a adminOperationDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
+	count, err = a.Count()
+	if err != nil {
+		return
+	}
+
+	err = a.Offset(offset).Limit(limit).Scan(result)
+	return
+}
+
+func (a adminOperationDo) Scan(result interface{}) (err error) {
+	return a.DO.Scan(result)
+}
+
+func (a adminOperationDo) Delete(models ...*model.AdminOperation) (result gen.ResultInfo, err error) {
+	return a.DO.Delete(models)
+}
+
+func (a *adminOperationDo) withDO(do gen.Dao) *adminOperationDo {
+	a.DO = *do.(*gen.DO)
+	return a
+}

+ 6 - 0
server/internal/gorm/query/gen.go

@@ -22,6 +22,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
 		AdminRoleMenu: newAdminRoleMenu(db, opts...),
 		AdminRolePermission: newAdminRolePermission(db, opts...),
 		AdminPermission: newAdminPermission(db, opts...),
+		AdminOperation: newAdminOperation(db, opts...),
 		AdminSystem:         newAdminSystem(db, opts...),
 		AdminEmail:                newAdminEmail(db, opts...),
 		AdminLog:                  newAdminLog(db, opts...),
@@ -154,6 +155,7 @@ type Query struct {
 	AdminRoleMenu adminRoleMenu
 	AdminRolePermission adminRolePermission
 	AdminPermission adminPermission
+	AdminOperation adminOperation
 	AdminSystem         adminSystem
 	AdminEmail                adminEmail
 	AdminLog                  adminLog
@@ -287,6 +289,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
 		AdminRoleMenu: q.AdminRoleMenu.clone(db),
 		AdminRolePermission: q.AdminRolePermission.clone(db),
 		AdminPermission: q.AdminPermission.clone(db),
+		AdminOperation: q.AdminOperation.clone(db),
 		AdminSystem:         q.AdminSystem.clone(db),
 		AdminEmail:                q.AdminEmail.clone(db),
 		AdminLog:                  q.AdminLog.clone(db),
@@ -427,6 +430,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
 		AdminRoleMenu: q.AdminRoleMenu.replaceDB(db),
 		AdminRolePermission: q.AdminRolePermission.replaceDB(db),
 		AdminPermission: q.AdminPermission.replaceDB(db),
+		AdminOperation: q.AdminOperation.replaceDB(db),
 		AdminSystem:         q.AdminSystem.replaceDB(db),
 		AdminEmail:                q.AdminEmail.replaceDB(db),
 		AdminLog:                  q.AdminLog.replaceDB(db),
@@ -557,6 +561,7 @@ type queryCtx struct {
 	AdminRoleMenu *adminRoleMenuDo
 	AdminRolePermission *adminRolePermissionDo
 	AdminPermission *adminPermissionDo
+	AdminOperation *adminOperationDo
 	AdminSystem         *adminSystemDo
 	AdminEmail                *adminEmailDo
 	AdminLog                  *adminLogDo
@@ -687,6 +692,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
 		AdminRoleMenu: q.AdminRoleMenu.WithContext(ctx),
 		AdminRolePermission: q.AdminRolePermission.WithContext(ctx),
 		AdminPermission: q.AdminPermission.WithContext(ctx),
+		AdminOperation: q.AdminOperation.WithContext(ctx),
 		AdminSystem:         q.AdminSystem.WithContext(ctx),
 		AdminEmail:                q.AdminEmail.WithContext(ctx),
 		AdminLog:                  q.AdminLog.WithContext(ctx),

+ 1 - 1
server/local.env

@@ -19,7 +19,7 @@ MYSQL_GRAVE_SLAVE_DSN="root:123456@(127.0.0.1:3306)/covenant1?charset=utf8mb4&pa
 # 游戏服务器的redis1
 REDIS_ADDR="127.0.0.1:6379"
 REDIS_PW=""
-REDIS_DB="6"
+REDIS_DB="5"
 
 # nats
 #NATS_URL="nats://127.0.0.1:4222"  # 服务地址

+ 3 - 1
web/.env.development

@@ -1,5 +1,5 @@
 # 只在开发模式中被载入
-VITE_PORT = 8252
+VITE_PORT = 8254
 
 VITE_PUBLIC_PATH = /cadmin
 
@@ -18,3 +18,5 @@ VITE_GLOB_UPLOAD_URL=
 VITE_GLOB_IMG_URL=
 
 VITE_GLOB_API_URL_PREFIX = /api
+
+VITE_GLOB_BASE_LOGIN_URL = 'http://192.168.0.186:8252/adminentrance/#/login'

+ 2 - 0
web/.env.production

@@ -20,3 +20,5 @@ VITE_BUILD_COMPRESS = 'none'
 VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
 
 VITE_LEGACY = true
+
+VITE_GLOB_BASE_LOGIN_URL = 'https://gravedylog.mg.xhgame.com/adminentrance/#/login'

+ 24 - 0
web/src/api/announcement/announcement.ts

@@ -0,0 +1,24 @@
+import { http } from '@/utils/http/axios';
+
+export function List(params) {
+  return http.request({
+    url: '/announcement/list',
+    method: 'get',
+    params,
+  });
+}
+
+export function Add(params) {
+  return http.request({
+    url: '/announcement/add',
+    method: 'post',
+    params,
+  });
+}
+
+export function Del() {
+  return http.request({
+    url: '/announcement/del',
+    method: 'post',
+  });
+}

+ 4 - 2
web/src/layout/components/Header/index.vue

@@ -159,7 +159,7 @@
   import { useRouter, useRoute } from 'vue-router';
   import components from './components';
   import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
-  import { BASE_LOGIN_URL, TABS_ROUTES } from '@/store/mutation-types';
+  import { ACCESS_TOKEN, TABS_ROUTES } from '@/store/mutation-types';
   import { useUserStore } from '@/store/modules/user';
   import { useLockscreenStore } from '@/store/modules/lockscreen';
   import ProjectSetting from './ProjectSetting.vue';
@@ -172,6 +172,7 @@
   import { storage } from '@/utils/Storage';
   import ServicesModal from '@/views/serverSelect/modal.vue';
   import { SwapHorizontalOutline } from '@vicons/ionicons5';
+  import { getBaseLoginUrl } from '@/utils/env';
 
   export default defineComponent({
     name: 'PageHeader',
@@ -282,7 +283,8 @@
               message.success('成功退出登录');
               // 移除标签页
               localStorage.removeItem(TABS_ROUTES);
-              window.open(storage.get(BASE_LOGIN_URL), '_self');
+              storage.remove(ACCESS_TOKEN);
+              window.open(getBaseLoginUrl(), '_self');
               // router
               //   .replace({
               //     name: 'Login',

+ 1 - 5
web/src/main.ts

@@ -7,7 +7,7 @@ import { setupDirectives, setupNaive } from '@/plugins';
 import { AppProvider } from '@/components/Application';
 import Websocket from '@/utils/websocket';
 import { storage } from '@/utils/Storage';
-import { ACCESS_TOKEN, BASE_LOGIN_URL } from '@/store/mutation-types';
+import { ACCESS_TOKEN } from '@/store/mutation-types';
 
 function getQueryParams() {
   const params = new URLSearchParams(window.location.search);
@@ -24,10 +24,6 @@ async function bootstrap() {
   setupStore(app);
   appProvider.mount('#appProvider', true);
 
-  // 各个环境的统一入口
-  storage.set(BASE_LOGIN_URL, 'http://192.168.0.186:8252/cadmin/#/login');
-  // storage.set(BASE_LOGIN_URL, 'http://101.43.249.6:7006/cadmin/#/login');
-
   // 调用函数获取查询参数
   const accessToken = getQueryParams();
   console.log('====accessToken:', accessToken);

+ 28 - 28
web/src/router/index.ts

@@ -29,44 +29,44 @@ export const RootRoute: RouteRecordRaw = {
   },
 };
 
-export const LoginRoute: RouteRecordRaw = {
-  path: '/login',
-  name: 'Login',
-  component: () => import('@/views/login/index.vue'),
-  // component: () => import('@/views/permission/role/role.vue'),
-  meta: {
-    title: '登录',
-  },
-};
+// export const LoginRoute: RouteRecordRaw = {
+//   path: '/login',
+//   name: 'Login',
+//   component: () => import('@/views/login/index.vue'),
+//   // component: () => import('@/views/permission/role/role.vue'),
+//   meta: {
+//     title: '登录',
+//   },
+// };
 
-export const FeiShuLoginCK: RouteRecordRaw = {
-  path: '/feishulogin',
-  name: 'FeishuLogin',
-  component: () => import('@/components/FeiShu/FeiShuLoginCK.vue'),
-  meta: {
-    title: '飞书扫码登录',
-  },
-};
+// export const FeiShuLoginCK: RouteRecordRaw = {
+//   path: '/feishulogin',
+//   name: 'FeishuLogin',
+//   component: () => import('@/components/FeiShu/FeiShuLoginCK.vue'),
+//   meta: {
+//     title: '飞书扫码登录',
+//   },
+// };
 
-export const SystemSelectPage: RouteRecordRaw = {
-  path: '/selectSystem',
-  name: 'SelectSystem',
-  component: () => import('@/views/entrance/index.vue'),
-  meta: {
-    title: '选择系统',
-  },
-};
+// export const SystemSelectPage: RouteRecordRaw = {
+//   path: '/selectSystem',
+//   name: 'SelectSystem',
+//   component: () => import('@/views/entrance/index.vue'),
+//   meta: {
+//     title: '选择系统',
+//   },
+// };
 
 //需要验证权限
 export const asyncRoutes = [...routeModuleList];
 
 //普通路由 无需验证权限
 export const constantRouter: any[] = [
-  LoginRoute,
+  // LoginRoute,
   RootRoute,
   RedirectRoute,
-  FeiShuLoginCK,
-  SystemSelectPage,
+  // FeiShuLoginCK,
+  // SystemSelectPage,
 ];
 
 const router = createRouter({

+ 19 - 13
web/src/router/router-guards.ts

@@ -4,16 +4,21 @@ import { useUserStoreWidthOut } from '@/store/modules/user';
 import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
 // import { downloadJsonStoreWidthOut } from '@/store/modules/downloadJson';
 
-import { ACCESS_TOKEN, BASE_LOGIN_URL } from '@/store/mutation-types';
+import { ACCESS_TOKEN } from '@/store/mutation-types';
 import { storage } from '@/utils/Storage';
 import { PageEnum } from '@/enums/pageEnum';
 import { ErrorPageRoute } from '@/router/base';
+import { getBaseLoginUrl } from '@/utils/env';
 
-const LOGIN_PATH = PageEnum.BASE_LOGIN;
-const FEISHU_LOGIN_PATH = PageEnum.BASE_FEISHU_LOGIN;
-const SYSTEM_SELECT_PATH = PageEnum.SYSTEM_SELECT_PAGE;
+// const LOGIN_PATH = PageEnum.BASE_LOGIN;
+// const FEISHU_LOGIN_PATH = PageEnum.BASE_FEISHU_LOGIN;
+// const SYSTEM_SELECT_PATH = PageEnum.SYSTEM_SELECT_PAGE;
 
-const whitePathList = [LOGIN_PATH, FEISHU_LOGIN_PATH, SYSTEM_SELECT_PATH]; // no redirect whitelist
+const whitePathList = [
+  // LOGIN_PATH,
+  // FEISHU_LOGIN_PATH,
+  // SYSTEM_SELECT_PATH
+]; // no redirect whitelist
 
 export function createRouterGuards(router: Router) {
   const userStore = useUserStoreWidthOut();
@@ -22,11 +27,11 @@ export function createRouterGuards(router: Router) {
   router.beforeEach(async (to, from, next) => {
     const Loading = window['$loading'] || null;
     Loading && Loading.start();
-    if (from.path === LOGIN_PATH && to.name === 'errorPage') {
-      next(PageEnum.BASE_HOME);
-      return;
-    }
-
+    // if (from.path === LOGIN_PATH && to.name === 'errorPage') {
+    //   next(PageEnum.BASE_HOME);
+    //   return;
+    // }
+    console.log('to.path:', to.path);
     // Whitelist can be directly entered
     if (whitePathList.includes(to.path as PageEnum)) {
       next();
@@ -36,7 +41,7 @@ export function createRouterGuards(router: Router) {
     const token = storage.get(ACCESS_TOKEN);
 
     if (!token) {
-      window.open(storage.get(BASE_LOGIN_URL), '_self');
+      window.open(getBaseLoginUrl(), '_self');
       next(PageEnum.BASE_LOGIN);
       return;
       // You can access without permissions. You need to set the routing meta.ignoreAuth to true
@@ -46,7 +51,8 @@ export function createRouterGuards(router: Router) {
       }
       // redirect login page
       const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
-        path: LOGIN_PATH,
+        // path: LOGIN_PATH,
+        path: PageEnum.BASE_HOME,
         replace: true,
       };
       if (to.path) {
@@ -63,7 +69,7 @@ export function createRouterGuards(router: Router) {
       return;
     }
     const userInfo = await userStore.GetInfo();
-    const config = await userStore.GetConfig();
+    await userStore.GetConfig();
     const routes = await asyncRouteStore.generateRoutes(userInfo);
     // @ts-ignore
     // downloadJsonStore.downloadALL(config.json_version);

+ 7 - 0
web/src/utils/env.ts

@@ -4,6 +4,11 @@ import { warn } from '@/utils/log';
 import pkg from '../../package.json';
 import { getConfigFileName } from '../../build/getConfigFileName';
 
+export function getBaseLoginUrl() {
+  const { VITE_GLOB_BASE_LOGIN_URL } = getAppEnvConfig();
+  return VITE_GLOB_BASE_LOGIN_URL;
+}
+
 export function getCommonStoragePrefix() {
   const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
   return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase();
@@ -30,6 +35,7 @@ export function getAppEnvConfig() {
     VITE_GLOB_UPLOAD_URL,
     VITE_GLOB_PROD_MOCK,
     VITE_GLOB_IMG_URL,
+    VITE_GLOB_BASE_LOGIN_URL,
   } = ENV;
 
   if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
@@ -46,6 +52,7 @@ export function getAppEnvConfig() {
     VITE_GLOB_UPLOAD_URL,
     VITE_GLOB_PROD_MOCK,
     VITE_GLOB_IMG_URL,
+    VITE_GLOB_BASE_LOGIN_URL,
   };
 }
 

+ 30 - 0
web/src/views/announcement/columns.ts

@@ -0,0 +1,30 @@
+import { timestampToTime } from '@/utils/dateUtil';
+import { h } from 'vue';
+
+export const columns = [
+  {
+    title: '广播标题',
+    key: 'title',
+    width: 200,
+  },
+  {
+    title: '广播内容',
+    key: 'content',
+    width: 460,
+    render(rows) {
+      return h('p', { id: 'app' }, [
+        h('div', {
+          innerHTML: '<div style="white-space: pre-wrap">' + rows.content + '</div>',
+        }),
+      ]);
+    },
+  },
+  {
+    title: '创建时间',
+    key: 'created_at',
+    render: (rows, _) => {
+      return timestampToTime(rows.created_at);
+    },
+    width: 180,
+  },
+];

+ 231 - 0
web/src/views/announcement/index.vue

@@ -0,0 +1,231 @@
+<template>
+  <div>
+    <n-card :bordered="false" class="proCard" title="游戏更新公告">
+      <BasicTable
+        :columns="columns"
+        :request="loadDataTable"
+        :row-key="(row) => row.id"
+        ref="actionRef"
+        :actionColumn="actionColumn"
+        @update:checked-row-keys="onCheckedRow"
+        :scroll-x="1090"
+      >
+        <template #tableTitle>
+          <n-button type="primary" @click="addTable"> 新增公告 </n-button>
+        </template>
+      </BasicTable>
+
+      <n-modal
+        v-model:show="showModal"
+        :show-icon="false"
+        preset="dialog"
+        :title="title"
+        :style="{
+          width: dialogWidth,
+        }"
+      >
+        <n-form
+          :model="formParams"
+          :rules="rules"
+          ref="formRef"
+          label-placement="left"
+          :label-width="80"
+          class="py-4"
+        >
+          <n-form-item label="公告标题" path="content">
+            <n-input
+              type="textarea"
+              rows="5"
+              placeholder="请输入公告标题"
+              v-model:value="formParams.title"
+            />
+          </n-form-item>
+          <n-form-item label="公告内容" path="content">
+            <n-input
+              type="textarea"
+              rows="5"
+              placeholder="请输入公告内容"
+              v-model:value="formParams.content"
+            />
+          </n-form-item>
+        </n-form>
+
+        <template #action>
+          <n-space>
+            <n-button @click="() => (showModal = false)">取消</n-button>
+            <n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
+          </n-space>
+        </template>
+      </n-modal>
+    </n-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { h, reactive, ref } from 'vue';
+  import { useMessage } from 'naive-ui';
+  import { BasicTable, TableAction } from '@/components/Table';
+  import { List, Add, Del } from '@/api/announcement/announcement';
+  import { columns } from './columns';
+
+  const dialogWidth = ref('50%');
+
+  function mapWidth() {
+    let val = document.body.clientWidth;
+    const def = 720; // 默认宽度
+    if (val < def) {
+      dialogWidth.value = '100%';
+    } else {
+      dialogWidth.value = def + 'px';
+    }
+
+    return dialogWidth.value;
+  }
+
+  const rules = {
+    title: {
+      required: true,
+      trigger: ['blur', 'input'],
+      message: '请输入公告标题',
+    },
+    content: {
+      required: true,
+      trigger: ['blur', 'input'],
+      message: '请输入公告内容',
+    },
+  };
+
+  const title = ref('新增公告');
+  const formRef: any = ref(null);
+  const message = useMessage();
+  const actionRef = ref();
+
+  const showModal = ref(false);
+  const formBtnLoading = ref(false);
+  const formParams = ref<any>({
+    id: 0,
+    content: '',
+    send_interval: 1,
+    notice_type: 1,
+    start_at: null,
+    end_at: null,
+    date: null,
+    server_ids: null,
+  });
+
+  const params = ref({
+    pageSize: 10,
+  });
+
+  const actionColumn = reactive({
+    width: 220,
+    title: '操作',
+    key: 'action',
+    // fixed: 'right',
+    render(record) {
+      return h(TableAction as any, {
+        style: 'button',
+        actions: [
+          {
+            label: '编辑',
+            onClick: handleEdit.bind(null, record),
+          },
+          {
+            label: '删除',
+            onClick: handleDelete.bind(null, record),
+          },
+        ],
+        select: (key) => {
+          message.info(`您点击了,${key} 按钮`);
+        },
+      });
+    },
+  });
+
+  function addTable() {
+    title.value = '新增公告';
+    formParams.value.title = '';
+    formParams.value.content = '';
+    showModal.value = true;
+  }
+
+  const loadDataTable = async (res) => {
+    mapWidth();
+    let data = await List({ ...formParams.value, ...params.value, ...res });
+
+    data.list.sort((a, b) => {
+      return a.create_at > b.create_at ? -1 : 1;
+    });
+    return data;
+  };
+
+  function onCheckedRow(rowKeys) {
+    console.log(rowKeys);
+  }
+
+  function reloadTable() {
+    actionRef.value.reload();
+  }
+
+  function confirmForm(e) {
+    e.preventDefault();
+
+    formRef.value.validate((errors) => {
+      if (!errors) {
+        console.log('formParams:' + JSON.stringify(formParams.value));
+        if (formParams.value.title === null || formParams.value.title === '') {
+          message.error('标题不能为空');
+          return;
+        }
+        if (formParams.value.content === null || formParams.value.content === '') {
+          message.error('内容不能为空');
+          return;
+        }
+
+        formBtnLoading.value = true;
+        Add(formParams.value)
+          .then((_res) => {
+            message.success('操作成功');
+            setTimeout(() => {
+              showModal.value = false;
+              reloadTable();
+            });
+          })
+          .catch((_error) => {
+            // message.error(error.toString());
+          });
+      } else {
+        message.error('请填写完整信息');
+      }
+      formBtnLoading.value = false;
+    });
+  }
+
+  function handleEdit(record: Recordable) {
+    title.value = '编辑公告';
+    formParams.value.date = [record.start_at * 1000, record.end_at * 1000];
+    formParams.value.content = record.content;
+    formParams.value.send_interval = record.send_interval;
+    formParams.value.notice_type = record.notice_type;
+    formParams.value.id = record.id;
+    showModal.value = true;
+
+    console.log('点击了编辑', record);
+  }
+
+  function handleDelete() {
+    Del()
+      .then((_res) => {
+        message.success('操作成功');
+        setTimeout(() => {
+          showModal.value = false;
+          reloadTable();
+        });
+      })
+      .catch((_error) => {
+        message.error(_error.toString());
+      });
+  }
+</script>
+
+<style lang="less" scoped></style>

+ 3 - 2
web/src/views/entrance/index.vue

@@ -75,7 +75,7 @@
   import { useUserStore } from '@/store/modules/user';
   import { useRouter } from 'vue-router';
   import { useDialog, useMessage } from 'naive-ui';
-  import { ACCESS_TOKEN, BASE_LOGIN_URL, TABS_ROUTES } from '@/store/mutation-types';
+  import { ACCESS_TOKEN, TABS_ROUTES } from '@/store/mutation-types';
   import { storage } from '@/utils/Storage';
   import { GetServiceList, SelectSystem } from '@/api/service/service';
   import { ref } from 'vue';
@@ -83,6 +83,7 @@
   import User from '../permission/user/user.vue';
   import { CheckRolePermission } from '@/api/system/role';
   import { SafetyCertificateOutlined } from '@vicons/antd';
+  import { getBaseLoginUrl } from '@/utils/env';
   interface server {
     id: number;
     name: string;
@@ -114,7 +115,7 @@
               message.success('成功退出登录');
               // 移除标签页
               localStorage.removeItem(TABS_ROUTES);
-              window.open(storage.get(BASE_LOGIN_URL), '_self');
+              window.open(getBaseLoginUrl(), '_self');
               // router
               //   .replace({
               //     name: 'Login',

+ 2 - 2
web/src/views/serverSelect/select.vue

@@ -29,8 +29,8 @@
     })
       .then((res) => {
         console.log('_res:' + JSON.stringify(res));
-        const ex = 7 * 24 * 60 * 60 * 1000;
-        storage.set(ACCESS_TOKEN, res.token, ex);
+        // const ex = 7 * 24 * 60 * 60 * 1000;
+        // storage.set(ACCESS_TOKEN, res.token, ex);
         const path = url.replace('{access-token}', storage.get(ACCESS_TOKEN));
         window.open(path, '_self');
       })

+ 2 - 0
web/types/config.d.ts

@@ -71,4 +71,6 @@ export interface GlobEnvConfig {
   VITE_GLOB_IMG_URL?: string;
   //生产环境开启mock
   VITE_GLOB_PROD_MOCK: boolean;
+  //统一登录入口
+  VITE_GLOB_BASE_LOGIN_URL: string;
 }