optimize some

This commit is contained in:
likun 2025-06-09 13:50:00 +08:00
parent 0e95de298e
commit c51c430967
20 changed files with 628 additions and 221 deletions

View File

@ -27,20 +27,20 @@ type CommonResourceService struct {
noticeRepo repo.INoticeRepo
}
func initCommonResourcesRepo(db *gorm.DB) {
func (svc *CommonResourceService) initCommonResourcesRepo(db *gorm.DB) {
r(consts.ResourcesName_Project, "项目管理", repo.NewCommonResourceRepo(db, &model.Project{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
{
serverRepo := r(consts.ResourcesName_Server, "服务器管理", repo.NewCommonResourceRepo(db, &model.Server{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
serverRepo.GlobalBtns = []*api.ResourceBtnInfo{
{Key: consts.BtnKeyGlobal_Server_DownAll, Name: "一键停服", BtnColorType: "info"},
{Key: consts.BtnKeyGlobal_Server_UpAll, Name: "一键起服", BtnColorType: "warning"},
{Key: consts.BtnKeyGlobal_Server_ExportCdn, Name: "一键导出cdn", BtnColorType: "danger"},
{Key: consts.BtnKeyGlobal_Server_ExportCdn, Name: "预览导出cdn", BtnColorType: "default"},
serverRepo.GlobalBtns = []*ResourceBtnInfo{
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Server_DownAll, Name: "一键停服", BtnColorType: "info"}, svc.handleServerUpOrDown},
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Server_UpAll, Name: "一键起服", BtnColorType: "warning"}, svc.handleServerUpOrDown},
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Server_ExportCdn, Name: "一键导出cdn", BtnColorType: "danger"}, svc.handleServerExportCdn},
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Server_ExportCdn, Name: "预览导出cdn", BtnColorType: "default"}, svc.handleServerExportCdn},
}
serverRepo.RowBtns = []*api.ResourceBtnInfo{
{Key: consts.BtnKeyRow_Server_Down, Name: "停服", BtnColorType: "info"},
{Key: consts.BtnKeyRow_Server_Up, Name: "起服", BtnColorType: "warning"},
serverRepo.RowBtns = []*ResourceBtnInfo{
{&api.ResourceBtnInfo{Key: consts.BtnKeyRow_Server_Down, Name: "停服", BtnColorType: "info"}, svc.handleServerUpOrDown},
{&api.ResourceBtnInfo{Key: consts.BtnKeyRow_Server_Up, Name: "起服", BtnColorType: "warning"}, svc.handleServerUpOrDown},
}
}
@ -60,24 +60,25 @@ func initCommonResourcesRepo(db *gorm.DB) {
r(consts.ResourcesName_ItemBag, "道具礼包", repo.NewCommonResourceRepo(db, &model.ItemBag{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
{
noticeRepo := r(consts.ResourcesName_Notice, "公告", repo.NewCommonResourceRepo(db, &model.Notice{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
noticeRepo.GlobalBtns = []*api.ResourceBtnInfo{
{Key: consts.BtnKeyGlobal_Server_DownAll, Name: "一键禁用所有", BtnColorType: "info"},
noticeRepo.GlobalBtns = []*ResourceBtnInfo{
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Notice_DisableAll, Name: "一键禁用", BtnColorType: "info"}, svc.handleNoticeDisable},
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Notice_EnableAll, Name: "一键启用", BtnColorType: "warning"}, svc.handleNoticeEnable},
}
noticeRepo.RowBtns = []*api.ResourceBtnInfo{
{Key: consts.BtnKeyRow_Server_Down, Name: "禁用", BtnColorType: "info"},
{Key: consts.BtnKeyRow_Server_Up, Name: "启用", BtnColorType: "warning"},
noticeRepo.RowBtns = []*ResourceBtnInfo{
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Notice_Disable, Name: "禁用", BtnColorType: "info"}, svc.handleNoticeDisable},
{&api.ResourceBtnInfo{Key: consts.BtnKeyGlobal_Notice_Enable, Name: "启用", BtnColorType: "warning"}, svc.handleNoticeEnable},
}
}
r(consts.ResourcesName_DevicePush, "设备推送(暂无)", repo.NewCommonResourceRepo(db, &model.DevicePush{}), ShowMethod_Get)
}
func NewCommonResourceService(db *gorm.DB) *CommonResourceService {
initCommonResourcesRepo(db)
svc := &CommonResourceService{
projectRepo: repo.NewProjectRepo(db),
serverRepo: repo.NewServerRepo(db),
noticeRepo: repo.NewNoticeRepo(db),
}
svc.initCommonResourcesRepo(db)
svc.startEventSubscriber()
svc.startLoadAllDelayInvokeDbData()
return svc
@ -305,33 +306,14 @@ func (svc *CommonResourceService) RowsSelection(projectId int, resourceName stri
return projectEt, nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
if params.BtnKey == consts.BtnKeyGlobal_Server_ExportCdn {
// 导出cdn聚合公告信息一起导
serverList, err := svc.serverRepo.List(projectId)
if err != nil {
return projectEt, nil, err
}
noticeList, err := svc.noticeRepo.List(projectId)
if err != nil {
return projectEt, nil, err
}
content, err := genCdnServerListContent(serverList, noticeList)
if err != nil {
return projectEt, nil, err
}
return projectEt, &dto2.CommonRowsSelectionRsp{
Msg: content,
NeedRefresh: false,
FileName: projectEt.GetProjectPo().Name + "-区服公告信息.txt",
}, nil
} else {
if hook, ok := projects.GetProjectResourceHook(projectEt, resourceName).(projects.IPostResourceOpRowsHook); ok {
rsp, err := hook.RowsSelection(projectEt, resourceName, params.BtnKey, params.Rows)
return projectEt, rsp, err
}
rRepo := findCommResourceRepo(resourceName)
btnInfo, find := rRepo.findRowsSelectionBtn(params.BtnKey)
if !find {
return projectEt, nil, errcode.New(errcode.ServerError, "not found project %v resource %v rows section btn:%v",
projectId, resourceName, params.BtnKey)
}
return projectEt, nil, nil
rsp, err := btnInfo.RowsSelectionHandler(projectEt, resourceName, params)
return projectEt, rsp, err
}
func (svc *CommonResourceService) GetResourceKeyByDto(resource string, dtoObj []dto2.CommonDtoValues) (string, string) {
@ -358,7 +340,7 @@ func (svc *CommonResourceService) GetResourceKeyByDto(resource string, dtoObj []
return desc, strings.Join(keyList, ",")
}
func (svc *CommonResourceService) GetResourceSpecBtnKey(resource string, btnKey string) *api.ResourceBtnInfo {
func (svc *CommonResourceService) GetResourceSpecBtnKey(resource string, btnKey string) *ResourceBtnInfo {
resourceInfo := findCommResourceRepo(resource)
for _, v := range resourceInfo.GlobalBtns {
if v.Key == btnKey {
@ -376,13 +358,18 @@ func (svc *CommonResourceService) GetResourceSpecBtnKey(resource string, btnKey
func (svc *CommonResourceService) GetSupportResourcesList(permissions []string) []*api.ResourceInitInfo {
list := make([]*api.ResourceInitInfo, 0, len(commResourcesRepo))
for _, v := range commResourcesRepo {
list = append(list, &api.ResourceInitInfo{
initInfo := &api.ResourceInitInfo{
Resource: v.Resource,
Desc: v.Desc,
ShowMethods: v.ShowMethods,
GlobalBtns: v.GlobalBtns,
RowBtns: v.RowBtns,
})
}
for _, v := range v.GlobalBtns {
initInfo.GlobalBtns = append(initInfo.GlobalBtns, v.ResourceBtnInfo)
}
for _, v := range v.RowBtns {
initInfo.RowBtns = append(initInfo.RowBtns, v.ResourceBtnInfo)
}
list = append(list, initInfo)
}
return list
}
@ -394,17 +381,36 @@ const (
ShowMethod_Delete = 1 << 3
)
type ResourceBtnInfo struct {
*api.ResourceBtnInfo
RowsSelectionHandler func(projectEt *entity.Project, resourceName string, params *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error)
}
type resourceRepoInfo struct {
Resource string
Desc string
Repo repo.ICommonResourceRepo
ShowMethods []string
showMethods int
GlobalBtns []*api.ResourceBtnInfo
RowBtns []*api.ResourceBtnInfo
GlobalBtns []*ResourceBtnInfo
RowBtns []*ResourceBtnInfo
HasDelayInvokeCreateHook bool
}
func (repoInfo *resourceRepoInfo) findRowsSelectionBtn(clickedBtn string) (*ResourceBtnInfo, bool) {
for _, btn := range repoInfo.GlobalBtns {
if btn.Key == clickedBtn {
return btn, true
}
}
for _, btn := range repoInfo.RowBtns {
if btn.Key == clickedBtn {
return btn, true
}
}
return nil, false
}
var commResourcesRepo = make([]*resourceRepoInfo, 0)
func r(resource, desc string, repo repo.ICommonResourceRepo, showMethods int) *resourceRepoInfo {

View File

@ -0,0 +1,78 @@
package domain
import (
"admin/apps/game/domain/entity"
"admin/apps/game/domain/projects"
dto2 "admin/internal/model/dto"
)
func (svc *CommonResourceService) handleServerUpOrDown(projectEt *entity.Project, resourceName string, params *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error) {
if hook, ok := projects.GetProjectResourceHook(projectEt, resourceName).(projects.IPostResourceOpRowsHook); ok {
rsp, err := hook.RowsSelection(projectEt, resourceName, params.BtnKey, params.Rows)
return rsp, err
}
return nil, nil
}
func (svc *CommonResourceService) handleServerExportCdn(projectEt *entity.Project, resourceName string, params *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error) {
// 导出cdn聚合公告信息一起导
serverList, err := svc.serverRepo.List(projectEt.GetProjectPo().ID)
if err != nil {
return nil, err
}
noticeList, err := svc.noticeRepo.List(projectEt.GetProjectPo().ID)
if err != nil {
return nil, err
}
content, err := genCdnServerListContent(serverList, noticeList)
if err != nil {
return nil, err
}
return &dto2.CommonRowsSelectionRsp{
Msg: content,
NeedRefresh: false,
FileName: projectEt.GetProjectPo().Name + "-区服公告信息.txt",
}, nil
}
func (svc *CommonResourceService) handleNoticeDisable(projectEt *entity.Project, resourceName string, params *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error) {
var err error
if len(params.Rows) == 0 {
// 禁用所有
err = svc.noticeRepo.DisableAll(projectEt.GetProjectID())
} else {
ids := make([]int, 0, len(params.Rows))
for _, v := range params.Rows {
ids = append(ids, int(v["ID"].(float64)))
}
err = svc.noticeRepo.DisableSome(projectEt.GetProjectID(), ids)
}
if err != nil {
return nil, err
}
return &dto2.CommonRowsSelectionRsp{
Msg: "禁用成功!",
NeedRefresh: true,
}, nil
}
func (svc *CommonResourceService) handleNoticeEnable(projectEt *entity.Project, resourceName string, params *dto2.CommonRowsSelectionReq) (*dto2.CommonRowsSelectionRsp, error) {
var err error
if len(params.Rows) == 0 {
// 禁用所有
err = svc.noticeRepo.EnableAll(projectEt.GetProjectID())
} else {
ids := make([]int, 0, len(params.Rows))
for _, v := range params.Rows {
ids = append(ids, int(v["ID"].(float64)))
}
err = svc.noticeRepo.EnableSome(projectEt.GetProjectID(), ids)
}
if err != nil {
return nil, err
}
return &dto2.CommonRowsSelectionRsp{
Msg: "启用成功!",
NeedRefresh: true,
}, nil
}

View File

@ -25,6 +25,10 @@ func (project *Project) ToCommonResource() *CommonResource {
return er.FromPo(project.Po)
}
func (project *Project) GetProjectID() int {
return project.Po.ID
}
func (project *Project) GetProjectPo() *model.Project {
return project.Po
}

View File

@ -45,13 +45,13 @@ func (hook *ServerHook) List(projectInfo *entity.Project, resource string, param
row["TotalRoleCount"] = findRemoteServerInfo.TotalRoleCount
row["TotalAccountCount"] = findRemoteServerInfo.TotalAccountCount
if findRemoteServerInfo.IsServerDown {
row["IsServerDown"] = "是"
row["IsServerDown"] = true
} else {
row["IsServerDown"] = "否"
row["IsServerDown"] = false
}
} else {
row["IsServerDown"] = "否"
row["RunningStatus"] = "未知"
row["IsServerDown"] = false
row["RunningStatus"] = "失联"
row["Ccu"] = 0
row["TotalRoleCount"] = 0
row["TotalAccountCount"] = 0

View File

@ -11,6 +11,10 @@ var NoticeRepoInstance INoticeRepo
type INoticeRepo interface {
List(projectId int) ([]*entity.CommonResource, error)
DisableAll(projectId int) error
EnableAll(projectId int) error
DisableSome(projectId int, ids []int) error
EnableSome(projectId int, ids []int) error
}
func NewNoticeRepo(db *gorm.DB) INoticeRepo {
@ -23,7 +27,7 @@ type noticeRepoImpl struct {
func (impl *noticeRepoImpl) List(projectId int) ([]*entity.CommonResource, error) {
list := make([]*model.Notice, 0)
err := impl.db.Where("project_id = ?", projectId).Find(&list).Error
err := impl.db.Where("project_id = ? and status = ?", projectId, true).Find(&list).Error
if err != nil {
return nil, errcode.New(errcode.DBError, "list server error:%v", err)
}
@ -34,3 +38,32 @@ func (impl *noticeRepoImpl) List(projectId int) ([]*entity.CommonResource, error
}
return list1, nil
}
func (impl *noticeRepoImpl) DisableAll(projectId int) error {
err := impl.db.Model(&model.Notice{}).Where("project_id = ?", projectId).Update("status", false).Error
if err != nil {
return errcode.New(errcode.DBError, "disable all error:%v", err)
}
return nil
}
func (impl *noticeRepoImpl) EnableAll(projectId int) error {
err := impl.db.Model(&model.Notice{}).Where("project_id = ?", projectId).Update("status", true).Error
if err != nil {
return errcode.New(errcode.DBError, "enable all error:%v", err)
}
return nil
}
func (impl *noticeRepoImpl) DisableSome(projectId int, ids []int) error {
err := impl.db.Model(&model.Notice{}).Where("project_id = ? and id in ?", projectId, ids).Update("status", false).Error
if err != nil {
return errcode.New(errcode.DBError, "disable all error:%v", err)
}
return nil
}
func (impl *noticeRepoImpl) EnableSome(projectId int, ids []int) error {
err := impl.db.Model(&model.Notice{}).Where("project_id = ? and id in ?", projectId, ids).Update("status", true).Error
if err != nil {
return errcode.New(errcode.DBError, "disable all error:%v", err)
}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"admin/apps/game/model"
"admin/internal/errcode"
"bytes"
"sort"
"strings"
"text/template"
"time"
@ -68,6 +69,13 @@ func genCdnServerListContent(serverList, noticeList []*entity.CommonResource) (s
}
info.NoticeVersion = time.Now().Format("200601021504")
sort.SliceStable(noticeList, func(i, j int) bool {
infoI := noticeList[i].Po.(*model.Notice)
infoJ := noticeList[j].Po.(*model.Notice)
return infoI.SortWeight > infoJ.SortWeight
})
for i, notice := range noticeList {
noticeDbInfo := notice.Po.(*model.Notice)
noticeInfo := &CdnNoticeInfo{}

View File

@ -19,7 +19,8 @@ type Notice struct {
Content string `name:"公告内容" desc:"仅支持<color=#xxx></color>颜色标签和\\n换行标签" type:"text" required:"true"` // 公告内容
//StartAt time.Time `name:"开始时间" required:"true"`
//EndAt time.Time `name:"结束时间" required:"true"`
Status bool `name:"是否启用" type:"tagStatus" choices:"GetStatusChoices" required:"true"`
Status bool `name:"是否启用" desc:"启用的公告才会导出到cdn被客户端显示" type:"tagStatus" choices:"GetStatusChoices" required:"true"`
SortWeight int `name:"排序" desc:"越大越靠前"`
CreatedAt time.Time `readonly:"true"`
UpdatedAt time.Time `readonly:"true"`
@ -39,7 +40,7 @@ func (m *Notice) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice
func (m *Notice) GetStatusChoices(project *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "禁用", Value: false},
{Desc: "启动", Value: true},
{Desc: "禁用", Value: false, Type: 3}, // type: 0:plain 1:primary 2:success 3:info 4:warning 5:danger
{Desc: "启用", Value: true, Type: 2},
}
}

View File

@ -17,7 +17,7 @@ type Role struct {
Account string `name:"账号" json:"account" where:"eq"`
RoleId string `name:"角色ID" json:"roleId" where:"eq"`
Name string `name:"名称" json:"roleName" where:"eq"`
Status string `name:"状态" desc:"离线|在线" type:"boolStatus" choices:"" json:"status"`
Status string `name:"状态" desc:"离线|在线" type:"tagStatus" choices:"GetStatusChoices"`
Level int `name:"等级" json:"roleLevel"`
Profession string `name:"职业" json:"profession"`
LatestLoginTime time.Time `name:"最近登录时间" json:"latestLoginAt"`
@ -38,7 +38,7 @@ func (m *Role) GetServerConfIDChoices(project *Project) []*dto.CommonDtoFieldCho
func (m *Role) GetStatusChoices(project *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "离线", Value: "离线"},
{Desc: "在线", Value: "在线"},
{Desc: "离线", Value: "离线", Type: 3}, // type: 0:plain 1:primary 2:success 3:info 4:warning 5:danger
{Desc: "在线", Value: "在线", Type: 2},
}
}

View File

@ -3,6 +3,7 @@ package model
import (
"admin/internal/db"
"admin/internal/global"
"admin/internal/model/dto"
"time"
)
@ -17,11 +18,11 @@ type Server struct {
ServerConfID string `gorm:"type:varchar(200);uniqueIndex:idx_server" name:"区服id" required:"true" uneditable:"true"`
Desc string `name:"描述"`
ClientConnAddr string `name:"客户端连接地址" desc:"填 公网ip:公网端口" required:"true"`
RunningStatus string `name:"进程运行状态" desc:"进程运行状态:未知、运行中、停止" readonly:"true" uneditable:"true"`
RunningStatus string `name:"进程运行状态" desc:"进程运行状态:未知、运行中、停止" readonly:"true" uneditable:"true" type:"tagStatus" choices:"GetRunningStatusChoices"`
Ccu int `name:"实时在线" desc:"ccu" readonly:"true" uneditable:"true"`
TotalRoleCount int `name:"总角色数" desc:"" readonly:"true" uneditable:"true"`
TotalAccountCount int `name:"总账号数" desc:"" readonly:"true" uneditable:"true"`
IsServerDown bool `name:"停服维护中" desc:"" readonly:"true" uneditable:"true"`
IsServerDown bool `name:"停服维护中" desc:"" readonly:"true" uneditable:"true" type:"tagStatus" choices:"GetServerDownStatusChoices"`
// command_list接口服务器地址为空代表由由项目统一提供command_list.
// 取决于每个项目改造难度:
// 为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行本后台执行指令只调用一次
@ -50,3 +51,17 @@ func (m *Server) ListByProjectId(projectId int) ([]*Server, error) {
err := global.GLOB_DB.Where("project_id=?", projectId).Find(&list).Error
return list, err
}
func (m *Server) GetRunningStatusChoices(project *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "失联", Value: "失联", Type: 3}, // type: 0:plain 1:primary 2:success 3:info 4:warning 5:danger
{Desc: "运行中", Value: "运行中", Type: 2},
}
}
func (m *Server) GetServerDownStatusChoices(project *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "否", Value: false, Type: 3}, // type: 0:plain 1:primary 2:success 3:info 4:warning 5:danger
{Desc: "是", Value: true, Type: 2},
}
}

View File

@ -5,13 +5,12 @@ import (
"admin/internal/context"
"admin/internal/errcode"
"admin/internal/permission"
"admin/lib/xlog"
"strings"
)
func (srv *Server) CheckToken(ctx *context.WebContext) {
reqPath := ctx.GinCtx().Request.URL.Path
xlog.Tracef("请求路径:%v 头:%+v", reqPath, ctx.GinCtx().Request.Header)
//xlog.Tracef("请求路径:%v 头:%+v", reqPath, ctx.GinCtx().Request.Header)
if strings.Contains(reqPath, "/login") {
return
}

View File

@ -3,13 +3,12 @@ package server
import (
"admin/internal/context"
"admin/internal/errcode"
"admin/lib/xlog"
"strings"
)
func (srv *Server) CheckToken(ctx *context.WebContext) {
reqPath := ctx.GinCtx().Request.URL.Path
xlog.Tracef("请求路径:%v 头:%+v", reqPath, ctx.GinCtx().Request.Header)
//xlog.Tracef("请求路径:%v 头:%+v", reqPath, ctx.GinCtx().Request.Header)
if strings.Contains(reqPath, "/login") {
return
}

View File

@ -58,9 +58,13 @@ const (
)
const (
BtnKeyGlobal_Server_DownAll = "server:down:all" // 一键维护
BtnKeyGlobal_Server_UpAll = "server:up:all" // 一键维护结束
BtnKeyGlobal_Server_ExportCdn = "server:export:cdn" // 一键导出cdn
BtnKeyGlobal_Server_DownAll = "server:down:all" // 一键维护
BtnKeyGlobal_Server_UpAll = "server:up:all" // 一键维护结束
BtnKeyGlobal_Server_ExportCdn = "server:export:cdn" // 一键导出cdn
BtnKeyGlobal_Notice_DisableAll = "notice:disable:all" // 一键禁用
BtnKeyGlobal_Notice_EnableAll = "notice:enable:all" // 一键启用
BtnKeyGlobal_Notice_Disable = "notice:disable" // 禁用
BtnKeyGlobal_Notice_Enable = "notice:enable" // 启用
)

View File

@ -37,7 +37,7 @@ func (ctx *WebContext) ExtractHeader() error {
if err != nil {
return err
}
xlog.Debugf("提取请求头:%+v", ctx.GinCtx().Request.Header)
//xlog.Debugf("提取请求头:%+v", ctx.GinCtx().Request.Header)
ctx.Header = header
return nil
}

View File

@ -0,0 +1,79 @@
<script setup>
import LocalCache from "@/stores/localCache.js";
const props = defineProps({
rowInfo: {},
fieldsDescInfo: {},
})
const dialogObjectForm = props.rowInfo
const fieldsDescInfo = props.fieldsDescInfo
const cachedResource = LocalCache.getCache("resource");
const resource_url = cachedResource.meta.resource_url;
</script>
<template>
<el-form ref="dialogLookFormRef" :model="dialogObjectForm" class="operation_form"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<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"/>
<el-table-column label="道具名" prop="desc"/>
</el-table>
</el-form-item>
</template>
<template v-else>
<!-- 有可选项的字段走下拉框或者多选框 -->
<template v-if="(fieldDescInfo.choices !== undefined && fieldDescInfo.choices.length > 0)">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-tooltip effect="light" :content="fieldDescInfo.help_text" placement="bottom-start">
<el-select :placeholder="(fieldDescInfo.multi_choice === true ? '--多选--' : '--单选--')"
disabled
v-model="dialogObjectForm[fieldDescInfo.key]" style="width: 150px"
:multiple="(fieldDescInfo.multi_choice === true)">
<el-option v-for="info in fieldDescInfo.choices" :key="info.desc" :label="info.desc"
:value="info.value"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
</template>
<!-- 时间戳字段展示时间选择器 -->
<template v-else-if="(fieldDescInfo.type === 'Time')">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-date-picker v-model="dialogObjectForm[fieldDescInfo.key]" type="datetime"
disabled
placeholder="空时间" format="YYYY/MM/DD HH:mm:ss"
value-format="YYYY/MM/DD HH:mm:ss"></el-date-picker>
</el-form-item>
</template>
<!-- 否则就是普通字段 -->
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogObjectForm[fieldDescInfo.key]"
disabled></el-input>
</el-form-item>
</template>
</template>
<!-- <el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">-->
<!-- <el-input v-model="dialogEditForm[fieldDescInfo.key]"></el-input>-->
<!-- </el-form-item>-->
</template>
</el-form>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,75 @@
<script setup>
import LocalCache from "@/stores/localCache.js";
import {cdkeyUsed} from "@/api/cdkey.js";
const props = defineProps({
rowInfo: {},
fieldsDescInfo: {},
})
const dialogObjectForm = props.rowInfo
const fieldsDescInfo = props.fieldsDescInfo
const cachedResource = LocalCache.getCache("resource");
const resource_url = cachedResource.meta.resource_url;
const cdkeyUsedHistoryList = ref([])
const cdkeyUsedHistoryTotal = ref([])
cdkeyUsed(resource_url, {id: dialogObjectForm.ID}).then((res) => {
cdkeyUsedHistoryList.value = res.data.list
cdkeyUsedHistoryTotal.value = [
{
"filedKey1": "礼包名称",
"filedValue1": dialogObjectForm.Name,
"filedKey2": "礼包总数量",
"filedValue2": dialogObjectForm.CodeNum,
}, {
"filedKey1": "礼包使用数量",
"filedValue1": cdkeyUsedHistoryList.value.length,
}]
}, (err) => {
})
const tableCellStyle = (row) => {
if (row.columnIndex === 0 || row.columnIndex === 2) {
// return 'background:#fde9d9 !import'
return {"font-weight": "bold", "color": "black"}
} else {
return {}
}
}
</script>
<template>
<el-row>
<el-table :data="cdkeyUsedHistoryTotal" style="width: 100%" table-layout="auto" border :cell-style="tableCellStyle"
:show-header="false">
<el-table-column prop="filedKey1" label="" width="180"/>
<el-table-column prop="filedValue1" label="" width="200"/>
<el-table-column prop="filedKey2" label="" width="180"/>
<el-table-column prop="filedValue2" label="" width="200"/>
</el-table>
</el-row>
<el-divider content-position="left">使用详情</el-divider>
<el-row>
<el-table :data="cdkeyUsedHistoryList" style="width: 100%" height="300" max-height="300" table-layout="auto"
stripe>
<el-table-column prop="server_id" label="区服"></el-table-column>
<el-table-column prop="account" label="账号名"></el-table-column>
<el-table-column prop="role_id" label="角色id"></el-table-column>
<el-table-column prop="role_name" label="角色名"></el-table-column>
<el-table-column prop="key" label="码"></el-table-column>
<el-table-column prop="ip" label="ip"></el-table-column>
<el-table-column prop="device_id" label="设备号"></el-table-column>
<el-table-column prop="created_at" label="使用时间"></el-table-column>
</el-table>
</el-row>
</template>
<style scoped>
</style>

View File

@ -7,6 +7,7 @@ import LocalCache from "@/stores/localCache.js";
const props = defineProps({
rowInfo: {},
fieldsDescInfo: {},
})
const cachedResource = LocalCache.getCache("resource");

View File

@ -46,7 +46,6 @@ const rowClickBtnSelectRow = ref(null)
const resource_url = cachedResource.meta.resource_url;
const fieldsDescInfo = ref([])
const whereFieldsDescInfo = ref([])
const calcElColSpan = ref(0)
const rows = ref([])
const itemBags = ref([])
const rules = ref({})
@ -63,6 +62,77 @@ const selectedItemBag = ref(null)
// console.log("enter table, resource:", cachedResource)
const handleServerRowData = (fieldsDescInfoData, rowData) => {
for (let i = 0; i < fieldsDescInfoData.length; i++) {
var field = fieldsDescInfoData[i]
// dialogObjectForm.value[field.key] = ''
if (field.required === true) {
rules.value[field.key] = [{required: true, message: field.name + "不能为空", trigger: "blur"}]
}
if (field.type === "items") {
dialogObjectForm.value[field.key] = []
rowData.jsonValue = JSON.stringify(rowData[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"],
}]
}
}
const tagStatusColorArray = ["plain", "primary", "success", "info", "waring", "danger"]
if (field.type === "tagStatus") {
for (let k = 0; k < field.choices.length; k++) {
if (rowData[field.key] === field.choices[k].value) {
rowData.tagValue = field.choices[k].desc
rowData.tagColor = tagStatusColorArray[field.choices[k].type]
}
}
}
if (field.where !== "") {
field.value1 = ""
field.value2 = ""
field.whereDesc = getWhereConditionDesc(field.where)
let find = false
for (let i = 0; i < whereFieldsDescInfo.value.length; i++) {
let whereField = whereFieldsDescInfo.value[i]
if (whereField.key === field.key) {
whereFieldsDescInfo.value[i].type = field.type
whereFieldsDescInfo.value[i].where = field.where
whereFieldsDescInfo.value[i].whereDesc = getWhereConditionDesc(field.where)
find = true
break
}
}
if (!find) {
whereFieldsDescInfo.value.push(field)
}
}
}
return rowData
}
const handleServerRowsData = (fieldsDescInfoData, rowsData) => {
let newRowsData = []
rowsData.forEach((rowData) => {
const newRowData = handleServerRowData(fieldsDescInfoData, rowData)
// console.log("new data:", newRowData)
newRowsData.push(newRowData)
})
return newRowsData
}
const listData = async () => {
try {
let listParams = {
@ -93,74 +163,9 @@ const listData = async () => {
if (listRsp.value.code !== 200) throw new Error("请求失败,错误码:", listRsp.code);
fieldsDescInfo.value = listRsp.value.data.fields_desc
totalRowCount.value = listRsp.value.data.total_count
rows.value = listRsp.value.data.rows
rows.value = handleServerRowsData(fieldsDescInfo.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"}]
}
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 !== "") {
field.value1 = ""
field.value2 = ""
field.whereDesc = getWhereConditionDesc(field.where)
let find = false
for (let i = 0; i < whereFieldsDescInfo.value.length; i++) {
let whereField = whereFieldsDescInfo.value[i]
if (whereField.key === field.key) {
whereFieldsDescInfo.value[i].type = field.type
whereFieldsDescInfo.value[i].where = field.where
whereFieldsDescInfo.value[i].whereDesc = getWhereConditionDesc(field.where)
find = true
break
}
}
if (!find) {
whereFieldsDescInfo.value.push(field)
}
}
}
calcElColSpan.value = 0
// el-col24span
let calcElColSpanTmp = 2
whereFieldsDescInfo.value.forEach((field) => {
if (field.where === "range") {
calcElColSpanTmp += 2
} else {
calcElColSpanTmp += 1
}
})
calcElColSpan.value = 24 / calcElColSpanTmp
// console.log("where fields:", whereFieldsDescInfo.value)
// console.log('await list rsp:', listRsp.value, fieldsDescInfo.value, toRaw(rows.value), toRaw(rules.value))
listDataOK.value = true
} catch (err) {
console.log(err)
@ -169,6 +174,29 @@ const listData = async () => {
}
}
const calcFuncColumnBtnSize = () => {
let btnCount = 0;
if (resource_raw_node.meta.methods.put === true) {
btnCount += 1
}
if (resource_raw_node.meta.methods.delete === true) {
btnCount += 1
}
btnCount += rowClickBtns.length
if (btnCount >= 4) {
return "small"
}
if (btnCount >= 3) {
return "default"
}
return "large"
}
const funcBtnSize = ref("default")
funcBtnSize.value = calcFuncColumnBtnSize()
onMounted(() => {
listData();
})
@ -287,7 +315,9 @@ const submitAdd = async () => {
duration: 4000,
"show-close": true,
})
rows.value.push(res.data.dto)
const newData = handleServerRowData(fieldsDescInfo.value, res.data.dto)
rows.value.unshift(newData)
// rows.value.push(res.data.dto)
dialogAddVisible.value = false
handleCloseDialog()
}, (err) => {
@ -320,7 +350,7 @@ const submitEdit = async () => {
"show-close": true,
})
dialogEditVisible.value = false
rows.value[oldIndex] = res.data.dto
rows.value[oldIndex] = handleServerRowData(fieldsDescInfo.value, res.data.dto)
handleCloseDialog()
}, (err) => {
console.log("添加报错:", err)
@ -564,29 +594,39 @@ const handlePaginationCurChange = (val) => {
<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.type === 'boolStatus')">
<el-table-column prop="tagValue" :label="fieldDescInfo.name"
v-else-if="(fieldDescInfo.type === 'tagStatus')">
<template #default="scope">
<el-tag type="info" v-if="scope.row.Status === '离线'">{{ scope.row.Status }}</el-tag>
<el-tag type="success" v-else-if="scope.row.Status === '在线'">{{ scope.row.Status }}</el-tag>
<el-tag :type="scope.row.tagColor">{{ scope.row.tagValue }}</el-tag>
<!-- <el-tag type="success" v-else-if="scope.row.Status === '在线'">{{ 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>
v-else>
<template #header>
<el-tooltip effect="light" :content="fieldDescInfo.help_text" placement="top"
v-if="fieldDescInfo.help_text !== ''">
<span>{{ fieldDescInfo.name }}</span>
</el-tooltip>
<span v-else>{{ fieldDescInfo.name }}</span>
</template>
</el-table-column>
</template>
<el-table-column prop="func" label="功 能">
<template #default="scope">
<el-button size="default" type="success" @click="handleEdit( scope.$index, scope.row)"
<el-button :size="funcBtnSize" type="success"
@click="handleEdit( scope.$index, scope.row)"
v-if="(resource_raw_node.meta.methods.put === true)">
<!-- <el-icon style="vertical-align: middle">-->
<!-- <Edit/>-->
<!-- </el-icon>-->
<span>编辑</span>
</el-button>
<el-button size="default" type="danger" @click="handleDelete( scope.$index, scope.row)"
<el-button :size="funcBtnSize" type="danger"
@click="handleDelete( scope.$index, scope.row)"
v-if="(resource_raw_node.meta.methods.delete === true)">
<!-- <el-icon style="vertical-align: middle">-->
<!-- <Delete/>-->
@ -594,22 +634,22 @@ const handlePaginationCurChange = (val) => {
<span>删除</span>
</el-button>
<template v-for="(btn, index) in rowClickBtns">
<template v-if="btn.btn_type === 0">
<el-button size="default" :type="btn.btn_color_type"
<template v-if="btn.btn_type === 0"> <!--直接发送选择行数据到服务器的按钮类型-->
<el-button :size="funcBtnSize" :type="btn.btn_color_type"
@click="tableSelectRows2(btn, scope.$index, scope.row)">
{{ btn.name }}
</el-button>
</template>
<template v-else-if="btn.btn_type === 1">
<el-button size="default" :type="btn.btn_color_type"
<template v-else-if="btn.btn_type === 1"> <!--带弹窗的按钮类型-->
<el-button :size="funcBtnSize" :type="btn.btn_color_type"
@click="tableSelectRow3(index, scope.row)">
{{ btn.name }}
</el-button>
</template>
<template v-else-if="btn.btn_type === 2">
<el-button size="default" :type="btn.btn_color_type"
<template v-else-if="btn.btn_type === 2"> <!--带点击回调函数的按钮类型-->
<el-button :size="funcBtnSize" :type="btn.btn_color_type"
@click="tableSelectRow4(btn, scope.row)">
{{ btn.name }}
</el-button>
@ -639,7 +679,8 @@ const handlePaginationCurChange = (val) => {
<el-dialog v-model="rowClickBtnVisibleList[index]" :title="btn.name"
@close="rowClickBtnVisibleList[index]=false"
destroy-on-close>
<component :is="btn.btn_callback_component" :rowInfo="rowClickBtnSelectRow"/>
<component :is="btn.btn_callback_component" :rowInfo="rowClickBtnSelectRow"
:fieldsDescInfo="fieldsDescInfo"/>
</el-dialog>
</template>

View File

@ -35,6 +35,78 @@ const selectedItemBag = ref(null)
// console.log("enter table, resource:", cachedResource)
const handleServerRowData = (fieldsDescInfoData, rowData) => {
for (let i = 0; i < fieldsDescInfoData.length; i++) {
var field = fieldsDescInfoData[i]
// dialogObjectForm.value[field.key] = ''
if (field.required === true) {
rules.value[field.key] = [{required: true, message: field.name + "不能为空", trigger: "blur"}]
}
if (field.type === "items") {
dialogObjectForm.value[field.key] = []
rowData.jsonValue = JSON.stringify(rowData[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"],
}]
}
}
const tagStatusColorArray = ["plain", "primary", "success", "info", "waring", "danger"]
if (field.type === "tagStatus") {
for (let k = 0; k < field.choices.length; k++) {
if (rowData[field.key] === field.choices[k].value) {
rowData.tagValue = field.choices[k].desc
rowData.tagColor = tagStatusColorArray[field.choices[k].type]
}
}
}
if (field.where !== "") {
field.value1 = ""
field.value2 = ""
field.whereDesc = getWhereConditionDesc(field.where)
let find = false
for (let i = 0; i < whereFieldsDescInfo.value.length; i++) {
let whereField = whereFieldsDescInfo.value[i]
if (whereField.key === field.key) {
whereFieldsDescInfo.value[i].type = field.type
whereFieldsDescInfo.value[i].where = field.where
whereFieldsDescInfo.value[i].whereDesc = getWhereConditionDesc(field.where)
find = true
break
}
}
if (!find) {
whereFieldsDescInfo.value.push(field)
}
}
}
return rowData
}
const handleServerRowsData = (fieldsDescInfoData, rowsData) => {
let newRowsData = []
rowsData.forEach((rowData) => {
const newRowData = handleServerRowData(fieldsDescInfoData, rowData)
// console.log("new data:", newRowData)
newRowsData.push(newRowData)
})
return newRowsData
}
const listData = async () => {
try {
let listParams = {
@ -65,72 +137,9 @@ const listData = async () => {
if (listRsp.value.code !== 200) throw new Error("请求失败,错误码:", listRsp.code);
fieldsDescInfo.value = listRsp.value.data.fields_desc
totalRowCount.value = listRsp.value.data.total_count
rows.value = listRsp.value.data.rows
rows.value = handleServerRowsData(fieldsDescInfo.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", "change"]}]
}
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])
}
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 !== "") {
field.value1 = ""
field.value2 = ""
field.whereDesc = getWhereConditionDesc(field.where)
let find = false
for (let i = 0; i < whereFieldsDescInfo.value.length; i++) {
let whereField = whereFieldsDescInfo.value[i]
if (whereField.key === field.key) {
whereFieldsDescInfo.value[i].type = field.type
whereFieldsDescInfo.value[i].where = field.where
whereFieldsDescInfo.value[i].whereDesc = getWhereConditionDesc(field.where)
find = true
break
}
}
if (!find) {
whereFieldsDescInfo.value.push(field)
}
}
}
calcElColSpan.value = 0
// el-col24span
let calcElColSpanTmp = 2
whereFieldsDescInfo.value.forEach((field) => {
if (field.where === "range") {
calcElColSpanTmp += 2
} else {
calcElColSpanTmp += 1
}
})
calcElColSpan.value = 24 / calcElColSpanTmp
// console.log("where fields:", whereFieldsDescInfo.value)
// console.log('await list rsp:', listRsp.value, fieldsDescInfo.value, toRaw(rows.value), toRaw(rules.value))
listDataOK.value = true
} catch (err) {
console.log(err)
@ -169,7 +178,8 @@ const submitAdd = async () => {
duration: 4000,
"show-close": true,
})
rows.value.push(res.data.dto)
const newData = handleServerRowData(fieldsDescInfo.value, res.data.dto)
rows.value.unshift(newData)
dialogAddVisible.value = false
handleCloseDialog()
}, (err) => {
@ -202,7 +212,7 @@ const submitEdit = async () => {
"show-close": true,
})
dialogEditVisible.value = false
rows.value[oldIndex] = res.data.dto
rows.value[oldIndex] = handleServerRowData(fieldsDescInfo.value, res.data.dto)
handleCloseDialog()
}, (err) => {
console.log("添加报错:", err)

View File

@ -119,9 +119,9 @@ const userStore = defineStore(
component: () => import('@/views/project/project_op.vue'),
props: true
}
if (resource.resource === 'cdkey') {
resourceRoute.component = () => import('@/views/project/project_cdkey.vue')
}
// if (resource.resource === 'cdkey') {
// resourceRoute.component = () => import('@/views/project/project_cdkey.vue')
// }
resource.show_methods.forEach((method) => {
if (method == "get") {
projectHasAnyResourcePermission = true

View File

@ -3,8 +3,11 @@
import tableView from "@/components/restful/table.vue";
import LocalCache from "@/stores/localCache.js";
import userDetail from "@/components/game/userDetail.vue";
import cdkeyDetail from "@/components/game/cdkeyDetail.vue";
import cdkeyUsedHistory from "@/components/game/cdkeyUsedHistory.vue";
import router from '@/router/index.js'
import userStore from "@/stores/user.js";
import {cdkeyExport} from "@/api/cdkey.js";
const cachedResource = LocalCache.getCache("resource");
const homeRoutes = userStore().dynamicRouteChildren
@ -83,6 +86,57 @@ switch (cachedResource.meta.resource) {
}
},)
break
case "cdkey":
if (rowClickDialogBtns.length > 0) {
break
}
rowClickDialogBtns.push({
key: "cdkey:detail",
name: "查看",
btn_color_type: "primary",
btn_type: 1,
btn_callback_component: cdkeyDetail,
}, {
key: "cdkey:export",
name: "导出",
btn_color_type: "warning",
btn_type: 2,
click_handler: (row) => {
const cachedResource = LocalCache.getCache("resource");
const resource_url = cachedResource.meta.resource_url;
cdkeyExport(resource_url, {id: row.ID}).then((res) => {
console.log("导出cdkey返回", res)
//
const contentDisposition = res.headers['content-disposition'];
let filename = 'default_filename.ext'; //
//
const filenameRegex = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i;
const matches = contentDisposition.match(filenameRegex);
if (matches && matches[1]) {
filename = decodeURIComponent(matches[1]); //
}
//
const blob = new Blob([res.data]);
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
//
window.URL.revokeObjectURL(link.href);
})
},
}, {
key: "cdkey:used:history",
name: "礼包使用",
btn_color_type: "default",
btn_type: 1,
btn_callback_component: cdkeyUsedHistory,
},)
}
</script>