修复好多

This commit is contained in:
likun 2025-05-15 17:30:33 +08:00
parent 5f9156137f
commit 6ece507c90
26 changed files with 696 additions and 131 deletions

View File

@ -9,10 +9,15 @@ import (
"admin/apps/game/model/dto"
"admin/internal/consts"
"admin/internal/errcode"
"admin/internal/event"
"admin/lib/xlog"
"database/sql"
"encoding/json"
"fmt"
"gorm.io/gorm"
"reflect"
"strings"
"time"
)
type CommonResourceService struct {
@ -39,8 +44,13 @@ func initCommonResourcesRepo(db *gorm.DB) {
r(consts.ResourcesName_Role, "角色列表", repo.NewCommonResourceRepo(db, &model.Role{}), ShowMethod_Get) // 角色管理不需要在后台读写数据都是通过项目api拉
r(consts.ResourcesName_WhiteList, "白名单", repo.NewCommonResourceRepo(db, &model.WhiteList{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Delete)
r(consts.ResourcesName_Ban, "封禁管理", repo.NewCommonResourceRepo(db, &model.Ban{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_MailRole, "个人邮件", repo.NewCommonResourceRepo(db, &model.RoleMail{}), ShowMethod_Get|ShowMethod_Post) // 个人邮件发放就没法撤回?
r(consts.ResourcesName_MailGlobal, "全服邮件", repo.NewCommonResourceRepo(db, &model.GlobalMail{}), ShowMethod_Get|ShowMethod_Post) // 直接删除,别修改了,玩家收到的更乱
r(consts.ResourcesName_MailRole, "个人邮件", repo.NewCommonResourceRepo(db, &model.RoleMail{}), ShowMethod_Get|ShowMethod_Post) // 个人邮件发放就没法撤回?
{
globalMailRepo := r(consts.ResourcesName_MailGlobal, "全服邮件", repo.NewCommonResourceRepo(db, &model.GlobalMail{}), ShowMethod_Get|ShowMethod_Post)
globalMailRepo.HasDelayInvokeCreateHook = true
} // 直接删除,别修改了,玩家收到的更乱
r(consts.ResourcesName_CDKey, "礼包码", repo.NewCommonResourceRepo(db, &model.CDKey{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_ItemBag, "道具礼包", repo.NewCommonResourceRepo(db, &model.ItemBag{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_Notice, "公告(暂无)", repo.NewCommonResourceRepo(db, &model.Notice{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
@ -52,9 +62,48 @@ func NewCommonResourceService(db *gorm.DB) *CommonResourceService {
svc := &CommonResourceService{
projectRepo: repo.NewProjectRepo(db),
}
svc.startEventSubscriber()
svc.startLoadAllDelayInvokeDbData()
return svc
}
func (svc *CommonResourceService) startLoadAllDelayInvokeDbData() {
for _, repo := range commResourcesRepo {
if repo.HasDelayInvokeCreateHook {
repo.Repo.ListPagination("`delay_invoke_create_hook` is not NULL and `delay_invoke_create_hook` > ?", []any{time.Now()}, func(po model.IModel) {
var projectId int
var resource string = repo.Resource
var delayAt time.Time
//xlog.Infof("过滤数据:%+v", po)
switch repo.Resource {
case consts.ResourcesName_MailGlobal:
dbData := po.(*model.GlobalMail)
projectId = dbData.ProjectId
if !dbData.DelayInvokeCreateHook.Valid || dbData.DelayInvokeCreateHook.Time.Before(time.Now()) {
return
}
delayAt = dbData.DelayInvokeCreateHook.Time
default:
return
}
// 延迟执行钩子调用
xlog.Infof("起服查看全局邮件:%+v延迟到%v发送添加到全局定时器。。", po, delayAt.Format(time.DateTime))
objBin, _ := json.Marshal(po)
payload := &event.EventPayload_DelayInvokeCreateHook{
ProjectId: projectId,
Resource: resource,
DelayAt: delayAt,
Obj: objBin,
}
event.GetMgrInstance().Publish(event.EventTopic_DelayInvokeCreateHook, payload)
return
})
}
}
}
func (svc *CommonResourceService) List(projectId int, resource string, listParams *dto.CommonListReq) (int, []*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
@ -65,7 +114,7 @@ func (svc *CommonResourceService) List(projectId int, resource string, listParam
}
rRepo := findCommResourceRepo(resource)
totalCount, fieldsDescInfo, etList, err := rRepo.List(projectEt, listParams)
totalCount, fieldsDescInfo, etList, err := rRepo.Repo.List(projectEt, listParams)
if err != nil {
return 0, nil, nil, err
}
@ -96,7 +145,7 @@ func (svc *CommonResourceService) GetById(projectId int, resource string, id int
return nil, nil, nil, false, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
fieldsDescInfo, et, find, err := findCommResourceRepo(resource).GetById(projectEt, id)
fieldsDescInfo, et, find, err := findCommResourceRepo(resource).Repo.GetById(projectEt, id)
if err != nil {
return nil, nil, nil, false, err
}
@ -114,7 +163,8 @@ func (svc *CommonResourceService) Create(projectId int, resource string, dtoObj
}
createOne := func(obj dto.CommonDtoValues) (dto.CommonDtoValues, error) {
et, err := findCommResourceRepo(resource).Create(projectEt, resource, obj)
resourceRepo := findCommResourceRepo(resource)
et, err := resourceRepo.Repo.Create(projectEt, resource, obj)
if err != nil {
return nil, err
}
@ -124,6 +174,23 @@ func (svc *CommonResourceService) Create(projectId int, resource string, dtoObj
newObj := et.ToCommonDto()
// 执行各个项目特定的钩子方法
if resourceRepo.HasDelayInvokeCreateHook {
field := reflect.ValueOf(et.Po).Elem().FieldByName("DelayInvokeCreateHook")
delayAt := field.Interface().(sql.NullTime)
if delayAt.Valid && delayAt.Time.After(time.Now()) {
// 延迟执行钩子调用
objBin, _ := json.Marshal(et.Po)
payload := &event.EventPayload_DelayInvokeCreateHook{
ProjectId: projectId,
Resource: resource,
DelayAt: delayAt.Time,
Obj: objBin,
}
event.GetMgrInstance().Publish(event.EventTopic_DelayInvokeCreateHook, payload)
return newObj, nil
}
}
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpCreateHook); ok {
err = hook.Create(projectEt, resource, newObj)
if err != nil {
@ -168,7 +235,7 @@ func (svc *CommonResourceService) Edit(projectId int, resource string, dtoObj dt
return errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
err = findCommResourceRepo(resource).Edit(projectEt, dtoObj)
err = findCommResourceRepo(resource).Repo.Edit(projectEt, dtoObj)
if err != nil {
return err
}
@ -194,7 +261,7 @@ func (svc *CommonResourceService) Delete(projectId int, resource string, id int)
return nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
oldEt, find, err := findCommResourceRepo(resource).Delete(projectEt, id)
oldEt, find, err := findCommResourceRepo(resource).Repo.Delete(projectEt, id)
if err != nil {
return nil, err
}
@ -249,13 +316,14 @@ const (
)
type resourceRepoInfo struct {
Resource string
Desc string
Repo repo.ICommonResourceRepo
ShowMethods []string
showMethods int
GlobalBtns []*api.ResourceBtnInfo
RowBtns []*api.ResourceBtnInfo
Resource string
Desc string
Repo repo.ICommonResourceRepo
ShowMethods []string
showMethods int
GlobalBtns []*api.ResourceBtnInfo
RowBtns []*api.ResourceBtnInfo
HasDelayInvokeCreateHook bool
}
var commResourcesRepo = make([]*resourceRepoInfo, 0)
@ -286,10 +354,10 @@ func r(resource, desc string, repo repo.ICommonResourceRepo, showMethods int) *r
return curRepo
}
func findCommResourceRepo(resource string) repo.ICommonResourceRepo {
func findCommResourceRepo(resource string) *resourceRepoInfo {
for _, v := range commResourcesRepo {
if v.Resource == resource {
return v.Repo
return v
}
}
panic(fmt.Errorf("not found Resource:%v", resource))

View File

@ -67,6 +67,10 @@ func getFieldTypeDtoDescInfo(project *Project, poValue reflect.Value, fieldType
f1.Name = "创建时间"
}
if f1.Key == "UpdatedAt" {
f1.Name = "更新时间"
}
if tagType := fieldType.Tag.Get("type"); tagType != "" {
f1.Type = tagType
}

View File

@ -0,0 +1,71 @@
package domain
import (
"admin/apps/game/domain/entity"
"admin/apps/game/domain/projects"
"admin/apps/game/model"
"admin/internal/consts"
"admin/internal/event"
"admin/internal/global"
"admin/lib/dtimer"
"admin/lib/xlog"
"encoding/json"
"fmt"
"time"
)
func (svc *CommonResourceService) startEventSubscriber() {
event.GetMgrInstance().Subscribe("resource.create", event.EventTopic_DelayInvokeCreateHook, svc.handleResourceDelayInvokeCreateHook)
}
func (svc *CommonResourceService) handleResourceDelayInvokeCreateHook(msg *event.Msg) {
payload := &event.EventPayload_DelayInvokeCreateHook{}
err := json.Unmarshal(msg.Payload, payload)
if err != nil {
xlog.Warnf("unmarshal EventPayload_DelayInvokeCreateHook(%+v) error:%v", string(msg.Payload), err)
return
}
if payload.Resource == consts.ResourcesName_MailGlobal {
mailInfo := &model.GlobalMail{}
err = json.Unmarshal(payload.Obj, mailInfo)
if err != nil {
xlog.Warnf("unmarshal mail(%+v) error:%v", string(payload.Obj), err)
return
}
delayAt := payload.DelayAt
if delayAt.After(time.Now()) {
xlog.Infof("全服邮件[%+v]延迟到%v发送添加到全局定时器。。。", mailInfo, delayAt.Format(time.DateTime))
global.GLOB_TIMER.Add(&dtimer.Timer{
Key: fmt.Sprintf("create.global.mail.%v", mailInfo.ID),
Duration: delayAt.Sub(time.Now()),
Callback: func() {
_, projectEt, _, err := svc.projectRepo.GetById(payload.ProjectId)
if err != nil {
return
}
obj := new(entity.CommonResource).FromPo(mailInfo).ToCommonDto()
hook, ok := projects.GetProjectResourceHook(projectEt, payload.Resource).(projects.IPostResourceOpCreateHook)
if !ok {
return
}
err = hook.Create(projectEt, payload.Resource, obj)
if err != nil {
xlog.Errorf("invoke hook create resource:%v obj:%+v error:%v", payload.Resource, obj)
return
}
xlog.Infof("delay send global mail ok, mail info:%+v", mailInfo)
// 更新数据库延迟时间为NULL
resourceRepo := findCommResourceRepo(payload.Resource)
err = resourceRepo.Repo.UpdateClearDelayInvokeCreateHookFieldN(mailInfo.ID)
if err != nil {
xlog.Errorf("UpdateClearDelayInvokeCreateHookFieldN id:%v error:%v", mailInfo.ID)
}
},
})
}
}
}

View File

@ -4,6 +4,7 @@ import (
"admin/apps/game/domain/entity"
"admin/apps/game/domain/projects"
"admin/apps/game/domain/repo"
"admin/apps/game/model"
"admin/apps/game/model/dto"
"admin/internal/errcode"
"gorm.io/gorm"
@ -87,3 +88,26 @@ func (svc *ProjectService) GetAllItems(projectId int) ([]*dto.CommonDtoFieldChoi
return handler.GetItems(projectEt)
}
func (svc *ProjectService) GetAllItemBag(projectId int) ([]*dto.ItemBagInfo, error) {
dbList, err := new(model.ItemBag).List(projectId)
if err != nil {
return nil, err
}
retList := make([]*dto.ItemBagInfo, 0, len(dbList))
for _, v := range dbList {
info := &dto.ItemBagInfo{
Name: v.Name,
}
for _, item := range v.Attach {
info.Items = append(info.Items, &dto.ItemInfo{
ItemID: int(item.ID),
ItemNum: item.Num,
ItemType: item.ItemType,
Desc: item.Desc,
})
}
retList = append(retList, info)
}
return retList, nil
}

View File

@ -11,6 +11,7 @@ import (
"net/url"
"strconv"
"strings"
"time"
)
type MailGlobalHook struct {
@ -68,6 +69,19 @@ func (hook *MailGlobalHook) Create(projectInfo *entity.Project, resource string,
params.Add("mailtitle", mailInfo.Title)
params.Add("mailcontent", mailInfo.Content)
if mailInfo.ExpireAt.Valid {
params.Add("expiretime", mailInfo.ExpireAt.Time.Format(time.DateTime))
}
if mailInfo.CreateRoleTimeBefore.Valid {
params.Add("createrole", mailInfo.CreateRoleTimeBefore.Time.Format(time.DateTime))
}
if mailInfo.TotalPayMoney > 0 {
params.Add("cash", strconv.Itoa(mailInfo.TotalPayMoney))
}
if mailInfo.RoleLevel > 0 {
params.Add("playerlevel", strconv.Itoa(mailInfo.RoleLevel))
}
// 通知对应服务器
for _, serverId := range serverIds {
serverIdInt, _ := strconv.Atoi(serverId)

View File

@ -9,6 +9,7 @@ import (
"admin/lib/httpclient"
"admin/lib/utils"
"net/url"
"sort"
"strconv"
)
@ -94,5 +95,9 @@ func (hook *RoleHook) List(projectInfo *entity.Project, resource string, params
}
}
sort.SliceStable(rows, func(i, j int) bool {
return rows[i]["Status"].(string) < rows[j]["Status"].(string)
})
return totalCount, fields, rows, nil
}

View File

@ -7,6 +7,7 @@ import (
"admin/internal/consts"
"admin/internal/errcode"
"admin/lib/xlog"
"database/sql"
"errors"
"fmt"
"gorm.io/gorm"
@ -26,6 +27,8 @@ type ICommonResourceRepo interface {
Create(projectEt *entity.Project, resource string, et dto.CommonDtoValues) (*entity.CommonResource, error)
Edit(projectEt *entity.Project, et dto.CommonDtoValues) error
Delete(projectEt *entity.Project, id int) (*entity.CommonResource, bool, error)
ListPagination(whereSql string, whereArgs []any, f func(po model.IModel)) error
UpdateClearDelayInvokeCreateHookFieldN(id int) error
}
func NewCommonResourceRepo(db *gorm.DB, poTemplate model.IModel) ICommonResourceRepo {
@ -96,11 +99,11 @@ func (repo *commonResourceRepoImpl) List(projectEt *entity.Project, params *dto.
}
func (repo *commonResourceRepoImpl) GetById(projectEt *entity.Project, id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error) {
po := repo.newEmptyPo()
po := repo.makeEmptyPo()
err := repo.db.Where("id = ?", id).First(po).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return repo.fieldsDescInfoFun(projectEt), (&entity.CommonResource{}).FromPo(repo.newEmptyPo()), false, nil
return repo.fieldsDescInfoFun(projectEt), (&entity.CommonResource{}).FromPo(repo.makeEmptyPo()), false, nil
}
return nil, nil, false, errcode.New(errcode.DBError, "get resource:%v by id:%v error:%v", repo.poTemplate.TableName(), id, err)
}
@ -108,7 +111,7 @@ func (repo *commonResourceRepoImpl) GetById(projectEt *entity.Project, id int) (
}
func (repo *commonResourceRepoImpl) Create(projectEt *entity.Project, resource string, dtoObj dto.CommonDtoValues) (*entity.CommonResource, error) {
et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj)
et := (&entity.CommonResource{}).FromPo(repo.makeEmptyPo()).FromDto(dtoObj)
if handler, find := createHooks[resource]; find {
if err := handler(projectEt, dtoObj); err != nil {
return et, err
@ -127,7 +130,7 @@ func (repo *commonResourceRepoImpl) Create(projectEt *entity.Project, resource s
}
func (repo *commonResourceRepoImpl) Edit(projectEt *entity.Project, dtoObj dto.CommonDtoValues) error {
et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj)
et := (&entity.CommonResource{}).FromPo(repo.makeEmptyPo()).FromDto(dtoObj)
err := repo.db.Where("id=?", et.Po.GetId()).Save(et.Po).Error
if err != nil {
return errcode.New(errcode.DBError, "edit resource:%v obj:%+v error:%v", repo.poTemplate.TableName(), et, err)
@ -151,7 +154,52 @@ func (repo *commonResourceRepoImpl) Delete(projectEt *entity.Project, id int) (*
return et, true, nil
}
func (repo *commonResourceRepoImpl) newEmptyPo() model.IModel {
func (repo *commonResourceRepoImpl) ListPagination(whereSql string, whereArgs []any, f func(po model.IModel)) error {
pageNo := 0
pageLen := 100
for {
limitStart := pageNo * pageLen
limitLen := pageLen
listType := reflect.New(reflect.SliceOf(reflect.TypeOf(repo.poTemplate)))
var txFind *gorm.DB
var err error
if len(whereSql) <= 0 {
txFind = repo.db.Offset(limitStart).Limit(limitLen).Order("created_at desc")
} else {
txFind = repo.db.Where(whereSql, whereArgs...).Offset(limitStart).Limit(limitLen)
}
err = txFind.Find(listType.Interface()).Error
if err != nil {
return err
}
listType1 := listType.Elem()
listLen := listType1.Len()
for i := 0; i < listType1.Len(); i++ {
po := listType1.Index(i).Interface().(model.IModel)
f(po)
}
if listLen < limitLen {
// 遍历完了
return nil
}
pageNo++
}
}
func (repo *commonResourceRepoImpl) UpdateClearDelayInvokeCreateHookFieldN(id int) error {
repo.makeEmptyPo()
err := repo.db.Model(repo.makeEmptyPo()).Where("id = ?", id).UpdateColumn("delay_invoke_create_hook", sql.NullTime{}).Error
if err != nil {
return err
}
return nil
}
func (repo *commonResourceRepoImpl) makeEmptyPo() model.IModel {
return reflect.New(reflect.TypeOf(repo.poTemplate).Elem()).Interface().(model.IModel)
}
@ -168,13 +216,28 @@ func (repo *commonResourceRepoImpl) parseWhereConditions2Sql(conditions []*dto.G
}
dbFieldName := namer.ColumnName("", field.Name)
if field.Type.Name() == "Time" {
cond.Value1, _ = time.ParseInLocation("2006/01/02 15:04:05", cond.Value1.(string), time.Local)
cond.Value2, _ = time.ParseInLocation("2006/01/02 15:04:05", cond.Value2.(string), time.Local)
if cond.Value1 == nil {
cond.Value1 = time.Time{}
} else {
cond.Value1, _ = time.ParseInLocation("2006/01/02 15:04:05", cond.Value1.(string), time.Local)
}
if cond.Value2 == nil {
cond.Value2 = time.Time{}
} else {
cond.Value2, _ = time.ParseInLocation("2006/01/02 15:04:05", cond.Value2.(string), time.Local)
}
}
switch field.Tag.Get("where") {
case "eq":
whereClause = append(whereClause, fmt.Sprintf("`%v` = ?", dbFieldName))
whereArgs = append(whereArgs, cond.Value1)
if field.Tag.Get("type") == "[]string" && field.Tag.Get("multi_choice") == "true" {
// eq也要查出来为空的
whereClause = append(whereClause, fmt.Sprintf("JSON_CONTAINS( `%v`, '\"%v\"', '$' ) or `%v` IS NULL",
dbFieldName, cond.Value1, dbFieldName))
} else {
whereClause = append(whereClause, fmt.Sprintf("`%v` = ?", dbFieldName))
whereArgs = append(whereArgs, cond.Value1)
}
case "gt":
whereClause = append(whereClause, fmt.Sprintf("`%v` > ?", dbFieldName))
whereArgs = append(whereArgs, cond.Value1)
@ -191,10 +254,10 @@ func (repo *commonResourceRepoImpl) parseWhereConditions2Sql(conditions []*dto.G
whereClause = append(whereClause, fmt.Sprintf("`%v` like ?", dbFieldName))
whereArgs = append(whereArgs, cond.Value1)
case "range":
if t1, ok1 := cond.Value1.(time.Time); ok1 {
t2, _ := cond.Value2.(time.Time)
if t1.IsZero() && t2.IsZero() {
t1, ok1 := cond.Value1.(time.Time)
t2, ok2 := cond.Value2.(time.Time)
if ok1 || ok2 {
if !t1.IsZero() && !t2.IsZero() {
whereClause = append(whereClause, fmt.Sprintf("`%v` >= ? and `%v` <= ?", dbFieldName, dbFieldName))
whereArgs = append(whereArgs, cond.Value1, cond.Value2)
} else if !t1.IsZero() {

View File

@ -23,6 +23,7 @@ type Ban struct {
ExpireAt sql.NullTime `name:"封禁到期时间" desc:"封禁到期时间0表示永久封禁"`
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *Ban) TableName() string {

View File

@ -14,9 +14,9 @@ func init() {
type CDKey struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId int `gorm:"index:idx_project_id"`
ProjectId int `gorm:"uniqueIndex:idx_project_cdkey"`
ServerIDs []string `gorm:"type:json;serializer:json" name:"区服" desc:"不选就是全服通用" type:"[]string" choices:"GetChoiceServers" multi_choice:"true" uneditable:"true""`
Name string `gorm:"type:varchar(100);unique" name:"礼包说明" required:"true" uneditable:"true"`
Name string `gorm:"type:varchar(100);uniqueIndex:idx_project_cdkey" name:"礼包说明" required:"true" uneditable:"true"`
CodeType int `name:"礼包类型" required:"true" choices:"GetCodeTypeChoices" uneditable:"true"`
Code string `gorm:"type:VARCHAR(50);index" name:"礼包码" desc:"一码通用才配置" uneditable:"true"`
CodeNum int `name:"礼包数量" desc:"一码一用才配置"`
@ -25,6 +25,7 @@ type CDKey struct {
Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"礼包码奖励道具" type:"items" desc:"搜索道具并点击添加"`
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *CDKey) TableName() string {

View File

@ -21,6 +21,7 @@ type CDKeyUsed struct {
IP string `gorm:"type:varchar(20)"`
DeviceId string `gorm:"type:varchar(50)"`
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *CDKeyUsed) TableName() string {

View File

@ -39,7 +39,8 @@ type CommonDtoValues map[string]any
type CommonDtoList struct {
FieldsDesc []*CommonDtoFieldDesc `json:"fields_desc"` // 数据字段描述信息
TotalCount int `json:"total_count"`
Rows []CommonDtoValues `json:"rows"` // 数据行
Rows []CommonDtoValues `json:"rows"` // 数据行
ItemBags []*ItemBagInfo `json:"item_bags"` // 礼包,用来给道具添加功能选择用
}
type PathInfo struct {
@ -61,6 +62,11 @@ type ItemInfo struct {
Desc string `json:"desc"`
}
type ItemBagInfo struct {
Name string `json:"name"`
Items []*ItemInfo `json:"items"`
}
type AccountDetailOrderInfo struct {
ServerId string `json:"server_id"`
AccountId string `json:"account_id"`

View File

@ -3,6 +3,7 @@ package model
import (
"admin/apps/game/model/dto"
"admin/internal/db"
"database/sql"
"time"
)
@ -11,14 +12,20 @@ func init() {
}
type GlobalMail struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId int `gorm:"index:idx_project_id"`
ServerIDs []string `gorm:"type:json;serializer:json" desc:"不选就是默认所有区服" name:"区服" type:"[]string" choices:"GetChoiceServers" multi_choice:"true"`
Title string `name:"邮件标题" required:"true"`
Content string `name:"邮件内容" required:"true"`
Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"邮件附件" type:"items" desc:"搜索道具并点击添加"`
ID int `gorm:"primarykey" readonly:"true"`
ProjectId int `gorm:"index:idx_project_id"`
ServerIDs []string `gorm:"type:json;serializer:json" desc:"不选就是默认所有区服" name:"区服" type:"[]string" choices:"GetChoiceServers" multi_choice:"true"`
Title string `name:"邮件标题" required:"true"`
Content string `name:"邮件内容" required:"true"`
DelayInvokeCreateHook sql.NullTime `name:"邮件定时发送时间" desc:"不填或者无效就立即发送"`
ExpireAt sql.NullTime `name:"邮件到期时间" desc:"不填就是永久有效"`
CreateRoleTimeBefore sql.NullTime `name:"创角时间" desc:"在这时间之前创建的角色才能收到邮件,不填就是都生效"`
TotalPayMoney int `name:"充值金额大于" desc:"充值金额大于的才能收到邮件,不填就是没有要求"`
RoleLevel int `name:"角色等级大于" desc:"角色等级大于的才能收到邮件,不填就是没有要求"`
Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"邮件附件" type:"items" desc:"搜索道具并点击添加"`
CreatedAt time.Time `readonly:"true" where:"range"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *GlobalMail) TableName() string {

View File

@ -3,6 +3,8 @@ package model
import (
"admin/apps/game/model/dto"
"admin/internal/db"
"admin/internal/errcode"
"admin/internal/global"
"time"
)
@ -13,11 +15,12 @@ func init() {
// 道具包,配置一系列道具,供邮件、礼包码填写使用
type ItemBag struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId int `gorm:"uniqueIndex:idx_whitelist"`
Name string `gorm:"type:varchar(255);uniqu" name:"礼包名称" desc:"请输入礼包名称,全项目唯一" required:"true" where:"eq"`
Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"邮件附件" type:"items" desc:"搜索道具并点击添加"`
ProjectId int `gorm:"uniqueIndex:idx_bag"`
Name string `gorm:"type:varchar(255);uniqueIndex:idx_bag" name:"礼包名称" desc:"请输入礼包名称,全项目唯一" required:"true" where:"eq"`
Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"邮件附件" type:"items" desc:"搜索道具并点击添加" required:"true"`
CreatedAt time.Time `readonly:"true" where:"range"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *ItemBag) TableName() string {
@ -38,3 +41,12 @@ func (m *ItemBag) GetWhitelistTypeChoices(project *Project) []*dto.CommonDtoFiel
{Desc: "账号", Value: "account"},
}
}
func (m *ItemBag) List(projectId int) ([]*ItemBag, error) {
list := make([]*ItemBag, 0)
err := global.GLOB_DB.Where("project_id = ?", projectId).Order("created_at desc").Find(&list).Error
if err != nil {
return list, errcode.New(errcode.DBError, "list all item bag error:%v", err)
}
return list, nil
}

View File

@ -19,6 +19,7 @@ type Notice struct {
EndAt time.Time `name:"结束时间" required:"true"`
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *Notice) TableName() string {

View File

@ -28,6 +28,7 @@ type Project struct {
ApiAddr string `name:"游戏api地址" desc:"api服务器地址例如神魔大陆就是alisrv服务器地址用于后台调用gm"`
SortWeight int `name:"菜单排序" desc:"越大越靠前"`
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *Project) TableName() string {

View File

@ -27,6 +27,7 @@ type RoleMail struct {
Attach []*MailAttachItem `gorm:"type:json;serializer:json" name:"邮件附件" type:"items" desc:"搜索道具并点击添加"`
CreatedAt time.Time `readonly:"true" where:"range"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *RoleMail) TableName() string {

View File

@ -29,6 +29,7 @@ type Server struct {
//ApiAddr string
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *Server) TableName() string {

View File

@ -17,6 +17,7 @@ type SupportAccount struct {
Account string `gorm:"type:varchar(200);uniqueIndex:idx_account;" name:"账号" desc:"用逗号标记多个" required:"true"`
CreatedAt time.Time `readonly:"true" where:"range"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *SupportAccount) TableName() string {

View File

@ -18,6 +18,7 @@ type WhiteList struct {
Value string `gorm:"type:varchar(128);uniqueIndex:idx_whitelist" name:"账号或者ip等值" required:"true"`
CreatedAt time.Time `readonly:"true" where:"range"`
UpdatedAt time.Time `readonly:"true"`
}
func (lm *WhiteList) TableName() string {

View File

@ -58,7 +58,11 @@ func (svc *Service) CommonList(ctx context.Context, projectId int, resourceName
}}, params.ParsedWhereConditions.Conditions...)
}
totalCount, fieldsDescInfo, rows, err := svc.resourceSvc.List(projectId, resourceName, params)
return &dto.CommonDtoList{FieldsDesc: fieldsDescInfo, TotalCount: totalCount, Rows: rows}, err
itemBags, err := svc.projectSvc.GetAllItemBag(projectId)
if err != nil {
return nil, err
}
return &dto.CommonDtoList{FieldsDesc: fieldsDescInfo, TotalCount: totalCount, Rows: rows, ItemBags: itemBags}, err
}
func (svc *Service) CommonPost(ctx context.Context, projectId int, resourceName string, params dto.CommonDtoValues) (dto.CommonDtoValues, error) {

View File

@ -1,7 +1,10 @@
package event
import "time"
const (
EventTopic_UserExecute = "user.execute"
EventTopic_UserExecute = "user.execute"
EventTopic_DelayInvokeCreateHook = "resource.create.delay"
)
type EventPayload_UserExecute struct {
@ -14,3 +17,10 @@ type EventPayload_UserExecute struct {
NewData any `json:"new_data"`
Any any `json:"any"`
}
type EventPayload_DelayInvokeCreateHook struct {
ProjectId int
Resource string
DelayAt time.Time
Obj []byte
}

View File

@ -3,6 +3,7 @@ package global
import (
"admin/internal/config"
"admin/lib/cache"
"admin/lib/dtimer"
"admin/lib/web"
"gorm.io/gorm"
)
@ -12,4 +13,5 @@ var (
GLOB_DB *gorm.DB // 数据库
GLOB_API_ENGINE *web.Engine // 全局api服务器
GLOB_LOCAL_CACHER = cache.NewLocalCache() // 全局本地内存缓存器
GLOB_TIMER = dtimer.NewDTimerMgr() // 全局定时器
)

View File

@ -1,9 +1,100 @@
package dtimer
import (
"admin/lib/xlog"
"fmt"
"runtime"
"sync"
"time"
)
/*
分布式定时器用于解决比如多个进程启动后只有一个进程触发定时器事件执行
目标不影响架构复杂性做个能用的出来思路是用mysql做分布式锁多个进程抢占单例执行
*/
type DTimer struct {
type Timer struct {
Key string
Duration time.Duration
IsInterval bool
Callback func()
expireAt time.Time
timerMgr *DTimerMgr
}
// todo 先实现一个本地数据库单例版本的
type DTimerMgr struct {
locker *sync.Mutex
timers map[string]*Timer
taskQueue chan *Timer
}
func NewDTimerMgr() *DTimerMgr {
mgr := new(DTimerMgr)
mgr.locker = new(sync.Mutex)
mgr.timers = make(map[string]*Timer)
mgr.taskQueue = make(chan *Timer, 1024)
go mgr.start()
return mgr
}
func (mgr *DTimerMgr) Add(task *Timer) (*Timer, bool) {
mgr.locker.Lock()
defer mgr.locker.Unlock()
old, find := mgr.timers[task.Key]
task.expireAt = time.Now().Add(task.Duration)
mgr.timers[task.Key] = task
return old, find
}
func (mgr *DTimerMgr) Del(key string) {
mgr.locker.Lock()
defer mgr.locker.Unlock()
delete(mgr.timers, key)
}
func (mgr *DTimerMgr) start() {
for i := 0; i < runtime.NumCPU(); i++ {
go func() {
for {
select {
case t := <-mgr.taskQueue:
func() {
defer xlog.CatchWithInfo(fmt.Sprintf("handle timer %v panic", t.Key))
t.Callback()
}()
}
}
}()
}
tk := time.NewTicker(time.Second)
defer tk.Stop()
for {
select {
case <-tk.C:
mgr.locker.Lock()
mgr.checkExpire()
mgr.locker.Unlock()
}
}
}
func (mgr *DTimerMgr) checkExpire() {
timeNow := time.Now()
for k, v := range mgr.timers {
if v.expireAt.Before(timeNow) {
if !v.IsInterval {
delete(mgr.timers, k)
} else {
v.expireAt = timeNow.Add(v.Duration)
}
select {
case mgr.taskQueue <- v:
default:
xlog.Warnf("timer channel full!!!")
}
}
}
}

View File

@ -0,0 +1,21 @@
package dtimer
import (
"log"
"testing"
"time"
)
func TestTimer(t *testing.T) {
mgr := NewDTimerMgr()
log.Printf("start ...")
mgr.Add(&Timer{
Key: "test1",
Duration: time.Second * 5,
Callback: func() {
log.Printf("trigger timer")
},
})
select {}
}

View File

@ -40,6 +40,7 @@ const fieldsDescInfo = ref([])
const whereFieldsDescInfo = ref([])
const calcElColSpan = ref(0)
const rows = ref([])
const itemBags = ref([])
const rules = ref({})
const current_page = ref(1)
@ -47,10 +48,10 @@ const page_size = ref(10)
const pageSizes = [10, 20, 50, 100]
const totalRowCount = ref(0)
const item = ref({
id: '',
number: 1,
})
const selectedItem = ref(null)
const selectedItemNum = ref(0)
const selectedItemBag = ref(null)
// console.log("enter table, resource:", cachedResource)
@ -68,7 +69,7 @@ const listData = async () => {
conditions: []
}
whereFieldsDescInfo.value.forEach((field) => {
if (field.value1) {
if (field.value1 || field.value2) {
whereReqConditions.conditions.push({
key: field.key,
op: field.where,
@ -85,20 +86,35 @@ const listData = async () => {
fieldsDescInfo.value = listRsp.value.data.fields_desc
totalRowCount.value = listRsp.value.data.total_count
rows.value = listRsp.value.data.rows
itemBags.value = listRsp.value.data.item_bags
for (let i = 0; i < fieldsDescInfo.value.length; i++) {
var field = fieldsDescInfo.value[i]
// dialogObjectForm.value[field.key] = ''
if (field.required == true) {
if (field.required === true) {
rules.value[field.key] = [{required: true, message: field.name + "不能为空", trigger: "blur"}]
}
if (field.type == "items") {
if (field.type === "items") {
dialogObjectForm.value[field.key] = []
for (let j = 0; j < rows.value.length; j++) {
rows.value[j].jsonValue = JSON.stringify(rows.value[j][field.key])
}
if (field.required === true) {
rules.value[field.key] = [{
required: true,
validator: (rule, value, callback) => {
console.log("触发校验道具列表规则:", dialogObjectForm.value)
if (dialogObjectForm.value.Attach === undefined || dialogObjectForm.value.Attach.length === 0) {
callback(new Error("请至少填写一个奖励道具!"))
} else {
callback()
}
},
trigger: ["blur", "change"],
}]
}
}
if (field.where !== "") {
@ -313,29 +329,64 @@ const handleDelete = (index, row) => {
}
function addItem(fieldDescInfo) {
if (item.value.id == null || item.value.id == '' || item.value.id < 0) {
ElMessage('请选择道具!')
return;
let hasValidInput = false;
if (selectedItemBag.value != null && selectedItemBag.value.name !== undefined && selectedItemBag.value.name !== '') {
selectedItemBag.value.items.forEach((bagItem) => {
if (typeof dialogObjectForm.value.Attach === typeof "") {
dialogObjectForm.value.Attach = [];
}
let d = {id: bagItem.item_id, num: bagItem.item_num, desc: bagItem.desc, item_type: bagItem.item_type};
dialogObjectForm.value.Attach.push(d);
})
console.log("添加礼包:", selectedItemBag.value)
hasValidInput = true;
}
if (item.value.num == null || item.value.num == '' || item.value.num <= 0) {
ElMessage('请输入有效道具数量!')
return;
if (selectedItem.value !== null && selectedItem.value.value !== undefined && selectedItem.value.value !== '') {
if (selectedItemNum.value <= 0) {
ElMessage('请输入有效道具数量!')
return;
}
let d = {
id: selectedItem.value.value,
num: Number(selectedItemNum.value),
desc: selectedItem.value.desc,
item_type: selectedItem.value.type
};
console.log("add item:", d)
if (typeof dialogObjectForm.value.Attach === typeof "") {
dialogObjectForm.value.Attach = [];
}
dialogObjectForm.value.Attach.push(d);
hasValidInput = true;
}
if (!hasValidInput) {
console.log("道具:", selectedItem.value)
console.log("礼包:", selectedItemBag.value)
ElMessage('请选择道具或者礼包!')
}
let d = {id: item.value.id, num: Number(item.value.num), desc: item.value.desc, item_type: item.value.item_type};
console.log("add item:", d)
if (typeof dialogObjectForm.value.Attach === typeof "") {
dialogObjectForm.value.Attach = [];
if (dialogAddVisible.value) {
dialogAddFormRef.value.validateField("Attach");
} else if (dialogEditVisible.value) {
// console.log("", rules.value)
// console.log("", dialogEditFormRef.value)
dialogEditFormRef.value.validateField("Attach");
}
dialogObjectForm.value.Attach.push(d);
}
function deleteItem(row) {
//
let number = dialogObjectForm.value.Attach.findIndex(item => item === row);
dialogObjectForm.value.Attach.splice(number, 1);
if (dialogAddVisible.value) {
dialogAddFormRef.value.validateField("Attach");
} else if (dialogEditVisible.value) {
// console.log("", rules.value)
dialogEditFormRef.value.validateField("Attach");
}
}
const handleCloseDialog = () => {
@ -345,18 +396,14 @@ const handleCloseDialog = () => {
dialogObjectForm.value = {
Attach: [],
}
item.value.desc = ''
selectedItem.value = null
selectedItemNum.value = 0
selectedItemBag.value = null
}
const loadingRemoteItems = ref(false)
const itemChoices = ref({})
const handleItemOnSelect = (itemOption) => {
console.log("选中:", itemOption)
item.value.id = itemOption.value
item.value.desc = itemOption.desc
}
const handleQueryItem = (itemQueryStr) => {
if (!itemQueryStr) {
itemChoices.value = []
@ -465,13 +512,24 @@ const handlePaginationCurChange = (val) => {
<el-main>
<el-table :data="rows" style="width: 100%" table-layout="auto" stripe
@selection-change="handleTableSelectChange">
<el-table-column type="selection" v-if="globalClickBtns.length > 0 || rowClickBtns.length > 0">
<el-table-column type="selection" v-if="globalClickBtns.length > 0">
</el-table-column>
<template v-for="fieldDescInfo in fieldsDescInfo">
<!-- 道具列表 -->
<el-table-column prop="jsonValue" :label="fieldDescInfo.name" show-overflow-tooltip
v-if="(fieldDescInfo.type === 'items')"></el-table-column>
<!-- 角色状态 -->
<el-table-column prop="jsonValue" :label="fieldDescInfo.name"
v-else-if="(fieldDescInfo.key === 'Status' && cachedResource.meta.resource === 'role' )">
<template #default="scope">
<el-tag type="info" v-if="scope.row.Status === '离线'">离线</el-tag>
<el-tag type="success" v-else-if="scope.row.Status === '在线'">在线</el-tag>
</template>
</el-table-column>
<!-- 其它普通字段 -->
<el-table-column :prop="fieldDescInfo.key" :label="fieldDescInfo.name"
:show-overflow-tooltip="(fieldDescInfo.key === 'Title' || fieldDescInfo.key === 'Content')"
v-else></el-table-column>
</template>
<el-table-column prop="func" label="功 能">
@ -547,15 +605,14 @@ const handlePaginationCurChange = (val) => {
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如何是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" label-position="right">
<el-form :inline="true" :model="selectedItem" label-position="right">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key" label-width="130px">
<el-tooltip effect="light" :content="fieldDescInfo.help_text" placement="bottom-start">
<el-select v-model="item" placeholder="--搜索道具--" style="width: 150px"
filterable remote
<el-select v-model="selectedItem" placeholder="--搜索道具--" style="width: 150px"
filterable remote clearable
:remote-method="handleQueryItem"
:loading="loadingRemoteItems"
value-key="value"
@change="handleItemOnSelect"
>
<el-option v-for="info in itemChoices" :key="info.value" :label="info.desc"
:value="info"></el-option>
@ -563,7 +620,16 @@ const handlePaginationCurChange = (val) => {
</el-tooltip>
</el-form-item>
<el-form-item label="数量" prop="num">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
<el-input type="number" v-model="selectedItemNum" placeholder="请输入数量" style="width: 150px"/>
</el-form-item>
<!-- 选择礼包 -->
<el-form-item v-if="cachedResource.meta.resource !== 'item_bag'">
<el-tooltip effect="light" content="选择礼包,点击添加到奖励列表">
<el-select placeholder="--礼包--" v-model="selectedItemBag" clearable style="width: 150px"
value-key="name">
<el-option v-for="bag in itemBags" :key="bag.name" :label="bag.name" :value="bag"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addItem(fieldDescInfo)">添加</el-button>
@ -633,11 +699,11 @@ const handlePaginationCurChange = (val) => {
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" label-position="right"
<el-form :inline="true" :model="selectedItem" label-position="right"
label-width="130px">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-tooltip effect="light" :content="fieldDescInfo.help_text" placement="bottom-start">
<el-select placeholder="--搜索道具--" v-model="item" style="width: 150px"
<el-select placeholder="--搜索道具--" v-model="selectedItem" style="width: 150px"
filterable remote :remote-method="handleQueryItem"
:loading="loadingRemoteItems"
value-key="value"
@ -648,13 +714,22 @@ const handlePaginationCurChange = (val) => {
</el-tooltip>
</el-form-item>
<el-form-item label="数量" prop="number">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
<el-input type="number" v-model="selectedItemNum" placeholder="请输入数量" style="width: 150px"/>
</el-form-item>
<!-- 选择礼包 -->
<el-form-item v-if="cachedResource.meta.resource !== 'item_bag'">
<el-tooltip effect="light" content="选择礼包,点击添加到奖励列表">
<el-select placeholder="--礼包--" v-model="selectedItemBag" clearable style="width: 150px"
value-key="name">
<el-option v-for="bag in itemBags" :key="bag.name" :label="bag.name" :value="bag"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addItem(fieldDescInfo)">添加</el-button>
</el-form-item>
</el-form>
<el-form-item label="奖励列表" prop="attachmentsList">
<el-form-item label="奖励列表" prop="Attach">
<el-table :data="dialogObjectForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>

View File

@ -21,6 +21,7 @@ const fieldsDescInfo = ref([])
const whereFieldsDescInfo = ref([])
const calcElColSpan = ref(0)
const rows = ref([])
const itemBags = ref([])
const rules = ref({})
const current_page = ref(1)
@ -28,11 +29,9 @@ const page_size = ref(10)
const pageSizes = [10, 20, 50, 100]
const totalRowCount = ref(0)
const item = ref({
id: '',
number: 1,
desc: '',
})
const selectedItem = ref(null)
const selectedItemNum = ref(0)
const selectedItemBag = ref(null)
// console.log("enter table, resource:", cachedResource)
@ -67,13 +66,14 @@ const listData = async () => {
fieldsDescInfo.value = listRsp.value.data.fields_desc
totalRowCount.value = listRsp.value.data.total_count
rows.value = listRsp.value.data.rows
itemBags.value = listRsp.value.data.item_bags
for (let i = 0; i < fieldsDescInfo.value.length; i++) {
var field = fieldsDescInfo.value[i]
dialogObjectForm.value[field.key] = ''
if (field.required == true) {
rules.value[field.key] = [{required: true, message: field.name + "不能为空", trigger: "blur"}]
rules.value[field.key] = [{required: true, message: field.name + "不能为空", trigger: ["blur", "change"]}]
}
if (field.type == "items") {
@ -91,7 +91,7 @@ const listData = async () => {
callback()
}
},
trigger: "blur",
trigger: ["blur", "change"],
}]
}
@ -160,8 +160,7 @@ const submitAdd = async () => {
try {
await dialogAddFormRef.value.validate(valid => {
if (valid) {
console.log("commit add form:", dialogObjectForm.value)
return
// console.log("commit add form:", dialogObjectForm.value)
resourcePost(resource_url, dialogObjectForm.value).then((res) => {
ElNotification({
title: "添加结果通知",
@ -253,11 +252,19 @@ const handleLook = (index, row) => {
}
const handleEdit = (index, row) => {
dialogObjectForm.value = row
dialogObjectForm.value.oldData = row
console.log("点击行:", row)
const rawRow = JSON.parse(JSON.stringify(row))
dialogObjectForm.value = rawRow
dialogObjectForm.value.oldData = rawRow
dialogObjectForm.value.oldIndex = index
rules.value["Code"] = []
rules.value["CodeNum"] = []
if (row.CodeType === 0) {
cdkeyCodeInputDisabled.value = true
cdkeyCodeNumInputDisabled.value = true
} else {
cdkeyCodeInputDisabled.value = true
cdkeyCodeNumInputDisabled.value = false
}
handleCodeTypeOnSelect(row.CodeType)
console.log("edit data:", row)
dialogEditVisible.value = true
}
@ -282,36 +289,74 @@ const handleDelete = (index, row) => {
}
function addItem() {
if (item.value.id == null || item.value.id == '' || item.value.id < 0) {
ElMessage('请选择道具!')
return;
}
if (item.value.num == null || item.value.num == '' || item.value.num <= 0) {
ElMessage('请输入有效道具数量!')
return;
let hasValidInput = false;
if (selectedItemBag.value != null && selectedItemBag.value.name !== undefined && selectedItemBag.value.name !== '') {
selectedItemBag.value.items.forEach((bagItem) => {
if (typeof dialogObjectForm.value.Attach === typeof "") {
dialogObjectForm.value.Attach = [];
}
let d = {id: bagItem.item_id, num: bagItem.item_num, desc: bagItem.desc, item_type: bagItem.item_type};
dialogObjectForm.value.Attach.push(d);
})
console.log("添加礼包:", selectedItemBag.value)
hasValidInput = true;
}
let d = {id: item.value.id, num: Number(item.value.num), desc: item.value.desc, item_type: item.value.item_type};
if (selectedItem.value !== null && selectedItem.value.value !== undefined && selectedItem.value.value !== '') {
if (selectedItemNum.value <= 0) {
ElMessage('请输入有效道具数量!')
return;
}
let d = {
id: selectedItem.value.value,
num: Number(selectedItemNum.value),
desc: selectedItem.value.desc,
item_type: selectedItem.value.type
};
console.log("add item:", d)
console.log("add item:", d)
if (typeof dialogObjectForm.value.Attach === typeof "") {
dialogObjectForm.value.Attach = [];
if (typeof dialogObjectForm.value.Attach === typeof "") {
dialogObjectForm.value.Attach = [];
}
dialogObjectForm.value.Attach.push(d);
hasValidInput = true;
}
if (!hasValidInput) {
console.log("道具:", selectedItem.value)
console.log("礼包:", selectedItemBag.value)
ElMessage('请选择道具或者礼包!')
return
}
// console.log("", rules.value)
//
if (dialogAddVisible.value) {
dialogAddFormRef.value.validateField("Attach");
} else if (dialogEditVisible.value) {
// console.log("", rules.value)
// console.log("", dialogEditFormRef.value)
dialogEditFormRef.value.validateField("Attach");
}
dialogObjectForm.value.Attach.push(d);
console.log("校验规则:", rules.value)
dialogAddFormRef.value.validateField("Attach");
}
function deleteItem(row) {
//
let number = dialogObjectForm.value.Attach.findIndex(item => item === row);
dialogObjectForm.value.Attach.splice(number, 1);
dialogAddFormRef.value.validateField("Attach");
if (dialogAddVisible.value) {
dialogAddFormRef.value.validateField("Attach");
} else if (dialogEditVisible.value) {
// console.log("", rules.value)
dialogEditFormRef.value.validateField("Attach");
}
}
const handleCloseDialog = () => {
console.log("关闭添加/编辑弹窗")
if (dialogEditVisible.value) {
}
dialogAddVisible.value = false
dialogLookVisible.value = false
dialogEditVisible.value = false
@ -319,7 +364,9 @@ const handleCloseDialog = () => {
dialogObjectForm.value = {
Attach: [],
}
item.value.desc = ''
selectedItem.value = null
selectedItemNum.value = 0
selectedItemBag.value = null
}
const loadingRemoteItems = ref(false)
@ -343,7 +390,20 @@ const handleCodeTypeOnSelect = (codeType) => {
cdkeyCodeInputDisabled.value = true
cdkeyCodeNumInputDisabled.value = false
rules.value["Code"] = []
rules.value["CodeNum"] = [{required: true, message: "礼包数量不能为空", trigger: "blur"}]
rules.value["CodeNum"] = [{
required: true,
validator: (rule, value, callback) => {
const codeNum = dialogObjectForm.value['CodeNum']
console.log("校验礼包数量:", dialogObjectForm.value)
console.log("校验礼包数量:", codeNum)
if (dialogObjectForm.value['CodeType'] === 1 && codeNum <= 0) {
callback(new Error("请至少大于0的奖励码数量"))
} else {
callback()
}
},
trigger: ["blur", "change"],
}]
}
}
@ -485,6 +545,7 @@ const handlePaginationCurChange = (val) => {
<span>礼包使用</span>
</el-button>
<el-button size="default" type="danger" @click="handleDelete( scope.$index, scope.row)"
style="margin-top: 5px"
v-if="(resource_raw_node.meta.methods.delete === true)">
<span>删除</span>
</el-button>
@ -513,15 +574,14 @@ const handlePaginationCurChange = (val) => {
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如何是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" label-position="right">
<el-form :inline="true" :model="selectedItem" label-position="right">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key" label-width="130px">
<el-tooltip effect="light" :content="fieldDescInfo.help_text" placement="bottom-start">
<el-select v-model="item" placeholder="--搜索道具--" style="width: 150px"
filterable remote
<el-select v-model="selectedItem" placeholder="--搜索道具--" style="width: 150px"
filterable remote clearable
:remote-method="handleQueryItem"
:loading="loadingRemoteItems"
value-key="value"
@change="handleItemOnSelect"
>
<el-option v-for="info in itemChoices" :key="info.value" :label="info.desc"
:value="info"></el-option>
@ -529,7 +589,17 @@ const handlePaginationCurChange = (val) => {
</el-tooltip>
</el-form-item>
<el-form-item label="数量" prop="num">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
<el-input type="number" v-model="selectedItemNum" placeholder="请输入大于0的道具数量"
style="width: 150px"/>
</el-form-item>
<!-- 选择礼包 -->
<el-form-item>
<el-tooltip effect="light" content="选择礼包,点击添加到奖励列表">
<el-select placeholder="--礼包--" v-model="selectedItemBag" clearable style="width: 150px"
value-key="name">
<el-option v-for="bag in itemBags" :key="bag.name" :label="bag.name" :value="bag"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addItem()">添加</el-button>
@ -609,15 +679,15 @@ const handlePaginationCurChange = (val) => {
</el-form>
</el-dialog>
<el-dialog v-model="dialogLookVisible" :mask="true" title="编辑" :modal="true" :before-close="handleCloseDialog"
<el-dialog v-model="dialogLookVisible" :mask="true" title="查看" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogEditFormRef" :model="dialogObjectForm" :rules="rules" class="operation_form"
<el-form ref="dialogLookFormRef" :model="dialogObjectForm" :rules="rules" class="operation_form"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form-item label="奖励列表" prop="attachmentsList">
<el-form-item label="奖励列表" prop="Attach">
<el-table :data="dialogObjectForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
@ -665,10 +735,6 @@ const handlePaginationCurChange = (val) => {
<!-- <el-input v-model="dialogEditForm[fieldDescInfo.key]"></el-input>-->
<!-- </el-form-item>-->
</template>
<el-form-item>
<el-button @click="submitEdit(dialogEditFormRef)" size="large" type="primary">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
@ -680,30 +746,38 @@ const handlePaginationCurChange = (val) => {
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" label-position="right"
<el-form :inline="true" :model="selectedItem" label-position="right"
label-width="130px">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-tooltip effect="light" :content="fieldDescInfo.help_text" placement="bottom-start">
<el-select placeholder="--搜索道具--" v-model="item" style="width: 150px"
filterable remote
<el-select placeholder="--搜索道具--" v-model="selectedItem" style="width: 150px"
filterable remote clearable
:remote-method="handleQueryItem"
:loading="loadingRemoteItems"
value-key="value"
@change="handleItemOnSelect"
>
<el-option v-for="info in itemChoices" :key="info.value" :label="info.desc"
:value="info"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item label="数量" prop="number">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
<el-form-item label="数量" prop="num" label-width="40px">
<el-input type="number" v-model="selectedItemNum" placeholder="请输入数量" style="width: 150px"/>
</el-form-item>
<!-- 选择礼包 -->
<el-form-item>
<el-tooltip effect="light" content="选择礼包,点击添加到奖励列表">
<el-select placeholder="--礼包--" v-model="selectedItemBag" clearable style="width: 150px"
value-key="name">
<el-option v-for="bag in itemBags" :key="bag.name" :label="bag.name" :value="bag"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="addItem(fieldDescInfo)">添加</el-button>
</el-form-item>
</el-form>
<el-form-item label="奖励列表" prop="attachmentsList">
<el-form-item label="奖励列表" prop="Attach">
<el-table :data="dialogObjectForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
@ -745,8 +819,13 @@ const handlePaginationCurChange = (val) => {
<!-- 否则就是普通字段 -->
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogObjectForm[fieldDescInfo.key]" :disabled="cdkeyCodeInputDisabled"
:placeholder="fieldDescInfo.help_text" v-if="fieldDescInfo.key === 'Code'"></el-input>
<el-input v-model="dialogObjectForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text"></el-input>
:placeholder="fieldDescInfo.help_text" :disabled="cdkeyCodeNumInputDisabled"
v-else-if="fieldDescInfo.key === 'CodeNum'" type="number"></el-input>
<el-input v-model="dialogObjectForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text" v-else></el-input>
</el-form-item>
</template>
</template>